From c376c8b3f19664aa0aa4da4894a3f73de3e45654 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 6 Jul 2024 19:38:18 +0200 Subject: [PATCH 0001/3097] Open 1.12.x-dev --- .github/workflows/backward-compatibility.yml | 4 ++-- .github/workflows/build-issue-bot.yml | 4 ++-- .github/workflows/changelog-generator.yml | 4 ++-- .github/workflows/checksum-phar.yml | 10 ++++---- .github/workflows/e2e-tests.yml | 4 ++-- .github/workflows/lint.yml | 4 ++-- .github/workflows/phar.yml | 24 ++++++++++---------- .github/workflows/reflection-golden-test.yml | 4 ++-- .github/workflows/spelling.yml | 2 +- .github/workflows/static-analysis.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 11 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 5411d5b9e33..0233e1e422a 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -6,13 +6,13 @@ on: pull_request: push: branches: - - "1.11.x" + - "1.12.x" paths: - 'src/**' - '.github/workflows/backward-compatibility.yml' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: bc-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/build-issue-bot.yml b/.github/workflows/build-issue-bot.yml index 4769c56165b..278470b4660 100644 --- a/.github/workflows/build-issue-bot.yml +++ b/.github/workflows/build-issue-bot.yml @@ -9,13 +9,13 @@ on: - '.github/workflows/build-issue-bot.yml' push: branches: - - "1.11.x" + - "1.12.x" paths: - 'issue-bot/**' - '.github/workflows/build-issue-bot.yml' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: build-issue-bot-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/changelog-generator.yml b/.github/workflows/changelog-generator.yml index 1681e3a6e0c..21971571f3d 100644 --- a/.github/workflows/changelog-generator.yml +++ b/.github/workflows/changelog-generator.yml @@ -9,13 +9,13 @@ on: - '.github/workflows/changelog-generator.yml' push: branches: - - "1.11.x" + - "1.12.x" paths: - 'changelog-generator/**' - '.github/workflows/changelog-generator.yml' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: changelog-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index 1558436ad44..47256373d0c 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -12,13 +12,13 @@ on: - '.github/workflows/checksum-phar.yml' push: branches: - - "1.11.x" + - "1.12.x" paths: - 'compiler/**' - '.github/workflows/checksum-phar.yml' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: checksum-phar-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches @@ -37,7 +37,7 @@ jobs: with: repository: phpstan/phpstan path: phpstan-dist - ref: 1.11.x + ref: 1.12.x - name: "Get info" id: info @@ -101,14 +101,14 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4ca9c863293..80f23291afe 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.11.x" + - "1.12.x" paths-ignore: - 'compiler/**' - 'apigen/**' @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: e2e-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 14fbfbc500c..b0393de1099 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,10 +6,10 @@ on: pull_request: push: branches: - - "1.11.x" + - "1.12.x" env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: lint-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index af601aa93bd..c6c4934b44b 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -6,9 +6,9 @@ on: pull_request: push: branches: - - "1.11.x" + - "1.12.x" tags: - - '1.11.*' + - '1.12.*' concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests @@ -77,14 +77,14 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" @@ -107,30 +107,30 @@ jobs: integration-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.11.x + uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.12.x with: - ref: 1.11.x + ref: 1.12.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/extension-tests.yml@1.11.x + uses: phpstan/phpstan/.github/workflows/extension-tests.yml@1.12.x with: - ref: 1.11.x + ref: 1.12.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} other-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/other-tests.yml@1.11.x + uses: phpstan/phpstan/.github/workflows/other-tests.yml@1.12.x with: - ref: 1.11.x + ref: 1.12.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} commit: name: "Commit PHAR" - if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/1.11.x' || startsWith(github.ref, 'refs/tags/'))" + if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/1.12.x' || startsWith(github.ref, 'refs/tags/'))" needs: compiler-tests runs-on: "ubuntu-latest" timeout-minutes: 60 @@ -152,7 +152,7 @@ jobs: repository: phpstan/phpstan path: phpstan-dist token: ${{ secrets.PHPSTAN_BOT_TOKEN }} - ref: 1.11.x + ref: 1.12.x - name: "Get previous pushed dist commit" id: previous-commit diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index b3db1c394c1..3c13f9205b6 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.11.x" + - "1.12.x" paths-ignore: - 'compiler/**' - 'apigen/**' @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" REFLECTION_GOLDEN_TEST_FILE: "/tmp/reflection-golden.test" REFLECTION_GOLDEN_SYMBOLS_FILE: "/tmp/reflection-golden-symbols.txt" diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 239668a718a..b11ac9324bd 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.11.x" + - "1.12.x" jobs: typos: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 390bfa4b7e0..d38dd2726ea 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -9,13 +9,13 @@ on: - 'apigen/**' push: branches: - - "1.11.x" + - "1.12.x" paths-ignore: - 'compiler/**' - 'apigen/**' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: sa-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f1a49d48f39..e86c7738fb3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.11.x" + - "1.12.x" paths-ignore: - 'compiler/**' - 'apigen/**' @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.11.x-dev" + COMPOSER_ROOT_VERSION: "1.12.x-dev" concurrency: group: tests-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches From 7d1bde44afc0c0f7e3b29f2d75a7c33d5e6a56ec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 23 Jul 2024 14:02:23 +0200 Subject: [PATCH 0002/3097] BetterReflectionSourceLocator - playground mode --- conf/config.neon | 2 ++ conf/parametersSchema.neon | 3 +++ .../BetterReflectionSourceLocatorFactory.php | 14 ++++++++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ac8f18be39a..a16f09fd39c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -257,6 +257,7 @@ parameters: editorUrlTitle: null errorFormat: null sysGetTempDir: ::sys_get_temp_dir() + sourceLocatorPlaygroundMode: false pro: dnsServers: - '1.1.1.2' @@ -2063,6 +2064,7 @@ services: analysedPaths: %analysedPaths% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% analysedPathsFromConfig: %analysedPathsFromConfig% + playgroundMode: %sourceLocatorPlaygroundMode% - implement: PHPStan\Reflection\BetterReflection\BetterReflectionProviderFactory diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f33fc0ba5db..111d8644808 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -224,6 +224,9 @@ parametersSchema: env: arrayOf(string(), anyOf(int(), string())) sysGetTempDir: string() + # playground mode + sourceLocatorPlaygroundMode: bool() + # irrelevant Nette parameters debugMode: bool() productionMode: bool() diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 5a207d0476a..f9c3649cf94 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -55,6 +55,7 @@ public function __construct( private array $analysedPaths, private array $composerAutoloaderProjectPaths, private array $analysedPathsFromConfig, + private bool $playgroundMode, // makes all PHPStan classes in the PHAR discoverable with PSR-4 ) { } @@ -112,11 +113,16 @@ public function create(): SourceLocator if (extension_loaded('phar')) { $pharProtocolPath = Phar::running(); if ($pharProtocolPath !== '') { + $mappings = [ + 'PHPStan\\BetterReflection\\' => [$pharProtocolPath . '/vendor/ondrejmirtes/better-reflection/src/'], + ]; + if ($this->playgroundMode) { + $mappings['PHPStan\\'] = [$pharProtocolPath . '/src/']; + } else { + $mappings['PHPStan\\Testing\\'] = [$pharProtocolPath . '/src/Testing/']; + } $fileLocators[] = $this->optimizedPsrAutoloaderLocatorFactory->create( - Psr4Mapping::fromArrayMappings([ - 'PHPStan\\Testing\\' => [$pharProtocolPath . '/src/Testing/'], - 'PHPStan\\BetterReflection\\' => [$pharProtocolPath . '/vendor/ondrejmirtes/better-reflection/src/'], - ]), + Psr4Mapping::fromArrayMappings($mappings), ); } } From 4010b73ffd62ef9d4a2095fb3d7d00bb81199c89 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 25 Jul 2024 13:40:46 +0200 Subject: [PATCH 0003/3097] Internal classes made `final`, `@api` classes made `@final` `@api` classes will become `final` in PHPStan 2.0 PHPStan\Type\Type interface implementations are excluded from these changes If these changes impact your project, please open a GitHub Discussion so we can have a conversation about your use case and solve it properly. --- bin/generate-rule-error-classes.php | 2 +- build/enum-adapter-errors.neon | 5 ---- phpstan-baseline.neon | 25 ------------------- src/Analyser/Analyser.php | 2 +- src/Analyser/AnalyserResult.php | 2 +- src/Analyser/AnalyserResultFinalizer.php | 2 +- src/Analyser/ConditionalExpressionHolder.php | 2 +- src/Analyser/ConstantResolver.php | 2 +- src/Analyser/ConstantResolverFactory.php | 2 +- src/Analyser/DirectInternalScopeFactory.php | 2 +- src/Analyser/EndStatementResult.php | 2 +- src/Analyser/EnsuredNonNullabilityResult.php | 2 +- .../EnsuredNonNullabilityResultExpression.php | 2 +- src/Analyser/Error.php | 5 +++- src/Analyser/ExpressionContext.php | 2 +- src/Analyser/ExpressionResult.php | 2 +- src/Analyser/ExpressionTypeHolder.php | 2 +- src/Analyser/FileAnalyser.php | 2 +- src/Analyser/FileAnalyserResult.php | 2 +- src/Analyser/FinalizerResult.php | 2 +- src/Analyser/Ignore/IgnoreParseException.php | 2 +- src/Analyser/Ignore/IgnoredError.php | 2 +- src/Analyser/Ignore/IgnoredErrorHelper.php | 2 +- .../IgnoredErrorHelperProcessedResult.php | 2 +- .../Ignore/IgnoredErrorHelperResult.php | 2 +- src/Analyser/ImpurePoint.php | 1 + src/Analyser/InternalError.php | 1 + src/Analyser/LazyInternalScopeFactory.php | 2 +- src/Analyser/LocalIgnoresProcessor.php | 2 +- src/Analyser/LocalIgnoresProcessorResult.php | 2 +- src/Analyser/MutatingScope.php | 2 +- src/Analyser/NameScope.php | 5 +++- src/Analyser/NodeScopeResolver.php | 2 +- src/Analyser/NullsafeOperatorHelper.php | 2 +- src/Analyser/OutOfClassScope.php | 2 +- src/Analyser/ProcessClosureResult.php | 2 +- src/Analyser/ResultCache/ResultCache.php | 2 +- .../ResultCache/ResultCacheClearer.php | 2 +- .../ResultCache/ResultCacheManager.php | 2 +- .../ResultCache/ResultCacheProcessResult.php | 2 +- src/Analyser/RuleErrorTransformer.php | 2 +- src/Analyser/ScopeContext.php | 2 +- src/Analyser/ScopeFactory.php | 5 +++- src/Analyser/SpecifiedTypes.php | 2 +- src/Analyser/StatementContext.php | 2 +- src/Analyser/StatementExitPoint.php | 5 +++- src/Analyser/StatementResult.php | 5 +++- src/Analyser/ThrowPoint.php | 5 +++- src/Analyser/TypeSpecifier.php | 2 +- src/Analyser/TypeSpecifierContext.php | 5 +++- src/Analyser/TypeSpecifierFactory.php | 2 +- src/Analyser/UndefinedVariableException.php | 2 +- src/Broker/AnonymousClassNameHelper.php | 2 +- src/Broker/Broker.php | 5 +++- src/Broker/BrokerFactory.php | 2 +- src/Broker/ClassAutoloadingException.php | 4 +-- src/Broker/ClassNotFoundException.php | 4 +-- src/Broker/ConstantNotFoundException.php | 4 +-- src/Broker/FunctionNotFoundException.php | 4 +-- src/Cache/Cache.php | 2 +- src/Cache/CacheItem.php | 2 +- src/Cache/FileCacheStorage.php | 2 +- src/Cache/MemoryCacheStorage.php | 2 +- src/Collectors/CollectedData.php | 5 +++- src/Collectors/Registry.php | 2 +- src/Collectors/RegistryFactory.php | 2 +- src/Command/AnalyseApplication.php | 2 +- src/Command/AnalyseCommand.php | 2 +- src/Command/AnalyserRunner.php | 2 +- src/Command/AnalysisResult.php | 5 +++- src/Command/ClearResultCacheCommand.php | 2 +- src/Command/CommandHelper.php | 2 +- src/Command/DiagnoseCommand.php | 2 +- src/Command/DumpParametersCommand.php | 2 +- .../BaselineNeonErrorFormatter.php | 2 +- .../BaselinePhpErrorFormatter.php | 2 +- .../CheckstyleErrorFormatter.php | 2 +- .../CiDetectedErrorFormatter.php | 5 +++- .../ErrorFormatter/GithubErrorFormatter.php | 2 +- .../ErrorFormatter/GitlabErrorFormatter.php | 2 +- .../ErrorFormatter/JsonErrorFormatter.php | 2 +- .../ErrorFormatter/JunitErrorFormatter.php | 2 +- .../ErrorFormatter/RawErrorFormatter.php | 2 +- .../ErrorFormatter/TableErrorFormatter.php | 2 +- .../ErrorFormatter/TeamcityErrorFormatter.php | 2 +- src/Command/ErrorsConsoleStyle.php | 2 +- src/Command/FixerApplication.php | 2 +- src/Command/FixerProcessException.php | 2 +- src/Command/FixerWorkerCommand.php | 2 +- src/Command/IgnoredRegexValidator.php | 2 +- src/Command/IgnoredRegexValidatorResult.php | 2 +- .../InceptionNotSuccessfulException.php | 2 +- src/Command/InceptionResult.php | 2 +- src/Command/Symfony/SymfonyOutput.php | 2 +- src/Command/Symfony/SymfonyStyle.php | 2 +- src/Command/WorkerCommand.php | 2 +- src/Dependency/DependencyResolver.php | 2 +- .../ExportedNode/ExportedAttributeNode.php | 2 +- .../ExportedClassConstantNode.php | 2 +- .../ExportedClassConstantsNode.php | 2 +- .../ExportedNode/ExportedClassNode.php | 5 +++- .../ExportedNode/ExportedEnumCaseNode.php | 2 +- .../ExportedNode/ExportedEnumNode.php | 5 +++- .../ExportedNode/ExportedFunctionNode.php | 5 +++- .../ExportedNode/ExportedInterfaceNode.php | 5 +++- .../ExportedNode/ExportedMethodNode.php | 2 +- .../ExportedNode/ExportedParameterNode.php | 2 +- .../ExportedNode/ExportedPhpDocNode.php | 2 +- .../ExportedNode/ExportedPropertiesNode.php | 2 +- .../ExportedNode/ExportedTraitNode.php | 5 +++- .../ExportedTraitUseAdaptation.php | 2 +- src/Dependency/ExportedNodeFetcher.php | 2 +- src/Dependency/ExportedNodeResolver.php | 2 +- src/Dependency/ExportedNodeVisitor.php | 2 +- src/Dependency/NodeDependencies.php | 2 +- .../BleedingEdgeToggle.php | 2 +- .../ConditionalTagsExtension.php | 2 +- src/DependencyInjection/Configurator.php | 2 +- src/DependencyInjection/ContainerFactory.php | 5 +++- .../DerivativeContainerFactory.php | 2 +- .../DuplicateIncludedFilesException.php | 2 +- .../InvalidIgnoredErrorPatternsException.php | 2 +- src/DependencyInjection/LoaderFactory.php | 2 +- .../MemoizingContainer.php | 2 +- src/DependencyInjection/NeonAdapter.php | 2 +- src/DependencyInjection/NeonLoader.php | 2 +- .../Nette/NetteContainer.php | 2 +- .../ParameterNotFoundException.php | 2 +- .../ParametersSchemaExtension.php | 2 +- .../ProjectConfigHelper.php | 2 +- ...assReflectionExtensionRegistryProvider.php | 2 +- src/DependencyInjection/RulesExtension.php | 2 +- ...micReturnTypeExtensionRegistryProvider.php | 2 +- .../LazyDynamicThrowTypeExtensionProvider.php | 2 +- ...nTypeResolverExtensionRegistryProvider.php | 2 +- ...ypeSpecifyingExtensionRegistryProvider.php | 2 +- ...yParameterClosureTypeExtensionProvider.php | 2 +- .../LazyParameterOutTypeExtensionProvider.php | 2 +- .../ValidateIgnoredErrorsExtension.php | 2 +- src/Diagnose/PHPStanDiagnoseExtension.php | 2 +- src/File/CouldNotReadFileException.php | 2 +- src/File/CouldNotWriteFileException.php | 2 +- src/File/FileExcluder.php | 2 +- src/File/FileExcluderFactory.php | 2 +- src/File/FileFinder.php | 2 +- src/File/FileFinderResult.php | 2 +- src/File/FileHelper.php | 2 +- src/File/FileMonitor.php | 2 +- src/File/FileMonitorResult.php | 2 +- src/File/FileReader.php | 2 +- src/File/FileWriter.php | 2 +- src/File/FuzzyRelativePathHelper.php | 2 +- src/File/NullRelativePathHelper.php | 2 +- .../ParentDirectoryRelativePathHelper.php | 2 +- src/File/PathNotFoundException.php | 2 +- src/File/SimpleRelativePathHelper.php | 2 +- ...SystemAgnosticSimpleRelativePathHelper.php | 2 +- src/Internal/BytesHelper.php | 2 +- .../ContainerDynamicReturnTypeExtension.php | 2 +- src/Internal/SprintfHelper.php | 2 +- src/Node/BooleanAndNode.php | 5 +++- src/Node/BooleanOrNode.php | 5 +++- src/Node/BreaklessWhileLoopNode.php | 5 +++- src/Node/CatchWithUnthrownExceptionNode.php | 5 +++- src/Node/ClassConstantsNode.php | 5 +++- src/Node/ClassMethod.php | 5 +++- src/Node/ClassMethodsNode.php | 5 +++- src/Node/ClassPropertiesNode.php | 5 +++- src/Node/ClassPropertyNode.php | 5 +++- src/Node/ClassStatementsGatherer.php | 2 +- src/Node/ClosureReturnStatementsNode.php | 5 +++- src/Node/CollectedDataNode.php | 5 +++- src/Node/Constant/ClassConstantFetch.php | 5 +++- src/Node/DoWhileLoopConditionNode.php | 2 +- src/Node/ExecutionEndNode.php | 5 +++- src/Node/Expr/AlwaysRememberedExpr.php | 2 +- src/Node/Expr/ExistingArrayDimFetch.php | 2 +- src/Node/Expr/GetIterableKeyTypeExpr.php | 2 +- src/Node/Expr/GetIterableValueTypeExpr.php | 2 +- src/Node/Expr/GetOffsetValueTypeExpr.php | 2 +- src/Node/Expr/OriginalPropertyTypeExpr.php | 2 +- .../ParameterVariableOriginalValueExpr.php | 2 +- src/Node/Expr/PropertyInitializationExpr.php | 2 +- .../Expr/SetExistingOffsetValueTypeExpr.php | 2 +- src/Node/Expr/SetOffsetValueTypeExpr.php | 2 +- src/Node/Expr/TypeExpr.php | 2 +- src/Node/Expr/UnsetOffsetExpr.php | 2 +- src/Node/FileNode.php | 5 +++- src/Node/FinallyExitPointsNode.php | 5 +++- src/Node/FunctionCallableNode.php | 5 +++- src/Node/FunctionReturnStatementsNode.php | 5 +++- src/Node/InArrowFunctionNode.php | 5 +++- src/Node/InClassMethodNode.php | 5 +++- src/Node/InClassNode.php | 5 +++- src/Node/InClosureNode.php | 5 +++- src/Node/InForeachNode.php | 2 +- src/Node/InFunctionNode.php | 5 +++- src/Node/InTraitNode.php | 5 +++- src/Node/InstantiationCallableNode.php | 5 +++- src/Node/InvalidateExprNode.php | 5 +++- src/Node/IssetExpr.php | 2 +- src/Node/LiteralArrayItem.php | 5 +++- src/Node/LiteralArrayNode.php | 5 +++- src/Node/MatchExpressionArm.php | 5 +++- src/Node/MatchExpressionArmBody.php | 5 +++- src/Node/MatchExpressionArmCondition.php | 5 +++- src/Node/MatchExpressionNode.php | 5 +++- src/Node/Method/MethodCall.php | 5 +++- src/Node/MethodCallableNode.php | 5 +++- src/Node/MethodReturnStatementsNode.php | 5 +++- src/Node/NoopExpressionNode.php | 2 +- src/Node/Printer/ExprPrinter.php | 5 +++- src/Node/Printer/Printer.php | 2 +- src/Node/Property/PropertyRead.php | 5 +++- src/Node/Property/PropertyWrite.php | 5 +++- src/Node/PropertyAssignNode.php | 2 +- src/Node/ReturnStatement.php | 5 +++- src/Node/StaticMethodCallableNode.php | 5 +++- src/Node/UnreachableStatementNode.php | 5 +++- src/Node/VarTagChangedExpressionTypeNode.php | 2 +- src/Node/VariableAssignNode.php | 2 +- src/Parallel/ParallelAnalyser.php | 2 +- src/Parallel/Process.php | 2 +- src/Parallel/ProcessPool.php | 2 +- src/Parallel/ProcessTimedOutException.php | 2 +- src/Parallel/Schedule.php | 2 +- src/Parallel/Scheduler.php | 2 +- src/Parser/ArrayFilterArgVisitor.php | 2 +- src/Parser/ArrayMapArgVisitor.php | 2 +- src/Parser/ArrayWalkArgVisitor.php | 2 +- src/Parser/ArrowFunctionArgVisitor.php | 2 +- src/Parser/CachedParser.php | 2 +- src/Parser/CleaningParser.php | 2 +- src/Parser/CleaningVisitor.php | 2 +- src/Parser/ClosureArgVisitor.php | 2 +- src/Parser/ClosureBindArgVisitor.php | 2 +- src/Parser/ClosureBindToVarVisitor.php | 2 +- src/Parser/CurlSetOptArgVisitor.php | 2 +- src/Parser/DeclarePositionVisitor.php | 2 +- src/Parser/FunctionCallStatementFinder.php | 2 +- src/Parser/LastConditionVisitor.php | 2 +- src/Parser/LexerFactory.php | 2 +- .../MagicConstantParamDefaultVisitor.php | 2 +- src/Parser/NewAssignedToPropertyVisitor.php | 2 +- src/Parser/ParserErrorsException.php | 2 +- src/Parser/PathRoutingParser.php | 2 +- src/Parser/PhpParserDecorator.php | 2 +- .../RemoveUnusedCodeByPhpVersionIdVisitor.php | 4 +-- src/Parser/RichParser.php | 2 +- src/Parser/SimpleParser.php | 2 +- src/Parser/TypeTraverserInstanceofVisitor.php | 2 +- src/Php/PhpVersion.php | 5 +++- src/Php/PhpVersionFactory.php | 2 +- src/Php/PhpVersionFactoryFactory.php | 2 +- src/PhpDoc/ConstExprNodeResolver.php | 2 +- src/PhpDoc/ConstExprParserFactory.php | 2 +- src/PhpDoc/CountableStubFilesExtension.php | 2 +- src/PhpDoc/DefaultStubFilesProvider.php | 2 +- ...eNodeResolverExtensionRegistryProvider.php | 2 +- src/PhpDoc/JsonValidateStubFilesExtension.php | 2 +- ...eNodeResolverExtensionRegistryProvider.php | 2 +- src/PhpDoc/PhpDocBlock.php | 2 +- src/PhpDoc/PhpDocInheritanceResolver.php | 2 +- src/PhpDoc/PhpDocNodeResolver.php | 2 +- src/PhpDoc/PhpDocStringResolver.php | 2 +- .../ReflectionEnumStubFilesExtension.php | 2 +- src/PhpDoc/ResolvedPhpDocBlock.php | 5 +++- src/PhpDoc/SocketSelectStubFilesExtension.php | 2 +- src/PhpDoc/StubPhpDocProvider.php | 2 +- src/PhpDoc/StubSourceLocatorFactory.php | 2 +- src/PhpDoc/StubValidator.php | 2 +- src/PhpDoc/Tag/AssertTagParameter.php | 2 +- src/PhpDoc/Tag/DeprecatedTag.php | 5 +++- src/PhpDoc/Tag/ExtendsTag.php | 5 +++- src/PhpDoc/Tag/ImplementsTag.php | 5 +++- src/PhpDoc/Tag/MethodTag.php | 5 +++- src/PhpDoc/Tag/MethodTagParameter.php | 5 +++- src/PhpDoc/Tag/MixinTag.php | 5 +++- src/PhpDoc/Tag/ParamClosureThisTag.php | 5 +++- src/PhpDoc/Tag/ParamOutTag.php | 5 +++- src/PhpDoc/Tag/ParamTag.php | 5 +++- src/PhpDoc/Tag/PropertyTag.php | 5 +++- src/PhpDoc/Tag/RequireExtendsTag.php | 5 +++- src/PhpDoc/Tag/RequireImplementsTag.php | 5 +++- src/PhpDoc/Tag/ReturnTag.php | 5 +++- src/PhpDoc/Tag/SelfOutTypeTag.php | 4 +++ src/PhpDoc/Tag/TemplateTag.php | 5 +++- src/PhpDoc/Tag/ThrowsTag.php | 5 +++- src/PhpDoc/Tag/TypeAliasTag.php | 5 +++- src/PhpDoc/Tag/UsesTag.php | 5 +++- src/PhpDoc/Tag/VarTag.php | 5 +++- src/PhpDoc/TypeNodeResolver.php | 2 +- ...TypeNodeResolverExtensionAwareRegistry.php | 2 +- src/PhpDoc/TypeStringResolver.php | 2 +- src/Process/CpuCoreCounter.php | 2 +- src/Process/ProcessCanceledException.php | 2 +- src/Process/ProcessCrashedException.php | 2 +- src/Process/ProcessHelper.php | 2 +- src/Process/ProcessPromise.php | 2 +- .../AnnotationMethodReflection.php | 2 +- .../AnnotationPropertyReflection.php | 2 +- .../AnnotationsMethodParameterReflection.php | 2 +- ...tationsMethodsClassReflectionExtension.php | 2 +- ...ionsPropertiesClassReflectionExtension.php | 2 +- src/Reflection/Assertions.php | 1 + .../BetterReflectionProvider.php | 2 +- .../BetterReflectionSourceLocatorFactory.php | 2 +- .../AutoloadFunctionsSourceLocator.php | 2 +- .../SourceLocator/AutoloadSourceLocator.php | 2 +- .../SourceLocator/CachingVisitor.php | 2 +- ...JsonAndInstalledJsonSourceLocatorMaker.php | 2 +- .../SourceLocator/FetchedNode.php | 2 +- .../SourceLocator/FetchedNodesResult.php | 2 +- .../SourceLocator/FileNodesFetcher.php | 2 +- .../NewOptimizedDirectorySourceLocator.php | 2 +- .../OptimizedDirectorySourceLocator.php | 2 +- ...OptimizedDirectorySourceLocatorFactory.php | 2 +- ...imizedDirectorySourceLocatorRepository.php | 2 +- .../OptimizedPsrAutoloaderLocator.php | 2 +- .../OptimizedSingleFileSourceLocator.php | 2 +- ...mizedSingleFileSourceLocatorRepository.php | 2 +- .../SourceLocator/PhpFileCleaner.php | 2 +- .../PhpVersionBlacklistSourceLocator.php | 2 +- .../ReflectionClassSourceLocator.php | 2 +- .../RewriteClassAliasSourceLocator.php | 2 +- .../SkipClassAliasSourceLocator.php | 2 +- .../PhpStormStubsSourceStubberFactory.php | 2 +- .../ReflectionSourceStubberFactory.php | 2 +- .../CallableFunctionVariantWithPhpDocs.php | 2 +- .../Callables/FunctionCallableVariant.php | 2 +- .../Callables/SimpleImpurePoint.php | 2 +- src/Reflection/Callables/SimpleThrowPoint.php | 2 +- src/Reflection/ClassConstantReflection.php | 5 +++- src/Reflection/ClassNameHelper.php | 2 +- src/Reflection/ClassReflection.php | 5 +++- .../ClassReflectionExtensionRegistry.php | 2 +- .../Constant/RuntimeConstantReflection.php | 2 +- src/Reflection/ConstantNameHelper.php | 2 +- .../Dummy/ChangedTypeMethodReflection.php | 2 +- .../Dummy/ChangedTypePropertyReflection.php | 2 +- .../Dummy/DummyConstantReflection.php | 2 +- .../Dummy/DummyConstructorReflection.php | 2 +- .../Dummy/DummyMethodReflection.php | 2 +- .../Dummy/DummyPropertyReflection.php | 2 +- src/Reflection/EnumCaseReflection.php | 5 +++- src/Reflection/FunctionVariant.php | 4 ++- src/Reflection/FunctionVariantWithPhpDocs.php | 4 ++- .../GenericParametersAcceptorResolver.php | 2 +- src/Reflection/InaccessibleMethod.php | 2 +- src/Reflection/InitializerExprContext.php | 5 +++- .../InitializerExprTypeResolver.php | 2 +- src/Reflection/MethodPrototypeReflection.php | 2 +- ...MissingConstantFromReflectionException.php | 2 +- .../MissingMethodFromReflectionException.php | 2 +- ...MissingPropertyFromReflectionException.php | 2 +- .../Mixin/MixinMethodReflection.php | 2 +- .../MixinMethodsClassReflectionExtension.php | 2 +- ...ixinPropertiesClassReflectionExtension.php | 2 +- .../Native/NativeFunctionReflection.php | 2 +- .../Native/NativeMethodReflection.php | 2 +- .../Native/NativeParameterReflection.php | 2 +- .../NativeParameterWithPhpDocsReflection.php | 2 +- ...onEnumReturnDynamicReturnTypeExtension.php | 2 +- src/Reflection/ParametersAcceptorSelector.php | 5 +++- src/Reflection/PassedByReference.php | 5 +++- ...allUnresolvedMethodPrototypeReflection.php | 2 +- .../Php/DummyParameterWithPhpDocs.php | 2 +- ...llowedSubTypesClassReflectionExtension.php | 2 +- .../Php/EnumCasesMethodReflection.php | 2 +- src/Reflection/Php/EnumPropertyReflection.php | 2 +- ...mUnresolvedPropertyPrototypeReflection.php | 2 +- .../Php/NativeBuiltinMethodReflection.php | 7 +++--- .../Php/PhpClassReflectionExtension.php | 2 +- src/Reflection/Php/PhpFunctionReflection.php | 2 +- .../Php/PhpMethodFromParserNodeReflection.php | 1 + src/Reflection/Php/PhpMethodReflection.php | 5 +++- .../PhpParameterFromParserNodeReflection.php | 2 +- src/Reflection/Php/PhpParameterReflection.php | 2 +- src/Reflection/Php/PhpPropertyReflection.php | 5 +++- .../Php/SimpleXMLElementProperty.php | 2 +- .../Php/Soap/SoapClientMethodReflection.php | 4 +-- ...pClientMethodsClassReflectionExtension.php | 2 +- .../Php/UniversalObjectCrateProperty.php | 2 +- ...alObjectCratesClassReflectionExtension.php | 2 +- src/Reflection/PhpVersionStaticAccessor.php | 2 +- .../DirectReflectionProviderProvider.php | 2 +- .../DummyReflectionProvider.php | 2 +- .../LazyReflectionProviderProvider.php | 2 +- .../MemoizingReflectionProvider.php | 2 +- .../ReflectionProviderFactory.php | 2 +- .../SetterReflectionProviderProvider.php | 2 +- .../ReflectionProviderStaticAccessor.php | 2 +- ...ExtendsMethodsClassReflectionExtension.php | 2 +- ...endsPropertiesClassReflectionExtension.php | 2 +- .../ResolvedFunctionVariantWithCallable.php | 2 +- .../ResolvedFunctionVariantWithOriginal.php | 2 +- src/Reflection/ResolvedMethodReflection.php | 2 +- src/Reflection/ResolvedPropertyReflection.php | 2 +- .../SignatureMap/FunctionSignature.php | 2 +- .../FunctionSignatureMapProvider.php | 2 +- .../NativeFunctionReflectionProvider.php | 2 +- .../SignatureMap/ParameterSignature.php | 2 +- .../SignatureMap/Php8SignatureMapProvider.php | 2 +- .../SignatureMap/SignatureMapParser.php | 2 +- .../SignatureMapProviderFactory.php | 2 +- src/Reflection/TrivialParametersAcceptor.php | 5 +++- ...ackUnresolvedMethodPrototypeReflection.php | 2 +- ...kUnresolvedPropertyPrototypeReflection.php | 2 +- ...ypeUnresolvedMethodPrototypeReflection.php | 2 +- ...eUnresolvedPropertyPrototypeReflection.php | 2 +- .../Type/IntersectionTypeMethodReflection.php | 2 +- .../IntersectionTypePropertyReflection.php | 2 +- ...ypeUnresolvedMethodPrototypeReflection.php | 2 +- ...eUnresolvedPropertyPrototypeReflection.php | 2 +- .../Type/UnionTypeMethodReflection.php | 2 +- .../Type/UnionTypePropertyReflection.php | 2 +- ...ypeUnresolvedMethodPrototypeReflection.php | 2 +- ...eUnresolvedPropertyPrototypeReflection.php | 2 +- .../WrappedExtendedMethodReflection.php | 2 +- src/Rules/Api/ApiClassConstFetchRule.php | 2 +- src/Rules/Api/ApiClassExtendsRule.php | 2 +- src/Rules/Api/ApiClassImplementsRule.php | 2 +- src/Rules/Api/ApiInstanceofRule.php | 2 +- src/Rules/Api/ApiInstanceofTypeRule.php | 2 +- src/Rules/Api/ApiInstantiationRule.php | 2 +- src/Rules/Api/ApiInterfaceExtendsRule.php | 2 +- src/Rules/Api/ApiMethodCallRule.php | 2 +- src/Rules/Api/ApiRuleHelper.php | 2 +- src/Rules/Api/ApiStaticCallRule.php | 2 +- src/Rules/Api/ApiTraitUseRule.php | 2 +- src/Rules/Api/GetTemplateTypeRule.php | 2 +- .../NodeConnectingVisitorAttributesRule.php | 2 +- .../PhpStanNamespaceIn3rdPartyPackageRule.php | 2 +- .../Api/RuntimeReflectionFunctionRule.php | 2 +- .../RuntimeReflectionInstantiationRule.php | 2 +- src/Rules/Arrays/AllowedArrayKeysTypes.php | 2 +- .../Arrays/AppendedArrayItemTypeRule.php | 2 +- src/Rules/Arrays/AppendedArrayKeyTypeRule.php | 2 +- src/Rules/Arrays/ArrayDestructuringRule.php | 2 +- src/Rules/Arrays/ArrayUnpackingRule.php | 2 +- src/Rules/Arrays/DeadForeachRule.php | 2 +- .../DuplicateKeysInLiteralArraysRule.php | 2 +- src/Rules/Arrays/EmptyArrayItemRule.php | 2 +- .../Arrays/InvalidKeyInArrayDimFetchRule.php | 2 +- .../Arrays/InvalidKeyInArrayItemRule.php | 2 +- src/Rules/Arrays/IterableInForeachRule.php | 2 +- .../NonexistentOffsetInArrayDimFetchCheck.php | 2 +- .../NonexistentOffsetInArrayDimFetchRule.php | 2 +- src/Rules/Arrays/OffsetAccessAssignOpRule.php | 2 +- .../Arrays/OffsetAccessAssignmentRule.php | 2 +- .../OffsetAccessValueAssignmentRule.php | 2 +- .../OffsetAccessWithoutDimForReadingRule.php | 2 +- .../Arrays/UnpackIterableInArrayRule.php | 2 +- src/Rules/AttributesCheck.php | 2 +- src/Rules/Cast/EchoRule.php | 2 +- src/Rules/Cast/InvalidCastRule.php | 2 +- .../Cast/InvalidPartOfEncapsedStringRule.php | 2 +- src/Rules/Cast/PrintRule.php | 2 +- src/Rules/Cast/UnsetCastRule.php | 2 +- src/Rules/ClassCaseSensitivityCheck.php | 2 +- src/Rules/ClassForbiddenNameCheck.php | 2 +- src/Rules/ClassNameCheck.php | 2 +- src/Rules/ClassNameNodePair.php | 2 +- ...AccessPrivateConstantThroughStaticRule.php | 2 +- src/Rules/Classes/AllowedSubTypesRule.php | 2 +- src/Rules/Classes/ClassAttributesRule.php | 2 +- .../Classes/ClassConstantAttributesRule.php | 2 +- src/Rules/Classes/ClassConstantRule.php | 2 +- .../Classes/DuplicateClassDeclarationRule.php | 2 +- .../Classes/DuplicateDeclarationRule.php | 2 +- src/Rules/Classes/EnumSanityRule.php | 2 +- .../ExistingClassInClassExtendsRule.php | 2 +- .../Classes/ExistingClassInInstanceOfRule.php | 2 +- .../Classes/ExistingClassInTraitUseRule.php | 2 +- .../ExistingClassesInClassImplementsRule.php | 2 +- .../ExistingClassesInEnumImplementsRule.php | 2 +- .../ExistingClassesInInterfaceExtendsRule.php | 2 +- .../Classes/ImpossibleInstanceOfRule.php | 2 +- .../Classes/InstantiationCallableRule.php | 2 +- src/Rules/Classes/InstantiationRule.php | 2 +- .../Classes/InvalidPromotedPropertiesRule.php | 2 +- src/Rules/Classes/LocalTypeAliasesCheck.php | 2 +- src/Rules/Classes/LocalTypeAliasesRule.php | 2 +- .../Classes/LocalTypeTraitAliasesRule.php | 2 +- src/Rules/Classes/MixinRule.php | 2 +- src/Rules/Classes/NewStaticRule.php | 2 +- .../Classes/NonClassAttributeClassRule.php | 2 +- src/Rules/Classes/ReadOnlyClassRule.php | 2 +- src/Rules/Classes/RequireExtendsRule.php | 2 +- src/Rules/Classes/RequireImplementsRule.php | 2 +- src/Rules/Classes/TraitAttributeClassRule.php | 2 +- .../UnusedConstructorParametersRule.php | 2 +- .../BooleanAndConstantConditionRule.php | 2 +- .../BooleanNotConstantConditionRule.php | 2 +- .../BooleanOrConstantConditionRule.php | 2 +- .../ConstantConditionRuleHelper.php | 2 +- .../ConstantLooseComparisonRule.php | 2 +- .../DoWhileLoopConstantConditionRule.php | 2 +- .../ElseIfConstantConditionRule.php | 2 +- .../Comparison/IfConstantConditionRule.php | 2 +- .../ImpossibleCheckTypeFunctionCallRule.php | 2 +- .../Comparison/ImpossibleCheckTypeHelper.php | 2 +- .../ImpossibleCheckTypeMethodCallRule.php | 2 +- ...mpossibleCheckTypeStaticMethodCallRule.php | 2 +- .../LogicalXorConstantConditionRule.php | 2 +- src/Rules/Comparison/MatchExpressionRule.php | 2 +- ...mparisonOperatorsConstantConditionRule.php | 2 +- .../StrictComparisonOfDifferentTypesRule.php | 2 +- .../TernaryOperatorConstantConditionRule.php | 2 +- .../Comparison/UnreachableIfBranchesRule.php | 2 +- .../UnreachableTernaryElseBranchRule.php | 2 +- .../UsageOfVoidMatchExpressionRule.php | 2 +- .../WhileLoopAlwaysFalseConditionRule.php | 2 +- .../WhileLoopAlwaysTrueConditionRule.php | 2 +- src/Rules/Constants/ConstantRule.php | 2 +- .../DynamicClassConstantFetchRule.php | 2 +- src/Rules/Constants/FinalConstantRule.php | 2 +- ...aysUsedClassConstantsExtensionProvider.php | 2 +- .../Constants/MagicConstantContextRule.php | 2 +- .../NativeTypedClassConstantRule.php | 2 +- .../Constants/OverridingConstantRule.php | 2 +- .../ValueAssignedToClassConstantRule.php | 2 +- src/Rules/DateTimeInstantiationRule.php | 2 +- src/Rules/DeadCode/BetterNoopRule.php | 2 +- ...ructorStatementWithoutImpurePointsRule.php | 2 +- ...nctionStatementWithoutImpurePointsRule.php | 2 +- ...MethodStatementWithoutImpurePointsRule.php | 2 +- ...MethodStatementWithoutImpurePointsRule.php | 2 +- ...onstructorWithoutImpurePointsCollector.php | 2 +- .../FunctionWithoutImpurePointsCollector.php | 2 +- .../MethodWithoutImpurePointsCollector.php | 2 +- src/Rules/DeadCode/NoopRule.php | 2 +- .../PossiblyPureFuncCallCollector.php | 2 +- .../PossiblyPureMethodCallCollector.php | 2 +- .../DeadCode/PossiblyPureNewCollector.php | 2 +- .../PossiblyPureStaticCallCollector.php | 2 +- .../DeadCode/UnreachableStatementRule.php | 2 +- .../DeadCode/UnusedPrivateConstantRule.php | 2 +- .../DeadCode/UnusedPrivateMethodRule.php | 2 +- .../DeadCode/UnusedPrivatePropertyRule.php | 2 +- src/Rules/Debug/DumpTypeRule.php | 2 +- src/Rules/Debug/FileAssertRule.php | 2 +- src/Rules/DirectRegistry.php | 3 +++ .../EnumCases/EnumCaseAttributesRule.php | 2 +- .../CatchWithUnthrownExceptionRule.php | 2 +- .../CaughtExceptionExistenceRule.php | 2 +- .../DefaultExceptionTypeResolver.php | 5 +++- ...ngCheckedExceptionInFunctionThrowsRule.php | 2 +- ...singCheckedExceptionInMethodThrowsRule.php | 2 +- .../MissingCheckedExceptionInThrowsCheck.php | 2 +- .../Exceptions/NoncapturingCatchRule.php | 2 +- .../OverwrittenExitPointByFinallyRule.php | 2 +- src/Rules/Exceptions/ThrowExprTypeRule.php | 2 +- src/Rules/Exceptions/ThrowExpressionRule.php | 2 +- ...VoidFunctionWithExplicitThrowPointRule.php | 2 +- ...wsVoidMethodWithExplicitThrowPointRule.php | 2 +- .../TooWideFunctionThrowTypeRule.php | 2 +- .../Exceptions/TooWideMethodThrowTypeRule.php | 2 +- .../Exceptions/TooWideThrowTypeCheck.php | 2 +- src/Rules/FoundTypeResult.php | 5 +++- src/Rules/FunctionCallParametersCheck.php | 2 +- src/Rules/FunctionDefinitionCheck.php | 2 +- src/Rules/FunctionReturnTypeCheck.php | 2 +- src/Rules/Functions/ArrayFilterRule.php | 2 +- src/Rules/Functions/ArrayValuesRule.php | 2 +- .../Functions/ArrowFunctionAttributesRule.php | 2 +- .../ArrowFunctionReturnNullsafeByRefRule.php | 2 +- .../Functions/ArrowFunctionReturnTypeRule.php | 2 +- src/Rules/Functions/CallCallablesRule.php | 2 +- .../CallToFunctionParametersRule.php | 2 +- ...unctionStatementWithoutSideEffectsRule.php | 2 +- .../CallToNonExistentFunctionRule.php | 2 +- src/Rules/Functions/CallUserFuncRule.php | 2 +- src/Rules/Functions/ClosureAttributesRule.php | 2 +- src/Rules/Functions/ClosureReturnTypeRule.php | 2 +- src/Rules/Functions/DefineParametersRule.php | 2 +- .../DuplicateFunctionDeclarationRule.php | 2 +- ...ingClassesInArrowFunctionTypehintsRule.php | 2 +- .../ExistingClassesInClosureTypehintsRule.php | 2 +- .../ExistingClassesInTypehintsRule.php | 2 +- .../Functions/FunctionAttributesRule.php | 2 +- src/Rules/Functions/FunctionCallableRule.php | 2 +- src/Rules/Functions/ImplodeFunctionRule.php | 2 +- .../ImplodeParameterCastableToStringRule.php | 2 +- ...eArrowFunctionDefaultParameterTypeRule.php | 2 +- ...patibleClosureDefaultParameterTypeRule.php | 2 +- .../IncompatibleDefaultParameterTypeRule.php | 2 +- src/Rules/Functions/InnerFunctionRule.php | 2 +- ...nvalidLexicalVariablesInClosureUseRule.php | 2 +- src/Rules/Functions/ParamAttributesRule.php | 2 +- .../ParameterCastableToStringRule.php | 2 +- .../Functions/PrintfArrayParametersRule.php | 2 +- src/Rules/Functions/PrintfParametersRule.php | 2 +- .../Functions/RandomIntParametersRule.php | 2 +- .../Functions/RedefinedParametersRule.php | 2 +- .../Functions/ReturnNullsafeByRefRule.php | 2 +- src/Rules/Functions/ReturnTypeRule.php | 2 +- .../SortParameterCastableToStringRule.php | 2 +- src/Rules/Functions/UnusedClosureUsesRule.php | 2 +- .../UselessFunctionReturnValueRule.php | 2 +- .../VariadicParametersDeclarationRule.php | 2 +- src/Rules/Generators/YieldFromTypeRule.php | 2 +- src/Rules/Generators/YieldInGeneratorRule.php | 2 +- src/Rules/Generators/YieldTypeRule.php | 2 +- src/Rules/Generics/ClassAncestorsRule.php | 2 +- src/Rules/Generics/ClassTemplateTypeRule.php | 2 +- .../Generics/CrossCheckInterfacesHelper.php | 2 +- src/Rules/Generics/EnumAncestorsRule.php | 2 +- src/Rules/Generics/EnumTemplateTypeRule.php | 2 +- .../FunctionSignatureVarianceRule.php | 2 +- .../Generics/FunctionTemplateTypeRule.php | 2 +- src/Rules/Generics/GenericAncestorsCheck.php | 2 +- src/Rules/Generics/GenericObjectTypeCheck.php | 2 +- src/Rules/Generics/InterfaceAncestorsRule.php | 2 +- .../Generics/InterfaceTemplateTypeRule.php | 2 +- .../Generics/MethodSignatureVarianceRule.php | 2 +- .../Generics/MethodTagTemplateTypeRule.php | 2 +- src/Rules/Generics/MethodTemplateTypeRule.php | 2 +- src/Rules/Generics/PropertyVarianceRule.php | 2 +- src/Rules/Generics/TemplateTypeCheck.php | 2 +- src/Rules/Generics/TraitTemplateTypeRule.php | 2 +- src/Rules/Generics/UsedTraitsRule.php | 2 +- src/Rules/Generics/VarianceCheck.php | 2 +- src/Rules/Ignore/IgnoreParseErrorRule.php | 2 +- src/Rules/IssetCheck.php | 2 +- .../Keywords/ContinueBreakInLoopRule.php | 2 +- src/Rules/Keywords/DeclareStrictTypesRule.php | 2 +- src/Rules/LazyRegistry.php | 2 +- .../AbstractMethodInNonAbstractClassRule.php | 2 +- .../Methods/AbstractPrivateMethodRule.php | 2 +- src/Rules/Methods/CallMethodsRule.php | 2 +- .../CallPrivateMethodThroughStaticRule.php | 2 +- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- ...tructorStatementWithoutSideEffectsRule.php | 2 +- ...oMethodStatementWithoutSideEffectsRule.php | 2 +- ...cMethodStatementWithoutSideEffectsRule.php | 2 +- .../Methods/ConsistentConstructorRule.php | 2 +- .../Methods/ConstructorReturnTypeRule.php | 2 +- ...irectAlwaysUsedMethodExtensionProvider.php | 2 +- .../ExistingClassesInTypehintsRule.php | 2 +- src/Rules/Methods/FinalPrivateMethodRule.php | 2 +- .../IllegalConstructorMethodCallRule.php | 2 +- .../IllegalConstructorStaticCallRule.php | 2 +- .../IncompatibleDefaultParameterTypeRule.php | 2 +- .../LazyAlwaysUsedMethodExtensionProvider.php | 2 +- src/Rules/Methods/MethodAttributesRule.php | 2 +- src/Rules/Methods/MethodCallCheck.php | 2 +- src/Rules/Methods/MethodCallableRule.php | 2 +- .../MethodParameterComparisonHelper.php | 2 +- src/Rules/Methods/MethodSignatureRule.php | 2 +- .../MethodVisibilityInInterfaceRule.php | 2 +- .../MissingMagicSerializationMethodsRule.php | 2 +- .../MissingMethodImplementationRule.php | 2 +- src/Rules/Methods/NullsafeMethodCallRule.php | 2 +- src/Rules/Methods/OverridingMethodRule.php | 2 +- src/Rules/Methods/ReturnTypeRule.php | 2 +- src/Rules/Methods/StaticMethodCallCheck.php | 2 +- .../Methods/StaticMethodCallableRule.php | 2 +- src/Rules/Missing/MissingReturnRule.php | 2 +- src/Rules/MissingTypehintCheck.php | 2 +- .../ExistingNamesInGroupUseRule.php | 2 +- .../Namespaces/ExistingNamesInUseRule.php | 2 +- src/Rules/NullsafeCheck.php | 2 +- src/Rules/Operators/InvalidAssignVarRule.php | 2 +- .../Operators/InvalidBinaryOperationRule.php | 2 +- .../InvalidComparisonOperationRule.php | 2 +- .../Operators/InvalidIncDecOperationRule.php | 2 +- .../Operators/InvalidUnaryOperationRule.php | 2 +- src/Rules/ParameterCastableToStringCheck.php | 2 +- src/Rules/PhpDoc/AssertRuleHelper.php | 2 +- .../ConditionalReturnTypeRuleHelper.php | 2 +- src/Rules/PhpDoc/FunctionAssertRule.php | 2 +- .../FunctionConditionalReturnTypeRule.php | 2 +- .../PhpDoc/GenericCallableRuleHelper.php | 2 +- ...ncompatibleClassConstantPhpDocTypeRule.php | 2 +- .../PhpDoc/IncompatiblePhpDocTypeRule.php | 2 +- .../IncompatiblePropertyPhpDocTypeRule.php | 2 +- .../PhpDoc/IncompatibleSelfOutTypeRule.php | 2 +- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 2 +- .../PhpDoc/InvalidPhpDocTagValueRule.php | 2 +- .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 2 +- .../PhpDoc/InvalidThrowsPhpDocValueRule.php | 2 +- src/Rules/PhpDoc/MethodAssertRule.php | 2 +- .../MethodConditionalReturnTypeRule.php | 2 +- src/Rules/PhpDoc/PhpDocLineHelper.php | 2 +- .../RequireExtendsDefinitionClassRule.php | 2 +- .../RequireExtendsDefinitionTraitRule.php | 2 +- .../RequireImplementsDefinitionClassRule.php | 2 +- .../RequireImplementsDefinitionTraitRule.php | 2 +- src/Rules/PhpDoc/UnresolvableTypeHelper.php | 2 +- .../VarTagChangedExpressionTypeRule.php | 2 +- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 2 +- .../PhpDoc/WrongVariableNameInVarTagRule.php | 2 +- src/Rules/Playground/FunctionNeverRule.php | 2 +- src/Rules/Playground/MethodNeverRule.php | 2 +- src/Rules/Playground/NeverRuleHelper.php | 2 +- src/Rules/Playground/NoPhpCodeRule.php | 2 +- src/Rules/Playground/NotAnalysedTraitRule.php | 2 +- ...AccessPrivatePropertyThroughStaticRule.php | 2 +- .../AccessPropertiesInAssignRule.php | 2 +- src/Rules/Properties/AccessPropertiesRule.php | 2 +- .../AccessStaticPropertiesInAssignRule.php | 2 +- .../Properties/AccessStaticPropertiesRule.php | 2 +- ...aultValueTypesAssignedToPropertiesRule.php | 2 +- ...ctReadWritePropertiesExtensionProvider.php | 2 +- .../ExistingClassesInPropertiesRule.php | 2 +- .../Properties/FoundPropertyReflection.php | 2 +- .../InvalidCallablePropertyTypeRule.php | 2 +- ...zyReadWritePropertiesExtensionProvider.php | 2 +- ...singReadOnlyByPhpDocPropertyAssignRule.php | 2 +- .../MissingReadOnlyPropertyAssignRule.php | 2 +- .../Properties/NullsafePropertyFetchRule.php | 2 +- .../Properties/OverridingPropertyRule.php | 2 +- .../Properties/PropertiesInInterfaceRule.php | 2 +- .../Properties/PropertyAttributesRule.php | 2 +- src/Rules/Properties/PropertyDescriptor.php | 2 +- .../Properties/PropertyReflectionFinder.php | 2 +- .../ReadOnlyByPhpDocPropertyAssignRefRule.php | 2 +- .../ReadOnlyByPhpDocPropertyAssignRule.php | 2 +- .../ReadOnlyByPhpDocPropertyRule.php | 2 +- .../ReadOnlyPropertyAssignRefRule.php | 2 +- .../Properties/ReadOnlyPropertyAssignRule.php | 2 +- src/Rules/Properties/ReadOnlyPropertyRule.php | 2 +- .../ReadingWriteOnlyPropertiesRule.php | 2 +- .../TypesAssignedToPropertiesRule.php | 2 +- .../Properties/UninitializedPropertyRule.php | 2 +- .../WritingToReadOnlyPropertiesRule.php | 2 +- src/Rules/Pure/FunctionPurityCheck.php | 2 +- src/Rules/Pure/PureFunctionRule.php | 2 +- src/Rules/Pure/PureMethodRule.php | 2 +- .../Regexp/RegularExpressionPatternRule.php | 2 +- .../Regexp/RegularExpressionQuotingRule.php | 2 +- src/Rules/RuleErrorBuilder.php | 1 + src/Rules/RuleErrors/RuleError1.php | 2 +- src/Rules/RuleErrors/RuleError101.php | 2 +- src/Rules/RuleErrors/RuleError103.php | 2 +- src/Rules/RuleErrors/RuleError105.php | 2 +- src/Rules/RuleErrors/RuleError107.php | 2 +- src/Rules/RuleErrors/RuleError109.php | 2 +- src/Rules/RuleErrors/RuleError11.php | 2 +- src/Rules/RuleErrors/RuleError111.php | 2 +- src/Rules/RuleErrors/RuleError113.php | 2 +- src/Rules/RuleErrors/RuleError115.php | 2 +- src/Rules/RuleErrors/RuleError117.php | 2 +- src/Rules/RuleErrors/RuleError119.php | 2 +- src/Rules/RuleErrors/RuleError121.php | 2 +- src/Rules/RuleErrors/RuleError123.php | 2 +- src/Rules/RuleErrors/RuleError125.php | 2 +- src/Rules/RuleErrors/RuleError127.php | 2 +- src/Rules/RuleErrors/RuleError13.php | 2 +- src/Rules/RuleErrors/RuleError15.php | 2 +- src/Rules/RuleErrors/RuleError17.php | 2 +- src/Rules/RuleErrors/RuleError19.php | 2 +- src/Rules/RuleErrors/RuleError21.php | 2 +- src/Rules/RuleErrors/RuleError23.php | 2 +- src/Rules/RuleErrors/RuleError25.php | 2 +- src/Rules/RuleErrors/RuleError27.php | 2 +- src/Rules/RuleErrors/RuleError29.php | 2 +- src/Rules/RuleErrors/RuleError3.php | 2 +- src/Rules/RuleErrors/RuleError31.php | 2 +- src/Rules/RuleErrors/RuleError33.php | 2 +- src/Rules/RuleErrors/RuleError35.php | 2 +- src/Rules/RuleErrors/RuleError37.php | 2 +- src/Rules/RuleErrors/RuleError39.php | 2 +- src/Rules/RuleErrors/RuleError41.php | 2 +- src/Rules/RuleErrors/RuleError43.php | 2 +- src/Rules/RuleErrors/RuleError45.php | 2 +- src/Rules/RuleErrors/RuleError47.php | 2 +- src/Rules/RuleErrors/RuleError49.php | 2 +- src/Rules/RuleErrors/RuleError5.php | 2 +- src/Rules/RuleErrors/RuleError51.php | 2 +- src/Rules/RuleErrors/RuleError53.php | 2 +- src/Rules/RuleErrors/RuleError55.php | 2 +- src/Rules/RuleErrors/RuleError57.php | 2 +- src/Rules/RuleErrors/RuleError59.php | 2 +- src/Rules/RuleErrors/RuleError61.php | 2 +- src/Rules/RuleErrors/RuleError63.php | 2 +- src/Rules/RuleErrors/RuleError65.php | 2 +- src/Rules/RuleErrors/RuleError67.php | 2 +- src/Rules/RuleErrors/RuleError69.php | 2 +- src/Rules/RuleErrors/RuleError7.php | 2 +- src/Rules/RuleErrors/RuleError71.php | 2 +- src/Rules/RuleErrors/RuleError73.php | 2 +- src/Rules/RuleErrors/RuleError75.php | 2 +- src/Rules/RuleErrors/RuleError77.php | 2 +- src/Rules/RuleErrors/RuleError79.php | 2 +- src/Rules/RuleErrors/RuleError81.php | 2 +- src/Rules/RuleErrors/RuleError83.php | 2 +- src/Rules/RuleErrors/RuleError85.php | 2 +- src/Rules/RuleErrors/RuleError87.php | 2 +- src/Rules/RuleErrors/RuleError89.php | 2 +- src/Rules/RuleErrors/RuleError9.php | 2 +- src/Rules/RuleErrors/RuleError91.php | 2 +- src/Rules/RuleErrors/RuleError93.php | 2 +- src/Rules/RuleErrors/RuleError95.php | 2 +- src/Rules/RuleErrors/RuleError97.php | 2 +- src/Rules/RuleErrors/RuleError99.php | 2 +- src/Rules/RuleLevelHelper.php | 2 +- src/Rules/RuleLevelHelperAcceptsResult.php | 2 +- ...TooWideArrowFunctionReturnTypehintRule.php | 2 +- .../TooWideClosureReturnTypehintRule.php | 2 +- .../TooWideFunctionParameterOutTypeRule.php | 2 +- .../TooWideFunctionReturnTypehintRule.php | 2 +- .../TooWideMethodParameterOutTypeRule.php | 2 +- .../TooWideMethodReturnTypehintRule.php | 2 +- .../TooWideParameterOutTypeCheck.php | 2 +- .../Traits/ConflictingTraitConstantsRule.php | 2 +- src/Rules/Traits/ConstantsInTraitsRule.php | 2 +- src/Rules/Traits/NotAnalysedTraitRule.php | 2 +- .../Traits/TraitDeclarationCollector.php | 2 +- src/Rules/Traits/TraitUseCollector.php | 7 ++++-- src/Rules/Types/InvalidTypesInUnionRule.php | 2 +- src/Rules/UnusedFunctionParametersCheck.php | 2 +- src/Rules/Variables/DefinedVariableRule.php | 2 +- src/Rules/Variables/EmptyRule.php | 2 +- src/Rules/Variables/IssetRule.php | 2 +- src/Rules/Variables/NullCoalesceRule.php | 2 +- .../ParameterOutAssignedTypeRule.php | 2 +- .../ParameterOutExecutionEndTypeRule.php | 2 +- src/Rules/Variables/ThrowTypeRule.php | 2 +- src/Rules/Variables/UnsetRule.php | 2 +- src/Rules/Variables/VariableCloningRule.php | 2 +- src/Rules/Whitespace/FileWhitespaceRule.php | 2 +- src/Testing/TestCaseSourceLocatorFactory.php | 2 +- src/TrinaryLogic.php | 1 + src/Type/AcceptsResult.php | 5 +++- src/Type/CallableTypeHelper.php | 2 +- .../CircularTypeAliasDefinitionException.php | 2 +- src/Type/ClosureTypeFactory.php | 5 +++- .../Constant/ConstantArrayTypeAndMethod.php | 5 +++- .../Constant/ConstantArrayTypeBuilder.php | 5 +++- src/Type/Constant/OversizedArrayBuilder.php | 2 +- src/Type/ConstantTypeHelper.php | 5 +++- src/Type/DirectTypeAliasResolverProvider.php | 2 +- .../DynamicReturnTypeExtensionRegistry.php | 2 +- ...xpressionTypeResolverExtensionRegistry.php | 2 +- src/Type/FileTypeMapper.php | 2 +- src/Type/GeneralizePrecision.php | 2 +- .../Generic/TemplateTypeArgumentStrategy.php | 2 +- src/Type/Generic/TemplateTypeHelper.php | 2 +- src/Type/Generic/TemplateTypeMap.php | 5 +++- .../Generic/TemplateTypeParameterStrategy.php | 2 +- src/Type/Generic/TemplateTypeReference.php | 2 +- src/Type/Generic/TemplateTypeScope.php | 2 +- src/Type/Generic/TemplateTypeVariance.php | 5 +++- src/Type/Generic/TemplateTypeVarianceMap.php | 5 +++- src/Type/Generic/TypeProjectionHelper.php | 2 +- src/Type/GenericTypeVariableResolver.php | 5 +++- src/Type/LazyTypeAliasResolverProvider.php | 2 +- src/Type/ObjectShapePropertyReflection.php | 2 +- ...peratorTypeSpecifyingExtensionRegistry.php | 2 +- src/Type/ParserNodeTypeToPHPStanType.php | 2 +- ...gumentBasedFunctionReturnTypeExtension.php | 2 +- ...ArrayColumnFunctionReturnTypeExtension.php | 2 +- ...rrayCombineFunctionReturnTypeExtension.php | 2 +- ...ArrayCurrentDynamicReturnTypeExtension.php | 2 +- .../ArrayFillFunctionReturnTypeExtension.php | 2 +- ...rayFillKeysFunctionReturnTypeExtension.php | 2 +- ...rFunctionReturnTypeReturnTypeExtension.php | 2 +- .../ArrayFlipFunctionReturnTypeExtension.php | 2 +- ...ntersectKeyFunctionReturnTypeExtension.php | 2 +- .../ArrayKeyDynamicReturnTypeExtension.php | 2 +- ...yExistsFunctionTypeSpecifyingExtension.php | 2 +- ...rrayKeyFirstDynamicReturnTypeExtension.php | 2 +- ...ArrayKeyLastDynamicReturnTypeExtension.php | 2 +- ...KeysFunctionDynamicReturnTypeExtension.php | 2 +- .../ArrayMapFunctionReturnTypeExtension.php | 2 +- ...ergeFunctionDynamicReturnTypeExtension.php | 2 +- .../ArrayNextDynamicReturnTypeExtension.php | 2 +- ...terFunctionsDynamicReturnTypeExtension.php | 2 +- .../ArrayPopFunctionReturnTypeExtension.php | 2 +- .../ArrayRandFunctionReturnTypeExtension.php | 2 +- ...ArrayReduceFunctionReturnTypeExtension.php | 2 +- ...rrayReplaceFunctionReturnTypeExtension.php | 2 +- ...rrayReverseFunctionReturnTypeExtension.php | 2 +- ...ySearchFunctionTypeSpecifyingExtension.php | 2 +- .../ArrayShiftFunctionReturnTypeExtension.php | 2 +- .../ArraySliceFunctionReturnTypeExtension.php | 2 +- ...ArraySpliceFunctionReturnTypeExtension.php | 2 +- ...luesFunctionDynamicReturnTypeExtension.php | 2 +- .../AssertFunctionTypeSpecifyingExtension.php | 2 +- src/Type/Php/AssertThrowTypeExtension.php | 2 +- ...umFromMethodDynamicReturnTypeExtension.php | 2 +- ...codeDynamicFunctionReturnTypeExtension.php | 2 +- .../BcMathStringOrNullReturnTypeExtension.php | 2 +- ...sExistsFunctionTypeSpecifyingExtension.php | 2 +- ...sImplementsFunctionReturnTypeExtension.php | 2 +- .../ClosureBindDynamicReturnTypeExtension.php | 2 +- ...losureBindToDynamicReturnTypeExtension.php | 2 +- ...FromCallableDynamicReturnTypeExtension.php | 2 +- .../CompactFunctionReturnTypeExtension.php | 2 +- .../ConstantFunctionReturnTypeExtension.php | 2 +- src/Type/Php/ConstantHelper.php | 2 +- .../Php/CountFunctionReturnTypeExtension.php | 2 +- .../CountFunctionTypeSpecifyingExtension.php | 2 +- ...peDigitFunctionTypeSpecifyingExtension.php | 2 +- src/Type/Php/CurlInitReturnTypeExtension.php | 2 +- .../DateFormatFunctionReturnTypeExtension.php | 4 +-- .../DateFormatMethodReturnTypeExtension.php | 4 +-- .../Php/DateFunctionReturnTypeExtension.php | 2 +- src/Type/Php/DateFunctionReturnTypeHelper.php | 4 +-- ...eIntervalConstructorThrowTypeExtension.php | 2 +- ...DateIntervalDynamicReturnTypeExtension.php | 2 +- ...tePeriodConstructorReturnTypeExtension.php | 2 +- .../DateTimeConstructorThrowTypeExtension.php | 2 +- ...teTimeCreateDynamicReturnTypeExtension.php | 2 +- .../DateTimeDynamicReturnTypeExtension.php | 2 +- .../Php/DateTimeModifyReturnTypeExtension.php | 2 +- ...eTimeZoneConstructorThrowTypeExtension.php | 2 +- .../DefineConstantTypeSpecifyingExtension.php | 2 +- ...DefinedConstantTypeSpecifyingExtension.php | 2 +- ...StatDynamicFunctionReturnTypeExtension.php | 2 +- ...lodeFunctionDynamicReturnTypeExtension.php | 2 +- .../FilterInputDynamicReturnTypeExtension.php | 2 +- ...lterVarArrayDynamicReturnTypeExtension.php | 2 +- .../FilterVarDynamicReturnTypeExtension.php | 2 +- ...nExistsFunctionTypeSpecifyingExtension.php | 2 +- ...tCalledClassDynamicReturnTypeExtension.php | 2 +- .../GetClassDynamicReturnTypeExtension.php | 2 +- ...etDebugTypeFunctionReturnTypeExtension.php | 2 +- ...lassDynamicFunctionReturnTypeExtension.php | 2 +- ...fdayDynamicFunctionReturnTypeExtension.php | 2 +- .../GettypeFunctionReturnTypeExtension.php | 2 +- .../Php/HrtimeFunctionReturnTypeExtension.php | 2 +- .../ImplodeFunctionReturnTypeExtension.php | 2 +- ...InArrayFunctionTypeSpecifyingExtension.php | 2 +- src/Type/Php/IniGetReturnTypeExtension.php | 2 +- src/Type/Php/IntdivThrowTypeExtension.php | 2 +- .../IsAFunctionTypeSpecifyingExtension.php | 2 +- ...IsArrayFunctionTypeSpecifyingExtension.php | 2 +- ...allableFunctionTypeSpecifyingExtension.php | 2 +- ...terableFunctionTypeSpecifyingExtension.php | 2 +- ...classOfFunctionTypeSpecifyingExtension.php | 2 +- ...ThrowOnErrorDynamicReturnTypeExtension.php | 2 +- src/Type/Php/JsonThrowTypeExtension.php | 2 +- .../Php/LtrimFunctionReturnTypeExtension.php | 2 +- ...ertEncodingFunctionReturnTypeExtension.php | 2 +- .../Php/MbFunctionsReturnTypeExtension.php | 2 +- .../MbStrlenFunctionReturnTypeExtension.php | 2 +- ...uteCharacterDynamicReturnTypeExtension.php | 2 +- .../MethodExistsTypeSpecifyingExtension.php | 2 +- .../MicrotimeFunctionReturnTypeExtension.php | 2 +- .../Php/MinMaxFunctionReturnTypeExtension.php | 2 +- ...mptyStringFunctionsReturnTypeExtension.php | 2 +- ...infoFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/PowFunctionReturnTypeExtension.php | 2 +- .../PregFilterFunctionReturnTypeExtension.php | 2 +- .../PregSplitDynamicReturnTypeExtension.php | 2 +- .../PropertyExistsTypeSpecifyingExtension.php | 2 +- .../RandomIntFunctionReturnTypeExtension.php | 2 +- .../Php/RangeFunctionReturnTypeExtension.php | 2 +- ...tionClassConstructorThrowTypeExtension.php | 2 +- ...assIsSubclassOfTypeSpecifyingExtension.php | 2 +- ...nFunctionConstructorThrowTypeExtension.php | 2 +- ...GetAttributesMethodReturnTypeExtension.php | 2 +- ...ionMethodConstructorThrowTypeExtension.php | 2 +- ...nPropertyConstructorThrowTypeExtension.php | 2 +- src/Type/Php/RegexCapturingGroup.php | 2 +- src/Type/Php/RegexNonCapturingGroup.php | 2 +- ...aceFunctionsDynamicReturnTypeExtension.php | 2 +- .../Php/RoundFunctionReturnTypeExtension.php | 2 +- ...SetTypeFunctionTypeSpecifyingExtension.php | 2 +- ...LElementAsXMLMethodReturnTypeExtension.php | 2 +- ...lementClassPropertyReflectionExtension.php | 2 +- ...MLElementConstructorThrowTypeExtension.php | 2 +- ...LElementXpathMethodReturnTypeExtension.php | 2 +- ...intfFunctionDynamicReturnTypeExtension.php | 2 +- ...canfFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/StatDynamicReturnTypeExtension.php | 2 +- .../StrCaseFunctionsReturnTypeExtension.php | 2 +- ...ntDecrementFunctionReturnTypeExtension.php | 2 +- .../Php/StrPadFunctionReturnTypeExtension.php | 2 +- .../StrRepeatFunctionReturnTypeExtension.php | 2 +- .../Php/StrTokFunctionReturnTypeExtension.php | 2 +- ...ountFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/StrlenFunctionReturnTypeExtension.php | 2 +- .../StrtotimeFunctionReturnTypeExtension.php | 2 +- ...trvalFamilyFunctionReturnTypeExtension.php | 2 +- .../Php/SubstrDynamicReturnTypeExtension.php | 2 +- ...TriggerErrorDynamicReturnTypeExtension.php | 2 +- ...ingFunctionsDynamicReturnTypeExtension.php | 2 +- ...pareFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/XMLReaderOpenReturnTypeExtension.php | 2 +- src/Type/RecursionGuard.php | 2 +- src/Type/SimultaneousTypeTraverser.php | 2 +- src/Type/StaticTypeFactory.php | 2 +- src/Type/TypeAlias.php | 2 +- src/Type/TypeCombinator.php | 5 +++- src/Type/TypeTraverser.php | 2 +- src/Type/TypeUtils.php | 5 +++- src/Type/TypehintHelper.php | 2 +- src/Type/UnionTypeHelper.php | 2 +- src/Type/UsefulTypeAliasResolver.php | 2 +- src/Type/VerbosityLevel.php | 2 +- 994 files changed, 1313 insertions(+), 1027 deletions(-) diff --git a/bin/generate-rule-error-classes.php b/bin/generate-rule-error-classes.php index b8b3219e5dc..633b1824b42 100755 --- a/bin/generate-rule-error-classes.php +++ b/bin/generate-rule-error-classes.php @@ -14,7 +14,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError%s implements %s +final class RuleError%s implements %s { %s diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon index 04a18bb846f..eccbd3eb1a0 100644 --- a/build/enum-adapter-errors.neon +++ b/build/enum-adapter-errors.neon @@ -144,8 +144,3 @@ parameters: message: "#^Method PHPStan\\\\Reflection\\\\Php\\\\BuiltinMethodReflection\\:\\:getDeclaringClass\\(\\) has invalid return type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" count: 1 path: ../src/Reflection/Php/BuiltinMethodReflection.php - - - - message: "#^Method PHPStan\\\\Reflection\\\\Php\\\\NativeBuiltinMethodReflection\\:\\:getDeclaringClass\\(\\) has invalid return type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/Php/NativeBuiltinMethodReflection.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c664a131e77..d79870622a9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -212,31 +212,6 @@ parameters: count: 1 path: src/PhpDoc/StubValidator.php - - - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ParamOutTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ParamOutTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" - count: 1 - path: src/PhpDoc/Tag/ParamOutTag.php - - - - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" - count: 1 - path: src/PhpDoc/Tag/ParamTag.php - - - - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ReturnTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ReturnTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" - count: 1 - path: src/PhpDoc/Tag/ReturnTag.php - - - - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\SelfOutTypeTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\SelfOutTypeTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" - count: 1 - path: src/PhpDoc/Tag/SelfOutTypeTag.php - - - - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\VarTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\VarTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" - count: 1 - path: src/PhpDoc/Tag/VarTag.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index ed161671c60..4ab99fc5d37 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -12,7 +12,7 @@ use function count; use function memory_get_peak_usage; -class Analyser +final class Analyser { public function __construct( diff --git a/src/Analyser/AnalyserResult.php b/src/Analyser/AnalyserResult.php index 903ab9c5dc8..212fdcf422e 100644 --- a/src/Analyser/AnalyserResult.php +++ b/src/Analyser/AnalyserResult.php @@ -9,7 +9,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ -class AnalyserResult +final class AnalyserResult { /** @var list|null */ diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index 707ac020052..b88b3e0d310 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -14,7 +14,7 @@ use function get_class; use function sprintf; -class AnalyserResultFinalizer +final class AnalyserResultFinalizer { public function __construct( diff --git a/src/Analyser/ConditionalExpressionHolder.php b/src/Analyser/ConditionalExpressionHolder.php index 561feac059d..69071839660 100644 --- a/src/Analyser/ConditionalExpressionHolder.php +++ b/src/Analyser/ConditionalExpressionHolder.php @@ -8,7 +8,7 @@ use function implode; use function sprintf; -class ConditionalExpressionHolder +final class ConditionalExpressionHolder { /** diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index d8df652c428..b209533fac8 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -25,7 +25,7 @@ use const NAN; use const PHP_INT_SIZE; -class ConstantResolver +final class ConstantResolver { /** @var array */ diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index bd63830f59c..67a98408a03 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -5,7 +5,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; -class ConstantResolverFactory +final class ConstantResolverFactory { public function __construct( diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index c662e1d9a07..49a3395d2e6 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -17,7 +17,7 @@ use PHPStan\ShouldNotHappenException; use function is_a; -class DirectInternalScopeFactory implements InternalScopeFactory +final class DirectInternalScopeFactory implements InternalScopeFactory { /** diff --git a/src/Analyser/EndStatementResult.php b/src/Analyser/EndStatementResult.php index 32d0809befa..18f97ddc506 100644 --- a/src/Analyser/EndStatementResult.php +++ b/src/Analyser/EndStatementResult.php @@ -4,7 +4,7 @@ use PhpParser\Node\Stmt; -class EndStatementResult +final class EndStatementResult { public function __construct( diff --git a/src/Analyser/EnsuredNonNullabilityResult.php b/src/Analyser/EnsuredNonNullabilityResult.php index 258a16b18bb..6a9e539cf1c 100644 --- a/src/Analyser/EnsuredNonNullabilityResult.php +++ b/src/Analyser/EnsuredNonNullabilityResult.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class EnsuredNonNullabilityResult +final class EnsuredNonNullabilityResult { /** diff --git a/src/Analyser/EnsuredNonNullabilityResultExpression.php b/src/Analyser/EnsuredNonNullabilityResultExpression.php index 33f94341e6e..59f5eba2eea 100644 --- a/src/Analyser/EnsuredNonNullabilityResultExpression.php +++ b/src/Analyser/EnsuredNonNullabilityResultExpression.php @@ -6,7 +6,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class EnsuredNonNullabilityResultExpression +final class EnsuredNonNullabilityResultExpression { public function __construct( diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index f378d912d90..d92f983a3e8 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -12,7 +12,10 @@ use function is_bool; use function sprintf; -/** @api */ +/** + * @api + * @final + */ class Error implements JsonSerializable { diff --git a/src/Analyser/ExpressionContext.php b/src/Analyser/ExpressionContext.php index c2a5a6c78ab..c910b0cc3d7 100644 --- a/src/Analyser/ExpressionContext.php +++ b/src/Analyser/ExpressionContext.php @@ -4,7 +4,7 @@ use PHPStan\Type\Type; -class ExpressionContext +final class ExpressionContext { private function __construct( diff --git a/src/Analyser/ExpressionResult.php b/src/Analyser/ExpressionResult.php index cae75b16094..0a504651695 100644 --- a/src/Analyser/ExpressionResult.php +++ b/src/Analyser/ExpressionResult.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class ExpressionResult +final class ExpressionResult { /** @var (callable(): MutatingScope)|null */ diff --git a/src/Analyser/ExpressionTypeHolder.php b/src/Analyser/ExpressionTypeHolder.php index 6211e211c85..a3ea8273e69 100644 --- a/src/Analyser/ExpressionTypeHolder.php +++ b/src/Analyser/ExpressionTypeHolder.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ExpressionTypeHolder +final class ExpressionTypeHolder { public function __construct(private Expr $expr, private Type $type, private TrinaryLogic $certainty) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 13ee1ba1b60..8d8cf590162 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -36,7 +36,7 @@ use const E_USER_WARNING; use const E_WARNING; -class FileAnalyser +final class FileAnalyser { /** @var list */ diff --git a/src/Analyser/FileAnalyserResult.php b/src/Analyser/FileAnalyserResult.php index 23e34a92789..d1727f5824c 100644 --- a/src/Analyser/FileAnalyserResult.php +++ b/src/Analyser/FileAnalyserResult.php @@ -8,7 +8,7 @@ /** * @phpstan-type LinesToIgnore = array|null>> */ -class FileAnalyserResult +final class FileAnalyserResult { /** diff --git a/src/Analyser/FinalizerResult.php b/src/Analyser/FinalizerResult.php index dfc77617432..c196b25d99f 100644 --- a/src/Analyser/FinalizerResult.php +++ b/src/Analyser/FinalizerResult.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class FinalizerResult +final class FinalizerResult { /** diff --git a/src/Analyser/Ignore/IgnoreParseException.php b/src/Analyser/Ignore/IgnoreParseException.php index fc8eec134c0..c355cb5ad29 100644 --- a/src/Analyser/Ignore/IgnoreParseException.php +++ b/src/Analyser/Ignore/IgnoreParseException.php @@ -4,7 +4,7 @@ use Exception; -class IgnoreParseException extends Exception +final class IgnoreParseException extends Exception { public function __construct(string $message, private int $phpDocLine) diff --git a/src/Analyser/Ignore/IgnoredError.php b/src/Analyser/Ignore/IgnoredError.php index 00cc1e7df86..b420ae29ce2 100644 --- a/src/Analyser/Ignore/IgnoredError.php +++ b/src/Analyser/Ignore/IgnoredError.php @@ -14,7 +14,7 @@ use function sprintf; use function str_replace; -class IgnoredError +final class IgnoredError { /** diff --git a/src/Analyser/Ignore/IgnoredErrorHelper.php b/src/Analyser/Ignore/IgnoredErrorHelper.php index bff07c10853..d72f73ed82c 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelper.php +++ b/src/Analyser/Ignore/IgnoredErrorHelper.php @@ -12,7 +12,7 @@ use function is_file; use function sprintf; -class IgnoredErrorHelper +final class IgnoredErrorHelper { /** diff --git a/src/Analyser/Ignore/IgnoredErrorHelperProcessedResult.php b/src/Analyser/Ignore/IgnoredErrorHelperProcessedResult.php index 4ffacf46d47..67bcfa176c0 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelperProcessedResult.php +++ b/src/Analyser/Ignore/IgnoredErrorHelperProcessedResult.php @@ -4,7 +4,7 @@ use PHPStan\Analyser\Error; -class IgnoredErrorHelperProcessedResult +final class IgnoredErrorHelperProcessedResult { /** diff --git a/src/Analyser/Ignore/IgnoredErrorHelperResult.php b/src/Analyser/Ignore/IgnoredErrorHelperResult.php index 95d4273ef86..529e1ae4a46 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelperResult.php +++ b/src/Analyser/Ignore/IgnoredErrorHelperResult.php @@ -13,7 +13,7 @@ use function is_string; use function sprintf; -class IgnoredErrorHelperResult +final class IgnoredErrorHelperResult { /** diff --git a/src/Analyser/ImpurePoint.php b/src/Analyser/ImpurePoint.php index e87e8866b0b..d4dc6fe133f 100644 --- a/src/Analyser/ImpurePoint.php +++ b/src/Analyser/ImpurePoint.php @@ -8,6 +8,7 @@ /** * @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyAssignByRef'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags' * @api + * @final */ class ImpurePoint { diff --git a/src/Analyser/InternalError.php b/src/Analyser/InternalError.php index 9a3ddd2820f..d778e894623 100644 --- a/src/Analyser/InternalError.php +++ b/src/Analyser/InternalError.php @@ -10,6 +10,7 @@ /** * @api + * @final * @phpstan-type Trace = list */ class InternalError implements JsonSerializable diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 028600646c7..fec15217689 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -17,7 +17,7 @@ use PHPStan\ShouldNotHappenException; use function is_a; -class LazyInternalScopeFactory implements InternalScopeFactory +final class LazyInternalScopeFactory implements InternalScopeFactory { private bool $explicitMixedInUnknownGenericNew; diff --git a/src/Analyser/LocalIgnoresProcessor.php b/src/Analyser/LocalIgnoresProcessor.php index 04368960b08..d5c0197dd64 100644 --- a/src/Analyser/LocalIgnoresProcessor.php +++ b/src/Analyser/LocalIgnoresProcessor.php @@ -10,7 +10,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ -class LocalIgnoresProcessor +final class LocalIgnoresProcessor { /** diff --git a/src/Analyser/LocalIgnoresProcessorResult.php b/src/Analyser/LocalIgnoresProcessorResult.php index 8c7a36287f9..1d44c98f1f3 100644 --- a/src/Analyser/LocalIgnoresProcessorResult.php +++ b/src/Analyser/LocalIgnoresProcessorResult.php @@ -5,7 +5,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ -class LocalIgnoresProcessorResult +final class LocalIgnoresProcessorResult { /** diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6632716bd7d..fca60cc8695 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -158,7 +158,7 @@ use const PHP_INT_MAX; use const PHP_INT_MIN; -class MutatingScope implements Scope +final class MutatingScope implements Scope { private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4; diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index e083daf91b2..fbc602329e2 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -16,7 +16,10 @@ use function str_starts_with; use function strtolower; -/** @api */ +/** + * @api + * @final + */ class NameScope { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index dbd1418a1d3..c48b2e439be 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -211,7 +211,7 @@ use const PHP_VERSION_ID; use const SORT_NUMERIC; -class NodeScopeResolver +final class NodeScopeResolver { private const LOOP_SCOPE_ITERATIONS = 3; diff --git a/src/Analyser/NullsafeOperatorHelper.php b/src/Analyser/NullsafeOperatorHelper.php index 9b34346264f..0b439191c77 100644 --- a/src/Analyser/NullsafeOperatorHelper.php +++ b/src/Analyser/NullsafeOperatorHelper.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Type\TypeCombinator; -class NullsafeOperatorHelper +final class NullsafeOperatorHelper { public static function getNullsafeShortcircuitedExprRespectingScope(Scope $scope, Expr $expr): Expr diff --git a/src/Analyser/OutOfClassScope.php b/src/Analyser/OutOfClassScope.php index d9ddf23f2fa..42a85108c99 100644 --- a/src/Analyser/OutOfClassScope.php +++ b/src/Analyser/OutOfClassScope.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\PropertyReflection; -class OutOfClassScope implements ClassMemberAccessAnswerer +final class OutOfClassScope implements ClassMemberAccessAnswerer { /** @api */ diff --git a/src/Analyser/ProcessClosureResult.php b/src/Analyser/ProcessClosureResult.php index 7423ac220da..00513832789 100644 --- a/src/Analyser/ProcessClosureResult.php +++ b/src/Analyser/ProcessClosureResult.php @@ -4,7 +4,7 @@ use PHPStan\Node\InvalidateExprNode; -class ProcessClosureResult +final class ProcessClosureResult { /** diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index 8b592f68fee..f409f9c0627 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -10,7 +10,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ -class ResultCache +final class ResultCache { /** diff --git a/src/Analyser/ResultCache/ResultCacheClearer.php b/src/Analyser/ResultCache/ResultCacheClearer.php index d9cbee81c6e..75f3d25628e 100644 --- a/src/Analyser/ResultCache/ResultCacheClearer.php +++ b/src/Analyser/ResultCache/ResultCacheClearer.php @@ -6,7 +6,7 @@ use function is_file; use function unlink; -class ResultCacheClearer +final class ResultCacheClearer { public function __construct(private string $cacheFilePath) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 416ce411085..d8cfceb646c 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -47,7 +47,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ -class ResultCacheManager +final class ResultCacheManager { private const CACHE_VERSION = 'v12-linesToIgnore'; diff --git a/src/Analyser/ResultCache/ResultCacheProcessResult.php b/src/Analyser/ResultCache/ResultCacheProcessResult.php index 27326f33a07..9789eefcbb3 100644 --- a/src/Analyser/ResultCache/ResultCacheProcessResult.php +++ b/src/Analyser/ResultCache/ResultCacheProcessResult.php @@ -4,7 +4,7 @@ use PHPStan\Analyser\AnalyserResult; -class ResultCacheProcessResult +final class ResultCacheProcessResult { public function __construct(private AnalyserResult $analyserResult, private bool $saved) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 464e8cd7763..0cb9f9ef98a 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -12,7 +12,7 @@ use PHPStan\Rules\TipRuleError; use function is_string; -class RuleErrorTransformer +final class RuleErrorTransformer { /** diff --git a/src/Analyser/ScopeContext.php b/src/Analyser/ScopeContext.php index 4118909860e..dfa0c1f17bb 100644 --- a/src/Analyser/ScopeContext.php +++ b/src/Analyser/ScopeContext.php @@ -5,7 +5,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\ShouldNotHappenException; -class ScopeContext +final class ScopeContext { private function __construct( diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index c2401d375c2..ae35c9d74cb 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -2,7 +2,10 @@ namespace PHPStan\Analyser; -/** @api */ +/** + * @api + * @final + */ class ScopeFactory { diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index 578a34cdc22..ca290a17c4c 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class SpecifiedTypes +final class SpecifiedTypes { /** diff --git a/src/Analyser/StatementContext.php b/src/Analyser/StatementContext.php index 222d768b522..e9308cfb777 100644 --- a/src/Analyser/StatementContext.php +++ b/src/Analyser/StatementContext.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class StatementContext +final class StatementContext { private function __construct( diff --git a/src/Analyser/StatementExitPoint.php b/src/Analyser/StatementExitPoint.php index f7f46ca0916..14c8d24824c 100644 --- a/src/Analyser/StatementExitPoint.php +++ b/src/Analyser/StatementExitPoint.php @@ -4,7 +4,10 @@ use PhpParser\Node\Stmt; -/** @api */ +/** + * @api + * @final + */ class StatementExitPoint { diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index 20365299c00..bb9ae78f2e6 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -5,7 +5,10 @@ use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Stmt; -/** @api */ +/** + * @api + * @final + */ class StatementResult { diff --git a/src/Analyser/ThrowPoint.php b/src/Analyser/ThrowPoint.php index 26ceec42604..1de4b937f9e 100644 --- a/src/Analyser/ThrowPoint.php +++ b/src/Analyser/ThrowPoint.php @@ -8,7 +8,10 @@ use PHPStan\Type\TypeCombinator; use Throwable; -/** @api */ +/** + * @api + * @final + */ class ThrowPoint { diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4115a752e0c..06b9afceed1 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -82,7 +82,7 @@ use function substr; use const COUNT_NORMAL; -class TypeSpecifier +final class TypeSpecifier { /** @var MethodTypeSpecifyingExtension[][]|null */ diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index a14aba7112c..3cd0ead0f9f 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -4,7 +4,10 @@ use PHPStan\ShouldNotHappenException; -/** @api */ +/** + * @api + * @final + */ class TypeSpecifierContext { diff --git a/src/Analyser/TypeSpecifierFactory.php b/src/Analyser/TypeSpecifierFactory.php index eddf559900f..60219c3b6dc 100644 --- a/src/Analyser/TypeSpecifierFactory.php +++ b/src/Analyser/TypeSpecifierFactory.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use function array_merge; -class TypeSpecifierFactory +final class TypeSpecifierFactory { public const FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.functionTypeSpecifyingExtension'; diff --git a/src/Analyser/UndefinedVariableException.php b/src/Analyser/UndefinedVariableException.php index 6719de349ce..4755296c6bc 100644 --- a/src/Analyser/UndefinedVariableException.php +++ b/src/Analyser/UndefinedVariableException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class UndefinedVariableException extends AnalysedCodeException +final class UndefinedVariableException extends AnalysedCodeException { public function __construct(private Scope $scope, private string $variableName) diff --git a/src/Broker/AnonymousClassNameHelper.php b/src/Broker/AnonymousClassNameHelper.php index 20e3087b002..ba1c0554fa7 100644 --- a/src/Broker/AnonymousClassNameHelper.php +++ b/src/Broker/AnonymousClassNameHelper.php @@ -9,7 +9,7 @@ use function md5; use function sprintf; -class AnonymousClassNameHelper +final class AnonymousClassNameHelper { public function __construct( diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index fee5eab88db..8db42d1f45f 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -11,7 +11,10 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; -/** @api */ +/** + * @api + * @final + */ class Broker implements ReflectionProvider { diff --git a/src/Broker/BrokerFactory.php b/src/Broker/BrokerFactory.php index d35b68e30d4..177b6b5843d 100644 --- a/src/Broker/BrokerFactory.php +++ b/src/Broker/BrokerFactory.php @@ -5,7 +5,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; -class BrokerFactory +final class BrokerFactory { public const PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.propertiesClassReflectionExtension'; diff --git a/src/Broker/ClassAutoloadingException.php b/src/Broker/ClassAutoloadingException.php index 23e99fe5a54..5451987023d 100644 --- a/src/Broker/ClassAutoloadingException.php +++ b/src/Broker/ClassAutoloadingException.php @@ -7,7 +7,7 @@ use function get_class; use function sprintf; -class ClassAutoloadingException extends AnalysedCodeException +final class ClassAutoloadingException extends AnalysedCodeException { private string $className; @@ -39,7 +39,7 @@ public function getClassName(): string return $this->className; } - public function getTip(): ?string + public function getTip(): string { return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; } diff --git a/src/Broker/ClassNotFoundException.php b/src/Broker/ClassNotFoundException.php index d86a1bcb08b..1276d663ff3 100644 --- a/src/Broker/ClassNotFoundException.php +++ b/src/Broker/ClassNotFoundException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class ClassNotFoundException extends AnalysedCodeException +final class ClassNotFoundException extends AnalysedCodeException { public function __construct(private string $className) @@ -18,7 +18,7 @@ public function getClassName(): string return $this->className; } - public function getTip(): ?string + public function getTip(): string { return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; } diff --git a/src/Broker/ConstantNotFoundException.php b/src/Broker/ConstantNotFoundException.php index 2c490e36538..5d633de3808 100644 --- a/src/Broker/ConstantNotFoundException.php +++ b/src/Broker/ConstantNotFoundException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class ConstantNotFoundException extends AnalysedCodeException +final class ConstantNotFoundException extends AnalysedCodeException { public function __construct(private string $constantName) @@ -18,7 +18,7 @@ public function getConstantName(): string return $this->constantName; } - public function getTip(): ?string + public function getTip(): string { return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; } diff --git a/src/Broker/FunctionNotFoundException.php b/src/Broker/FunctionNotFoundException.php index de2728001f9..a313b60e2aa 100644 --- a/src/Broker/FunctionNotFoundException.php +++ b/src/Broker/FunctionNotFoundException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class FunctionNotFoundException extends AnalysedCodeException +final class FunctionNotFoundException extends AnalysedCodeException { public function __construct(private string $functionName) @@ -18,7 +18,7 @@ public function getFunctionName(): string return $this->functionName; } - public function getTip(): ?string + public function getTip(): string { return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; } diff --git a/src/Cache/Cache.php b/src/Cache/Cache.php index 0180ab6610f..a4b66596fa5 100644 --- a/src/Cache/Cache.php +++ b/src/Cache/Cache.php @@ -2,7 +2,7 @@ namespace PHPStan\Cache; -class Cache +final class Cache { public function __construct(private CacheStorage $storage) diff --git a/src/Cache/CacheItem.php b/src/Cache/CacheItem.php index 61d9946c901..101bbfe2fd3 100644 --- a/src/Cache/CacheItem.php +++ b/src/Cache/CacheItem.php @@ -2,7 +2,7 @@ namespace PHPStan\Cache; -class CacheItem +final class CacheItem { /** diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 38b36d25aa7..5ba76a768a5 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -18,7 +18,7 @@ use function var_export; use const DIRECTORY_SEPARATOR; -class FileCacheStorage implements CacheStorage +final class FileCacheStorage implements CacheStorage { public function __construct(private string $directory) diff --git a/src/Cache/MemoryCacheStorage.php b/src/Cache/MemoryCacheStorage.php index 158c2850311..324dfcf37f4 100644 --- a/src/Cache/MemoryCacheStorage.php +++ b/src/Cache/MemoryCacheStorage.php @@ -4,7 +4,7 @@ use function var_export; -class MemoryCacheStorage implements CacheStorage +final class MemoryCacheStorage implements CacheStorage { /** @var array */ diff --git a/src/Collectors/CollectedData.php b/src/Collectors/CollectedData.php index 76d7bb5fe4c..e6817382b66 100644 --- a/src/Collectors/CollectedData.php +++ b/src/Collectors/CollectedData.php @@ -6,7 +6,10 @@ use PhpParser\Node; use ReturnTypeWillChange; -/** @api */ +/** + * @api + * @final + */ class CollectedData implements JsonSerializable { diff --git a/src/Collectors/Registry.php b/src/Collectors/Registry.php index 65b80118b20..11586e3097b 100644 --- a/src/Collectors/Registry.php +++ b/src/Collectors/Registry.php @@ -6,7 +6,7 @@ use function class_implements; use function class_parents; -class Registry +final class Registry { /** @var Collector[][] */ diff --git a/src/Collectors/RegistryFactory.php b/src/Collectors/RegistryFactory.php index 0f852d32109..675740d99ae 100644 --- a/src/Collectors/RegistryFactory.php +++ b/src/Collectors/RegistryFactory.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class RegistryFactory +final class RegistryFactory { public const COLLECTOR_TAG = 'phpstan.collector'; diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 85acf2d88cb..af739bc02c6 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -19,7 +19,7 @@ use function sha1_file; use function sprintf; -class AnalyseApplication +final class AnalyseApplication { public function __construct( diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 6ecfba91057..b4d73589a51 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -56,7 +56,7 @@ use const PATHINFO_BASENAME; use const PATHINFO_EXTENSION; -class AnalyseCommand extends Command +final class AnalyseCommand extends Command { private const NAME = 'analyse'; diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index da6eb429629..5b885293828 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -16,7 +16,7 @@ use function is_file; use function memory_get_peak_usage; -class AnalyserRunner +final class AnalyserRunner { public function __construct( diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index f7c4424284e..6ddf536bc1b 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -9,7 +9,10 @@ use function count; use function usort; -/** @api */ +/** + * @api + * @final + */ class AnalysisResult { diff --git a/src/Command/ClearResultCacheCommand.php b/src/Command/ClearResultCacheCommand.php index 30c59100ec7..89e3c45ee70 100644 --- a/src/Command/ClearResultCacheCommand.php +++ b/src/Command/ClearResultCacheCommand.php @@ -11,7 +11,7 @@ use function is_bool; use function is_string; -class ClearResultCacheCommand extends Command +final class ClearResultCacheCommand extends Command { private const NAME = 'clear-result-cache'; diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index d38aee7f236..10abe7d23d7 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -64,7 +64,7 @@ use const E_ERROR; use const PHP_VERSION_ID; -class CommandHelper +final class CommandHelper { public const DEFAULT_LEVEL = '0'; diff --git a/src/Command/DiagnoseCommand.php b/src/Command/DiagnoseCommand.php index 608cf732f1f..2f8a5a47b0c 100644 --- a/src/Command/DiagnoseCommand.php +++ b/src/Command/DiagnoseCommand.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface; use function is_string; -class DiagnoseCommand extends Command +final class DiagnoseCommand extends Command { private const NAME = 'diagnose'; diff --git a/src/Command/DumpParametersCommand.php b/src/Command/DumpParametersCommand.php index 50e6e57b4bc..b3085db0c64 100644 --- a/src/Command/DumpParametersCommand.php +++ b/src/Command/DumpParametersCommand.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface; use function is_string; -class DumpParametersCommand extends Command +final class DumpParametersCommand extends Command { private const NAME = 'dump-parameters'; diff --git a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php index 545eeed111c..b6dd1e2c2f2 100644 --- a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php @@ -14,7 +14,7 @@ use function substr; use const SORT_STRING; -class BaselineNeonErrorFormatter +final class BaselineNeonErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php index 0af50dba479..8107aba19b9 100644 --- a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php @@ -16,7 +16,7 @@ use function var_export; use const SORT_STRING; -class BaselinePhpErrorFormatter +final class BaselinePhpErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php b/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php index cfb2ddc5593..9072bd8dd22 100644 --- a/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php +++ b/src/Command/ErrorFormatter/CheckstyleErrorFormatter.php @@ -12,7 +12,7 @@ use const ENT_COMPAT; use const ENT_XML1; -class CheckstyleErrorFormatter implements ErrorFormatter +final class CheckstyleErrorFormatter implements ErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php b/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php index 541dfd77e31..a6c66bbafb7 100644 --- a/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php +++ b/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php @@ -7,7 +7,10 @@ use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; -/** @api */ +/** + * @api + * @final + */ class CiDetectedErrorFormatter implements ErrorFormatter { diff --git a/src/Command/ErrorFormatter/GithubErrorFormatter.php b/src/Command/ErrorFormatter/GithubErrorFormatter.php index 03dc1aa8d00..21ffdd26501 100644 --- a/src/Command/ErrorFormatter/GithubErrorFormatter.php +++ b/src/Command/ErrorFormatter/GithubErrorFormatter.php @@ -14,7 +14,7 @@ * Allow errors to be reported in pull-requests diff when run in a GitHub Action * @see https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message */ -class GithubErrorFormatter implements ErrorFormatter +final class GithubErrorFormatter implements ErrorFormatter { public function __construct( diff --git a/src/Command/ErrorFormatter/GitlabErrorFormatter.php b/src/Command/ErrorFormatter/GitlabErrorFormatter.php index 6aa4b61befc..9a8ccb35cdd 100644 --- a/src/Command/ErrorFormatter/GitlabErrorFormatter.php +++ b/src/Command/ErrorFormatter/GitlabErrorFormatter.php @@ -12,7 +12,7 @@ /** * @see https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html#implementing-a-custom-tool */ -class GitlabErrorFormatter implements ErrorFormatter +final class GitlabErrorFormatter implements ErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorFormatter/JsonErrorFormatter.php b/src/Command/ErrorFormatter/JsonErrorFormatter.php index 9f9f1edcfbb..a46396f12ca 100644 --- a/src/Command/ErrorFormatter/JsonErrorFormatter.php +++ b/src/Command/ErrorFormatter/JsonErrorFormatter.php @@ -9,7 +9,7 @@ use function array_key_exists; use function count; -class JsonErrorFormatter implements ErrorFormatter +final class JsonErrorFormatter implements ErrorFormatter { public function __construct(private bool $pretty) diff --git a/src/Command/ErrorFormatter/JunitErrorFormatter.php b/src/Command/ErrorFormatter/JunitErrorFormatter.php index b7562f4733f..59131dcf011 100644 --- a/src/Command/ErrorFormatter/JunitErrorFormatter.php +++ b/src/Command/ErrorFormatter/JunitErrorFormatter.php @@ -10,7 +10,7 @@ use const ENT_COMPAT; use const ENT_XML1; -class JunitErrorFormatter implements ErrorFormatter +final class JunitErrorFormatter implements ErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorFormatter/RawErrorFormatter.php b/src/Command/ErrorFormatter/RawErrorFormatter.php index add31fa48ff..f761a5a47e2 100644 --- a/src/Command/ErrorFormatter/RawErrorFormatter.php +++ b/src/Command/ErrorFormatter/RawErrorFormatter.php @@ -6,7 +6,7 @@ use PHPStan\Command\Output; use function sprintf; -class RawErrorFormatter implements ErrorFormatter +final class RawErrorFormatter implements ErrorFormatter { public function formatErrors( diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index eb040735d00..fb3d949c4e6 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -21,7 +21,7 @@ use function str_contains; use function str_replace; -class TableErrorFormatter implements ErrorFormatter +final class TableErrorFormatter implements ErrorFormatter { public function __construct( diff --git a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php index a6e6e2cf32b..8ea1d5bb1e3 100644 --- a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php +++ b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php @@ -15,7 +15,7 @@ /** * @see https://www.jetbrains.com/help/teamcity/build-script-interaction-with-teamcity.html#Reporting+Inspections */ -class TeamcityErrorFormatter implements ErrorFormatter +final class TeamcityErrorFormatter implements ErrorFormatter { public function __construct(private RelativePathHelper $relativePathHelper) diff --git a/src/Command/ErrorsConsoleStyle.php b/src/Command/ErrorsConsoleStyle.php index 520ce7add31..f953d40c493 100644 --- a/src/Command/ErrorsConsoleStyle.php +++ b/src/Command/ErrorsConsoleStyle.php @@ -18,7 +18,7 @@ use function wordwrap; use const DIRECTORY_SEPARATOR; -class ErrorsConsoleStyle extends SymfonyStyle +final class ErrorsConsoleStyle extends SymfonyStyle { public const OPTION_NO_PROGRESS = 'no-progress'; diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 75caa6420c2..6236c3c63f6 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -63,7 +63,7 @@ use const PHP_URL_PORT; use const PHP_VERSION_ID; -class FixerApplication +final class FixerApplication { /** @var PromiseInterface|null */ diff --git a/src/Command/FixerProcessException.php b/src/Command/FixerProcessException.php index 9473f35716c..c9e4097d58e 100644 --- a/src/Command/FixerProcessException.php +++ b/src/Command/FixerProcessException.php @@ -4,7 +4,7 @@ use Exception; -class FixerProcessException extends Exception +final class FixerProcessException extends Exception { } diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index 3cc415b3f48..70a76244538 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -42,7 +42,7 @@ use function usort; use const JSON_INVALID_UTF8_IGNORE; -class FixerWorkerCommand extends Command +final class FixerWorkerCommand extends Command { private const NAME = 'fixer:worker'; diff --git a/src/Command/IgnoredRegexValidator.php b/src/Command/IgnoredRegexValidator.php index e7a90c5bd24..613779b2e64 100644 --- a/src/Command/IgnoredRegexValidator.php +++ b/src/Command/IgnoredRegexValidator.php @@ -17,7 +17,7 @@ use function strrpos; use function substr; -class IgnoredRegexValidator +final class IgnoredRegexValidator { public function __construct( diff --git a/src/Command/IgnoredRegexValidatorResult.php b/src/Command/IgnoredRegexValidatorResult.php index fcda6a015bf..0906a3c84b1 100644 --- a/src/Command/IgnoredRegexValidatorResult.php +++ b/src/Command/IgnoredRegexValidatorResult.php @@ -2,7 +2,7 @@ namespace PHPStan\Command; -class IgnoredRegexValidatorResult +final class IgnoredRegexValidatorResult { /** diff --git a/src/Command/InceptionNotSuccessfulException.php b/src/Command/InceptionNotSuccessfulException.php index f17c3e08ece..5cbcf4425ec 100644 --- a/src/Command/InceptionNotSuccessfulException.php +++ b/src/Command/InceptionNotSuccessfulException.php @@ -4,7 +4,7 @@ use Exception; -class InceptionNotSuccessfulException extends Exception +final class InceptionNotSuccessfulException extends Exception { } diff --git a/src/Command/InceptionResult.php b/src/Command/InceptionResult.php index 14a1d5202d4..fc6056eccb1 100644 --- a/src/Command/InceptionResult.php +++ b/src/Command/InceptionResult.php @@ -13,7 +13,7 @@ use function round; use function sprintf; -class InceptionResult +final class InceptionResult { /** @var callable(): (array{string[], bool}) */ diff --git a/src/Command/Symfony/SymfonyOutput.php b/src/Command/Symfony/SymfonyOutput.php index 5e1a46273ae..7f60d04bfc2 100644 --- a/src/Command/Symfony/SymfonyOutput.php +++ b/src/Command/Symfony/SymfonyOutput.php @@ -9,7 +9,7 @@ /** * @internal */ -class SymfonyOutput implements Output +final class SymfonyOutput implements Output { public function __construct( diff --git a/src/Command/Symfony/SymfonyStyle.php b/src/Command/Symfony/SymfonyStyle.php index 99ed87ec330..e8782a5f59e 100644 --- a/src/Command/Symfony/SymfonyStyle.php +++ b/src/Command/Symfony/SymfonyStyle.php @@ -8,7 +8,7 @@ /** * @internal */ -class SymfonyStyle implements OutputStyle +final class SymfonyStyle implements OutputStyle { public function __construct(private StyleInterface $symfonyStyle) diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 96465188399..6e44a112177 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -32,7 +32,7 @@ use function memory_get_peak_usage; use function sprintf; -class WorkerCommand extends Command +final class WorkerCommand extends Command { private const NAME = 'worker'; diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 0688b5d19e3..57a80a5a2e9 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -28,7 +28,7 @@ use function array_merge; use function count; -class DependencyResolver +final class DependencyResolver { public function __construct( diff --git a/src/Dependency/ExportedNode/ExportedAttributeNode.php b/src/Dependency/ExportedNode/ExportedAttributeNode.php index 02714f2f592..5612e74ea10 100644 --- a/src/Dependency/ExportedNode/ExportedAttributeNode.php +++ b/src/Dependency/ExportedNode/ExportedAttributeNode.php @@ -7,7 +7,7 @@ use ReturnTypeWillChange; use function count; -class ExportedAttributeNode implements ExportedNode, JsonSerializable +final class ExportedAttributeNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index aab53925f16..8827656c48b 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedClassConstantNode implements ExportedNode, JsonSerializable +final class ExportedClassConstantNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedClassConstantsNode.php b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php index 04bace72824..23ebd61ae0a 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantsNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedClassConstantsNode implements ExportedNode, JsonSerializable +final class ExportedClassConstantsNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index b96096cff25..b420a0a9ada 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -10,7 +10,7 @@ use function array_map; use function count; -class ExportedClassNode implements RootExportedNode, JsonSerializable +final class ExportedClassNode implements RootExportedNode, JsonSerializable { /** @@ -171,6 +171,9 @@ public static function decode(array $data): ExportedNode ); } + /** + * @return self::TYPE_CLASS + */ public function getType(): string { return self::TYPE_CLASS; diff --git a/src/Dependency/ExportedNode/ExportedEnumCaseNode.php b/src/Dependency/ExportedNode/ExportedEnumCaseNode.php index da304b58937..19f8f6a22bd 100644 --- a/src/Dependency/ExportedNode/ExportedEnumCaseNode.php +++ b/src/Dependency/ExportedNode/ExportedEnumCaseNode.php @@ -6,7 +6,7 @@ use PHPStan\Dependency\ExportedNode; use ReturnTypeWillChange; -class ExportedEnumCaseNode implements ExportedNode, JsonSerializable +final class ExportedEnumCaseNode implements ExportedNode, JsonSerializable { public function __construct(private string $name, private ?string $value, private ?ExportedPhpDocNode $phpDoc) diff --git a/src/Dependency/ExportedNode/ExportedEnumNode.php b/src/Dependency/ExportedNode/ExportedEnumNode.php index 3d75abd6277..ea5ce3970bc 100644 --- a/src/Dependency/ExportedNode/ExportedEnumNode.php +++ b/src/Dependency/ExportedNode/ExportedEnumNode.php @@ -10,7 +10,7 @@ use function array_map; use function count; -class ExportedEnumNode implements RootExportedNode, JsonSerializable +final class ExportedEnumNode implements RootExportedNode, JsonSerializable { /** @@ -134,6 +134,9 @@ public static function decode(array $data): ExportedNode ); } + /** + * @return self::TYPE_ENUM + */ public function getType(): string { return self::TYPE_ENUM; diff --git a/src/Dependency/ExportedNode/ExportedFunctionNode.php b/src/Dependency/ExportedNode/ExportedFunctionNode.php index fcc0f51ad55..c7e98d36439 100644 --- a/src/Dependency/ExportedNode/ExportedFunctionNode.php +++ b/src/Dependency/ExportedNode/ExportedFunctionNode.php @@ -10,7 +10,7 @@ use function array_map; use function count; -class ExportedFunctionNode implements RootExportedNode, JsonSerializable +final class ExportedFunctionNode implements RootExportedNode, JsonSerializable { /** @@ -133,6 +133,9 @@ public static function decode(array $data): ExportedNode ); } + /** + * @return self::TYPE_FUNCTION + */ public function getType(): string { return self::TYPE_FUNCTION; diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index 394eef707d2..ef72841c5e8 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedInterfaceNode implements RootExportedNode, JsonSerializable +final class ExportedInterfaceNode implements RootExportedNode, JsonSerializable { /** @@ -103,6 +103,9 @@ public static function decode(array $data): ExportedNode ); } + /** + * @return self::TYPE_INTERFACE + */ public function getType(): string { return self::TYPE_INTERFACE; diff --git a/src/Dependency/ExportedNode/ExportedMethodNode.php b/src/Dependency/ExportedNode/ExportedMethodNode.php index a60a6d205ee..817482ef933 100644 --- a/src/Dependency/ExportedNode/ExportedMethodNode.php +++ b/src/Dependency/ExportedNode/ExportedMethodNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedMethodNode implements ExportedNode, JsonSerializable +final class ExportedMethodNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedParameterNode.php b/src/Dependency/ExportedNode/ExportedParameterNode.php index d205afafdbc..c4a7769e28a 100644 --- a/src/Dependency/ExportedNode/ExportedParameterNode.php +++ b/src/Dependency/ExportedNode/ExportedParameterNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedParameterNode implements ExportedNode, JsonSerializable +final class ExportedParameterNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedPhpDocNode.php b/src/Dependency/ExportedNode/ExportedPhpDocNode.php index 64a37c96a46..a39cb4ef8a1 100644 --- a/src/Dependency/ExportedNode/ExportedPhpDocNode.php +++ b/src/Dependency/ExportedNode/ExportedPhpDocNode.php @@ -6,7 +6,7 @@ use PHPStan\Dependency\ExportedNode; use ReturnTypeWillChange; -class ExportedPhpDocNode implements ExportedNode, JsonSerializable +final class ExportedPhpDocNode implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNode/ExportedPropertiesNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php index c801f256428..de0b4d2b490 100644 --- a/src/Dependency/ExportedNode/ExportedPropertiesNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -9,7 +9,7 @@ use function array_map; use function count; -class ExportedPropertiesNode implements JsonSerializable, ExportedNode +final class ExportedPropertiesNode implements JsonSerializable, ExportedNode { /** diff --git a/src/Dependency/ExportedNode/ExportedTraitNode.php b/src/Dependency/ExportedNode/ExportedTraitNode.php index f90246ad154..f0f47ae0219 100644 --- a/src/Dependency/ExportedNode/ExportedTraitNode.php +++ b/src/Dependency/ExportedNode/ExportedTraitNode.php @@ -7,7 +7,7 @@ use PHPStan\Dependency\RootExportedNode; use ReturnTypeWillChange; -class ExportedTraitNode implements RootExportedNode, JsonSerializable +final class ExportedTraitNode implements RootExportedNode, JsonSerializable { public function __construct(private string $traitName) @@ -51,6 +51,9 @@ public function jsonSerialize() ]; } + /** + * @return self::TYPE_TRAIT + */ public function getType(): string { return self::TYPE_TRAIT; diff --git a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php index 57f3491010b..38e92276247 100644 --- a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php +++ b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php @@ -6,7 +6,7 @@ use PHPStan\Dependency\ExportedNode; use ReturnTypeWillChange; -class ExportedTraitUseAdaptation implements ExportedNode, JsonSerializable +final class ExportedTraitUseAdaptation implements ExportedNode, JsonSerializable { /** diff --git a/src/Dependency/ExportedNodeFetcher.php b/src/Dependency/ExportedNodeFetcher.php index 4d562f1275f..dbc36c155a0 100644 --- a/src/Dependency/ExportedNodeFetcher.php +++ b/src/Dependency/ExportedNodeFetcher.php @@ -6,7 +6,7 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; -class ExportedNodeFetcher +final class ExportedNodeFetcher { public function __construct( diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 89d532512cb..c9a0c521545 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -28,7 +28,7 @@ use function implode; use function is_string; -class ExportedNodeResolver +final class ExportedNodeResolver { public function __construct(private FileTypeMapper $fileTypeMapper, private ExprPrinter $exprPrinter) diff --git a/src/Dependency/ExportedNodeVisitor.php b/src/Dependency/ExportedNodeVisitor.php index 902ca005d63..90df53887b5 100644 --- a/src/Dependency/ExportedNodeVisitor.php +++ b/src/Dependency/ExportedNodeVisitor.php @@ -7,7 +7,7 @@ use PhpParser\NodeVisitorAbstract; use PHPStan\ShouldNotHappenException; -class ExportedNodeVisitor extends NodeVisitorAbstract +final class ExportedNodeVisitor extends NodeVisitorAbstract { private ?string $fileName = null; diff --git a/src/Dependency/NodeDependencies.php b/src/Dependency/NodeDependencies.php index a31a6eeadd5..ac30175b353 100644 --- a/src/Dependency/NodeDependencies.php +++ b/src/Dependency/NodeDependencies.php @@ -7,7 +7,7 @@ use PHPStan\Reflection\FunctionReflection; use function array_values; -class NodeDependencies +final class NodeDependencies { /** diff --git a/src/DependencyInjection/BleedingEdgeToggle.php b/src/DependencyInjection/BleedingEdgeToggle.php index f510a64c8d7..29170f7fe9a 100644 --- a/src/DependencyInjection/BleedingEdgeToggle.php +++ b/src/DependencyInjection/BleedingEdgeToggle.php @@ -2,7 +2,7 @@ namespace PHPStan\DependencyInjection; -class BleedingEdgeToggle +final class BleedingEdgeToggle { private static bool $bleedingEdge = false; diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index e11f095fdac..7c593c43dde 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -24,7 +24,7 @@ use function is_array; use function sprintf; -class ConditionalTagsExtension extends CompilerExtension +final class ConditionalTagsExtension extends CompilerExtension { public function getConfigSchema(): Nette\Schema\Schema diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 1c8b1e7bc6a..31886c52bf6 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -15,7 +15,7 @@ use const PHP_RELEASE_VERSION; use const PHP_VERSION_ID; -class Configurator extends \Nette\Bootstrap\Configurator +final class Configurator extends \Nette\Bootstrap\Configurator { /** @var string[] */ diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index cb3bf3006a0..5c8dad2f238 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -55,7 +55,10 @@ use function time; use function unlink; -/** @api */ +/** + * @api + * @final + */ class ContainerFactory { diff --git a/src/DependencyInjection/DerivativeContainerFactory.php b/src/DependencyInjection/DerivativeContainerFactory.php index 48270f714d1..a32bc2eb6a0 100644 --- a/src/DependencyInjection/DerivativeContainerFactory.php +++ b/src/DependencyInjection/DerivativeContainerFactory.php @@ -4,7 +4,7 @@ use function array_merge; -class DerivativeContainerFactory +final class DerivativeContainerFactory { /** diff --git a/src/DependencyInjection/DuplicateIncludedFilesException.php b/src/DependencyInjection/DuplicateIncludedFilesException.php index 13ea681636c..e377e425531 100644 --- a/src/DependencyInjection/DuplicateIncludedFilesException.php +++ b/src/DependencyInjection/DuplicateIncludedFilesException.php @@ -6,7 +6,7 @@ use function implode; use function sprintf; -class DuplicateIncludedFilesException extends Exception +final class DuplicateIncludedFilesException extends Exception { /** diff --git a/src/DependencyInjection/InvalidIgnoredErrorPatternsException.php b/src/DependencyInjection/InvalidIgnoredErrorPatternsException.php index dd9258c64d8..bd4f2466a15 100644 --- a/src/DependencyInjection/InvalidIgnoredErrorPatternsException.php +++ b/src/DependencyInjection/InvalidIgnoredErrorPatternsException.php @@ -5,7 +5,7 @@ use Exception; use function implode; -class InvalidIgnoredErrorPatternsException extends Exception +final class InvalidIgnoredErrorPatternsException extends Exception { /** diff --git a/src/DependencyInjection/LoaderFactory.php b/src/DependencyInjection/LoaderFactory.php index 600fede4351..6053ca58282 100644 --- a/src/DependencyInjection/LoaderFactory.php +++ b/src/DependencyInjection/LoaderFactory.php @@ -6,7 +6,7 @@ use PHPStan\File\FileHelper; use function getenv; -class LoaderFactory +final class LoaderFactory { public function __construct( diff --git a/src/DependencyInjection/MemoizingContainer.php b/src/DependencyInjection/MemoizingContainer.php index 07e92fbac52..bdd24bf291e 100644 --- a/src/DependencyInjection/MemoizingContainer.php +++ b/src/DependencyInjection/MemoizingContainer.php @@ -4,7 +4,7 @@ use function array_key_exists; -class MemoizingContainer implements Container +final class MemoizingContainer implements Container { /** @var array */ diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index 15fb210b336..15f4ab8384f 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -26,7 +26,7 @@ use function str_starts_with; use function substr; -class NeonAdapter implements Adapter +final class NeonAdapter implements Adapter { public const CACHE_KEY = 'v25-nette-di-again'; diff --git a/src/DependencyInjection/NeonLoader.php b/src/DependencyInjection/NeonLoader.php index 4f797d1af76..010b80cfab9 100644 --- a/src/DependencyInjection/NeonLoader.php +++ b/src/DependencyInjection/NeonLoader.php @@ -5,7 +5,7 @@ use Nette\DI\Config\Loader; use PHPStan\File\FileHelper; -class NeonLoader extends Loader +final class NeonLoader extends Loader { public function __construct( diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index b5e488ca323..75469932976 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -11,7 +11,7 @@ /** * @internal */ -class NetteContainer implements Container +final class NetteContainer implements Container { public function __construct(private \Nette\DI\Container $container) diff --git a/src/DependencyInjection/ParameterNotFoundException.php b/src/DependencyInjection/ParameterNotFoundException.php index 07a92a376f5..ad461ddef54 100644 --- a/src/DependencyInjection/ParameterNotFoundException.php +++ b/src/DependencyInjection/ParameterNotFoundException.php @@ -5,7 +5,7 @@ use Exception; use function sprintf; -class ParameterNotFoundException extends Exception +final class ParameterNotFoundException extends Exception { public function __construct(string $parameterName) diff --git a/src/DependencyInjection/ParametersSchemaExtension.php b/src/DependencyInjection/ParametersSchemaExtension.php index 2b19bafe563..9a703ed8e96 100644 --- a/src/DependencyInjection/ParametersSchemaExtension.php +++ b/src/DependencyInjection/ParametersSchemaExtension.php @@ -7,7 +7,7 @@ use Nette\Schema\Expect; use Nette\Schema\Schema; -class ParametersSchemaExtension extends CompilerExtension +final class ParametersSchemaExtension extends CompilerExtension { public function getConfigSchema(): Schema diff --git a/src/DependencyInjection/ProjectConfigHelper.php b/src/DependencyInjection/ProjectConfigHelper.php index 0ac6ef97a27..47e84813998 100644 --- a/src/DependencyInjection/ProjectConfigHelper.php +++ b/src/DependencyInjection/ProjectConfigHelper.php @@ -9,7 +9,7 @@ use function is_array; use function is_string; -class ProjectConfigHelper +final class ProjectConfigHelper { /** diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index 899b9153d40..34b91d99a53 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -13,7 +13,7 @@ use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension; use function array_merge; -class LazyClassReflectionExtensionRegistryProvider implements ClassReflectionExtensionRegistryProvider +final class LazyClassReflectionExtensionRegistryProvider implements ClassReflectionExtensionRegistryProvider { private ?ClassReflectionExtensionRegistry $registry = null; diff --git a/src/DependencyInjection/RulesExtension.php b/src/DependencyInjection/RulesExtension.php index 0c15bfb8b9e..f38caac26dc 100644 --- a/src/DependencyInjection/RulesExtension.php +++ b/src/DependencyInjection/RulesExtension.php @@ -7,7 +7,7 @@ use Nette\Schema\Schema; use PHPStan\Rules\LazyRegistry; -class RulesExtension extends CompilerExtension +final class RulesExtension extends CompilerExtension { public function getConfigSchema(): Schema diff --git a/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php index f992ad9ddf5..9a15af910f9 100644 --- a/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\DynamicReturnTypeExtensionRegistry; -class LazyDynamicReturnTypeExtensionRegistryProvider implements DynamicReturnTypeExtensionRegistryProvider +final class LazyDynamicReturnTypeExtensionRegistryProvider implements DynamicReturnTypeExtensionRegistryProvider { private ?DynamicReturnTypeExtensionRegistry $registry = null; diff --git a/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php b/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php index 4696fb8b217..0eb55cbf5b0 100644 --- a/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php +++ b/src/DependencyInjection/Type/LazyDynamicThrowTypeExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyDynamicThrowTypeExtensionProvider implements DynamicThrowTypeExtensionProvider +final class LazyDynamicThrowTypeExtensionProvider implements DynamicThrowTypeExtensionProvider { public const FUNCTION_TAG = 'phpstan.dynamicFunctionThrowTypeExtension'; diff --git a/src/DependencyInjection/Type/LazyExpressionTypeResolverExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyExpressionTypeResolverExtensionRegistryProvider.php index 43910f86688..7136eb40d36 100644 --- a/src/DependencyInjection/Type/LazyExpressionTypeResolverExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyExpressionTypeResolverExtensionRegistryProvider.php @@ -6,7 +6,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\Type\ExpressionTypeResolverExtensionRegistry; -class LazyExpressionTypeResolverExtensionRegistryProvider implements ExpressionTypeResolverExtensionRegistryProvider +final class LazyExpressionTypeResolverExtensionRegistryProvider implements ExpressionTypeResolverExtensionRegistryProvider { private ?ExpressionTypeResolverExtensionRegistry $registry = null; diff --git a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php index 22e356fe70f..5f1a719b487 100644 --- a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php @@ -7,7 +7,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry; -class LazyOperatorTypeSpecifyingExtensionRegistryProvider implements OperatorTypeSpecifyingExtensionRegistryProvider +final class LazyOperatorTypeSpecifyingExtensionRegistryProvider implements OperatorTypeSpecifyingExtensionRegistryProvider { private ?OperatorTypeSpecifyingExtensionRegistry $registry = null; diff --git a/src/DependencyInjection/Type/LazyParameterClosureTypeExtensionProvider.php b/src/DependencyInjection/Type/LazyParameterClosureTypeExtensionProvider.php index 9b1db71fc59..b2877e59934 100644 --- a/src/DependencyInjection/Type/LazyParameterClosureTypeExtensionProvider.php +++ b/src/DependencyInjection/Type/LazyParameterClosureTypeExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyParameterClosureTypeExtensionProvider implements ParameterClosureTypeExtensionProvider +final class LazyParameterClosureTypeExtensionProvider implements ParameterClosureTypeExtensionProvider { public const FUNCTION_TAG = 'phpstan.functionParameterClosureTypeExtension'; diff --git a/src/DependencyInjection/Type/LazyParameterOutTypeExtensionProvider.php b/src/DependencyInjection/Type/LazyParameterOutTypeExtensionProvider.php index 3cf13bf0ba9..5fa75939c57 100644 --- a/src/DependencyInjection/Type/LazyParameterOutTypeExtensionProvider.php +++ b/src/DependencyInjection/Type/LazyParameterOutTypeExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyParameterOutTypeExtensionProvider implements ParameterOutTypeExtensionProvider +final class LazyParameterOutTypeExtensionProvider implements ParameterOutTypeExtensionProvider { public const FUNCTION_TAG = 'phpstan.functionParameterOutTypeExtension'; diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index cd93ab2247f..8a429629fd3 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -37,7 +37,7 @@ use function sprintf; use const PHP_VERSION_ID; -class ValidateIgnoredErrorsExtension extends CompilerExtension +final class ValidateIgnoredErrorsExtension extends CompilerExtension { /** diff --git a/src/Diagnose/PHPStanDiagnoseExtension.php b/src/Diagnose/PHPStanDiagnoseExtension.php index 0873b4f8023..5d336b845d5 100644 --- a/src/Diagnose/PHPStanDiagnoseExtension.php +++ b/src/Diagnose/PHPStanDiagnoseExtension.php @@ -14,7 +14,7 @@ use function sprintf; use const PHP_VERSION_ID; -class PHPStanDiagnoseExtension implements DiagnoseExtension +final class PHPStanDiagnoseExtension implements DiagnoseExtension { /** diff --git a/src/File/CouldNotReadFileException.php b/src/File/CouldNotReadFileException.php index 5803fd53dfa..63d5a2e41dd 100644 --- a/src/File/CouldNotReadFileException.php +++ b/src/File/CouldNotReadFileException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class CouldNotReadFileException extends AnalysedCodeException +final class CouldNotReadFileException extends AnalysedCodeException { public function __construct(string $fileName) diff --git a/src/File/CouldNotWriteFileException.php b/src/File/CouldNotWriteFileException.php index 0fffa54dcfe..72e00464b76 100644 --- a/src/File/CouldNotWriteFileException.php +++ b/src/File/CouldNotWriteFileException.php @@ -5,7 +5,7 @@ use PHPStan\AnalysedCodeException; use function sprintf; -class CouldNotWriteFileException extends AnalysedCodeException +final class CouldNotWriteFileException extends AnalysedCodeException { public function __construct(string $fileName, string $error) diff --git a/src/File/FileExcluder.php b/src/File/FileExcluder.php index 2ea5271d5dd..9a7db5cbd80 100644 --- a/src/File/FileExcluder.php +++ b/src/File/FileExcluder.php @@ -11,7 +11,7 @@ use const FNM_CASEFOLD; use const FNM_NOESCAPE; -class FileExcluder +final class FileExcluder { /** diff --git a/src/File/FileExcluderFactory.php b/src/File/FileExcluderFactory.php index 26976a3dfbd..c15b426f6ee 100644 --- a/src/File/FileExcluderFactory.php +++ b/src/File/FileExcluderFactory.php @@ -7,7 +7,7 @@ use function array_unique; use function array_values; -class FileExcluderFactory +final class FileExcluderFactory { /** diff --git a/src/File/FileFinder.php b/src/File/FileFinder.php index 6ca9db6dddc..34ab2c5a160 100644 --- a/src/File/FileFinder.php +++ b/src/File/FileFinder.php @@ -10,7 +10,7 @@ use function implode; use function is_file; -class FileFinder +final class FileFinder { /** diff --git a/src/File/FileFinderResult.php b/src/File/FileFinderResult.php index d88bcbb84ce..7ae7634c96b 100644 --- a/src/File/FileFinderResult.php +++ b/src/File/FileFinderResult.php @@ -2,7 +2,7 @@ namespace PHPStan\File; -class FileFinderResult +final class FileFinderResult { /** diff --git a/src/File/FileHelper.php b/src/File/FileHelper.php index 06bca961f32..32fad2d253a 100644 --- a/src/File/FileHelper.php +++ b/src/File/FileHelper.php @@ -18,7 +18,7 @@ use function trim; use const DIRECTORY_SEPARATOR; -class FileHelper +final class FileHelper { private string $workingDirectory; diff --git a/src/File/FileMonitor.php b/src/File/FileMonitor.php index 26e27454ff7..6fd0eaf8ef9 100644 --- a/src/File/FileMonitor.php +++ b/src/File/FileMonitor.php @@ -8,7 +8,7 @@ use function count; use function sha1_file; -class FileMonitor +final class FileMonitor { /** @var array|null */ diff --git a/src/File/FileMonitorResult.php b/src/File/FileMonitorResult.php index 940c21e965e..8c7e405dc09 100644 --- a/src/File/FileMonitorResult.php +++ b/src/File/FileMonitorResult.php @@ -4,7 +4,7 @@ use function count; -class FileMonitorResult +final class FileMonitorResult { /** diff --git a/src/File/FileReader.php b/src/File/FileReader.php index 46f89339169..7f5a8dc3694 100644 --- a/src/File/FileReader.php +++ b/src/File/FileReader.php @@ -5,7 +5,7 @@ use function file_get_contents; use function stream_resolve_include_path; -class FileReader +final class FileReader { /** diff --git a/src/File/FileWriter.php b/src/File/FileWriter.php index 2a40c35bb1c..b3659d95366 100644 --- a/src/File/FileWriter.php +++ b/src/File/FileWriter.php @@ -5,7 +5,7 @@ use function error_get_last; use function file_put_contents; -class FileWriter +final class FileWriter { public static function write(string $fileName, string $contents): void diff --git a/src/File/FuzzyRelativePathHelper.php b/src/File/FuzzyRelativePathHelper.php index dc2d44e5058..cf56ee124a3 100644 --- a/src/File/FuzzyRelativePathHelper.php +++ b/src/File/FuzzyRelativePathHelper.php @@ -14,7 +14,7 @@ use function substr; use const DIRECTORY_SEPARATOR; -class FuzzyRelativePathHelper implements RelativePathHelper +final class FuzzyRelativePathHelper implements RelativePathHelper { private string $directorySeparator; diff --git a/src/File/NullRelativePathHelper.php b/src/File/NullRelativePathHelper.php index 1556984a900..5e5a07dc62c 100644 --- a/src/File/NullRelativePathHelper.php +++ b/src/File/NullRelativePathHelper.php @@ -2,7 +2,7 @@ namespace PHPStan\File; -class NullRelativePathHelper implements RelativePathHelper +final class NullRelativePathHelper implements RelativePathHelper { public function getRelativePath(string $filename): string diff --git a/src/File/ParentDirectoryRelativePathHelper.php b/src/File/ParentDirectoryRelativePathHelper.php index 218d4597fca..7654b3ce1e1 100644 --- a/src/File/ParentDirectoryRelativePathHelper.php +++ b/src/File/ParentDirectoryRelativePathHelper.php @@ -14,7 +14,7 @@ use function substr; use function trim; -class ParentDirectoryRelativePathHelper implements RelativePathHelper +final class ParentDirectoryRelativePathHelper implements RelativePathHelper { public function __construct(private string $parentDirectory) diff --git a/src/File/PathNotFoundException.php b/src/File/PathNotFoundException.php index fb8f4dd1911..b58cda6e15b 100644 --- a/src/File/PathNotFoundException.php +++ b/src/File/PathNotFoundException.php @@ -5,7 +5,7 @@ use Exception; use function sprintf; -class PathNotFoundException extends Exception +final class PathNotFoundException extends Exception { public function __construct(private string $path) diff --git a/src/File/SimpleRelativePathHelper.php b/src/File/SimpleRelativePathHelper.php index 854f584b478..eff28f55410 100644 --- a/src/File/SimpleRelativePathHelper.php +++ b/src/File/SimpleRelativePathHelper.php @@ -7,7 +7,7 @@ use function strlen; use function substr; -class SimpleRelativePathHelper implements RelativePathHelper +final class SimpleRelativePathHelper implements RelativePathHelper { public function __construct(private string $currentWorkingDirectory) diff --git a/src/File/SystemAgnosticSimpleRelativePathHelper.php b/src/File/SystemAgnosticSimpleRelativePathHelper.php index d040956fb2d..bd4ac68d67f 100644 --- a/src/File/SystemAgnosticSimpleRelativePathHelper.php +++ b/src/File/SystemAgnosticSimpleRelativePathHelper.php @@ -6,7 +6,7 @@ use function strlen; use function substr; -class SystemAgnosticSimpleRelativePathHelper implements RelativePathHelper +final class SystemAgnosticSimpleRelativePathHelper implements RelativePathHelper { public function __construct(private FileHelper $fileHelper) diff --git a/src/Internal/BytesHelper.php b/src/Internal/BytesHelper.php index 48a852c0a3d..3279501f515 100644 --- a/src/Internal/BytesHelper.php +++ b/src/Internal/BytesHelper.php @@ -7,7 +7,7 @@ use function end; use function round; -class BytesHelper +final class BytesHelper { public static function bytes(int $bytes): string diff --git a/src/Internal/ContainerDynamicReturnTypeExtension.php b/src/Internal/ContainerDynamicReturnTypeExtension.php index f4b0a340bb9..7ca1a0a70b0 100644 --- a/src/Internal/ContainerDynamicReturnTypeExtension.php +++ b/src/Internal/ContainerDynamicReturnTypeExtension.php @@ -15,7 +15,7 @@ use function count; use function in_array; -class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Internal/SprintfHelper.php b/src/Internal/SprintfHelper.php index 30d08500fa6..6938c898f1f 100644 --- a/src/Internal/SprintfHelper.php +++ b/src/Internal/SprintfHelper.php @@ -4,7 +4,7 @@ use function str_replace; -class SprintfHelper +final class SprintfHelper { public static function escapeFormatString(string $format): string diff --git a/src/Node/BooleanAndNode.php b/src/Node/BooleanAndNode.php index 55573e7eb8b..6d508713e72 100644 --- a/src/Node/BooleanAndNode.php +++ b/src/Node/BooleanAndNode.php @@ -7,7 +7,10 @@ use PhpParser\Node\Expr\BinaryOp\LogicalAnd; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class BooleanAndNode extends Expr implements VirtualNode { diff --git a/src/Node/BooleanOrNode.php b/src/Node/BooleanOrNode.php index 0c1523e60b5..ca327164ee1 100644 --- a/src/Node/BooleanOrNode.php +++ b/src/Node/BooleanOrNode.php @@ -7,7 +7,10 @@ use PhpParser\Node\Expr\BinaryOp\LogicalOr; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class BooleanOrNode extends Expr implements VirtualNode { diff --git a/src/Node/BreaklessWhileLoopNode.php b/src/Node/BreaklessWhileLoopNode.php index 8a824778bca..3d3ff248a20 100644 --- a/src/Node/BreaklessWhileLoopNode.php +++ b/src/Node/BreaklessWhileLoopNode.php @@ -6,7 +6,10 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\StatementExitPoint; -/** @api */ +/** + * @api + * @final + */ class BreaklessWhileLoopNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/CatchWithUnthrownExceptionNode.php b/src/Node/CatchWithUnthrownExceptionNode.php index 007fa832189..d1872895ddb 100644 --- a/src/Node/CatchWithUnthrownExceptionNode.php +++ b/src/Node/CatchWithUnthrownExceptionNode.php @@ -6,7 +6,10 @@ use PhpParser\NodeAbstract; use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class CatchWithUnthrownExceptionNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/ClassConstantsNode.php b/src/Node/ClassConstantsNode.php index dee7d0019a8..0b12946d8cf 100644 --- a/src/Node/ClassConstantsNode.php +++ b/src/Node/ClassConstantsNode.php @@ -8,7 +8,10 @@ use PHPStan\Node\Constant\ClassConstantFetch; use PHPStan\Reflection\ClassReflection; -/** @api */ +/** + * @api + * @final + */ class ClassConstantsNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/ClassMethod.php b/src/Node/ClassMethod.php index 6ecfaeead8f..e3f2cef2217 100644 --- a/src/Node/ClassMethod.php +++ b/src/Node/ClassMethod.php @@ -4,7 +4,10 @@ use PhpParser\Node\Stmt\ClassMethod as PhpParserClassMethod; -/** @api */ +/** + * @api + * @final + */ class ClassMethod extends PhpParserClassMethod { diff --git a/src/Node/ClassMethodsNode.php b/src/Node/ClassMethodsNode.php index e02620532bd..3a8a2df77d1 100644 --- a/src/Node/ClassMethodsNode.php +++ b/src/Node/ClassMethodsNode.php @@ -7,7 +7,10 @@ use PHPStan\Node\Method\MethodCall; use PHPStan\Reflection\ClassReflection; -/** @api */ +/** + * @api + * @final + */ class ClassMethodsNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 8d9659a68ba..302dab88f69 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -28,7 +28,10 @@ use function in_array; use function strtolower; -/** @api */ +/** + * @api + * @final + */ class ClassPropertiesNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index a7c81ff9d40..d8aaea62185 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -11,7 +11,10 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ClassPropertyNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 6b8b2438c39..a344e4349fd 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -23,7 +23,7 @@ use function in_array; use function strtolower; -class ClassStatementsGatherer +final class ClassStatementsGatherer { private const PROPERTY_ENUMERATING_FUNCTIONS = [ diff --git a/src/Node/ClosureReturnStatementsNode.php b/src/Node/ClosureReturnStatementsNode.php index 0a20615ca88..7231c02e5f1 100644 --- a/src/Node/ClosureReturnStatementsNode.php +++ b/src/Node/ClosureReturnStatementsNode.php @@ -11,7 +11,10 @@ use PHPStan\Analyser\StatementResult; use function count; -/** @api */ +/** + * @api + * @final + */ class ClosureReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { diff --git a/src/Node/CollectedDataNode.php b/src/Node/CollectedDataNode.php index 8f7684e1ec5..c02175b246a 100644 --- a/src/Node/CollectedDataNode.php +++ b/src/Node/CollectedDataNode.php @@ -8,7 +8,10 @@ use PHPStan\Collectors\Collector; use function array_key_exists; -/** @api */ +/** + * @api + * @final + */ class CollectedDataNode extends NodeAbstract { diff --git a/src/Node/Constant/ClassConstantFetch.php b/src/Node/Constant/ClassConstantFetch.php index 30129007d01..8f23935dd1c 100644 --- a/src/Node/Constant/ClassConstantFetch.php +++ b/src/Node/Constant/ClassConstantFetch.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr\ClassConstFetch; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class ClassConstantFetch { diff --git a/src/Node/DoWhileLoopConditionNode.php b/src/Node/DoWhileLoopConditionNode.php index c6cbc865728..89f6c7f4bf1 100644 --- a/src/Node/DoWhileLoopConditionNode.php +++ b/src/Node/DoWhileLoopConditionNode.php @@ -6,7 +6,7 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\StatementExitPoint; -class DoWhileLoopConditionNode extends NodeAbstract implements VirtualNode +final class DoWhileLoopConditionNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/ExecutionEndNode.php b/src/Node/ExecutionEndNode.php index c0a0f52dd89..e9cf081b135 100644 --- a/src/Node/ExecutionEndNode.php +++ b/src/Node/ExecutionEndNode.php @@ -6,7 +6,10 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\StatementResult; -/** @api */ +/** + * @api + * @final + */ class ExecutionEndNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/Expr/AlwaysRememberedExpr.php b/src/Node/Expr/AlwaysRememberedExpr.php index 049ad46a6b4..b3ed1cc12e8 100644 --- a/src/Node/Expr/AlwaysRememberedExpr.php +++ b/src/Node/Expr/AlwaysRememberedExpr.php @@ -6,7 +6,7 @@ use PHPStan\Node\VirtualNode; use PHPStan\Type\Type; -class AlwaysRememberedExpr extends Expr implements VirtualNode +final class AlwaysRememberedExpr extends Expr implements VirtualNode { public function __construct(public Expr $expr, private Type $type, private Type $nativeType) diff --git a/src/Node/Expr/ExistingArrayDimFetch.php b/src/Node/Expr/ExistingArrayDimFetch.php index 80412b4fa36..c388667cac1 100644 --- a/src/Node/Expr/ExistingArrayDimFetch.php +++ b/src/Node/Expr/ExistingArrayDimFetch.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class ExistingArrayDimFetch extends Expr implements VirtualNode +final class ExistingArrayDimFetch extends Expr implements VirtualNode { public function __construct(private Expr $var, private Expr $dim) diff --git a/src/Node/Expr/GetIterableKeyTypeExpr.php b/src/Node/Expr/GetIterableKeyTypeExpr.php index 40301cc29f1..4bc9c39ecc3 100644 --- a/src/Node/Expr/GetIterableKeyTypeExpr.php +++ b/src/Node/Expr/GetIterableKeyTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class GetIterableKeyTypeExpr extends Expr implements VirtualNode +final class GetIterableKeyTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr $expr) diff --git a/src/Node/Expr/GetIterableValueTypeExpr.php b/src/Node/Expr/GetIterableValueTypeExpr.php index 2fd920f21a8..43d3a19a090 100644 --- a/src/Node/Expr/GetIterableValueTypeExpr.php +++ b/src/Node/Expr/GetIterableValueTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class GetIterableValueTypeExpr extends Expr implements VirtualNode +final class GetIterableValueTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr $expr) diff --git a/src/Node/Expr/GetOffsetValueTypeExpr.php b/src/Node/Expr/GetOffsetValueTypeExpr.php index ed7b33a1249..6bb047212a5 100644 --- a/src/Node/Expr/GetOffsetValueTypeExpr.php +++ b/src/Node/Expr/GetOffsetValueTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class GetOffsetValueTypeExpr extends Expr implements VirtualNode +final class GetOffsetValueTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr $var, private Expr $dim) diff --git a/src/Node/Expr/OriginalPropertyTypeExpr.php b/src/Node/Expr/OriginalPropertyTypeExpr.php index 2250a8158b4..b04990efdcd 100644 --- a/src/Node/Expr/OriginalPropertyTypeExpr.php +++ b/src/Node/Expr/OriginalPropertyTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class OriginalPropertyTypeExpr extends Expr implements VirtualNode +final class OriginalPropertyTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr\PropertyFetch|Expr\StaticPropertyFetch $propertyFetch) diff --git a/src/Node/Expr/ParameterVariableOriginalValueExpr.php b/src/Node/Expr/ParameterVariableOriginalValueExpr.php index 277d51c0409..7f3408becbc 100644 --- a/src/Node/Expr/ParameterVariableOriginalValueExpr.php +++ b/src/Node/Expr/ParameterVariableOriginalValueExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class ParameterVariableOriginalValueExpr extends Expr implements VirtualNode +final class ParameterVariableOriginalValueExpr extends Expr implements VirtualNode { public function __construct(private string $variableName) diff --git a/src/Node/Expr/PropertyInitializationExpr.php b/src/Node/Expr/PropertyInitializationExpr.php index 942fa08d5c0..6963d256707 100644 --- a/src/Node/Expr/PropertyInitializationExpr.php +++ b/src/Node/Expr/PropertyInitializationExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class PropertyInitializationExpr extends Expr implements VirtualNode +final class PropertyInitializationExpr extends Expr implements VirtualNode { public function __construct(private string $propertyName) diff --git a/src/Node/Expr/SetExistingOffsetValueTypeExpr.php b/src/Node/Expr/SetExistingOffsetValueTypeExpr.php index 52eb9a4b370..ee4292f65c8 100644 --- a/src/Node/Expr/SetExistingOffsetValueTypeExpr.php +++ b/src/Node/Expr/SetExistingOffsetValueTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class SetExistingOffsetValueTypeExpr extends Expr implements VirtualNode +final class SetExistingOffsetValueTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr $var, private Expr $dim, private Expr $value) diff --git a/src/Node/Expr/SetOffsetValueTypeExpr.php b/src/Node/Expr/SetOffsetValueTypeExpr.php index 3e42e13b0c3..ed6ddae6577 100644 --- a/src/Node/Expr/SetOffsetValueTypeExpr.php +++ b/src/Node/Expr/SetOffsetValueTypeExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class SetOffsetValueTypeExpr extends Expr implements VirtualNode +final class SetOffsetValueTypeExpr extends Expr implements VirtualNode { public function __construct(private Expr $var, private ?Expr $dim, private Expr $value) diff --git a/src/Node/Expr/TypeExpr.php b/src/Node/Expr/TypeExpr.php index 4adc5be0e33..35e364f15cf 100644 --- a/src/Node/Expr/TypeExpr.php +++ b/src/Node/Expr/TypeExpr.php @@ -6,7 +6,7 @@ use PHPStan\Node\VirtualNode; use PHPStan\Type\Type; -class TypeExpr extends Expr implements VirtualNode +final class TypeExpr extends Expr implements VirtualNode { public function __construct(private Type $exprType) diff --git a/src/Node/Expr/UnsetOffsetExpr.php b/src/Node/Expr/UnsetOffsetExpr.php index 55c81eef925..422affc9aff 100644 --- a/src/Node/Expr/UnsetOffsetExpr.php +++ b/src/Node/Expr/UnsetOffsetExpr.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PHPStan\Node\VirtualNode; -class UnsetOffsetExpr extends Expr implements VirtualNode +final class UnsetOffsetExpr extends Expr implements VirtualNode { public function __construct(private Expr $var, private Expr $dim) diff --git a/src/Node/FileNode.php b/src/Node/FileNode.php index 13a6596ced3..355a4b9559d 100644 --- a/src/Node/FileNode.php +++ b/src/Node/FileNode.php @@ -5,7 +5,10 @@ use PhpParser\Node; use PhpParser\NodeAbstract; -/** @api */ +/** + * @api + * @final + */ class FileNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/FinallyExitPointsNode.php b/src/Node/FinallyExitPointsNode.php index a87bdc516cc..32c2adb50c7 100644 --- a/src/Node/FinallyExitPointsNode.php +++ b/src/Node/FinallyExitPointsNode.php @@ -5,7 +5,10 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\StatementExitPoint; -/** @api */ +/** + * @api + * @final + */ class FinallyExitPointsNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/FunctionCallableNode.php b/src/Node/FunctionCallableNode.php index d43e0228ce2..00e9e24fba9 100644 --- a/src/Node/FunctionCallableNode.php +++ b/src/Node/FunctionCallableNode.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; -/** @api */ +/** + * @api + * @final + */ class FunctionCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/FunctionReturnStatementsNode.php b/src/Node/FunctionReturnStatementsNode.php index 86df35e24c3..0b065f8a25f 100644 --- a/src/Node/FunctionReturnStatementsNode.php +++ b/src/Node/FunctionReturnStatementsNode.php @@ -12,7 +12,10 @@ use PHPStan\Reflection\FunctionReflection; use function count; -/** @api */ +/** + * @api + * @final + */ class FunctionReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { diff --git a/src/Node/InArrowFunctionNode.php b/src/Node/InArrowFunctionNode.php index 7716f332b4a..fb60b2b4045 100644 --- a/src/Node/InArrowFunctionNode.php +++ b/src/Node/InArrowFunctionNode.php @@ -7,7 +7,10 @@ use PhpParser\NodeAbstract; use PHPStan\Type\ClosureType; -/** @api */ +/** + * @api + * @final + */ class InArrowFunctionNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/InClassMethodNode.php b/src/Node/InClassMethodNode.php index da1d8a09b7b..f74db8e890c 100644 --- a/src/Node/InClassMethodNode.php +++ b/src/Node/InClassMethodNode.php @@ -6,7 +6,10 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; -/** @api */ +/** + * @api + * @final + */ class InClassMethodNode extends Node\Stmt implements VirtualNode { diff --git a/src/Node/InClassNode.php b/src/Node/InClassNode.php index 23310aaec0d..1be1b7fab28 100644 --- a/src/Node/InClassNode.php +++ b/src/Node/InClassNode.php @@ -6,7 +6,10 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Reflection\ClassReflection; -/** @api */ +/** + * @api + * @final + */ class InClassNode extends Node\Stmt implements VirtualNode { diff --git a/src/Node/InClosureNode.php b/src/Node/InClosureNode.php index 8882ebd0e3d..21def5dbefe 100644 --- a/src/Node/InClosureNode.php +++ b/src/Node/InClosureNode.php @@ -7,7 +7,10 @@ use PhpParser\NodeAbstract; use PHPStan\Type\ClosureType; -/** @api */ +/** + * @api + * @final + */ class InClosureNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/InForeachNode.php b/src/Node/InForeachNode.php index b39026a9c95..4e474c98b2f 100644 --- a/src/Node/InForeachNode.php +++ b/src/Node/InForeachNode.php @@ -5,7 +5,7 @@ use PhpParser\Node\Stmt\Foreach_; use PhpParser\NodeAbstract; -class InForeachNode extends NodeAbstract implements VirtualNode +final class InForeachNode extends NodeAbstract implements VirtualNode { public function __construct(private Foreach_ $originalNode) diff --git a/src/Node/InFunctionNode.php b/src/Node/InFunctionNode.php index 548c2ac4224..550c00e41b2 100644 --- a/src/Node/InFunctionNode.php +++ b/src/Node/InFunctionNode.php @@ -5,7 +5,10 @@ use PhpParser\Node; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; -/** @api */ +/** + * @api + * @final + */ class InFunctionNode extends Node\Stmt implements VirtualNode { diff --git a/src/Node/InTraitNode.php b/src/Node/InTraitNode.php index 688c04499bd..39b0dc509b3 100644 --- a/src/Node/InTraitNode.php +++ b/src/Node/InTraitNode.php @@ -5,7 +5,10 @@ use PhpParser\Node; use PHPStan\Reflection\ClassReflection; -/** @api */ +/** + * @api + * @final + */ class InTraitNode extends Node\Stmt implements VirtualNode { diff --git a/src/Node/InstantiationCallableNode.php b/src/Node/InstantiationCallableNode.php index 382431f70ad..289fb6fe5f6 100644 --- a/src/Node/InstantiationCallableNode.php +++ b/src/Node/InstantiationCallableNode.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; -/** @api */ +/** + * @api + * @final + */ class InstantiationCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/InvalidateExprNode.php b/src/Node/InvalidateExprNode.php index fa8ab6b0618..a59799f0aa0 100644 --- a/src/Node/InvalidateExprNode.php +++ b/src/Node/InvalidateExprNode.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PhpParser\NodeAbstract; -/** @api */ +/** + * @api + * @final + */ class InvalidateExprNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/IssetExpr.php b/src/Node/IssetExpr.php index 5c45df0ebc2..fbe53cbc6a1 100644 --- a/src/Node/IssetExpr.php +++ b/src/Node/IssetExpr.php @@ -4,7 +4,7 @@ use PhpParser\Node\Expr; -class IssetExpr extends Expr implements VirtualNode +final class IssetExpr extends Expr implements VirtualNode { public function __construct( diff --git a/src/Node/LiteralArrayItem.php b/src/Node/LiteralArrayItem.php index 48dd0e3705d..ea9be27be6c 100644 --- a/src/Node/LiteralArrayItem.php +++ b/src/Node/LiteralArrayItem.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr\ArrayItem; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class LiteralArrayItem { diff --git a/src/Node/LiteralArrayNode.php b/src/Node/LiteralArrayNode.php index bcbd3f0a937..e0bd824fad1 100644 --- a/src/Node/LiteralArrayNode.php +++ b/src/Node/LiteralArrayNode.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr\Array_; use PhpParser\NodeAbstract; -/** @api */ +/** + * @api + * @final + */ class LiteralArrayNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/MatchExpressionArm.php b/src/Node/MatchExpressionArm.php index bc8643af458..427ed83cae3 100644 --- a/src/Node/MatchExpressionArm.php +++ b/src/Node/MatchExpressionArm.php @@ -2,7 +2,10 @@ namespace PHPStan\Node; -/** @api */ +/** + * @api + * @final + */ class MatchExpressionArm { diff --git a/src/Node/MatchExpressionArmBody.php b/src/Node/MatchExpressionArmBody.php index 544d6237dea..628f0f777c1 100644 --- a/src/Node/MatchExpressionArmBody.php +++ b/src/Node/MatchExpressionArmBody.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class MatchExpressionArmBody { diff --git a/src/Node/MatchExpressionArmCondition.php b/src/Node/MatchExpressionArmCondition.php index 2928175be79..4b3bc547203 100644 --- a/src/Node/MatchExpressionArmCondition.php +++ b/src/Node/MatchExpressionArmCondition.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class MatchExpressionArmCondition { diff --git a/src/Node/MatchExpressionNode.php b/src/Node/MatchExpressionNode.php index 00ec3bcf8c7..0dd31d9b2ba 100644 --- a/src/Node/MatchExpressionNode.php +++ b/src/Node/MatchExpressionNode.php @@ -6,7 +6,10 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class MatchExpressionNode extends NodeAbstract implements VirtualNode { diff --git a/src/Node/Method/MethodCall.php b/src/Node/Method/MethodCall.php index 19c77316fb7..c88997a3f9e 100644 --- a/src/Node/Method/MethodCall.php +++ b/src/Node/Method/MethodCall.php @@ -7,7 +7,10 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class MethodCall { diff --git a/src/Node/MethodCallableNode.php b/src/Node/MethodCallableNode.php index aff7b5cefc8..7e7f1240ddc 100644 --- a/src/Node/MethodCallableNode.php +++ b/src/Node/MethodCallableNode.php @@ -5,7 +5,10 @@ use PhpParser\Node\Expr; use PhpParser\Node\Identifier; -/** @api */ +/** + * @api + * @final + */ class MethodCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/MethodReturnStatementsNode.php b/src/Node/MethodReturnStatementsNode.php index 3151d12ae3a..0103f580597 100644 --- a/src/Node/MethodReturnStatementsNode.php +++ b/src/Node/MethodReturnStatementsNode.php @@ -13,7 +13,10 @@ use PHPStan\Reflection\ExtendedMethodReflection; use function count; -/** @api */ +/** + * @api + * @final + */ class MethodReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { diff --git a/src/Node/NoopExpressionNode.php b/src/Node/NoopExpressionNode.php index 38e9222a8c6..6723ee92595 100644 --- a/src/Node/NoopExpressionNode.php +++ b/src/Node/NoopExpressionNode.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\NodeAbstract; -class NoopExpressionNode extends NodeAbstract implements VirtualNode +final class NoopExpressionNode extends NodeAbstract implements VirtualNode { public function __construct(private Expr $originalExpr, private bool $hasAssign) diff --git a/src/Node/Printer/ExprPrinter.php b/src/Node/Printer/ExprPrinter.php index 3763201b5ff..6df730ddfc4 100644 --- a/src/Node/Printer/ExprPrinter.php +++ b/src/Node/Printer/ExprPrinter.php @@ -4,7 +4,10 @@ use PhpParser\Node\Expr; -/** @api */ +/** + * @api + * @final + */ class ExprPrinter { diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 953174fc70f..78455184f0b 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -19,7 +19,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class Printer extends Standard +final class Printer extends Standard { public function __construct() diff --git a/src/Node/Property/PropertyRead.php b/src/Node/Property/PropertyRead.php index 27bc3ba5c47..1c24537453b 100644 --- a/src/Node/Property/PropertyRead.php +++ b/src/Node/Property/PropertyRead.php @@ -6,7 +6,10 @@ use PhpParser\Node\Expr\StaticPropertyFetch; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class PropertyRead { diff --git a/src/Node/Property/PropertyWrite.php b/src/Node/Property/PropertyWrite.php index 9577dc7fa85..fec7a1c4f0f 100644 --- a/src/Node/Property/PropertyWrite.php +++ b/src/Node/Property/PropertyWrite.php @@ -6,7 +6,10 @@ use PhpParser\Node\Expr\StaticPropertyFetch; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class PropertyWrite { diff --git a/src/Node/PropertyAssignNode.php b/src/Node/PropertyAssignNode.php index 62a5d7ac1e3..7537b8935dd 100644 --- a/src/Node/PropertyAssignNode.php +++ b/src/Node/PropertyAssignNode.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\NodeAbstract; -class PropertyAssignNode extends NodeAbstract implements VirtualNode +final class PropertyAssignNode extends NodeAbstract implements VirtualNode { public function __construct( diff --git a/src/Node/ReturnStatement.php b/src/Node/ReturnStatement.php index 99767fba7b4..7a5da6f2035 100644 --- a/src/Node/ReturnStatement.php +++ b/src/Node/ReturnStatement.php @@ -6,7 +6,10 @@ use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\Scope; -/** @api */ +/** + * @api + * @final + */ class ReturnStatement { diff --git a/src/Node/StaticMethodCallableNode.php b/src/Node/StaticMethodCallableNode.php index a4c6e675ef2..19e823234bb 100644 --- a/src/Node/StaticMethodCallableNode.php +++ b/src/Node/StaticMethodCallableNode.php @@ -6,7 +6,10 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; -/** @api */ +/** + * @api + * @final + */ class StaticMethodCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index fb05c30a509..7c3cfb163c0 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -4,7 +4,10 @@ use PhpParser\Node\Stmt; -/** @api */ +/** + * @api + * @final + */ class UnreachableStatementNode extends Stmt implements VirtualNode { diff --git a/src/Node/VarTagChangedExpressionTypeNode.php b/src/Node/VarTagChangedExpressionTypeNode.php index 6689aa7a0dc..c57a90f7a52 100644 --- a/src/Node/VarTagChangedExpressionTypeNode.php +++ b/src/Node/VarTagChangedExpressionTypeNode.php @@ -6,7 +6,7 @@ use PhpParser\NodeAbstract; use PHPStan\PhpDoc\Tag\VarTag; -class VarTagChangedExpressionTypeNode extends NodeAbstract implements VirtualNode +final class VarTagChangedExpressionTypeNode extends NodeAbstract implements VirtualNode { public function __construct(private VarTag $varTag, private Expr $expr) diff --git a/src/Node/VariableAssignNode.php b/src/Node/VariableAssignNode.php index 2857e853ba7..695f59bf2ae 100644 --- a/src/Node/VariableAssignNode.php +++ b/src/Node/VariableAssignNode.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\NodeAbstract; -class VariableAssignNode extends NodeAbstract implements VirtualNode +final class VariableAssignNode extends NodeAbstract implements VirtualNode { public function __construct( diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 65f8776c197..2ce90b21604 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -33,7 +33,7 @@ use function str_contains; use const PHP_URL_PORT; -class ParallelAnalyser +final class ParallelAnalyser { private const DEFAULT_TIMEOUT = 600.0; diff --git a/src/Parallel/Process.php b/src/Parallel/Process.php index d95609b6ef8..e5cf90566fe 100644 --- a/src/Parallel/Process.php +++ b/src/Parallel/Process.php @@ -15,7 +15,7 @@ use function stream_get_contents; use function tmpfile; -class Process +final class Process { public \React\ChildProcess\Process $process; diff --git a/src/Parallel/ProcessPool.php b/src/Parallel/ProcessPool.php index ac0569c5091..574cabcf6e6 100644 --- a/src/Parallel/ProcessPool.php +++ b/src/Parallel/ProcessPool.php @@ -9,7 +9,7 @@ use function count; use function sprintf; -class ProcessPool +final class ProcessPool { /** @var array */ diff --git a/src/Parallel/ProcessTimedOutException.php b/src/Parallel/ProcessTimedOutException.php index 77e42107f72..50667999a1d 100644 --- a/src/Parallel/ProcessTimedOutException.php +++ b/src/Parallel/ProcessTimedOutException.php @@ -4,7 +4,7 @@ use Exception; -class ProcessTimedOutException extends Exception +final class ProcessTimedOutException extends Exception { } diff --git a/src/Parallel/Schedule.php b/src/Parallel/Schedule.php index 42935bd6c58..b37935a4ce8 100644 --- a/src/Parallel/Schedule.php +++ b/src/Parallel/Schedule.php @@ -2,7 +2,7 @@ namespace PHPStan\Parallel; -class Schedule +final class Schedule { /** diff --git a/src/Parallel/Scheduler.php b/src/Parallel/Scheduler.php index 77394959b2b..45ad9086f1f 100644 --- a/src/Parallel/Scheduler.php +++ b/src/Parallel/Scheduler.php @@ -11,7 +11,7 @@ use function min; use function sprintf; -class Scheduler implements DiagnoseExtension +final class Scheduler implements DiagnoseExtension { /** @var array{int, int, int, int}|null */ diff --git a/src/Parser/ArrayFilterArgVisitor.php b/src/Parser/ArrayFilterArgVisitor.php index bf7f9eef75a..a2730deb3d5 100644 --- a/src/Parser/ArrayFilterArgVisitor.php +++ b/src/Parser/ArrayFilterArgVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class ArrayFilterArgVisitor extends NodeVisitorAbstract +final class ArrayFilterArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'isArrayFilterArg'; diff --git a/src/Parser/ArrayMapArgVisitor.php b/src/Parser/ArrayMapArgVisitor.php index b6e7a12ccbe..0c62d0c7c4d 100644 --- a/src/Parser/ArrayMapArgVisitor.php +++ b/src/Parser/ArrayMapArgVisitor.php @@ -7,7 +7,7 @@ use function array_slice; use function count; -class ArrayMapArgVisitor extends NodeVisitorAbstract +final class ArrayMapArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'arrayMapArgs'; diff --git a/src/Parser/ArrayWalkArgVisitor.php b/src/Parser/ArrayWalkArgVisitor.php index addc7dc1ba4..ad776bb1751 100644 --- a/src/Parser/ArrayWalkArgVisitor.php +++ b/src/Parser/ArrayWalkArgVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class ArrayWalkArgVisitor extends NodeVisitorAbstract +final class ArrayWalkArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'isArrayWalkArg'; diff --git a/src/Parser/ArrowFunctionArgVisitor.php b/src/Parser/ArrowFunctionArgVisitor.php index 24b04718fa9..f8149dad21c 100644 --- a/src/Parser/ArrowFunctionArgVisitor.php +++ b/src/Parser/ArrowFunctionArgVisitor.php @@ -6,7 +6,7 @@ use PhpParser\NodeVisitorAbstract; use function count; -class ArrowFunctionArgVisitor extends NodeVisitorAbstract +final class ArrowFunctionArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'arrowFunctionCallArgs'; diff --git a/src/Parser/CachedParser.php b/src/Parser/CachedParser.php index 225375678cb..62bba62a2ba 100644 --- a/src/Parser/CachedParser.php +++ b/src/Parser/CachedParser.php @@ -6,7 +6,7 @@ use PHPStan\File\FileReader; use function array_slice; -class CachedParser implements Parser +final class CachedParser implements Parser { /** @var array*/ diff --git a/src/Parser/CleaningParser.php b/src/Parser/CleaningParser.php index 98db0e64ef8..0f874eafbff 100644 --- a/src/Parser/CleaningParser.php +++ b/src/Parser/CleaningParser.php @@ -6,7 +6,7 @@ use PhpParser\NodeTraverser; use PHPStan\Php\PhpVersion; -class CleaningParser implements Parser +final class CleaningParser implements Parser { private NodeTraverser $traverser; diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index 0a2c9aecff2..773c36f6e4f 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ParametersAcceptor; use function in_array; -class CleaningVisitor extends NodeVisitorAbstract +final class CleaningVisitor extends NodeVisitorAbstract { private NodeFinder $nodeFinder; diff --git a/src/Parser/ClosureArgVisitor.php b/src/Parser/ClosureArgVisitor.php index df36796570a..58d53a808e3 100644 --- a/src/Parser/ClosureArgVisitor.php +++ b/src/Parser/ClosureArgVisitor.php @@ -6,7 +6,7 @@ use PhpParser\NodeVisitorAbstract; use function count; -class ClosureArgVisitor extends NodeVisitorAbstract +final class ClosureArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'closureCallArgs'; diff --git a/src/Parser/ClosureBindArgVisitor.php b/src/Parser/ClosureBindArgVisitor.php index de341a57d98..291ede59b45 100644 --- a/src/Parser/ClosureBindArgVisitor.php +++ b/src/Parser/ClosureBindArgVisitor.php @@ -7,7 +7,7 @@ use PhpParser\NodeVisitorAbstract; use function count; -class ClosureBindArgVisitor extends NodeVisitorAbstract +final class ClosureBindArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'closureBindArg'; diff --git a/src/Parser/ClosureBindToVarVisitor.php b/src/Parser/ClosureBindToVarVisitor.php index f3582ba617e..7196d50093b 100644 --- a/src/Parser/ClosureBindToVarVisitor.php +++ b/src/Parser/ClosureBindToVarVisitor.php @@ -6,7 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\NodeVisitorAbstract; -class ClosureBindToVarVisitor extends NodeVisitorAbstract +final class ClosureBindToVarVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'closureBindToVar'; diff --git a/src/Parser/CurlSetOptArgVisitor.php b/src/Parser/CurlSetOptArgVisitor.php index 13910ec5a1f..f9be2fd5c3c 100644 --- a/src/Parser/CurlSetOptArgVisitor.php +++ b/src/Parser/CurlSetOptArgVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class CurlSetOptArgVisitor extends NodeVisitorAbstract +final class CurlSetOptArgVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'isCurlSetOptArg'; diff --git a/src/Parser/DeclarePositionVisitor.php b/src/Parser/DeclarePositionVisitor.php index 08818c16522..e0d523603f4 100644 --- a/src/Parser/DeclarePositionVisitor.php +++ b/src/Parser/DeclarePositionVisitor.php @@ -6,7 +6,7 @@ use PhpParser\NodeVisitorAbstract; use function str_starts_with; -class DeclarePositionVisitor extends NodeVisitorAbstract +final class DeclarePositionVisitor extends NodeVisitorAbstract { private bool $isFirstStatement = true; diff --git a/src/Parser/FunctionCallStatementFinder.php b/src/Parser/FunctionCallStatementFinder.php index 4f1b190d35e..9a4c1dd6bbf 100644 --- a/src/Parser/FunctionCallStatementFinder.php +++ b/src/Parser/FunctionCallStatementFinder.php @@ -8,7 +8,7 @@ use function in_array; use function is_array; -class FunctionCallStatementFinder +final class FunctionCallStatementFinder { /** diff --git a/src/Parser/LastConditionVisitor.php b/src/Parser/LastConditionVisitor.php index 5edd559ed09..ce21f571fd4 100644 --- a/src/Parser/LastConditionVisitor.php +++ b/src/Parser/LastConditionVisitor.php @@ -6,7 +6,7 @@ use PhpParser\NodeVisitorAbstract; use function count; -class LastConditionVisitor extends NodeVisitorAbstract +final class LastConditionVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'isLastCondition'; diff --git a/src/Parser/LexerFactory.php b/src/Parser/LexerFactory.php index 624eb251bfd..5fa801ddec3 100644 --- a/src/Parser/LexerFactory.php +++ b/src/Parser/LexerFactory.php @@ -6,7 +6,7 @@ use PHPStan\Php\PhpVersion; use const PHP_VERSION_ID; -class LexerFactory +final class LexerFactory { private const OPTIONS = ['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos']]; diff --git a/src/Parser/MagicConstantParamDefaultVisitor.php b/src/Parser/MagicConstantParamDefaultVisitor.php index 5bc27ada080..455c341e4e3 100644 --- a/src/Parser/MagicConstantParamDefaultVisitor.php +++ b/src/Parser/MagicConstantParamDefaultVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class MagicConstantParamDefaultVisitor extends NodeVisitorAbstract +final class MagicConstantParamDefaultVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'isMagicConstantParamDefault'; diff --git a/src/Parser/NewAssignedToPropertyVisitor.php b/src/Parser/NewAssignedToPropertyVisitor.php index a38bab16fb0..05df87b423c 100644 --- a/src/Parser/NewAssignedToPropertyVisitor.php +++ b/src/Parser/NewAssignedToPropertyVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class NewAssignedToPropertyVisitor extends NodeVisitorAbstract +final class NewAssignedToPropertyVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'assignedToProperty'; diff --git a/src/Parser/ParserErrorsException.php b/src/Parser/ParserErrorsException.php index 1013be73d4a..d68d18220a7 100644 --- a/src/Parser/ParserErrorsException.php +++ b/src/Parser/ParserErrorsException.php @@ -8,7 +8,7 @@ use function count; use function implode; -class ParserErrorsException extends Exception +final class ParserErrorsException extends Exception { /** @var mixed[] */ diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index def6786f5ef..83fae0a8910 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -13,7 +13,7 @@ use function str_contains; use const DIRECTORY_SEPARATOR; -class PathRoutingParser implements Parser +final class PathRoutingParser implements Parser { /** @var bool[] filePath(string) => bool(true) */ diff --git a/src/Parser/PhpParserDecorator.php b/src/Parser/PhpParserDecorator.php index 5626f20b1ab..14c462c39fc 100644 --- a/src/Parser/PhpParserDecorator.php +++ b/src/Parser/PhpParserDecorator.php @@ -8,7 +8,7 @@ use PhpParser\Parser; use function sprintf; -class PhpParserDecorator implements Parser +final class PhpParserDecorator implements Parser { public function __construct(private \PHPStan\Parser\Parser $wrappedParser) diff --git a/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php b/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php index eed9b93bf7c..48722eeca51 100644 --- a/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php +++ b/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php @@ -8,14 +8,14 @@ use function count; use function version_compare; -class RemoveUnusedCodeByPhpVersionIdVisitor extends NodeVisitorAbstract +final class RemoveUnusedCodeByPhpVersionIdVisitor extends NodeVisitorAbstract { public function __construct(private string $phpVersionString) { } - public function enterNode(Node $node): Node|int|null + public function enterNode(Node $node): ?Node { if (!$node instanceof Node\Stmt\If_) { return null; diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index 11281ca7fd3..b5863a4b800 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -31,7 +31,7 @@ use const T_DOC_COMMENT; use const T_WHITESPACE; -class RichParser implements Parser +final class RichParser implements Parser { public const VISITOR_SERVICE_TAG = 'phpstan.parser.richParserNodeVisitor'; diff --git a/src/Parser/SimpleParser.php b/src/Parser/SimpleParser.php index efcf47d786a..713c1502ef2 100644 --- a/src/Parser/SimpleParser.php +++ b/src/Parser/SimpleParser.php @@ -9,7 +9,7 @@ use PHPStan\File\FileReader; use PHPStan\ShouldNotHappenException; -class SimpleParser implements Parser +final class SimpleParser implements Parser { public function __construct( diff --git a/src/Parser/TypeTraverserInstanceofVisitor.php b/src/Parser/TypeTraverserInstanceofVisitor.php index a39226bbb25..e353d31af3c 100644 --- a/src/Parser/TypeTraverserInstanceofVisitor.php +++ b/src/Parser/TypeTraverserInstanceofVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class TypeTraverserInstanceofVisitor extends NodeVisitorAbstract +final class TypeTraverserInstanceofVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'insideTypeTraverserMap'; diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index c5afa1e22f8..3e3d1e112d8 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -4,7 +4,10 @@ use function floor; -/** @api */ +/** + * @api + * @final + */ class PhpVersion { diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index 02c0c8b749f..ef1e244ac42 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -7,7 +7,7 @@ use function min; use const PHP_VERSION_ID; -class PhpVersionFactory +final class PhpVersionFactory { public function __construct( diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index 870d1ff276d..c578ef06fce 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -11,7 +11,7 @@ use function is_file; use function is_string; -class PhpVersionFactoryFactory +final class PhpVersionFactoryFactory { /** diff --git a/src/PhpDoc/ConstExprNodeResolver.php b/src/PhpDoc/ConstExprNodeResolver.php index 4719a4c8e77..9e3e86f1877 100644 --- a/src/PhpDoc/ConstExprNodeResolver.php +++ b/src/PhpDoc/ConstExprNodeResolver.php @@ -19,7 +19,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; -class ConstExprNodeResolver +final class ConstExprNodeResolver { public function resolve(ConstExprNode $node): Type diff --git a/src/PhpDoc/ConstExprParserFactory.php b/src/PhpDoc/ConstExprParserFactory.php index aa2ca2657de..4af1282a786 100644 --- a/src/PhpDoc/ConstExprParserFactory.php +++ b/src/PhpDoc/ConstExprParserFactory.php @@ -4,7 +4,7 @@ use PHPStan\PhpDocParser\Parser\ConstExprParser; -class ConstExprParserFactory +final class ConstExprParserFactory { public function __construct(private bool $unescapeStrings) diff --git a/src/PhpDoc/CountableStubFilesExtension.php b/src/PhpDoc/CountableStubFilesExtension.php index af7761e6256..3fc3a156668 100644 --- a/src/PhpDoc/CountableStubFilesExtension.php +++ b/src/PhpDoc/CountableStubFilesExtension.php @@ -2,7 +2,7 @@ namespace PHPStan\PhpDoc; -class CountableStubFilesExtension implements StubFilesExtension +final class CountableStubFilesExtension implements StubFilesExtension { public function __construct(private bool $bleedingEdge) diff --git a/src/PhpDoc/DefaultStubFilesProvider.php b/src/PhpDoc/DefaultStubFilesProvider.php index da52332a1f3..48121ba3dc8 100644 --- a/src/PhpDoc/DefaultStubFilesProvider.php +++ b/src/PhpDoc/DefaultStubFilesProvider.php @@ -9,7 +9,7 @@ use function str_contains; use function strtr; -class DefaultStubFilesProvider implements StubFilesProvider +final class DefaultStubFilesProvider implements StubFilesProvider { /** @var string[]|null */ diff --git a/src/PhpDoc/DirectTypeNodeResolverExtensionRegistryProvider.php b/src/PhpDoc/DirectTypeNodeResolverExtensionRegistryProvider.php index cd912d76bb9..c1c038ca81b 100644 --- a/src/PhpDoc/DirectTypeNodeResolverExtensionRegistryProvider.php +++ b/src/PhpDoc/DirectTypeNodeResolverExtensionRegistryProvider.php @@ -2,7 +2,7 @@ namespace PHPStan\PhpDoc; -class DirectTypeNodeResolverExtensionRegistryProvider implements TypeNodeResolverExtensionRegistryProvider +final class DirectTypeNodeResolverExtensionRegistryProvider implements TypeNodeResolverExtensionRegistryProvider { public function __construct(private TypeNodeResolverExtensionRegistry $registry) diff --git a/src/PhpDoc/JsonValidateStubFilesExtension.php b/src/PhpDoc/JsonValidateStubFilesExtension.php index 19c1f824d02..3bfcfe862c4 100644 --- a/src/PhpDoc/JsonValidateStubFilesExtension.php +++ b/src/PhpDoc/JsonValidateStubFilesExtension.php @@ -4,7 +4,7 @@ use PHPStan\Php\PhpVersion; -class JsonValidateStubFilesExtension implements StubFilesExtension +final class JsonValidateStubFilesExtension implements StubFilesExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php b/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php index 4a041e369e3..7c00dfe34d4 100644 --- a/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php +++ b/src/PhpDoc/LazyTypeNodeResolverExtensionRegistryProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyTypeNodeResolverExtensionRegistryProvider implements TypeNodeResolverExtensionRegistryProvider +final class LazyTypeNodeResolverExtensionRegistryProvider implements TypeNodeResolverExtensionRegistryProvider { private ?TypeNodeResolverExtensionRegistry $registry = null; diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index c4a6f6ce435..fd75fa53067 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -19,7 +19,7 @@ use function strtolower; use function substr; -class PhpDocBlock +final class PhpDocBlock { /** diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 0e909dd96a5..98366e15eab 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -8,7 +8,7 @@ use function array_map; use function strtolower; -class PhpDocInheritanceResolver +final class PhpDocInheritanceResolver { public function __construct( diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index c11c26d7b01..6d52d72f950 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -48,7 +48,7 @@ use function str_starts_with; use function substr; -class PhpDocNodeResolver +final class PhpDocNodeResolver { public function __construct( diff --git a/src/PhpDoc/PhpDocStringResolver.php b/src/PhpDoc/PhpDocStringResolver.php index d9d6203e5b1..7c8129a3cc5 100644 --- a/src/PhpDoc/PhpDocStringResolver.php +++ b/src/PhpDoc/PhpDocStringResolver.php @@ -7,7 +7,7 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; -class PhpDocStringResolver +final class PhpDocStringResolver { public function __construct(private Lexer $phpDocLexer, private PhpDocParser $phpDocParser) diff --git a/src/PhpDoc/ReflectionEnumStubFilesExtension.php b/src/PhpDoc/ReflectionEnumStubFilesExtension.php index 0fe8fbb5b29..38ab53a1247 100644 --- a/src/PhpDoc/ReflectionEnumStubFilesExtension.php +++ b/src/PhpDoc/ReflectionEnumStubFilesExtension.php @@ -4,7 +4,7 @@ use PHPStan\Php\PhpVersion; -class ReflectionEnumStubFilesExtension implements StubFilesExtension +final class ReflectionEnumStubFilesExtension implements StubFilesExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index 4783a8f710a..ed45d60e483 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -40,7 +40,10 @@ use function is_bool; use function substr; -/** @api */ +/** + * @api + * @final + */ class ResolvedPhpDocBlock { diff --git a/src/PhpDoc/SocketSelectStubFilesExtension.php b/src/PhpDoc/SocketSelectStubFilesExtension.php index 1113a10f89b..c5d60ea909a 100644 --- a/src/PhpDoc/SocketSelectStubFilesExtension.php +++ b/src/PhpDoc/SocketSelectStubFilesExtension.php @@ -4,7 +4,7 @@ use PHPStan\Php\PhpVersion; -class SocketSelectStubFilesExtension implements StubFilesExtension +final class SocketSelectStubFilesExtension implements StubFilesExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/PhpDoc/StubPhpDocProvider.php b/src/PhpDoc/StubPhpDocProvider.php index d00085e9850..c314c9c53c9 100644 --- a/src/PhpDoc/StubPhpDocProvider.php +++ b/src/PhpDoc/StubPhpDocProvider.php @@ -14,7 +14,7 @@ use function array_map; use function is_string; -class StubPhpDocProvider +final class StubPhpDocProvider { /** @var array */ diff --git a/src/PhpDoc/StubSourceLocatorFactory.php b/src/PhpDoc/StubSourceLocatorFactory.php index 27384f41b17..6eca6b0babc 100644 --- a/src/PhpDoc/StubSourceLocatorFactory.php +++ b/src/PhpDoc/StubSourceLocatorFactory.php @@ -14,7 +14,7 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository; use function dirname; -class StubSourceLocatorFactory +final class StubSourceLocatorFactory { public function __construct( diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index d0628f435ca..91ff9d9360d 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -70,7 +70,7 @@ use function count; use function sprintf; -class StubValidator +final class StubValidator { public function __construct( diff --git a/src/PhpDoc/Tag/AssertTagParameter.php b/src/PhpDoc/Tag/AssertTagParameter.php index 18717b34941..a3c32503261 100644 --- a/src/PhpDoc/Tag/AssertTagParameter.php +++ b/src/PhpDoc/Tag/AssertTagParameter.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr; use function sprintf; -class AssertTagParameter +final class AssertTagParameter { public function __construct( diff --git a/src/PhpDoc/Tag/DeprecatedTag.php b/src/PhpDoc/Tag/DeprecatedTag.php index ea798c2aab4..e7bf3c553f5 100644 --- a/src/PhpDoc/Tag/DeprecatedTag.php +++ b/src/PhpDoc/Tag/DeprecatedTag.php @@ -2,7 +2,10 @@ namespace PHPStan\PhpDoc\Tag; -/** @api */ +/** + * @api + * @final + */ class DeprecatedTag { diff --git a/src/PhpDoc/Tag/ExtendsTag.php b/src/PhpDoc/Tag/ExtendsTag.php index 74ed32b7a2a..e8a48922b44 100644 --- a/src/PhpDoc/Tag/ExtendsTag.php +++ b/src/PhpDoc/Tag/ExtendsTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ExtendsTag { diff --git a/src/PhpDoc/Tag/ImplementsTag.php b/src/PhpDoc/Tag/ImplementsTag.php index cc9376b47ae..bc82888d3fb 100644 --- a/src/PhpDoc/Tag/ImplementsTag.php +++ b/src/PhpDoc/Tag/ImplementsTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ImplementsTag { diff --git a/src/PhpDoc/Tag/MethodTag.php b/src/PhpDoc/Tag/MethodTag.php index 9f46c124d29..793fd3d6d2e 100644 --- a/src/PhpDoc/Tag/MethodTag.php +++ b/src/PhpDoc/Tag/MethodTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class MethodTag { diff --git a/src/PhpDoc/Tag/MethodTagParameter.php b/src/PhpDoc/Tag/MethodTagParameter.php index 1326c4cbc9a..21f4377ce66 100644 --- a/src/PhpDoc/Tag/MethodTagParameter.php +++ b/src/PhpDoc/Tag/MethodTagParameter.php @@ -5,7 +5,10 @@ use PHPStan\Reflection\PassedByReference; use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class MethodTagParameter { diff --git a/src/PhpDoc/Tag/MixinTag.php b/src/PhpDoc/Tag/MixinTag.php index 2a97b732645..5df36d74bd5 100644 --- a/src/PhpDoc/Tag/MixinTag.php +++ b/src/PhpDoc/Tag/MixinTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class MixinTag { diff --git a/src/PhpDoc/Tag/ParamClosureThisTag.php b/src/PhpDoc/Tag/ParamClosureThisTag.php index 1dacb4c41d2..ae601dabc21 100644 --- a/src/PhpDoc/Tag/ParamClosureThisTag.php +++ b/src/PhpDoc/Tag/ParamClosureThisTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ final class ParamClosureThisTag implements TypedTag { diff --git a/src/PhpDoc/Tag/ParamOutTag.php b/src/PhpDoc/Tag/ParamOutTag.php index c40018cb453..8bc982f2922 100644 --- a/src/PhpDoc/Tag/ParamOutTag.php +++ b/src/PhpDoc/Tag/ParamOutTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ParamOutTag implements TypedTag { diff --git a/src/PhpDoc/Tag/ParamTag.php b/src/PhpDoc/Tag/ParamTag.php index 8515df30b9c..f21038c4e17 100644 --- a/src/PhpDoc/Tag/ParamTag.php +++ b/src/PhpDoc/Tag/ParamTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ParamTag implements TypedTag { diff --git a/src/PhpDoc/Tag/PropertyTag.php b/src/PhpDoc/Tag/PropertyTag.php index f2e42c14b38..a46bb9cdbf3 100644 --- a/src/PhpDoc/Tag/PropertyTag.php +++ b/src/PhpDoc/Tag/PropertyTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class PropertyTag { diff --git a/src/PhpDoc/Tag/RequireExtendsTag.php b/src/PhpDoc/Tag/RequireExtendsTag.php index a1e60d45a63..60861f76155 100644 --- a/src/PhpDoc/Tag/RequireExtendsTag.php +++ b/src/PhpDoc/Tag/RequireExtendsTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class RequireExtendsTag { diff --git a/src/PhpDoc/Tag/RequireImplementsTag.php b/src/PhpDoc/Tag/RequireImplementsTag.php index 2a0f42303fb..12702bce71e 100644 --- a/src/PhpDoc/Tag/RequireImplementsTag.php +++ b/src/PhpDoc/Tag/RequireImplementsTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class RequireImplementsTag { diff --git a/src/PhpDoc/Tag/ReturnTag.php b/src/PhpDoc/Tag/ReturnTag.php index 683b0cb2590..ef5b1302936 100644 --- a/src/PhpDoc/Tag/ReturnTag.php +++ b/src/PhpDoc/Tag/ReturnTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ReturnTag implements TypedTag { diff --git a/src/PhpDoc/Tag/SelfOutTypeTag.php b/src/PhpDoc/Tag/SelfOutTypeTag.php index bbd5a2ca090..1e1dacc2fa5 100644 --- a/src/PhpDoc/Tag/SelfOutTypeTag.php +++ b/src/PhpDoc/Tag/SelfOutTypeTag.php @@ -4,6 +4,10 @@ use PHPStan\Type\Type; +/** + * @api + * @final + */ class SelfOutTypeTag implements TypedTag { diff --git a/src/PhpDoc/Tag/TemplateTag.php b/src/PhpDoc/Tag/TemplateTag.php index 78707555c86..a14fa2c6abb 100644 --- a/src/PhpDoc/Tag/TemplateTag.php +++ b/src/PhpDoc/Tag/TemplateTag.php @@ -5,7 +5,10 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class TemplateTag { diff --git a/src/PhpDoc/Tag/ThrowsTag.php b/src/PhpDoc/Tag/ThrowsTag.php index 15c1ac94d9e..220eefae956 100644 --- a/src/PhpDoc/Tag/ThrowsTag.php +++ b/src/PhpDoc/Tag/ThrowsTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ThrowsTag { diff --git a/src/PhpDoc/Tag/TypeAliasTag.php b/src/PhpDoc/Tag/TypeAliasTag.php index 35b613417a0..5a360b86139 100644 --- a/src/PhpDoc/Tag/TypeAliasTag.php +++ b/src/PhpDoc/Tag/TypeAliasTag.php @@ -6,7 +6,10 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Type\TypeAlias; -/** @api */ +/** + * @api + * @final + */ class TypeAliasTag { diff --git a/src/PhpDoc/Tag/UsesTag.php b/src/PhpDoc/Tag/UsesTag.php index 453eb5b2502..63ec60d0b4c 100644 --- a/src/PhpDoc/Tag/UsesTag.php +++ b/src/PhpDoc/Tag/UsesTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class UsesTag { diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index 672cb81d43f..0d93daeac81 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class VarTag implements TypedTag { diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 6a8a409e56d..18db8040308 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -119,7 +119,7 @@ use function strtolower; use function substr; -class TypeNodeResolver +final class TypeNodeResolver { /** @var array */ diff --git a/src/PhpDoc/TypeNodeResolverExtensionAwareRegistry.php b/src/PhpDoc/TypeNodeResolverExtensionAwareRegistry.php index a41b9c631cc..a525078b2e4 100644 --- a/src/PhpDoc/TypeNodeResolverExtensionAwareRegistry.php +++ b/src/PhpDoc/TypeNodeResolverExtensionAwareRegistry.php @@ -2,7 +2,7 @@ namespace PHPStan\PhpDoc; -class TypeNodeResolverExtensionAwareRegistry implements TypeNodeResolverExtensionRegistry +final class TypeNodeResolverExtensionAwareRegistry implements TypeNodeResolverExtensionRegistry { /** diff --git a/src/PhpDoc/TypeStringResolver.php b/src/PhpDoc/TypeStringResolver.php index 91333363ed2..2bdb4ff94f0 100644 --- a/src/PhpDoc/TypeStringResolver.php +++ b/src/PhpDoc/TypeStringResolver.php @@ -8,7 +8,7 @@ use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\Type\Type; -class TypeStringResolver +final class TypeStringResolver { public function __construct(private Lexer $typeLexer, private TypeParser $typeParser, private TypeNodeResolver $typeNodeResolver) diff --git a/src/Process/CpuCoreCounter.php b/src/Process/CpuCoreCounter.php index cff6084b875..2fd49e7cfa8 100644 --- a/src/Process/CpuCoreCounter.php +++ b/src/Process/CpuCoreCounter.php @@ -5,7 +5,7 @@ use Fidry\CpuCoreCounter\CpuCoreCounter as FidryCpuCoreCounter; use Fidry\CpuCoreCounter\NumberOfCpuCoreNotFound; -class CpuCoreCounter +final class CpuCoreCounter { private ?int $count = null; diff --git a/src/Process/ProcessCanceledException.php b/src/Process/ProcessCanceledException.php index 9d37c5b6caa..ae42c75d3b5 100644 --- a/src/Process/ProcessCanceledException.php +++ b/src/Process/ProcessCanceledException.php @@ -4,7 +4,7 @@ use Exception; -class ProcessCanceledException extends Exception +final class ProcessCanceledException extends Exception { } diff --git a/src/Process/ProcessCrashedException.php b/src/Process/ProcessCrashedException.php index d6278a50e73..fb75a7d94d7 100644 --- a/src/Process/ProcessCrashedException.php +++ b/src/Process/ProcessCrashedException.php @@ -4,7 +4,7 @@ use Exception; -class ProcessCrashedException extends Exception +final class ProcessCrashedException extends Exception { } diff --git a/src/Process/ProcessHelper.php b/src/Process/ProcessHelper.php index 2597e771a89..591e916feed 100644 --- a/src/Process/ProcessHelper.php +++ b/src/Process/ProcessHelper.php @@ -13,7 +13,7 @@ use function sprintf; use const PHP_BINARY; -class ProcessHelper +final class ProcessHelper { /** diff --git a/src/Process/ProcessPromise.php b/src/Process/ProcessPromise.php index 55e3cbcf2e2..31f975460a1 100644 --- a/src/Process/ProcessPromise.php +++ b/src/Process/ProcessPromise.php @@ -12,7 +12,7 @@ use function stream_get_contents; use function tmpfile; -class ProcessPromise +final class ProcessPromise { /** @var Deferred */ diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index cfd5b74e030..79445fdbe12 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -13,7 +13,7 @@ use PHPStan\Type\ThisType; use PHPStan\Type\Type; -class AnnotationMethodReflection implements ExtendedMethodReflection +final class AnnotationMethodReflection implements ExtendedMethodReflection { /** @var FunctionVariantWithPhpDocs[]|null */ diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 6fe9b9d1256..e6506a4a212 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -7,7 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class AnnotationPropertyReflection implements PropertyReflection +final class AnnotationPropertyReflection implements PropertyReflection { public function __construct( diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index fac7e5a9bbb..cb86e0fec45 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -8,7 +8,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; -class AnnotationsMethodParameterReflection implements ParameterReflectionWithPhpDocs +final class AnnotationsMethodParameterReflection implements ParameterReflectionWithPhpDocs { public function __construct(private string $name, private Type $type, private PassedByReference $passedByReference, private bool $isOptional, private bool $isVariadic, private ?Type $defaultValue) diff --git a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php index 2860988c915..76fac3fa37c 100644 --- a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php @@ -16,7 +16,7 @@ use function array_map; use function count; -class AnnotationsMethodsClassReflectionExtension implements MethodsClassReflectionExtension +final class AnnotationsMethodsClassReflectionExtension implements MethodsClassReflectionExtension { /** @var ExtendedMethodReflection[][] */ diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index 3ef4959ab8b..3a12dcdefb5 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\NeverType; -class AnnotationsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension +final class AnnotationsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension { /** @var PropertyReflection[][] */ diff --git a/src/Reflection/Assertions.php b/src/Reflection/Assertions.php index 6adb97136c4..988652d0d43 100644 --- a/src/Reflection/Assertions.php +++ b/src/Reflection/Assertions.php @@ -12,6 +12,7 @@ /** * @api + * @final */ class Assertions { diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 67166c39d1b..8ec65fd9d22 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -55,7 +55,7 @@ use function strtolower; use const PHP_VERSION_ID; -class BetterReflectionProvider implements ReflectionProvider +final class BetterReflectionProvider implements ReflectionProvider { /** @var FunctionReflection[] */ diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index f9c3649cf94..8632d6b59c9 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -30,7 +30,7 @@ use function is_dir; use function is_file; -class BetterReflectionSourceLocatorFactory +final class BetterReflectionSourceLocatorFactory { /** diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadFunctionsSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadFunctionsSourceLocator.php index 2acb010ad72..7070fc318bc 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadFunctionsSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadFunctionsSourceLocator.php @@ -12,7 +12,7 @@ use function PHPStan\autoloadFunctions; use function trait_exists; -class AutoloadFunctionsSourceLocator implements SourceLocator +final class AutoloadFunctionsSourceLocator implements SourceLocator { public function __construct( diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index 969d9783334..7506682426a 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -47,7 +47,7 @@ * * Modified code from Roave/BetterReflection, Copyright (c) 2017 Roave, LLC. */ -class AutoloadSourceLocator implements SourceLocator +final class AutoloadSourceLocator implements SourceLocator { /** @var array{classes: array, functions: array, constants: array} */ diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 7e8367df3ca..3a6d1943957 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -12,7 +12,7 @@ use PHPStan\Reflection\ConstantNameHelper; use function strtolower; -class CachingVisitor extends NodeVisitorAbstract +final class CachingVisitor extends NodeVisitorAbstract { private string $fileName; diff --git a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php index 3d123ccfe23..9e687100f7b 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php +++ b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php @@ -25,7 +25,7 @@ use function str_contains; use const GLOB_ONLYDIR; -class ComposerJsonAndInstalledJsonSourceLocatorMaker +final class ComposerJsonAndInstalledJsonSourceLocatorMaker { public function __construct( diff --git a/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php b/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php index 89e33adb3db..70eaadffbe2 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php +++ b/src/Reflection/BetterReflection/SourceLocator/FetchedNode.php @@ -8,7 +8,7 @@ /** * @template-covariant T of Node */ -class FetchedNode +final class FetchedNode { /** diff --git a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php index aa98107ae34..ac90178d49e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php +++ b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php @@ -4,7 +4,7 @@ use PhpParser\Node; -class FetchedNodesResult +final class FetchedNodesResult { /** diff --git a/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php b/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php index 0fa3440e29b..bd6892cf881 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileNodesFetcher.php @@ -7,7 +7,7 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; -class FileNodesFetcher +final class FileNodesFetcher { public function __construct( diff --git a/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php index 557ac4848c2..cc939049bc9 100644 --- a/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php @@ -16,7 +16,7 @@ use function current; use function strtolower; -class NewOptimizedDirectorySourceLocator implements SourceLocator +final class NewOptimizedDirectorySourceLocator implements SourceLocator { /** diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index b6fc6e471cf..32fabbc7572 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -27,7 +27,7 @@ /** * @deprecated Use NewOptimizedDirectorySourceLocator */ -class OptimizedDirectorySourceLocator implements SourceLocator +final class OptimizedDirectorySourceLocator implements SourceLocator { private PhpFileCleaner $cleaner; diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php index a147d1872ee..864f1fff218 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php @@ -17,7 +17,7 @@ use function sprintf; use function strtolower; -class OptimizedDirectorySourceLocatorFactory +final class OptimizedDirectorySourceLocatorFactory { private PhpFileCleaner $cleaner; diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php index c636424c2fe..25f5bd3e84e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php @@ -4,7 +4,7 @@ use function array_key_exists; -class OptimizedDirectorySourceLocatorRepository +final class OptimizedDirectorySourceLocatorRepository { /** @var array */ diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php index 7abfec64dac..78fb07f24d2 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php @@ -10,7 +10,7 @@ use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; use function is_file; -class OptimizedPsrAutoloaderLocator implements SourceLocator +final class OptimizedPsrAutoloaderLocator implements SourceLocator { /** @var array */ diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php index 982a372d4a9..4285d93bbd1 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php @@ -18,7 +18,7 @@ use function array_keys; use function strtolower; -class OptimizedSingleFileSourceLocator implements SourceLocator +final class OptimizedSingleFileSourceLocator implements SourceLocator { /** @var array{classes: array, functions: array, constants: array}|null */ diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php index 59e26df1269..bd857f74898 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php @@ -4,7 +4,7 @@ use function array_key_exists; -class OptimizedSingleFileSourceLocatorRepository +final class OptimizedSingleFileSourceLocatorRepository { /** @var array */ diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php index 6e76066fee0..84985abbce5 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php @@ -14,7 +14,7 @@ * @author Jordi Boggiano * @see https://github.com/composer/composer/pull/10107 */ -class PhpFileCleaner +final class PhpFileCleaner { /** @var array */ diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php index 0731d733b0b..f2225f9b8fe 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php @@ -9,7 +9,7 @@ use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; -class PhpVersionBlacklistSourceLocator implements SourceLocator +final class PhpVersionBlacklistSourceLocator implements SourceLocator { public function __construct( diff --git a/src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php index 1e204254516..604b1ad58d4 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php @@ -12,7 +12,7 @@ use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; use ReflectionClass; -class ReflectionClassSourceLocator implements SourceLocator +final class ReflectionClassSourceLocator implements SourceLocator { public function __construct( diff --git a/src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php index 3993684ddde..564950462f9 100644 --- a/src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php @@ -12,7 +12,7 @@ use function interface_exists; use function trait_exists; -class RewriteClassAliasSourceLocator implements SourceLocator +final class RewriteClassAliasSourceLocator implements SourceLocator { public function __construct(private SourceLocator $originalSourceLocator) diff --git a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php index fc8e931ffe1..2b4f2726461 100644 --- a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php @@ -10,7 +10,7 @@ use ReflectionClass; use function class_exists; -class SkipClassAliasSourceLocator implements SourceLocator +final class SkipClassAliasSourceLocator implements SourceLocator { public function __construct(private SourceLocator $sourceLocator) diff --git a/src/Reflection/BetterReflection/SourceStubber/PhpStormStubsSourceStubberFactory.php b/src/Reflection/BetterReflection/SourceStubber/PhpStormStubsSourceStubberFactory.php index 3d5615b24f8..9ea23cd6a93 100644 --- a/src/Reflection/BetterReflection/SourceStubber/PhpStormStubsSourceStubberFactory.php +++ b/src/Reflection/BetterReflection/SourceStubber/PhpStormStubsSourceStubberFactory.php @@ -7,7 +7,7 @@ use PHPStan\Node\Printer\Printer; use PHPStan\Php\PhpVersion; -class PhpStormStubsSourceStubberFactory +final class PhpStormStubsSourceStubberFactory { public function __construct(private Parser $phpParser, private Printer $printer, private PhpVersion $phpVersion) diff --git a/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php b/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php index 04da7cd6057..8f7533ba5fc 100644 --- a/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php +++ b/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php @@ -6,7 +6,7 @@ use PHPStan\Node\Printer\Printer; use PHPStan\Php\PhpVersion; -class ReflectionSourceStubberFactory +final class ReflectionSourceStubberFactory { public function __construct(private Printer $printer, private PhpVersion $phpVersion) diff --git a/src/Reflection/CallableFunctionVariantWithPhpDocs.php b/src/Reflection/CallableFunctionVariantWithPhpDocs.php index f9a8c002c66..8b8aa99567e 100644 --- a/src/Reflection/CallableFunctionVariantWithPhpDocs.php +++ b/src/Reflection/CallableFunctionVariantWithPhpDocs.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -class CallableFunctionVariantWithPhpDocs extends FunctionVariantWithPhpDocs implements CallableParametersAcceptor +final class CallableFunctionVariantWithPhpDocs extends FunctionVariantWithPhpDocs implements CallableParametersAcceptor { /** diff --git a/src/Reflection/Callables/FunctionCallableVariant.php b/src/Reflection/Callables/FunctionCallableVariant.php index f2a338946af..992d53d13ab 100644 --- a/src/Reflection/Callables/FunctionCallableVariant.php +++ b/src/Reflection/Callables/FunctionCallableVariant.php @@ -16,7 +16,7 @@ use function array_map; use function count; -class FunctionCallableVariant implements CallableParametersAcceptor, ParametersAcceptorWithPhpDocs +final class FunctionCallableVariant implements CallableParametersAcceptor, ParametersAcceptorWithPhpDocs { /** @var SimpleThrowPoint[]|null */ diff --git a/src/Reflection/Callables/SimpleImpurePoint.php b/src/Reflection/Callables/SimpleImpurePoint.php index 91b807b3ad6..79a3c96761f 100644 --- a/src/Reflection/Callables/SimpleImpurePoint.php +++ b/src/Reflection/Callables/SimpleImpurePoint.php @@ -11,7 +11,7 @@ /** * @phpstan-import-type ImpurePointIdentifier from ImpurePoint */ -class SimpleImpurePoint +final class SimpleImpurePoint { /** diff --git a/src/Reflection/Callables/SimpleThrowPoint.php b/src/Reflection/Callables/SimpleThrowPoint.php index 6a32d4eacd9..5cde43a1554 100644 --- a/src/Reflection/Callables/SimpleThrowPoint.php +++ b/src/Reflection/Callables/SimpleThrowPoint.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; use Throwable; -class SimpleThrowPoint +final class SimpleThrowPoint { private function __construct( diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index fd69ce81297..f54fc37ba42 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -10,7 +10,10 @@ use PHPStan\Type\TypehintHelper; use const NAN; -/** @api */ +/** + * @api + * @final + */ class ClassConstantReflection implements ConstantReflection { diff --git a/src/Reflection/ClassNameHelper.php b/src/Reflection/ClassNameHelper.php index adf7905bfcf..8fe817e91d9 100644 --- a/src/Reflection/ClassNameHelper.php +++ b/src/Reflection/ClassNameHelper.php @@ -5,7 +5,7 @@ use Nette\Utils\Strings; use function ltrim; -class ClassNameHelper +final class ClassNameHelper { public static function isValidClassName(string $name): bool diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 7c9a569dcc9..4c7410e2f0d 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -69,7 +69,10 @@ use function sprintf; use function strtolower; -/** @api */ +/** + * @api + * @final + */ class ClassReflection { diff --git a/src/Reflection/ClassReflectionExtensionRegistry.php b/src/Reflection/ClassReflectionExtensionRegistry.php index 6ba9c4d28aa..2dd864952ac 100644 --- a/src/Reflection/ClassReflectionExtensionRegistry.php +++ b/src/Reflection/ClassReflectionExtensionRegistry.php @@ -7,7 +7,7 @@ use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension; use function array_merge; -class ClassReflectionExtensionRegistry +final class ClassReflectionExtensionRegistry { /** diff --git a/src/Reflection/Constant/RuntimeConstantReflection.php b/src/Reflection/Constant/RuntimeConstantReflection.php index 7bc10879f40..9940b285057 100644 --- a/src/Reflection/Constant/RuntimeConstantReflection.php +++ b/src/Reflection/Constant/RuntimeConstantReflection.php @@ -6,7 +6,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class RuntimeConstantReflection implements GlobalConstantReflection +final class RuntimeConstantReflection implements GlobalConstantReflection { public function __construct( diff --git a/src/Reflection/ConstantNameHelper.php b/src/Reflection/ConstantNameHelper.php index cd4b526a771..45b65f43a61 100644 --- a/src/Reflection/ConstantNameHelper.php +++ b/src/Reflection/ConstantNameHelper.php @@ -10,7 +10,7 @@ use function str_contains; use function strtolower; -class ConstantNameHelper +final class ConstantNameHelper { public static function normalize(string $name): string diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 4fc7daca3d6..351d90b467d 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -11,7 +11,7 @@ use PHPStan\Type\Type; use function is_bool; -class ChangedTypeMethodReflection implements ExtendedMethodReflection +final class ChangedTypeMethodReflection implements ExtendedMethodReflection { /** diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 018d8593fa8..73917153131 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -8,7 +8,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class ChangedTypePropertyReflection implements WrapperPropertyReflection +final class ChangedTypePropertyReflection implements WrapperPropertyReflection { public function __construct(private ClassReflection $declaringClass, private PropertyReflection $reflection, private Type $readableType, private Type $writableType) diff --git a/src/Reflection/Dummy/DummyConstantReflection.php b/src/Reflection/Dummy/DummyConstantReflection.php index 2f2ef828ec0..330c77562c8 100644 --- a/src/Reflection/Dummy/DummyConstantReflection.php +++ b/src/Reflection/Dummy/DummyConstantReflection.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use stdClass; -class DummyConstantReflection implements ConstantReflection +final class DummyConstantReflection implements ConstantReflection { public function __construct(private string $name) diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index b3cdde365f1..4c98f8d5962 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -13,7 +13,7 @@ use PHPStan\Type\Type; use PHPStan\Type\VoidType; -class DummyConstructorReflection implements ExtendedMethodReflection +final class DummyConstructorReflection implements ExtendedMethodReflection { public function __construct(private ClassReflection $declaringClass) diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index 806c24c815b..d26ea0c4beb 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use stdClass; -class DummyMethodReflection implements ExtendedMethodReflection +final class DummyMethodReflection implements ExtendedMethodReflection { public function __construct(private string $name) diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index b72a011960a..90ef29825ed 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use stdClass; -class DummyPropertyReflection implements PropertyReflection +final class DummyPropertyReflection implements PropertyReflection { public function getDeclaringClass(): ClassReflection diff --git a/src/Reflection/EnumCaseReflection.php b/src/Reflection/EnumCaseReflection.php index b538f44b3f9..234ccbf95bd 100644 --- a/src/Reflection/EnumCaseReflection.php +++ b/src/Reflection/EnumCaseReflection.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class EnumCaseReflection { diff --git a/src/Reflection/FunctionVariant.php b/src/Reflection/FunctionVariant.php index 3c5947ac5c8..b6023cae163 100644 --- a/src/Reflection/FunctionVariant.php +++ b/src/Reflection/FunctionVariant.php @@ -6,7 +6,9 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -/** @api */ +/** + * @api + */ class FunctionVariant implements ParametersAcceptor { diff --git a/src/Reflection/FunctionVariantWithPhpDocs.php b/src/Reflection/FunctionVariantWithPhpDocs.php index 2efd1c1a97d..0b047b08b55 100644 --- a/src/Reflection/FunctionVariantWithPhpDocs.php +++ b/src/Reflection/FunctionVariantWithPhpDocs.php @@ -6,7 +6,9 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -/** @api */ +/** + * @api + */ class FunctionVariantWithPhpDocs extends FunctionVariant implements ParametersAcceptorWithPhpDocs { diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index edbb6929317..09e9968a1a0 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -18,7 +18,7 @@ use function count; use function is_int; -class GenericParametersAcceptorResolver +final class GenericParametersAcceptorResolver { /** diff --git a/src/Reflection/InaccessibleMethod.php b/src/Reflection/InaccessibleMethod.php index 5b8504a7d6f..58b63fe1c5c 100644 --- a/src/Reflection/InaccessibleMethod.php +++ b/src/Reflection/InaccessibleMethod.php @@ -10,7 +10,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; -class InaccessibleMethod implements CallableParametersAcceptor +final class InaccessibleMethod implements CallableParametersAcceptor { public function __construct(private MethodReflection $methodReflection) diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index c6e2f890a88..4289056bcf5 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -15,7 +15,10 @@ use function implode; use function sprintf; -/** @api */ +/** + * @api + * @final + */ class InitializerExprContext implements NamespaceAnswerer { diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 3eee9061b68..9c510755ff7 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -88,7 +88,7 @@ use function strtolower; use const INF; -class InitializerExprTypeResolver +final class InitializerExprTypeResolver { public const CALCULATE_SCALARS_LIMIT = 128; diff --git a/src/Reflection/MethodPrototypeReflection.php b/src/Reflection/MethodPrototypeReflection.php index cf1a6ff76ba..c92c6c5b748 100644 --- a/src/Reflection/MethodPrototypeReflection.php +++ b/src/Reflection/MethodPrototypeReflection.php @@ -4,7 +4,7 @@ use PHPStan\Type\Type; -class MethodPrototypeReflection implements ClassMemberReflection +final class MethodPrototypeReflection implements ClassMemberReflection { /** diff --git a/src/Reflection/MissingConstantFromReflectionException.php b/src/Reflection/MissingConstantFromReflectionException.php index 230ba5902db..e57d3c3f4f8 100644 --- a/src/Reflection/MissingConstantFromReflectionException.php +++ b/src/Reflection/MissingConstantFromReflectionException.php @@ -5,7 +5,7 @@ use Exception; use function sprintf; -class MissingConstantFromReflectionException extends Exception +final class MissingConstantFromReflectionException extends Exception { public function __construct( diff --git a/src/Reflection/MissingMethodFromReflectionException.php b/src/Reflection/MissingMethodFromReflectionException.php index 49c7778cd63..48051aafc16 100644 --- a/src/Reflection/MissingMethodFromReflectionException.php +++ b/src/Reflection/MissingMethodFromReflectionException.php @@ -5,7 +5,7 @@ use Exception; use function sprintf; -class MissingMethodFromReflectionException extends Exception +final class MissingMethodFromReflectionException extends Exception { public function __construct( diff --git a/src/Reflection/MissingPropertyFromReflectionException.php b/src/Reflection/MissingPropertyFromReflectionException.php index 4d62565c1f2..2e64aee94c3 100644 --- a/src/Reflection/MissingPropertyFromReflectionException.php +++ b/src/Reflection/MissingPropertyFromReflectionException.php @@ -5,7 +5,7 @@ use Exception; use function sprintf; -class MissingPropertyFromReflectionException extends Exception +final class MissingPropertyFromReflectionException extends Exception { public function __construct( diff --git a/src/Reflection/Mixin/MixinMethodReflection.php b/src/Reflection/Mixin/MixinMethodReflection.php index 745a8cae157..15fc4d8ad8b 100644 --- a/src/Reflection/Mixin/MixinMethodReflection.php +++ b/src/Reflection/Mixin/MixinMethodReflection.php @@ -8,7 +8,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class MixinMethodReflection implements MethodReflection +final class MixinMethodReflection implements MethodReflection { public function __construct(private MethodReflection $reflection, private bool $static) diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index 484690a8b79..68ccf90ed91 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -11,7 +11,7 @@ use function array_intersect; use function count; -class MixinMethodsClassReflectionExtension implements MethodsClassReflectionExtension +final class MixinMethodsClassReflectionExtension implements MethodsClassReflectionExtension { /** @var array> */ diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index b891da3894c..8e6d32054fa 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -11,7 +11,7 @@ use function array_intersect; use function count; -class MixinPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension +final class MixinPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension { /** @var array> */ diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index ff521949080..870f0b7c66f 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -8,7 +8,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class NativeFunctionReflection implements FunctionReflection +final class NativeFunctionReflection implements FunctionReflection { private Assertions $assertions; diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 3112fefa48a..7c60edd6cc2 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -16,7 +16,7 @@ use ReflectionException; use function strtolower; -class NativeMethodReflection implements ExtendedMethodReflection +final class NativeMethodReflection implements ExtendedMethodReflection { /** diff --git a/src/Reflection/Native/NativeParameterReflection.php b/src/Reflection/Native/NativeParameterReflection.php index 7f92bfdb5e6..bdfce9f04c6 100644 --- a/src/Reflection/Native/NativeParameterReflection.php +++ b/src/Reflection/Native/NativeParameterReflection.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class NativeParameterReflection implements ParameterReflection +final class NativeParameterReflection implements ParameterReflection { public function __construct( diff --git a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php index 3a81fbb4dad..3e303b82b9e 100644 --- a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php +++ b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php @@ -7,7 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class NativeParameterWithPhpDocsReflection implements ParameterReflectionWithPhpDocs +final class NativeParameterWithPhpDocsReflection implements ParameterReflectionWithPhpDocs { public function __construct( diff --git a/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php b/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php index d0b71b91a7c..683a341f2e5 100644 --- a/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php +++ b/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -class NativeReflectionEnumReturnDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class NativeReflectionEnumReturnDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function __construct(private PhpVersion $phpVersion, private string $className, private string $methodName) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 33f536fe491..5cbdbd2907b 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -53,7 +53,10 @@ use const ARRAY_FILTER_USE_KEY; use const CURLOPT_SSL_VERIFYHOST; -/** @api */ +/** + * @api + * @final + */ class ParametersAcceptorSelector { diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index d4741bc1b64..5a6bbd4a046 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -4,7 +4,10 @@ use function array_key_exists; -/** @api */ +/** + * @api + * @final + */ class PassedByReference { diff --git a/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php b/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php index bd3c9b54ed5..abeb693630d 100644 --- a/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Php/ClosureCallUnresolvedMethodPrototypeReflection.php @@ -7,7 +7,7 @@ use PHPStan\Type\ClosureType; use PHPStan\Type\Type; -class ClosureCallUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection +final class ClosureCallUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { public function __construct(private UnresolvedMethodPrototypeReflection $prototype, private ClosureType $closure) diff --git a/src/Reflection/Php/DummyParameterWithPhpDocs.php b/src/Reflection/Php/DummyParameterWithPhpDocs.php index 262b601bea3..c7d8cc141ce 100644 --- a/src/Reflection/Php/DummyParameterWithPhpDocs.php +++ b/src/Reflection/Php/DummyParameterWithPhpDocs.php @@ -7,7 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class DummyParameterWithPhpDocs extends DummyParameter implements ParameterReflectionWithPhpDocs +final class DummyParameterWithPhpDocs extends DummyParameter implements ParameterReflectionWithPhpDocs { public function __construct( diff --git a/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php index 3ba005da825..04f4fbe8e74 100644 --- a/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php +++ b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php @@ -7,7 +7,7 @@ use PHPStan\Type\Enum\EnumCaseObjectType; use function array_keys; -class EnumAllowedSubTypesClassReflectionExtension implements AllowedSubTypesClassReflectionExtension +final class EnumAllowedSubTypesClassReflectionExtension implements AllowedSubTypesClassReflectionExtension { public function supports(ClassReflection $classReflection): bool diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index 0665eee6569..b66c18b8058 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -13,7 +13,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; -class EnumCasesMethodReflection implements ExtendedMethodReflection +final class EnumCasesMethodReflection implements ExtendedMethodReflection { public function __construct(private ClassReflection $declaringClass, private Type $returnType) diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index e3f7927dec9..96cdfca07df 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -7,7 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class EnumPropertyReflection implements PropertyReflection +final class EnumPropertyReflection implements PropertyReflection { public function __construct(private ClassReflection $declaringClass, private Type $type) diff --git a/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php index 5d76711ce46..a9ea2848c9a 100644 --- a/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Php/EnumUnresolvedPropertyPrototypeReflection.php @@ -6,7 +6,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\Type\Type; -class EnumUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection +final class EnumUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { public function __construct(private EnumPropertyReflection $property) diff --git a/src/Reflection/Php/NativeBuiltinMethodReflection.php b/src/Reflection/Php/NativeBuiltinMethodReflection.php index 2f10d431c50..ba559afac7b 100644 --- a/src/Reflection/Php/NativeBuiltinMethodReflection.php +++ b/src/Reflection/Php/NativeBuiltinMethodReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; @@ -11,7 +10,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; use PHPStan\TrinaryLogic; -class NativeBuiltinMethodReflection implements BuiltinMethodReflection +final class NativeBuiltinMethodReflection implements BuiltinMethodReflection { public function __construct(private ReflectionMethod $reflection) @@ -23,7 +22,7 @@ public function getName(): string return $this->reflection->getName(); } - public function getReflection(): ?ReflectionMethod + public function getReflection(): ReflectionMethod { return $this->reflection; } @@ -38,7 +37,7 @@ public function getFileName(): ?string return $fileName; } - public function getDeclaringClass(): ReflectionClass|ReflectionEnum + public function getDeclaringClass(): ReflectionClass { return $this->reflection->getDeclaringClass(); } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index f3ff0aa2abd..d99bbf89550 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -60,7 +60,7 @@ use function sprintf; use function strtolower; -class PhpClassReflectionExtension +final class PhpClassReflectionExtension implements PropertiesClassReflectionExtension, MethodsClassReflectionExtension { diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index d437c86f5ef..ed044586682 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -31,7 +31,7 @@ use function sprintf; use function time; -class PhpFunctionReflection implements FunctionReflection +final class PhpFunctionReflection implements FunctionReflection { /** @var FunctionVariantWithPhpDocs[]|null */ diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 7e858b714e2..a05f5501058 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -25,6 +25,7 @@ /** * @api + * @final */ class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 0fba6c34ce5..ce6306bada4 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -45,7 +45,10 @@ use function time; use const PHP_VERSION_ID; -/** @api */ +/** + * @api + * @final + */ class PhpMethodReflection implements ExtendedMethodReflection { diff --git a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php index 9dd5a25958b..61d1852c7d8 100644 --- a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php @@ -10,7 +10,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; -class PhpParameterFromParserNodeReflection implements ParameterReflectionWithPhpDocs +final class PhpParameterFromParserNodeReflection implements ParameterReflectionWithPhpDocs { private ?Type $type = null; diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index 0e6ce5ae771..26cb1be0076 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -13,7 +13,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; -class PhpParameterReflection implements ParameterReflectionWithPhpDocs +final class PhpParameterReflection implements ParameterReflectionWithPhpDocs { private ?Type $type = null; diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 00fc7e3e8eb..63d7eff0d8e 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -13,7 +13,10 @@ use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; -/** @api */ +/** + * @api + * @final + */ class PhpPropertyReflection implements PropertyReflection { diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index 9668d523be3..dbb69f8e9f4 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class SimpleXMLElementProperty implements PropertyReflection +final class SimpleXMLElementProperty implements PropertyReflection { public function __construct( diff --git a/src/Reflection/Php/Soap/SoapClientMethodReflection.php b/src/Reflection/Php/Soap/SoapClientMethodReflection.php index 15a814b20a2..1017888c596 100644 --- a/src/Reflection/Php/Soap/SoapClientMethodReflection.php +++ b/src/Reflection/Php/Soap/SoapClientMethodReflection.php @@ -12,7 +12,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -class SoapClientMethodReflection implements MethodReflection +final class SoapClientMethodReflection implements MethodReflection { public function __construct(private ClassReflection $declaringClass, private string $name) @@ -87,7 +87,7 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getThrowType(): ?Type + public function getThrowType(): Type { return new ObjectType('SoapFault'); } diff --git a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php index 73924b6081f..3db627179e3 100644 --- a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php +++ b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php @@ -6,7 +6,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; -class SoapClientMethodsClassReflectionExtension implements MethodsClassReflectionExtension +final class SoapClientMethodsClassReflectionExtension implements MethodsClassReflectionExtension { public function hasMethod(ClassReflection $classReflection, string $methodName): bool diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 3d766f5feeb..ad10221352a 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -7,7 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class UniversalObjectCrateProperty implements PropertyReflection +final class UniversalObjectCrateProperty implements PropertyReflection { public function __construct( diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index a5aaa8443f3..be769e05a08 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -10,7 +10,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; -class UniversalObjectCratesClassReflectionExtension +final class UniversalObjectCratesClassReflectionExtension implements PropertiesClassReflectionExtension { diff --git a/src/Reflection/PhpVersionStaticAccessor.php b/src/Reflection/PhpVersionStaticAccessor.php index 909c3578743..363109b7c01 100644 --- a/src/Reflection/PhpVersionStaticAccessor.php +++ b/src/Reflection/PhpVersionStaticAccessor.php @@ -5,7 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\ShouldNotHappenException; -class PhpVersionStaticAccessor +final class PhpVersionStaticAccessor { private static ?PhpVersion $instance = null; diff --git a/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php index e1c7e31e1f1..2fccc5b3c4b 100644 --- a/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/DirectReflectionProviderProvider.php @@ -4,7 +4,7 @@ use PHPStan\Reflection\ReflectionProvider; -class DirectReflectionProviderProvider implements ReflectionProviderProvider +final class DirectReflectionProviderProvider implements ReflectionProviderProvider { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php index ff3e666cff2..7ee57294830 100644 --- a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php @@ -11,7 +11,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; -class DummyReflectionProvider implements ReflectionProvider +final class DummyReflectionProvider implements ReflectionProvider { public function hasClass(string $className): bool diff --git a/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php index 9e4b1957a02..240d3d3bc52 100644 --- a/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/LazyReflectionProviderProvider.php @@ -5,7 +5,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; -class LazyReflectionProviderProvider implements ReflectionProviderProvider +final class LazyReflectionProviderProvider implements ReflectionProviderProvider { public function __construct(private Container $container) diff --git a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php index b4059b76231..9d6cacd951f 100644 --- a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php @@ -11,7 +11,7 @@ use PHPStan\Reflection\ReflectionProvider; use function strtolower; -class MemoizingReflectionProvider implements ReflectionProvider +final class MemoizingReflectionProvider implements ReflectionProvider { /** @var array */ diff --git a/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php b/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php index 7c9501cbdde..06441ef778b 100644 --- a/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php +++ b/src/Reflection/ReflectionProvider/ReflectionProviderFactory.php @@ -4,7 +4,7 @@ use PHPStan\Reflection\ReflectionProvider; -class ReflectionProviderFactory +final class ReflectionProviderFactory { public function __construct( diff --git a/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php index 9e897084cb7..2ae0d7c9ffd 100644 --- a/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php +++ b/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php @@ -4,7 +4,7 @@ use PHPStan\Reflection\ReflectionProvider; -class SetterReflectionProviderProvider implements ReflectionProviderProvider +final class SetterReflectionProviderProvider implements ReflectionProviderProvider { private ReflectionProvider $reflectionProvider; diff --git a/src/Reflection/ReflectionProviderStaticAccessor.php b/src/Reflection/ReflectionProviderStaticAccessor.php index bb772064c74..90ad0dd1859 100644 --- a/src/Reflection/ReflectionProviderStaticAccessor.php +++ b/src/Reflection/ReflectionProviderStaticAccessor.php @@ -4,7 +4,7 @@ use PHPStan\ShouldNotHappenException; -class ReflectionProviderStaticAccessor +final class ReflectionProviderStaticAccessor { private static ?ReflectionProvider $instance = null; diff --git a/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php index 920c84e7d40..f439f811e90 100644 --- a/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php @@ -9,7 +9,7 @@ use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\ShouldNotHappenException; -class RequireExtendsMethodsClassReflectionExtension implements MethodsClassReflectionExtension +final class RequireExtendsMethodsClassReflectionExtension implements MethodsClassReflectionExtension { public function hasMethod(ClassReflection $classReflection, string $methodName): bool diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php index ecaa6d31090..600405d3638 100644 --- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\PropertyReflection; use PHPStan\ShouldNotHappenException; -class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension +final class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension { public function hasProperty(ClassReflection $classReflection, string $propertyName): bool diff --git a/src/Reflection/ResolvedFunctionVariantWithCallable.php b/src/Reflection/ResolvedFunctionVariantWithCallable.php index 47147381675..c84670be53b 100644 --- a/src/Reflection/ResolvedFunctionVariantWithCallable.php +++ b/src/Reflection/ResolvedFunctionVariantWithCallable.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -class ResolvedFunctionVariantWithCallable implements ResolvedFunctionVariant, CallableParametersAcceptor +final class ResolvedFunctionVariantWithCallable implements ResolvedFunctionVariant, CallableParametersAcceptor { /** diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index 6d72ef75bc6..c9f0d94ac6a 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -19,7 +19,7 @@ use function array_key_exists; use function array_map; -class ResolvedFunctionVariantWithOriginal implements ResolvedFunctionVariant +final class ResolvedFunctionVariantWithOriginal implements ResolvedFunctionVariant { /** @var ParameterReflectionWithPhpDocs[]|null */ diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 31433330b86..0f4cd81a03b 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -11,7 +11,7 @@ use PHPStan\Type\Type; use function is_bool; -class ResolvedMethodReflection implements ExtendedMethodReflection +final class ResolvedMethodReflection implements ExtendedMethodReflection { /** @var ParametersAcceptorWithPhpDocs[]|null */ diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 7888b26abdb..3e111949cba 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -10,7 +10,7 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -class ResolvedPropertyReflection implements WrapperPropertyReflection +final class ResolvedPropertyReflection implements WrapperPropertyReflection { private ?Type $readableType = null; diff --git a/src/Reflection/SignatureMap/FunctionSignature.php b/src/Reflection/SignatureMap/FunctionSignature.php index 8473684acc6..07886541f81 100644 --- a/src/Reflection/SignatureMap/FunctionSignature.php +++ b/src/Reflection/SignatureMap/FunctionSignature.php @@ -4,7 +4,7 @@ use PHPStan\Type\Type; -class FunctionSignature +final class FunctionSignature { /** diff --git a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php index 66ca12c4873..7f58e257fe5 100644 --- a/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php +++ b/src/Reflection/SignatureMap/FunctionSignatureMapProvider.php @@ -19,7 +19,7 @@ use function strtolower; use const CASE_LOWER; -class FunctionSignatureMapProvider implements SignatureMapProvider +final class FunctionSignatureMapProvider implements SignatureMapProvider { /** @var array */ diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 7c850a582e5..4b980e21d7a 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -22,7 +22,7 @@ use function array_map; use function strtolower; -class NativeFunctionReflectionProvider +final class NativeFunctionReflectionProvider { /** @var NativeFunctionReflection[] */ diff --git a/src/Reflection/SignatureMap/ParameterSignature.php b/src/Reflection/SignatureMap/ParameterSignature.php index fc9316c3001..7649825119e 100644 --- a/src/Reflection/SignatureMap/ParameterSignature.php +++ b/src/Reflection/SignatureMap/ParameterSignature.php @@ -5,7 +5,7 @@ use PHPStan\Reflection\PassedByReference; use PHPStan\Type\Type; -class ParameterSignature +final class ParameterSignature { public function __construct( diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index d114609b351..7bfdf37f9b2 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -31,7 +31,7 @@ use function sprintf; use function strtolower; -class Php8SignatureMapProvider implements SignatureMapProvider +final class Php8SignatureMapProvider implements SignatureMapProvider { private const DIRECTORY = __DIR__ . '/../../../vendor/phpstan/php-8-stubs'; diff --git a/src/Reflection/SignatureMap/SignatureMapParser.php b/src/Reflection/SignatureMap/SignatureMapParser.php index b74f447ffad..0652b11383b 100644 --- a/src/Reflection/SignatureMap/SignatureMapParser.php +++ b/src/Reflection/SignatureMap/SignatureMapParser.php @@ -13,7 +13,7 @@ use function str_starts_with; use function substr; -class SignatureMapParser +final class SignatureMapParser { private TypeStringResolver $typeStringResolver; diff --git a/src/Reflection/SignatureMap/SignatureMapProviderFactory.php b/src/Reflection/SignatureMap/SignatureMapProviderFactory.php index 2e318eefeb3..4aa6d510da2 100644 --- a/src/Reflection/SignatureMap/SignatureMapProviderFactory.php +++ b/src/Reflection/SignatureMap/SignatureMapProviderFactory.php @@ -4,7 +4,7 @@ use PHPStan\Php\PhpVersion; -class SignatureMapProviderFactory +final class SignatureMapProviderFactory { public function __construct( diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index 3f663f79095..51a89fc3293 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -11,7 +11,10 @@ use PHPStan\Type\Type; use function sprintf; -/** @api */ +/** + * @api + * @final + */ class TrivialParametersAcceptor implements ParametersAcceptorWithPhpDocs, CallableParametersAcceptor { diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index a6f23841f38..5fd19b31058 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -13,7 +13,7 @@ use PHPStan\Type\Type; use function array_map; -class CallbackUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection +final class CallbackUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { /** @var callable(Type): Type */ diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 5140d7296ba..38b4bae4e9c 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ResolvedPropertyReflection; use PHPStan\Type\Type; -class CallbackUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection +final class CallbackUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { /** @var callable(Type): Type */ diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index bf4421d1f7a..607a08a37f4 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -15,7 +15,7 @@ use PHPStan\Type\TypeTraverser; use function array_map; -class CalledOnTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection +final class CalledOnTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { private ?ExtendedMethodReflection $transformedMethod = null; diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index e9a8b8a1612..6582c358b75 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; -class CalledOnTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection +final class CalledOnTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { private ?PropertyReflection $transformedProperty = null; diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index fb9a8ecdd33..8698e6196ca 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -18,7 +18,7 @@ use function implode; use function is_bool; -class IntersectionTypeMethodReflection implements ExtendedMethodReflection +final class IntersectionTypeMethodReflection implements ExtendedMethodReflection { /** diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index e93cc59e671..e81c0b22ddd 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -11,7 +11,7 @@ use function count; use function implode; -class IntersectionTypePropertyReflection implements PropertyReflection +final class IntersectionTypePropertyReflection implements PropertyReflection { /** diff --git a/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php index 5cda2321f1b..fe3e09bd4e3 100644 --- a/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/IntersectionTypeUnresolvedMethodPrototypeReflection.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use function array_map; -class IntersectionTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection +final class IntersectionTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { private ?ExtendedMethodReflection $transformedMethod = null; diff --git a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php index fdfea4ebf94..ac2f0b9bcd2 100644 --- a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; use function array_map; -class IntersectionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection +final class IntersectionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { private ?PropertyReflection $transformedProperty = null; diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 65ca5bd674a..2982f71a624 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -17,7 +17,7 @@ use function implode; use function is_bool; -class UnionTypeMethodReflection implements ExtendedMethodReflection +final class UnionTypeMethodReflection implements ExtendedMethodReflection { /** diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index d2587839c37..f23f2f12343 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -11,7 +11,7 @@ use function count; use function implode; -class UnionTypePropertyReflection implements PropertyReflection +final class UnionTypePropertyReflection implements PropertyReflection { /** diff --git a/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php index 9f0e62bc0ca..627a1e5b80d 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedMethodPrototypeReflection.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use function array_map; -class UnionTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection +final class UnionTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { private ?ExtendedMethodReflection $transformedMethod = null; diff --git a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php index fe47fa14529..f3f2d389653 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; use function array_map; -class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection +final class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { private ?PropertyReflection $transformedProperty = null; diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index c5656012618..2a953cf36b9 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -9,7 +9,7 @@ use PHPStan\Type\Type; use function array_map; -class WrappedExtendedMethodReflection implements ExtendedMethodReflection +final class WrappedExtendedMethodReflection implements ExtendedMethodReflection { public function __construct(private MethodReflection $method) diff --git a/src/Rules/Api/ApiClassConstFetchRule.php b/src/Rules/Api/ApiClassConstFetchRule.php index 528d2daf8bf..1503fe6f07f 100644 --- a/src/Rules/Api/ApiClassConstFetchRule.php +++ b/src/Rules/Api/ApiClassConstFetchRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class ApiClassConstFetchRule implements Rule +final class ApiClassConstFetchRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiClassExtendsRule.php b/src/Rules/Api/ApiClassExtendsRule.php index 72aad19740c..cf908f004bc 100644 --- a/src/Rules/Api/ApiClassExtendsRule.php +++ b/src/Rules/Api/ApiClassExtendsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ApiClassExtendsRule implements Rule +final class ApiClassExtendsRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiClassImplementsRule.php b/src/Rules/Api/ApiClassImplementsRule.php index 521279becaa..d1615303b53 100644 --- a/src/Rules/Api/ApiClassImplementsRule.php +++ b/src/Rules/Api/ApiClassImplementsRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ApiClassImplementsRule implements Rule +final class ApiClassImplementsRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiInstanceofRule.php b/src/Rules/Api/ApiInstanceofRule.php index 5f2c4d531ae..7882c3473e0 100644 --- a/src/Rules/Api/ApiInstanceofRule.php +++ b/src/Rules/Api/ApiInstanceofRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ApiInstanceofRule implements Rule +final class ApiInstanceofRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index fd4a3c6e20f..1ae24aa0cae 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -50,7 +50,7 @@ /** * @implements Rule */ -class ApiInstanceofTypeRule implements Rule +final class ApiInstanceofTypeRule implements Rule { private const MAP = [ diff --git a/src/Rules/Api/ApiInstantiationRule.php b/src/Rules/Api/ApiInstantiationRule.php index bea7e29b681..80079c5495b 100644 --- a/src/Rules/Api/ApiInstantiationRule.php +++ b/src/Rules/Api/ApiInstantiationRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ApiInstantiationRule implements Rule +final class ApiInstantiationRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiInterfaceExtendsRule.php b/src/Rules/Api/ApiInterfaceExtendsRule.php index fc5d8ca505f..048aa82a1b0 100644 --- a/src/Rules/Api/ApiInterfaceExtendsRule.php +++ b/src/Rules/Api/ApiInterfaceExtendsRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ApiInterfaceExtendsRule implements Rule +final class ApiInterfaceExtendsRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiMethodCallRule.php b/src/Rules/Api/ApiMethodCallRule.php index 52472fb47eb..8c733903c46 100644 --- a/src/Rules/Api/ApiMethodCallRule.php +++ b/src/Rules/Api/ApiMethodCallRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class ApiMethodCallRule implements Rule +final class ApiMethodCallRule implements Rule { public function __construct(private ApiRuleHelper $apiRuleHelper) diff --git a/src/Rules/Api/ApiRuleHelper.php b/src/Rules/Api/ApiRuleHelper.php index 021353bbd02..8fe066e60b9 100644 --- a/src/Rules/Api/ApiRuleHelper.php +++ b/src/Rules/Api/ApiRuleHelper.php @@ -11,7 +11,7 @@ use function strtolower; use const PATHINFO_BASENAME; -class ApiRuleHelper +final class ApiRuleHelper { public function isPhpStanCode(Scope $scope, string $namespace, ?string $declaringFile): bool diff --git a/src/Rules/Api/ApiStaticCallRule.php b/src/Rules/Api/ApiStaticCallRule.php index 6d48c393c05..8c1b53457a7 100644 --- a/src/Rules/Api/ApiStaticCallRule.php +++ b/src/Rules/Api/ApiStaticCallRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ApiStaticCallRule implements Rule +final class ApiStaticCallRule implements Rule { public function __construct( diff --git a/src/Rules/Api/ApiTraitUseRule.php b/src/Rules/Api/ApiTraitUseRule.php index 33da77eb364..a0074cbd4a8 100644 --- a/src/Rules/Api/ApiTraitUseRule.php +++ b/src/Rules/Api/ApiTraitUseRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ApiTraitUseRule implements Rule +final class ApiTraitUseRule implements Rule { public function __construct( diff --git a/src/Rules/Api/GetTemplateTypeRule.php b/src/Rules/Api/GetTemplateTypeRule.php index 05c48497d0f..ad5e08981a9 100644 --- a/src/Rules/Api/GetTemplateTypeRule.php +++ b/src/Rules/Api/GetTemplateTypeRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class GetTemplateTypeRule implements Rule +final class GetTemplateTypeRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index 0907413ca76..a1f27a33e2d 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -21,7 +21,7 @@ /** * @implements Rule */ -class NodeConnectingVisitorAttributesRule implements Rule +final class NodeConnectingVisitorAttributesRule implements Rule { public function __construct(private Container $container) diff --git a/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php b/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php index cc449b8f306..8547f7b2ed3 100644 --- a/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php +++ b/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class PhpStanNamespaceIn3rdPartyPackageRule implements Rule +final class PhpStanNamespaceIn3rdPartyPackageRule implements Rule { public function __construct(private ApiRuleHelper $apiRuleHelper) diff --git a/src/Rules/Api/RuntimeReflectionFunctionRule.php b/src/Rules/Api/RuntimeReflectionFunctionRule.php index 8b8fbf67222..229b681818a 100644 --- a/src/Rules/Api/RuntimeReflectionFunctionRule.php +++ b/src/Rules/Api/RuntimeReflectionFunctionRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class RuntimeReflectionFunctionRule implements Rule +final class RuntimeReflectionFunctionRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Api/RuntimeReflectionInstantiationRule.php b/src/Rules/Api/RuntimeReflectionInstantiationRule.php index 3e5dc7524c7..f358c5070a1 100644 --- a/src/Rules/Api/RuntimeReflectionInstantiationRule.php +++ b/src/Rules/Api/RuntimeReflectionInstantiationRule.php @@ -25,7 +25,7 @@ /** * @implements Rule */ -class RuntimeReflectionInstantiationRule implements Rule +final class RuntimeReflectionInstantiationRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index 6a2cc625420..0bc7c1f4c42 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; -class AllowedArrayKeysTypes +final class AllowedArrayKeysTypes { public static function getType(): Type diff --git a/src/Rules/Arrays/AppendedArrayItemTypeRule.php b/src/Rules/Arrays/AppendedArrayItemTypeRule.php index 96409f9aa10..dee1dc6c308 100644 --- a/src/Rules/Arrays/AppendedArrayItemTypeRule.php +++ b/src/Rules/Arrays/AppendedArrayItemTypeRule.php @@ -19,7 +19,7 @@ * @deprecated Replaced by PHPStan\Rules\Properties\TypesAssignedToPropertiesRule * @implements Rule */ -class AppendedArrayItemTypeRule implements Rule +final class AppendedArrayItemTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php index cf35062fafc..3e91fe8dbea 100644 --- a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php +++ b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php @@ -18,7 +18,7 @@ * @deprecated Replaced by PHPStan\Rules\Properties\TypesAssignedToPropertiesRule * @implements Rule */ -class AppendedArrayKeyTypeRule implements Rule +final class AppendedArrayKeyTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/ArrayDestructuringRule.php b/src/Rules/Arrays/ArrayDestructuringRule.php index 332d192698e..ab83f1d0197 100644 --- a/src/Rules/Arrays/ArrayDestructuringRule.php +++ b/src/Rules/Arrays/ArrayDestructuringRule.php @@ -23,7 +23,7 @@ /** * @implements Rule */ -class ArrayDestructuringRule implements Rule +final class ArrayDestructuringRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/ArrayUnpackingRule.php b/src/Rules/Arrays/ArrayUnpackingRule.php index 21092c21009..4be69c0ac05 100644 --- a/src/Rules/Arrays/ArrayUnpackingRule.php +++ b/src/Rules/Arrays/ArrayUnpackingRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ArrayUnpackingRule implements Rule +final class ArrayUnpackingRule implements Rule { public function __construct(private PhpVersion $phpVersion, private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Arrays/DeadForeachRule.php b/src/Rules/Arrays/DeadForeachRule.php index a9b64b441f7..61d2085fecd 100644 --- a/src/Rules/Arrays/DeadForeachRule.php +++ b/src/Rules/Arrays/DeadForeachRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class DeadForeachRule implements Rule +final class DeadForeachRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index a1eaf7e131b..46b5e712e10 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class DuplicateKeysInLiteralArraysRule implements Rule +final class DuplicateKeysInLiteralArraysRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/EmptyArrayItemRule.php b/src/Rules/Arrays/EmptyArrayItemRule.php index 94cb7e49b0a..0bfcebf9567 100644 --- a/src/Rules/Arrays/EmptyArrayItemRule.php +++ b/src/Rules/Arrays/EmptyArrayItemRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class EmptyArrayItemRule implements Rule +final class EmptyArrayItemRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php index 1242f85cfd5..e74b192a67a 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class InvalidKeyInArrayDimFetchRule implements Rule +final class InvalidKeyInArrayDimFetchRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 61c4fd342c5..5b5f3545a95 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class InvalidKeyInArrayItemRule implements Rule +final class InvalidKeyInArrayItemRule implements Rule { public function __construct(private bool $reportMaybes) diff --git a/src/Rules/Arrays/IterableInForeachRule.php b/src/Rules/Arrays/IterableInForeachRule.php index c455cd8d219..fd6351a0031 100644 --- a/src/Rules/Arrays/IterableInForeachRule.php +++ b/src/Rules/Arrays/IterableInForeachRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class IterableInForeachRule implements Rule +final class IterableInForeachRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 6ef401c5777..7b329d01a4a 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -16,7 +16,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class NonexistentOffsetInArrayDimFetchCheck +final class NonexistentOffsetInArrayDimFetchCheck { public function __construct( diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index 547e2f4fe4e..3cb1b933286 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class NonexistentOffsetInArrayDimFetchRule implements Rule +final class NonexistentOffsetInArrayDimFetchRule implements Rule { public function __construct( diff --git a/src/Rules/Arrays/OffsetAccessAssignOpRule.php b/src/Rules/Arrays/OffsetAccessAssignOpRule.php index ff66d9bb9f5..5ab745647b0 100644 --- a/src/Rules/Arrays/OffsetAccessAssignOpRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignOpRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class OffsetAccessAssignOpRule implements Rule +final class OffsetAccessAssignOpRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Arrays/OffsetAccessAssignmentRule.php b/src/Rules/Arrays/OffsetAccessAssignmentRule.php index 0bafe975d00..c6c0f92b140 100644 --- a/src/Rules/Arrays/OffsetAccessAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignmentRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class OffsetAccessAssignmentRule implements Rule +final class OffsetAccessAssignmentRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php index 03688957604..45f7b490b13 100644 --- a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class OffsetAccessValueAssignmentRule implements Rule +final class OffsetAccessValueAssignmentRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php b/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php index ce70e82425c..8d83452357e 100644 --- a/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php +++ b/src/Rules/Arrays/OffsetAccessWithoutDimForReadingRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class OffsetAccessWithoutDimForReadingRule implements Rule +final class OffsetAccessWithoutDimForReadingRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Arrays/UnpackIterableInArrayRule.php b/src/Rules/Arrays/UnpackIterableInArrayRule.php index 10a3a677581..bc515b9d04d 100644 --- a/src/Rules/Arrays/UnpackIterableInArrayRule.php +++ b/src/Rules/Arrays/UnpackIterableInArrayRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class UnpackIterableInArrayRule implements Rule +final class UnpackIterableInArrayRule implements Rule { public function __construct( diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 916c6e1c7dd..cd549f67e3a 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -14,7 +14,7 @@ use function sprintf; use function strtolower; -class AttributesCheck +final class AttributesCheck { public function __construct( diff --git a/src/Rules/Cast/EchoRule.php b/src/Rules/Cast/EchoRule.php index d8033f53942..697822b55f3 100644 --- a/src/Rules/Cast/EchoRule.php +++ b/src/Rules/Cast/EchoRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class EchoRule implements Rule +final class EchoRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Cast/InvalidCastRule.php b/src/Rules/Cast/InvalidCastRule.php index ceb074e36cd..4e3bddad6eb 100644 --- a/src/Rules/Cast/InvalidCastRule.php +++ b/src/Rules/Cast/InvalidCastRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class InvalidCastRule implements Rule +final class InvalidCastRule implements Rule { public function __construct( diff --git a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php index 773aabe5d5e..5cf96d8ad22 100644 --- a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php +++ b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class InvalidPartOfEncapsedStringRule implements Rule +final class InvalidPartOfEncapsedStringRule implements Rule { public function __construct( diff --git a/src/Rules/Cast/PrintRule.php b/src/Rules/Cast/PrintRule.php index 3b8ad5f97dc..a8f525c91ed 100644 --- a/src/Rules/Cast/PrintRule.php +++ b/src/Rules/Cast/PrintRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class PrintRule implements Rule +final class PrintRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Cast/UnsetCastRule.php b/src/Rules/Cast/UnsetCastRule.php index be527ffad08..9d0bdbf7246 100644 --- a/src/Rules/Cast/UnsetCastRule.php +++ b/src/Rules/Cast/UnsetCastRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class UnsetCastRule implements Rule +final class UnsetCastRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/ClassCaseSensitivityCheck.php b/src/Rules/ClassCaseSensitivityCheck.php index f7cdfdaab37..b58a5890748 100644 --- a/src/Rules/ClassCaseSensitivityCheck.php +++ b/src/Rules/ClassCaseSensitivityCheck.php @@ -6,7 +6,7 @@ use function sprintf; use function strtolower; -class ClassCaseSensitivityCheck +final class ClassCaseSensitivityCheck { public function __construct(private ReflectionProvider $reflectionProvider, private bool $checkInternalClassCaseSensitivity) diff --git a/src/Rules/ClassForbiddenNameCheck.php b/src/Rules/ClassForbiddenNameCheck.php index c86fede5872..c7658440abc 100644 --- a/src/Rules/ClassForbiddenNameCheck.php +++ b/src/Rules/ClassForbiddenNameCheck.php @@ -12,7 +12,7 @@ use function strpos; use function substr; -class ClassForbiddenNameCheck +final class ClassForbiddenNameCheck { private const INTERNAL_CLASS_PREFIXES = [ diff --git a/src/Rules/ClassNameCheck.php b/src/Rules/ClassNameCheck.php index c168b74ce79..80d3af0f778 100644 --- a/src/Rules/ClassNameCheck.php +++ b/src/Rules/ClassNameCheck.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules; -class ClassNameCheck +final class ClassNameCheck { public function __construct( diff --git a/src/Rules/ClassNameNodePair.php b/src/Rules/ClassNameNodePair.php index 355fa61e01d..cf39baa35c2 100644 --- a/src/Rules/ClassNameNodePair.php +++ b/src/Rules/ClassNameNodePair.php @@ -4,7 +4,7 @@ use PhpParser\Node; -class ClassNameNodePair +final class ClassNameNodePair { public function __construct(private string $className, private Node $node) diff --git a/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php b/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php index 438398a4f9b..551f56c464f 100644 --- a/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php +++ b/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class AccessPrivateConstantThroughStaticRule implements Rule +final class AccessPrivateConstantThroughStaticRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/AllowedSubTypesRule.php b/src/Rules/Classes/AllowedSubTypesRule.php index 164e63d9d75..5e58d49b3a3 100644 --- a/src/Rules/Classes/AllowedSubTypesRule.php +++ b/src/Rules/Classes/AllowedSubTypesRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class AllowedSubTypesRule implements Rule +final class AllowedSubTypesRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/ClassAttributesRule.php b/src/Rules/Classes/ClassAttributesRule.php index 21d6bda7499..319379ab2a2 100644 --- a/src/Rules/Classes/ClassAttributesRule.php +++ b/src/Rules/Classes/ClassAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ClassAttributesRule implements Rule +final class ClassAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Classes/ClassConstantAttributesRule.php b/src/Rules/Classes/ClassConstantAttributesRule.php index d8256191a09..71597d271a2 100644 --- a/src/Rules/Classes/ClassConstantAttributesRule.php +++ b/src/Rules/Classes/ClassConstantAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ClassConstantAttributesRule implements Rule +final class ClassConstantAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index f8425ff295b..71636b8ca09 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -28,7 +28,7 @@ /** * @implements Rule */ -class ClassConstantRule implements Rule +final class ClassConstantRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/DuplicateClassDeclarationRule.php b/src/Rules/Classes/DuplicateClassDeclarationRule.php index ae2658de794..bf8ac7f05df 100644 --- a/src/Rules/Classes/DuplicateClassDeclarationRule.php +++ b/src/Rules/Classes/DuplicateClassDeclarationRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class DuplicateClassDeclarationRule implements Rule +final class DuplicateClassDeclarationRule implements Rule { public function __construct(private Reflector $reflector, private RelativePathHelper $relativePathHelper) diff --git a/src/Rules/Classes/DuplicateDeclarationRule.php b/src/Rules/Classes/DuplicateDeclarationRule.php index b5e122eb0e7..047ce4cee90 100644 --- a/src/Rules/Classes/DuplicateDeclarationRule.php +++ b/src/Rules/Classes/DuplicateDeclarationRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class DuplicateDeclarationRule implements Rule +final class DuplicateDeclarationRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/EnumSanityRule.php b/src/Rules/Classes/EnumSanityRule.php index 742dbd18426..e9aebc1c264 100644 --- a/src/Rules/Classes/EnumSanityRule.php +++ b/src/Rules/Classes/EnumSanityRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class EnumSanityRule implements Rule +final class EnumSanityRule implements Rule { private const ALLOWED_MAGIC_METHODS = [ diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index b0a6aae26d9..d1863e19457 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class ExistingClassInClassExtendsRule implements Rule +final class ExistingClassInClassExtendsRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ExistingClassInInstanceOfRule.php b/src/Rules/Classes/ExistingClassInInstanceOfRule.php index 7eef8ba523e..5e408006319 100644 --- a/src/Rules/Classes/ExistingClassInInstanceOfRule.php +++ b/src/Rules/Classes/ExistingClassInInstanceOfRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ExistingClassInInstanceOfRule implements Rule +final class ExistingClassInInstanceOfRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ExistingClassInTraitUseRule.php b/src/Rules/Classes/ExistingClassInTraitUseRule.php index e47d9cf5d69..69ecc87fb51 100644 --- a/src/Rules/Classes/ExistingClassInTraitUseRule.php +++ b/src/Rules/Classes/ExistingClassInTraitUseRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class ExistingClassInTraitUseRule implements Rule +final class ExistingClassInTraitUseRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php index 5aeb3fde461..e0ff7c27ba4 100644 --- a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ExistingClassesInClassImplementsRule implements Rule +final class ExistingClassesInClassImplementsRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php index 413f32fcd81..aff2164a915 100644 --- a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ExistingClassesInEnumImplementsRule implements Rule +final class ExistingClassesInEnumImplementsRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php index e25a2ac974f..5fc694a46b7 100644 --- a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php +++ b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ExistingClassesInInterfaceExtendsRule implements Rule +final class ExistingClassesInInterfaceExtendsRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index c84092f8104..cf4df676e9c 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ImpossibleInstanceOfRule implements Rule +final class ImpossibleInstanceOfRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/InstantiationCallableRule.php b/src/Rules/Classes/InstantiationCallableRule.php index d36c15a33a8..019d6a29792 100644 --- a/src/Rules/Classes/InstantiationCallableRule.php +++ b/src/Rules/Classes/InstantiationCallableRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class InstantiationCallableRule implements Rule +final class InstantiationCallableRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 7e31e5adcdf..e90aee6b80f 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -26,7 +26,7 @@ /** * @implements Rule */ -class InstantiationRule implements Rule +final class InstantiationRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/InvalidPromotedPropertiesRule.php b/src/Rules/Classes/InvalidPromotedPropertiesRule.php index 5376f6b17e2..22434786a88 100644 --- a/src/Rules/Classes/InvalidPromotedPropertiesRule.php +++ b/src/Rules/Classes/InvalidPromotedPropertiesRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class InvalidPromotedPropertiesRule implements Rule +final class InvalidPromotedPropertiesRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 78940cac126..a8d1b1e476b 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -18,7 +18,7 @@ use function in_array; use function sprintf; -class LocalTypeAliasesCheck +final class LocalTypeAliasesCheck { /** diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index 86697971b80..7fb999403c0 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class LocalTypeAliasesRule implements Rule +final class LocalTypeAliasesRule implements Rule { public function __construct(private LocalTypeAliasesCheck $check) diff --git a/src/Rules/Classes/LocalTypeTraitAliasesRule.php b/src/Rules/Classes/LocalTypeTraitAliasesRule.php index 406108db1b9..241cef7c6c4 100644 --- a/src/Rules/Classes/LocalTypeTraitAliasesRule.php +++ b/src/Rules/Classes/LocalTypeTraitAliasesRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class LocalTypeTraitAliasesRule implements Rule +final class LocalTypeTraitAliasesRule implements Rule { public function __construct(private LocalTypeAliasesCheck $check, private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Classes/MixinRule.php b/src/Rules/Classes/MixinRule.php index ce397c3e21c..07e39383c77 100644 --- a/src/Rules/Classes/MixinRule.php +++ b/src/Rules/Classes/MixinRule.php @@ -21,7 +21,7 @@ /** * @implements Rule */ -class MixinRule implements Rule +final class MixinRule implements Rule { public function __construct( diff --git a/src/Rules/Classes/NewStaticRule.php b/src/Rules/Classes/NewStaticRule.php index b675469bac1..5540646616f 100644 --- a/src/Rules/Classes/NewStaticRule.php +++ b/src/Rules/Classes/NewStaticRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class NewStaticRule implements Rule +final class NewStaticRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/NonClassAttributeClassRule.php b/src/Rules/Classes/NonClassAttributeClassRule.php index c6318d07a4e..24f6850492e 100644 --- a/src/Rules/Classes/NonClassAttributeClassRule.php +++ b/src/Rules/Classes/NonClassAttributeClassRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class NonClassAttributeClassRule implements Rule +final class NonClassAttributeClassRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/ReadOnlyClassRule.php b/src/Rules/Classes/ReadOnlyClassRule.php index 21755c623cd..816e67e7b4d 100644 --- a/src/Rules/Classes/ReadOnlyClassRule.php +++ b/src/Rules/Classes/ReadOnlyClassRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ReadOnlyClassRule implements Rule +final class ReadOnlyClassRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Classes/RequireExtendsRule.php b/src/Rules/Classes/RequireExtendsRule.php index 34a35ab11f4..036cf3aa85a 100644 --- a/src/Rules/Classes/RequireExtendsRule.php +++ b/src/Rules/Classes/RequireExtendsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class RequireExtendsRule implements Rule +final class RequireExtendsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/RequireImplementsRule.php b/src/Rules/Classes/RequireImplementsRule.php index 32c9361d65a..3b1fdca9488 100644 --- a/src/Rules/Classes/RequireImplementsRule.php +++ b/src/Rules/Classes/RequireImplementsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class RequireImplementsRule implements Rule +final class RequireImplementsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/TraitAttributeClassRule.php b/src/Rules/Classes/TraitAttributeClassRule.php index 4d69f3a7bab..c73a1f83404 100644 --- a/src/Rules/Classes/TraitAttributeClassRule.php +++ b/src/Rules/Classes/TraitAttributeClassRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class TraitAttributeClassRule implements Rule +final class TraitAttributeClassRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index 2850e2bafeb..e42a60d5f7d 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -22,7 +22,7 @@ /** * @implements Rule */ -class UnusedConstructorParametersRule implements Rule +final class UnusedConstructorParametersRule implements Rule { public function __construct(private UnusedFunctionParametersCheck $check) diff --git a/src/Rules/Comparison/BooleanAndConstantConditionRule.php b/src/Rules/Comparison/BooleanAndConstantConditionRule.php index 44c574fb68b..8d680e93068 100644 --- a/src/Rules/Comparison/BooleanAndConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanAndConstantConditionRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class BooleanAndConstantConditionRule implements Rule +final class BooleanAndConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/BooleanNotConstantConditionRule.php b/src/Rules/Comparison/BooleanNotConstantConditionRule.php index d41cb743ca0..f107804843d 100644 --- a/src/Rules/Comparison/BooleanNotConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanNotConstantConditionRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class BooleanNotConstantConditionRule implements Rule +final class BooleanNotConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/BooleanOrConstantConditionRule.php b/src/Rules/Comparison/BooleanOrConstantConditionRule.php index 831829355cc..02f3db65905 100644 --- a/src/Rules/Comparison/BooleanOrConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanOrConstantConditionRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class BooleanOrConstantConditionRule implements Rule +final class BooleanOrConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/ConstantConditionRuleHelper.php b/src/Rules/Comparison/ConstantConditionRuleHelper.php index 60da2b56ff0..9271ef2782f 100644 --- a/src/Rules/Comparison/ConstantConditionRuleHelper.php +++ b/src/Rules/Comparison/ConstantConditionRuleHelper.php @@ -8,7 +8,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Type\BooleanType; -class ConstantConditionRuleHelper +final class ConstantConditionRuleHelper { public function __construct( diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 5222647d175..477210064ad 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class ConstantLooseComparisonRule implements Rule +final class ConstantLooseComparisonRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php index 6074f8d20c2..3777b5d6e59 100644 --- a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php +++ b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class DoWhileLoopConstantConditionRule implements Rule +final class DoWhileLoopConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/ElseIfConstantConditionRule.php b/src/Rules/Comparison/ElseIfConstantConditionRule.php index d1bf93a6dbd..80444eb3359 100644 --- a/src/Rules/Comparison/ElseIfConstantConditionRule.php +++ b/src/Rules/Comparison/ElseIfConstantConditionRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ElseIfConstantConditionRule implements Rule +final class ElseIfConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/IfConstantConditionRule.php b/src/Rules/Comparison/IfConstantConditionRule.php index 8bf57ebcd42..f0eb0e338c8 100644 --- a/src/Rules/Comparison/IfConstantConditionRule.php +++ b/src/Rules/Comparison/IfConstantConditionRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class IfConstantConditionRule implements Rule +final class IfConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 55423dbf508..a52cc8d718e 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ImpossibleCheckTypeFunctionCallRule implements Rule +final class ImpossibleCheckTypeFunctionCallRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 828cfa1b829..39a4da06bde 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -37,7 +37,7 @@ use function sprintf; use function strtolower; -class ImpossibleCheckTypeHelper +final class ImpossibleCheckTypeHelper { /** diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index 9fc4e4067dc..5387a18bfcf 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ImpossibleCheckTypeMethodCallRule implements Rule +final class ImpossibleCheckTypeMethodCallRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index 5d35d1be1a1..9ac004779d6 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ImpossibleCheckTypeStaticMethodCallRule implements Rule +final class ImpossibleCheckTypeStaticMethodCallRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/LogicalXorConstantConditionRule.php b/src/Rules/Comparison/LogicalXorConstantConditionRule.php index 250b55bd6e5..971868f1c4f 100644 --- a/src/Rules/Comparison/LogicalXorConstantConditionRule.php +++ b/src/Rules/Comparison/LogicalXorConstantConditionRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class LogicalXorConstantConditionRule implements Rule +final class LogicalXorConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/MatchExpressionRule.php b/src/Rules/Comparison/MatchExpressionRule.php index d7d318322e9..81cfb1f4714 100644 --- a/src/Rules/Comparison/MatchExpressionRule.php +++ b/src/Rules/Comparison/MatchExpressionRule.php @@ -22,7 +22,7 @@ /** * @implements Rule */ -class MatchExpressionRule implements Rule +final class MatchExpressionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php index 081cdef1e0f..b208766609d 100644 --- a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php +++ b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class NumberComparisonOperatorsConstantConditionRule implements Rule +final class NumberComparisonOperatorsConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index 66f447db3b2..6780fb91d32 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class StrictComparisonOfDifferentTypesRule implements Rule +final class StrictComparisonOfDifferentTypesRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php index 9513924e6b7..adbe33ac835 100644 --- a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php +++ b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class TernaryOperatorConstantConditionRule implements Rule +final class TernaryOperatorConstantConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/UnreachableIfBranchesRule.php b/src/Rules/Comparison/UnreachableIfBranchesRule.php index 4918a320d6d..7b3682f213b 100644 --- a/src/Rules/Comparison/UnreachableIfBranchesRule.php +++ b/src/Rules/Comparison/UnreachableIfBranchesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class UnreachableIfBranchesRule implements Rule +final class UnreachableIfBranchesRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php index 32ef874c5fa..0ce221250b0 100644 --- a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php +++ b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class UnreachableTernaryElseBranchRule implements Rule +final class UnreachableTernaryElseBranchRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php b/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php index 80a473b713f..ac87e2b8a16 100644 --- a/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php +++ b/src/Rules/Comparison/UsageOfVoidMatchExpressionRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class UsageOfVoidMatchExpressionRule implements Rule +final class UsageOfVoidMatchExpressionRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php index 01ae378030a..b6eaa9d7904 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class WhileLoopAlwaysFalseConditionRule implements Rule +final class WhileLoopAlwaysFalseConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php index 42a885b6ada..68ac27fbf2c 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class WhileLoopAlwaysTrueConditionRule implements Rule +final class WhileLoopAlwaysTrueConditionRule implements Rule { public function __construct( diff --git a/src/Rules/Constants/ConstantRule.php b/src/Rules/Constants/ConstantRule.php index 27003ecade7..d0e52ead39d 100644 --- a/src/Rules/Constants/ConstantRule.php +++ b/src/Rules/Constants/ConstantRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ConstantRule implements Rule +final class ConstantRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Constants/DynamicClassConstantFetchRule.php b/src/Rules/Constants/DynamicClassConstantFetchRule.php index ce1295ffc48..40588a1f56f 100644 --- a/src/Rules/Constants/DynamicClassConstantFetchRule.php +++ b/src/Rules/Constants/DynamicClassConstantFetchRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class DynamicClassConstantFetchRule implements Rule +final class DynamicClassConstantFetchRule implements Rule { public function __construct(private PhpVersion $phpVersion, private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Constants/FinalConstantRule.php b/src/Rules/Constants/FinalConstantRule.php index d6b891f4652..58b13d04e31 100644 --- a/src/Rules/Constants/FinalConstantRule.php +++ b/src/Rules/Constants/FinalConstantRule.php @@ -10,7 +10,7 @@ use PHPStan\Rules\RuleErrorBuilder; /** @implements Rule */ -class FinalConstantRule implements Rule +final class FinalConstantRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php index 895fb96360d..e91391e8bb8 100644 --- a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php +++ b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyAlwaysUsedClassConstantsExtensionProvider implements AlwaysUsedClassConstantsExtensionProvider +final class LazyAlwaysUsedClassConstantsExtensionProvider implements AlwaysUsedClassConstantsExtensionProvider { /** @var AlwaysUsedClassConstantsExtension[]|null */ diff --git a/src/Rules/Constants/MagicConstantContextRule.php b/src/Rules/Constants/MagicConstantContextRule.php index 5cfb38f0744..2de89b7f657 100644 --- a/src/Rules/Constants/MagicConstantContextRule.php +++ b/src/Rules/Constants/MagicConstantContextRule.php @@ -11,7 +11,7 @@ use function sprintf; /** @implements Rule */ -class MagicConstantContextRule implements Rule +final class MagicConstantContextRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Constants/NativeTypedClassConstantRule.php b/src/Rules/Constants/NativeTypedClassConstantRule.php index ce2ea802d8c..ad16fe919bb 100644 --- a/src/Rules/Constants/NativeTypedClassConstantRule.php +++ b/src/Rules/Constants/NativeTypedClassConstantRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class NativeTypedClassConstantRule implements Rule +final class NativeTypedClassConstantRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index 555cbfc0ddb..ca1a822f99b 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class OverridingConstantRule implements Rule +final class OverridingConstantRule implements Rule { public function __construct( diff --git a/src/Rules/Constants/ValueAssignedToClassConstantRule.php b/src/Rules/Constants/ValueAssignedToClassConstantRule.php index 37b72e1c721..afdbf2c5e74 100644 --- a/src/Rules/Constants/ValueAssignedToClassConstantRule.php +++ b/src/Rules/Constants/ValueAssignedToClassConstantRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ValueAssignedToClassConstantRule implements Rule +final class ValueAssignedToClassConstantRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DateTimeInstantiationRule.php b/src/Rules/DateTimeInstantiationRule.php index b8cd1684dd6..3e62e4b68b4 100644 --- a/src/Rules/DateTimeInstantiationRule.php +++ b/src/Rules/DateTimeInstantiationRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class DateTimeInstantiationRule implements Rule +final class DateTimeInstantiationRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/BetterNoopRule.php b/src/Rules/DeadCode/BetterNoopRule.php index 22ce47032ff..2e246941b79 100644 --- a/src/Rules/DeadCode/BetterNoopRule.php +++ b/src/Rules/DeadCode/BetterNoopRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class BetterNoopRule implements Rule +final class BetterNoopRule implements Rule { public function __construct(private ExprPrinter $exprPrinter) diff --git a/src/Rules/DeadCode/CallToConstructorStatementWithoutImpurePointsRule.php b/src/Rules/DeadCode/CallToConstructorStatementWithoutImpurePointsRule.php index ab14e74d36c..aa0bf2ec0bc 100644 --- a/src/Rules/DeadCode/CallToConstructorStatementWithoutImpurePointsRule.php +++ b/src/Rules/DeadCode/CallToConstructorStatementWithoutImpurePointsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToConstructorStatementWithoutImpurePointsRule implements Rule +final class CallToConstructorStatementWithoutImpurePointsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/CallToFunctionStatementWithoutImpurePointsRule.php b/src/Rules/DeadCode/CallToFunctionStatementWithoutImpurePointsRule.php index 1635b120505..3b123b3e4d3 100644 --- a/src/Rules/DeadCode/CallToFunctionStatementWithoutImpurePointsRule.php +++ b/src/Rules/DeadCode/CallToFunctionStatementWithoutImpurePointsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToFunctionStatementWithoutImpurePointsRule implements Rule +final class CallToFunctionStatementWithoutImpurePointsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRule.php b/src/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRule.php index 5c4d597cd9f..443b7dcc10a 100644 --- a/src/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRule.php +++ b/src/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToMethodStatementWithoutImpurePointsRule implements Rule +final class CallToMethodStatementWithoutImpurePointsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/CallToStaticMethodStatementWithoutImpurePointsRule.php b/src/Rules/DeadCode/CallToStaticMethodStatementWithoutImpurePointsRule.php index c64b29f8bf5..83d78428135 100644 --- a/src/Rules/DeadCode/CallToStaticMethodStatementWithoutImpurePointsRule.php +++ b/src/Rules/DeadCode/CallToStaticMethodStatementWithoutImpurePointsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToStaticMethodStatementWithoutImpurePointsRule implements Rule +final class CallToStaticMethodStatementWithoutImpurePointsRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php index dae162500a8..40839309e5d 100644 --- a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php @@ -13,7 +13,7 @@ /** * @implements Collector */ -class ConstructorWithoutImpurePointsCollector implements Collector +final class ConstructorWithoutImpurePointsCollector implements Collector { public function getNodeType(): string diff --git a/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php b/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php index 528e5c76e60..6dafe5d35b2 100644 --- a/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php @@ -12,7 +12,7 @@ /** * @implements Collector */ -class FunctionWithoutImpurePointsCollector implements Collector +final class FunctionWithoutImpurePointsCollector implements Collector { public function getNodeType(): string diff --git a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php index b6bdb3ca34a..f07b2aac37d 100644 --- a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php @@ -12,7 +12,7 @@ /** * @implements Collector */ -class MethodWithoutImpurePointsCollector implements Collector +final class MethodWithoutImpurePointsCollector implements Collector { public function getNodeType(): string diff --git a/src/Rules/DeadCode/NoopRule.php b/src/Rules/DeadCode/NoopRule.php index 34855261ef3..ff0d6eb8e7e 100644 --- a/src/Rules/DeadCode/NoopRule.php +++ b/src/Rules/DeadCode/NoopRule.php @@ -13,7 +13,7 @@ * @deprecated Replaced by PHPStan\Rules\DeadCode\BetterNoopRule * @implements Rule */ -class NoopRule implements Rule +final class NoopRule implements Rule { public function __construct(private ExprPrinter $exprPrinter, private bool $better) diff --git a/src/Rules/DeadCode/PossiblyPureFuncCallCollector.php b/src/Rules/DeadCode/PossiblyPureFuncCallCollector.php index 952da73ba28..85c126a00b2 100644 --- a/src/Rules/DeadCode/PossiblyPureFuncCallCollector.php +++ b/src/Rules/DeadCode/PossiblyPureFuncCallCollector.php @@ -11,7 +11,7 @@ /** * @implements Collector */ -class PossiblyPureFuncCallCollector implements Collector +final class PossiblyPureFuncCallCollector implements Collector { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/DeadCode/PossiblyPureMethodCallCollector.php b/src/Rules/DeadCode/PossiblyPureMethodCallCollector.php index 256d2bf781a..5869714cf89 100644 --- a/src/Rules/DeadCode/PossiblyPureMethodCallCollector.php +++ b/src/Rules/DeadCode/PossiblyPureMethodCallCollector.php @@ -10,7 +10,7 @@ /** * @implements Collector, string, int}> */ -class PossiblyPureMethodCallCollector implements Collector +final class PossiblyPureMethodCallCollector implements Collector { public function __construct() diff --git a/src/Rules/DeadCode/PossiblyPureNewCollector.php b/src/Rules/DeadCode/PossiblyPureNewCollector.php index e2fabe49ca0..f76f39c6163 100644 --- a/src/Rules/DeadCode/PossiblyPureNewCollector.php +++ b/src/Rules/DeadCode/PossiblyPureNewCollector.php @@ -12,7 +12,7 @@ /** * @implements Collector */ -class PossiblyPureNewCollector implements Collector +final class PossiblyPureNewCollector implements Collector { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/DeadCode/PossiblyPureStaticCallCollector.php b/src/Rules/DeadCode/PossiblyPureStaticCallCollector.php index d934b57a6d6..495cdf0248c 100644 --- a/src/Rules/DeadCode/PossiblyPureStaticCallCollector.php +++ b/src/Rules/DeadCode/PossiblyPureStaticCallCollector.php @@ -10,7 +10,7 @@ /** * @implements Collector */ -class PossiblyPureStaticCallCollector implements Collector +final class PossiblyPureStaticCallCollector implements Collector { public function __construct() diff --git a/src/Rules/DeadCode/UnreachableStatementRule.php b/src/Rules/DeadCode/UnreachableStatementRule.php index f1b226ad57a..17f166b7884 100644 --- a/src/Rules/DeadCode/UnreachableStatementRule.php +++ b/src/Rules/DeadCode/UnreachableStatementRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class UnreachableStatementRule implements Rule +final class UnreachableStatementRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/DeadCode/UnusedPrivateConstantRule.php b/src/Rules/DeadCode/UnusedPrivateConstantRule.php index ccb160f60f4..2e860a391fa 100644 --- a/src/Rules/DeadCode/UnusedPrivateConstantRule.php +++ b/src/Rules/DeadCode/UnusedPrivateConstantRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class UnusedPrivateConstantRule implements Rule +final class UnusedPrivateConstantRule implements Rule { public function __construct(private AlwaysUsedClassConstantsExtensionProvider $extensionProvider) diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index aa868808d49..52ec29d01aa 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class UnusedPrivateMethodRule implements Rule +final class UnusedPrivateMethodRule implements Rule { public function __construct(private AlwaysUsedMethodExtensionProvider $extensionProvider) diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 6af054659dd..f3373d4c107 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class UnusedPrivatePropertyRule implements Rule +final class UnusedPrivatePropertyRule implements Rule { /** diff --git a/src/Rules/Debug/DumpTypeRule.php b/src/Rules/Debug/DumpTypeRule.php index db1020b18a0..f7d2a6dcb4e 100644 --- a/src/Rules/Debug/DumpTypeRule.php +++ b/src/Rules/Debug/DumpTypeRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class DumpTypeRule implements Rule +final class DumpTypeRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Debug/FileAssertRule.php b/src/Rules/Debug/FileAssertRule.php index e0899850c33..3f8e6a62ee5 100644 --- a/src/Rules/Debug/FileAssertRule.php +++ b/src/Rules/Debug/FileAssertRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class FileAssertRule implements Rule +final class FileAssertRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/DirectRegistry.php b/src/Rules/DirectRegistry.php index 7ee53824ee8..0dfb5d71ecc 100644 --- a/src/Rules/DirectRegistry.php +++ b/src/Rules/DirectRegistry.php @@ -6,6 +6,9 @@ use function class_implements; use function class_parents; +/** + * @final + */ class DirectRegistry implements Registry { diff --git a/src/Rules/EnumCases/EnumCaseAttributesRule.php b/src/Rules/EnumCases/EnumCaseAttributesRule.php index 8d584b72f6f..c7e0113764d 100644 --- a/src/Rules/EnumCases/EnumCaseAttributesRule.php +++ b/src/Rules/EnumCases/EnumCaseAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class EnumCaseAttributesRule implements Rule +final class EnumCaseAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php index 267d8c8200d..d7d3b5bc712 100644 --- a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php +++ b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CatchWithUnthrownExceptionRule implements Rule +final class CatchWithUnthrownExceptionRule implements Rule { public function __construct( diff --git a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php index 1231a0d5e2f..1c826e424c5 100644 --- a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php +++ b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class CaughtExceptionExistenceRule implements Rule +final class CaughtExceptionExistenceRule implements Rule { public function __construct( diff --git a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php index cdb62d25973..fd6866bc72d 100644 --- a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php +++ b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php @@ -7,7 +7,10 @@ use PHPStan\Reflection\ReflectionProvider; use function count; -/** @api */ +/** + * @api + * @final + */ class DefaultExceptionTypeResolver implements ExceptionTypeResolver { diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php b/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php index e2ee44c4662..f3ed6e08f89 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class MissingCheckedExceptionInFunctionThrowsRule implements Rule +final class MissingCheckedExceptionInFunctionThrowsRule implements Rule { public function __construct(private MissingCheckedExceptionInThrowsCheck $check) diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php b/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php index 2aae5d488c9..c564711b2f7 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class MissingCheckedExceptionInMethodThrowsRule implements Rule +final class MissingCheckedExceptionInMethodThrowsRule implements Rule { public function __construct(private MissingCheckedExceptionInThrowsCheck $check) diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php b/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php index 508214f275e..0756fcac6b4 100644 --- a/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php +++ b/src/Rules/Exceptions/MissingCheckedExceptionInThrowsCheck.php @@ -12,7 +12,7 @@ use PHPStan\Type\VerbosityLevel; use Throwable; -class MissingCheckedExceptionInThrowsCheck +final class MissingCheckedExceptionInThrowsCheck { public function __construct(private ExceptionTypeResolver $exceptionTypeResolver) diff --git a/src/Rules/Exceptions/NoncapturingCatchRule.php b/src/Rules/Exceptions/NoncapturingCatchRule.php index d91bf2fc142..499b62e154f 100644 --- a/src/Rules/Exceptions/NoncapturingCatchRule.php +++ b/src/Rules/Exceptions/NoncapturingCatchRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class NoncapturingCatchRule implements Rule +final class NoncapturingCatchRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php index bcf73954a99..f4a6e17499d 100644 --- a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php +++ b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class OverwrittenExitPointByFinallyRule implements Rule +final class OverwrittenExitPointByFinallyRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Exceptions/ThrowExprTypeRule.php b/src/Rules/Exceptions/ThrowExprTypeRule.php index 85f648ab971..71087449d79 100644 --- a/src/Rules/Exceptions/ThrowExprTypeRule.php +++ b/src/Rules/Exceptions/ThrowExprTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ThrowExprTypeRule implements Rule +final class ThrowExprTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Exceptions/ThrowExpressionRule.php b/src/Rules/Exceptions/ThrowExpressionRule.php index d9b5655ff6d..5d3c7c2576d 100644 --- a/src/Rules/Exceptions/ThrowExpressionRule.php +++ b/src/Rules/Exceptions/ThrowExpressionRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ThrowExpressionRule implements Rule +final class ThrowExpressionRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php index f07d6dc5c07..a2ead680c73 100644 --- a/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php +++ b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ThrowsVoidFunctionWithExplicitThrowPointRule implements Rule +final class ThrowsVoidFunctionWithExplicitThrowPointRule implements Rule { public function __construct( diff --git a/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php index 59c0bd84486..327b55c2022 100644 --- a/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php +++ b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ThrowsVoidMethodWithExplicitThrowPointRule implements Rule +final class ThrowsVoidMethodWithExplicitThrowPointRule implements Rule { public function __construct( diff --git a/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php b/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php index e7f7f9849e4..6688dec466f 100644 --- a/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php +++ b/src/Rules/Exceptions/TooWideFunctionThrowTypeRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class TooWideFunctionThrowTypeRule implements Rule +final class TooWideFunctionThrowTypeRule implements Rule { public function __construct(private TooWideThrowTypeCheck $check) diff --git a/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php b/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php index c9a5f231b6c..55c69ee3a50 100644 --- a/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php +++ b/src/Rules/Exceptions/TooWideMethodThrowTypeRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class TooWideMethodThrowTypeRule implements Rule +final class TooWideMethodThrowTypeRule implements Rule { public function __construct(private FileTypeMapper $fileTypeMapper, private TooWideThrowTypeCheck $check) diff --git a/src/Rules/Exceptions/TooWideThrowTypeCheck.php b/src/Rules/Exceptions/TooWideThrowTypeCheck.php index 2a15d3139f9..830d0f9a62b 100644 --- a/src/Rules/Exceptions/TooWideThrowTypeCheck.php +++ b/src/Rules/Exceptions/TooWideThrowTypeCheck.php @@ -10,7 +10,7 @@ use PHPStan\Type\VerbosityLevel; use function array_map; -class TooWideThrowTypeCheck +final class TooWideThrowTypeCheck { /** diff --git a/src/Rules/FoundTypeResult.php b/src/Rules/FoundTypeResult.php index be17a4c76cc..4e89ed8ec52 100644 --- a/src/Rules/FoundTypeResult.php +++ b/src/Rules/FoundTypeResult.php @@ -4,7 +4,10 @@ use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class FoundTypeResult { diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4c34fb4f430..1925232132b 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -33,7 +33,7 @@ use function max; use function sprintf; -class FunctionCallParametersCheck +final class FunctionCallParametersCheck { public function __construct( diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index d2bdef50ba5..fbedaffbd7a 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -42,7 +42,7 @@ use function is_string; use function sprintf; -class FunctionDefinitionCheck +final class FunctionDefinitionCheck { public function __construct( diff --git a/src/Rules/FunctionReturnTypeCheck.php b/src/Rules/FunctionReturnTypeCheck.php index 82bb5d529b9..be2eb86b1aa 100644 --- a/src/Rules/FunctionReturnTypeCheck.php +++ b/src/Rules/FunctionReturnTypeCheck.php @@ -13,7 +13,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class FunctionReturnTypeCheck +final class FunctionReturnTypeCheck { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Functions/ArrayFilterRule.php b/src/Rules/Functions/ArrayFilterRule.php index 177d1d218d8..7eeb304c3d6 100644 --- a/src/Rules/Functions/ArrayFilterRule.php +++ b/src/Rules/Functions/ArrayFilterRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ArrayFilterRule implements Rule +final class ArrayFilterRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/ArrayValuesRule.php b/src/Rules/Functions/ArrayValuesRule.php index bb62442ec83..cf058b54a87 100644 --- a/src/Rules/Functions/ArrayValuesRule.php +++ b/src/Rules/Functions/ArrayValuesRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ArrayValuesRule implements Rule +final class ArrayValuesRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/ArrowFunctionAttributesRule.php b/src/Rules/Functions/ArrowFunctionAttributesRule.php index 1b4e1ddf031..b849f968aa7 100644 --- a/src/Rules/Functions/ArrowFunctionAttributesRule.php +++ b/src/Rules/Functions/ArrowFunctionAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ArrowFunctionAttributesRule implements Rule +final class ArrowFunctionAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php b/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php index 371f2fa61a2..cc7d673620f 100644 --- a/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php +++ b/src/Rules/Functions/ArrowFunctionReturnNullsafeByRefRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ArrowFunctionReturnNullsafeByRefRule implements Rule +final class ArrowFunctionReturnNullsafeByRefRule implements Rule { public function __construct(private NullsafeCheck $nullsafeCheck) diff --git a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php index aa0a9436de4..b9b489c12a4 100644 --- a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php +++ b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class ArrowFunctionReturnTypeRule implements Rule +final class ArrowFunctionReturnTypeRule implements Rule { public function __construct(private FunctionReturnTypeCheck $returnTypeCheck) diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index e5c0d7dc8cf..c21790b738f 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -24,7 +24,7 @@ /** * @implements Rule */ -class CallCallablesRule implements Rule +final class CallCallablesRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 01b80d7f689..24dc76e1868 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToFunctionParametersRule implements Rule +final class CallToFunctionParametersRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider, private FunctionCallParametersCheck $check) diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 685fa41de0e..2df15b78f02 100644 --- a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class CallToFunctionStatementWithoutSideEffectsRule implements Rule +final class CallToFunctionStatementWithoutSideEffectsRule implements Rule { private const SIDE_EFFECT_FLIP_PARAMETERS = [ diff --git a/src/Rules/Functions/CallToNonExistentFunctionRule.php b/src/Rules/Functions/CallToNonExistentFunctionRule.php index 72fca5073c6..a97e2caddbb 100644 --- a/src/Rules/Functions/CallToNonExistentFunctionRule.php +++ b/src/Rules/Functions/CallToNonExistentFunctionRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallToNonExistentFunctionRule implements Rule +final class CallToNonExistentFunctionRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index 040a5aeccec..415e04b748b 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class CallUserFuncRule implements Rule +final class CallUserFuncRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/ClosureAttributesRule.php b/src/Rules/Functions/ClosureAttributesRule.php index 170d9ad05e9..841ae2f46cc 100644 --- a/src/Rules/Functions/ClosureAttributesRule.php +++ b/src/Rules/Functions/ClosureAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ClosureAttributesRule implements Rule +final class ClosureAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Functions/ClosureReturnTypeRule.php b/src/Rules/Functions/ClosureReturnTypeRule.php index 98f72deb737..218edf57346 100644 --- a/src/Rules/Functions/ClosureReturnTypeRule.php +++ b/src/Rules/Functions/ClosureReturnTypeRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ClosureReturnTypeRule implements Rule +final class ClosureReturnTypeRule implements Rule { public function __construct(private FunctionReturnTypeCheck $returnTypeCheck) diff --git a/src/Rules/Functions/DefineParametersRule.php b/src/Rules/Functions/DefineParametersRule.php index 5aac3ee5c2c..83886f1ea2b 100644 --- a/src/Rules/Functions/DefineParametersRule.php +++ b/src/Rules/Functions/DefineParametersRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class DefineParametersRule implements Rule +final class DefineParametersRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Functions/DuplicateFunctionDeclarationRule.php b/src/Rules/Functions/DuplicateFunctionDeclarationRule.php index 06d92643aee..6cf2b6c5c11 100644 --- a/src/Rules/Functions/DuplicateFunctionDeclarationRule.php +++ b/src/Rules/Functions/DuplicateFunctionDeclarationRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class DuplicateFunctionDeclarationRule implements Rule +final class DuplicateFunctionDeclarationRule implements Rule { public function __construct(private Reflector $reflector, private RelativePathHelper $relativePathHelper) diff --git a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php index a2107c73a01..0b29af004c7 100644 --- a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ExistingClassesInArrowFunctionTypehintsRule implements Rule +final class ExistingClassesInArrowFunctionTypehintsRule implements Rule { public function __construct(private FunctionDefinitionCheck $check, private PhpVersion $phpVersion) diff --git a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php index 2c6dbd3ad04..0c5acfbd07d 100644 --- a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ExistingClassesInClosureTypehintsRule implements Rule +final class ExistingClassesInClosureTypehintsRule implements Rule { public function __construct(private FunctionDefinitionCheck $check) diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index 6a9586a820a..5df0e84af38 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ExistingClassesInTypehintsRule implements Rule +final class ExistingClassesInTypehintsRule implements Rule { public function __construct(private FunctionDefinitionCheck $check) diff --git a/src/Rules/Functions/FunctionAttributesRule.php b/src/Rules/Functions/FunctionAttributesRule.php index 2ccf122ac21..153c222091c 100644 --- a/src/Rules/Functions/FunctionAttributesRule.php +++ b/src/Rules/Functions/FunctionAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class FunctionAttributesRule implements Rule +final class FunctionAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Functions/FunctionCallableRule.php b/src/Rules/Functions/FunctionCallableRule.php index fdecd2073f8..827cf6e8764 100644 --- a/src/Rules/Functions/FunctionCallableRule.php +++ b/src/Rules/Functions/FunctionCallableRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class FunctionCallableRule implements Rule +final class FunctionCallableRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider, private RuleLevelHelper $ruleLevelHelper, private PhpVersion $phpVersion, private bool $checkFunctionNameCase, private bool $reportMaybes) diff --git a/src/Rules/Functions/ImplodeFunctionRule.php b/src/Rules/Functions/ImplodeFunctionRule.php index b890a8ff667..93ade0dafca 100644 --- a/src/Rules/Functions/ImplodeFunctionRule.php +++ b/src/Rules/Functions/ImplodeFunctionRule.php @@ -20,7 +20,7 @@ * @deprecated Replaced by PHPStan\Rules\Functions\ImplodeParameterCastableToStringRuleTest * @implements Rule */ -class ImplodeFunctionRule implements Rule +final class ImplodeFunctionRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/ImplodeParameterCastableToStringRule.php b/src/Rules/Functions/ImplodeParameterCastableToStringRule.php index df5136a8089..724a9ab079d 100644 --- a/src/Rules/Functions/ImplodeParameterCastableToStringRule.php +++ b/src/Rules/Functions/ImplodeParameterCastableToStringRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ImplodeParameterCastableToStringRule implements Rule +final class ImplodeParameterCastableToStringRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php index 585b00137a2..7d5967fb2b1 100644 --- a/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class IncompatibleArrowFunctionDefaultParameterTypeRule implements Rule +final class IncompatibleArrowFunctionDefaultParameterTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php index 3cd1c513902..ab3565a612c 100644 --- a/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class IncompatibleClosureDefaultParameterTypeRule implements Rule +final class IncompatibleClosureDefaultParameterTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php index a142269a68b..7a64f9e7eb4 100644 --- a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class IncompatibleDefaultParameterTypeRule implements Rule +final class IncompatibleDefaultParameterTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/InnerFunctionRule.php b/src/Rules/Functions/InnerFunctionRule.php index 24118f5fbbe..04694b44479 100644 --- a/src/Rules/Functions/InnerFunctionRule.php +++ b/src/Rules/Functions/InnerFunctionRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class InnerFunctionRule implements Rule +final class InnerFunctionRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/InvalidLexicalVariablesInClosureUseRule.php b/src/Rules/Functions/InvalidLexicalVariablesInClosureUseRule.php index d722abffe15..1a823e18fe5 100644 --- a/src/Rules/Functions/InvalidLexicalVariablesInClosureUseRule.php +++ b/src/Rules/Functions/InvalidLexicalVariablesInClosureUseRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class InvalidLexicalVariablesInClosureUseRule implements Rule +final class InvalidLexicalVariablesInClosureUseRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/ParamAttributesRule.php b/src/Rules/Functions/ParamAttributesRule.php index ee9e8f257c5..02006c1619f 100644 --- a/src/Rules/Functions/ParamAttributesRule.php +++ b/src/Rules/Functions/ParamAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ParamAttributesRule implements Rule +final class ParamAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Functions/ParameterCastableToStringRule.php b/src/Rules/Functions/ParameterCastableToStringRule.php index b6b26da76e5..d76428ff5db 100644 --- a/src/Rules/Functions/ParameterCastableToStringRule.php +++ b/src/Rules/Functions/ParameterCastableToStringRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ParameterCastableToStringRule implements Rule +final class ParameterCastableToStringRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/PrintfArrayParametersRule.php b/src/Rules/Functions/PrintfArrayParametersRule.php index d06cdc8f4a9..07adcf6b36a 100644 --- a/src/Rules/Functions/PrintfArrayParametersRule.php +++ b/src/Rules/Functions/PrintfArrayParametersRule.php @@ -22,7 +22,7 @@ /** * @implements Rule */ -class PrintfArrayParametersRule implements Rule +final class PrintfArrayParametersRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/PrintfParametersRule.php b/src/Rules/Functions/PrintfParametersRule.php index a1d8e52f35e..a80b44b9952 100644 --- a/src/Rules/Functions/PrintfParametersRule.php +++ b/src/Rules/Functions/PrintfParametersRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class PrintfParametersRule implements Rule +final class PrintfParametersRule implements Rule { private const FORMAT_ARGUMENT_POSITIONS = [ diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php index cc9da2c3ed0..ecca6e7d091 100644 --- a/src/Rules/Functions/RandomIntParametersRule.php +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class RandomIntParametersRule implements Rule +final class RandomIntParametersRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider, private bool $reportMaybes) diff --git a/src/Rules/Functions/RedefinedParametersRule.php b/src/Rules/Functions/RedefinedParametersRule.php index f364c82b9d1..6e056c6df35 100644 --- a/src/Rules/Functions/RedefinedParametersRule.php +++ b/src/Rules/Functions/RedefinedParametersRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class RedefinedParametersRule implements Rule +final class RedefinedParametersRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Functions/ReturnNullsafeByRefRule.php b/src/Rules/Functions/ReturnNullsafeByRefRule.php index 84f2c81f3cd..f90bdca70f7 100644 --- a/src/Rules/Functions/ReturnNullsafeByRefRule.php +++ b/src/Rules/Functions/ReturnNullsafeByRefRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ReturnNullsafeByRefRule implements Rule +final class ReturnNullsafeByRefRule implements Rule { public function __construct(private NullsafeCheck $nullsafeCheck) diff --git a/src/Rules/Functions/ReturnTypeRule.php b/src/Rules/Functions/ReturnTypeRule.php index ab9897713b9..4a02e64989d 100644 --- a/src/Rules/Functions/ReturnTypeRule.php +++ b/src/Rules/Functions/ReturnTypeRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class ReturnTypeRule implements Rule +final class ReturnTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/SortParameterCastableToStringRule.php b/src/Rules/Functions/SortParameterCastableToStringRule.php index dc1d4b63cf3..02cb8868866 100644 --- a/src/Rules/Functions/SortParameterCastableToStringRule.php +++ b/src/Rules/Functions/SortParameterCastableToStringRule.php @@ -26,7 +26,7 @@ /** * @implements Rule */ -class SortParameterCastableToStringRule implements Rule +final class SortParameterCastableToStringRule implements Rule { public function __construct( diff --git a/src/Rules/Functions/UnusedClosureUsesRule.php b/src/Rules/Functions/UnusedClosureUsesRule.php index 0caa2c2ae96..1494208b5b9 100644 --- a/src/Rules/Functions/UnusedClosureUsesRule.php +++ b/src/Rules/Functions/UnusedClosureUsesRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class UnusedClosureUsesRule implements Rule +final class UnusedClosureUsesRule implements Rule { public function __construct(private UnusedFunctionParametersCheck $check) diff --git a/src/Rules/Functions/UselessFunctionReturnValueRule.php b/src/Rules/Functions/UselessFunctionReturnValueRule.php index b5af3ebd1ca..d453fdca386 100644 --- a/src/Rules/Functions/UselessFunctionReturnValueRule.php +++ b/src/Rules/Functions/UselessFunctionReturnValueRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class UselessFunctionReturnValueRule implements Rule +final class UselessFunctionReturnValueRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Functions/VariadicParametersDeclarationRule.php b/src/Rules/Functions/VariadicParametersDeclarationRule.php index 2d7c048d80d..f346ec8e72a 100644 --- a/src/Rules/Functions/VariadicParametersDeclarationRule.php +++ b/src/Rules/Functions/VariadicParametersDeclarationRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class VariadicParametersDeclarationRule implements Rule +final class VariadicParametersDeclarationRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Generators/YieldFromTypeRule.php b/src/Rules/Generators/YieldFromTypeRule.php index deb3f8212ee..89a5d1fff1b 100644 --- a/src/Rules/Generators/YieldFromTypeRule.php +++ b/src/Rules/Generators/YieldFromTypeRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class YieldFromTypeRule implements Rule +final class YieldFromTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generators/YieldInGeneratorRule.php b/src/Rules/Generators/YieldInGeneratorRule.php index 9be06b937d4..25ddeef8bd9 100644 --- a/src/Rules/Generators/YieldInGeneratorRule.php +++ b/src/Rules/Generators/YieldInGeneratorRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class YieldInGeneratorRule implements Rule +final class YieldInGeneratorRule implements Rule { public function __construct(private bool $reportMaybes) diff --git a/src/Rules/Generators/YieldTypeRule.php b/src/Rules/Generators/YieldTypeRule.php index 3de12e41a38..d97c3eb76be 100644 --- a/src/Rules/Generators/YieldTypeRule.php +++ b/src/Rules/Generators/YieldTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class YieldTypeRule implements Rule +final class YieldTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index 0359073c44f..9c1efe6c4a3 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ClassAncestorsRule implements Rule +final class ClassAncestorsRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/ClassTemplateTypeRule.php b/src/Rules/Generics/ClassTemplateTypeRule.php index a21d0561e57..6c21c3a33d3 100644 --- a/src/Rules/Generics/ClassTemplateTypeRule.php +++ b/src/Rules/Generics/ClassTemplateTypeRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ClassTemplateTypeRule implements Rule +final class ClassTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/CrossCheckInterfacesHelper.php b/src/Rules/Generics/CrossCheckInterfacesHelper.php index e35854b5ecf..3e9c477cb35 100644 --- a/src/Rules/Generics/CrossCheckInterfacesHelper.php +++ b/src/Rules/Generics/CrossCheckInterfacesHelper.php @@ -9,7 +9,7 @@ use function array_key_exists; use function sprintf; -class CrossCheckInterfacesHelper +final class CrossCheckInterfacesHelper { /** diff --git a/src/Rules/Generics/EnumAncestorsRule.php b/src/Rules/Generics/EnumAncestorsRule.php index 8c786d73596..2cb7788d592 100644 --- a/src/Rules/Generics/EnumAncestorsRule.php +++ b/src/Rules/Generics/EnumAncestorsRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class EnumAncestorsRule implements Rule +final class EnumAncestorsRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/EnumTemplateTypeRule.php b/src/Rules/Generics/EnumTemplateTypeRule.php index 9c149811bfe..6f5b049c7b9 100644 --- a/src/Rules/Generics/EnumTemplateTypeRule.php +++ b/src/Rules/Generics/EnumTemplateTypeRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class EnumTemplateTypeRule implements Rule +final class EnumTemplateTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Generics/FunctionSignatureVarianceRule.php b/src/Rules/Generics/FunctionSignatureVarianceRule.php index e95b2e7712c..e73c28da760 100644 --- a/src/Rules/Generics/FunctionSignatureVarianceRule.php +++ b/src/Rules/Generics/FunctionSignatureVarianceRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class FunctionSignatureVarianceRule implements Rule +final class FunctionSignatureVarianceRule implements Rule { public function __construct(private VarianceCheck $varianceCheck) diff --git a/src/Rules/Generics/FunctionTemplateTypeRule.php b/src/Rules/Generics/FunctionTemplateTypeRule.php index 3700dd3b734..2fe0ab6bfbf 100644 --- a/src/Rules/Generics/FunctionTemplateTypeRule.php +++ b/src/Rules/Generics/FunctionTemplateTypeRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class FunctionTemplateTypeRule implements Rule +final class FunctionTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index f78cbaf01cb..fd60ba01a4d 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -21,7 +21,7 @@ use function in_array; use function sprintf; -class GenericAncestorsCheck +final class GenericAncestorsCheck { /** diff --git a/src/Rules/Generics/GenericObjectTypeCheck.php b/src/Rules/Generics/GenericObjectTypeCheck.php index 9df0d06b44b..46901218ef2 100644 --- a/src/Rules/Generics/GenericObjectTypeCheck.php +++ b/src/Rules/Generics/GenericObjectTypeCheck.php @@ -21,7 +21,7 @@ use function sprintf; use function strtolower; -class GenericObjectTypeCheck +final class GenericObjectTypeCheck { /** diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index 465c2b9f365..c26dd6f290b 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class InterfaceAncestorsRule implements Rule +final class InterfaceAncestorsRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/InterfaceTemplateTypeRule.php b/src/Rules/Generics/InterfaceTemplateTypeRule.php index e808174a9f9..30be451eaed 100644 --- a/src/Rules/Generics/InterfaceTemplateTypeRule.php +++ b/src/Rules/Generics/InterfaceTemplateTypeRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class InterfaceTemplateTypeRule implements Rule +final class InterfaceTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/MethodSignatureVarianceRule.php b/src/Rules/Generics/MethodSignatureVarianceRule.php index 4f99378be00..4590950baae 100644 --- a/src/Rules/Generics/MethodSignatureVarianceRule.php +++ b/src/Rules/Generics/MethodSignatureVarianceRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class MethodSignatureVarianceRule implements Rule +final class MethodSignatureVarianceRule implements Rule { public function __construct(private VarianceCheck $varianceCheck) diff --git a/src/Rules/Generics/MethodTagTemplateTypeRule.php b/src/Rules/Generics/MethodTagTemplateTypeRule.php index 6b60d6557a5..eafb0e946d1 100644 --- a/src/Rules/Generics/MethodTagTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTagTemplateTypeRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class MethodTagTemplateTypeRule implements Rule +final class MethodTagTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/MethodTemplateTypeRule.php b/src/Rules/Generics/MethodTemplateTypeRule.php index 80f21c3de76..fa9a6ecb060 100644 --- a/src/Rules/Generics/MethodTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTemplateTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class MethodTemplateTypeRule implements Rule +final class MethodTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/PropertyVarianceRule.php b/src/Rules/Generics/PropertyVarianceRule.php index b62c494ba51..4ddff55f111 100644 --- a/src/Rules/Generics/PropertyVarianceRule.php +++ b/src/Rules/Generics/PropertyVarianceRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class PropertyVarianceRule implements Rule +final class PropertyVarianceRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 234c4e8a717..5eb8d8bdf4d 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -36,7 +36,7 @@ use function get_class; use function sprintf; -class TemplateTypeCheck +final class TemplateTypeCheck { public function __construct( diff --git a/src/Rules/Generics/TraitTemplateTypeRule.php b/src/Rules/Generics/TraitTemplateTypeRule.php index c90b398fcf1..b08f12f32d4 100644 --- a/src/Rules/Generics/TraitTemplateTypeRule.php +++ b/src/Rules/Generics/TraitTemplateTypeRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class TraitTemplateTypeRule implements Rule +final class TraitTemplateTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/UsedTraitsRule.php b/src/Rules/Generics/UsedTraitsRule.php index 5ad6141c065..72914da2e78 100644 --- a/src/Rules/Generics/UsedTraitsRule.php +++ b/src/Rules/Generics/UsedTraitsRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class UsedTraitsRule implements Rule +final class UsedTraitsRule implements Rule { public function __construct( diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index 7447c13cced..ca13ca07a6d 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use function sprintf; -class VarianceCheck +final class VarianceCheck { public function __construct( diff --git a/src/Rules/Ignore/IgnoreParseErrorRule.php b/src/Rules/Ignore/IgnoreParseErrorRule.php index 330ac5a8d12..e44f5de4c90 100644 --- a/src/Rules/Ignore/IgnoreParseErrorRule.php +++ b/src/Rules/Ignore/IgnoreParseErrorRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class IgnoreParseErrorRule implements Rule +final class IgnoreParseErrorRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index a660be4e612..1a07cb30c73 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -17,7 +17,7 @@ /** * @phpstan-type ErrorIdentifier = 'empty'|'isset'|'nullCoalesce' */ -class IssetCheck +final class IssetCheck { public function __construct( diff --git a/src/Rules/Keywords/ContinueBreakInLoopRule.php b/src/Rules/Keywords/ContinueBreakInLoopRule.php index d97e3fd119d..d8e8b00fcb4 100644 --- a/src/Rules/Keywords/ContinueBreakInLoopRule.php +++ b/src/Rules/Keywords/ContinueBreakInLoopRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class ContinueBreakInLoopRule implements Rule +final class ContinueBreakInLoopRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Keywords/DeclareStrictTypesRule.php b/src/Rules/Keywords/DeclareStrictTypesRule.php index 3878c1c77cb..b0b364030a5 100644 --- a/src/Rules/Keywords/DeclareStrictTypesRule.php +++ b/src/Rules/Keywords/DeclareStrictTypesRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class DeclareStrictTypesRule implements Rule +final class DeclareStrictTypesRule implements Rule { public function __construct( diff --git a/src/Rules/LazyRegistry.php b/src/Rules/LazyRegistry.php index a9a1a728ec3..f1b91819231 100644 --- a/src/Rules/LazyRegistry.php +++ b/src/Rules/LazyRegistry.php @@ -7,7 +7,7 @@ use function class_implements; use function class_parents; -class LazyRegistry implements Registry +final class LazyRegistry implements Registry { public const RULE_TAG = 'phpstan.rules.rule'; diff --git a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php index d6d7b603c8a..04ca707933b 100644 --- a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php +++ b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class AbstractMethodInNonAbstractClassRule implements Rule +final class AbstractMethodInNonAbstractClassRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/AbstractPrivateMethodRule.php b/src/Rules/Methods/AbstractPrivateMethodRule.php index 060bb2a27ad..d087a19ce17 100644 --- a/src/Rules/Methods/AbstractPrivateMethodRule.php +++ b/src/Rules/Methods/AbstractPrivateMethodRule.php @@ -10,7 +10,7 @@ use function sprintf; /** @implements Rule */ -class AbstractPrivateMethodRule implements Rule +final class AbstractPrivateMethodRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 388dcb3208c..67eee301571 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class CallMethodsRule implements Rule +final class CallMethodsRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php index 0a37e486570..d381cdc6593 100644 --- a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php +++ b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class CallPrivateMethodThroughStaticRule implements Rule +final class CallPrivateMethodThroughStaticRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index b1f2f013c23..1706bdb76e6 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class CallStaticMethodsRule implements Rule +final class CallStaticMethodsRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php index da361f018aa..00a2352c996 100644 --- a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class CallToConstructorStatementWithoutSideEffectsRule implements Rule +final class CallToConstructorStatementWithoutSideEffectsRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index abeaff4110d..ed78c46f170 100644 --- a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class CallToMethodStatementWithoutSideEffectsRule implements Rule +final class CallToMethodStatementWithoutSideEffectsRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index 9db2493f65e..8f1828cbe41 100644 --- a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class CallToStaticMethodStatementWithoutSideEffectsRule implements Rule +final class CallToStaticMethodStatementWithoutSideEffectsRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/ConsistentConstructorRule.php b/src/Rules/Methods/ConsistentConstructorRule.php index e32d6fa5512..ab8553b5cc0 100644 --- a/src/Rules/Methods/ConsistentConstructorRule.php +++ b/src/Rules/Methods/ConsistentConstructorRule.php @@ -10,7 +10,7 @@ use function strtolower; /** @implements Rule */ -class ConsistentConstructorRule implements Rule +final class ConsistentConstructorRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/ConstructorReturnTypeRule.php b/src/Rules/Methods/ConstructorReturnTypeRule.php index c93634b94e0..0f1388314db 100644 --- a/src/Rules/Methods/ConstructorReturnTypeRule.php +++ b/src/Rules/Methods/ConstructorReturnTypeRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ConstructorReturnTypeRule implements Rule +final class ConstructorReturnTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/DirectAlwaysUsedMethodExtensionProvider.php b/src/Rules/Methods/DirectAlwaysUsedMethodExtensionProvider.php index b51cf7763a6..3012ef21f23 100644 --- a/src/Rules/Methods/DirectAlwaysUsedMethodExtensionProvider.php +++ b/src/Rules/Methods/DirectAlwaysUsedMethodExtensionProvider.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Methods; -class DirectAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider +final class DirectAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider { /** diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 524f5093951..702fc0f299e 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class ExistingClassesInTypehintsRule implements Rule +final class ExistingClassesInTypehintsRule implements Rule { public function __construct(private FunctionDefinitionCheck $check) diff --git a/src/Rules/Methods/FinalPrivateMethodRule.php b/src/Rules/Methods/FinalPrivateMethodRule.php index b0934bb0c47..b205234dc23 100644 --- a/src/Rules/Methods/FinalPrivateMethodRule.php +++ b/src/Rules/Methods/FinalPrivateMethodRule.php @@ -11,7 +11,7 @@ use function sprintf; /** @implements Rule */ -class FinalPrivateMethodRule implements Rule +final class FinalPrivateMethodRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/IllegalConstructorMethodCallRule.php b/src/Rules/Methods/IllegalConstructorMethodCallRule.php index 3305529134c..1dba6ed6d24 100644 --- a/src/Rules/Methods/IllegalConstructorMethodCallRule.php +++ b/src/Rules/Methods/IllegalConstructorMethodCallRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class IllegalConstructorMethodCallRule implements Rule +final class IllegalConstructorMethodCallRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/IllegalConstructorStaticCallRule.php b/src/Rules/Methods/IllegalConstructorStaticCallRule.php index 6e839f54ca7..fa747d6a2b5 100644 --- a/src/Rules/Methods/IllegalConstructorStaticCallRule.php +++ b/src/Rules/Methods/IllegalConstructorStaticCallRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class IllegalConstructorStaticCallRule implements Rule +final class IllegalConstructorStaticCallRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php index 92aa955a0e4..409b32d65f4 100644 --- a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class IncompatibleDefaultParameterTypeRule implements Rule +final class IncompatibleDefaultParameterTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php index cbd397ee949..6fca3226a5c 100644 --- a/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php +++ b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider +final class LazyAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider { /** @var AlwaysUsedMethodExtension[]|null */ diff --git a/src/Rules/Methods/MethodAttributesRule.php b/src/Rules/Methods/MethodAttributesRule.php index ae220df96ed..fefc7bac89d 100644 --- a/src/Rules/Methods/MethodAttributesRule.php +++ b/src/Rules/Methods/MethodAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class MethodAttributesRule implements Rule +final class MethodAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index 57d0c165e66..165f41741cc 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -19,7 +19,7 @@ use function sprintf; use function strtolower; -class MethodCallCheck +final class MethodCallCheck { public function __construct( diff --git a/src/Rules/Methods/MethodCallableRule.php b/src/Rules/Methods/MethodCallableRule.php index 15027c8c2c2..2d189436089 100644 --- a/src/Rules/Methods/MethodCallableRule.php +++ b/src/Rules/Methods/MethodCallableRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class MethodCallableRule implements Rule +final class MethodCallableRule implements Rule { public function __construct(private MethodCallCheck $methodCallCheck, private PhpVersion $phpVersion) diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 357d3cf9b32..2f21d52123c 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -21,7 +21,7 @@ use function count; use function sprintf; -class MethodParameterComparisonHelper +final class MethodParameterComparisonHelper { public function __construct(private PhpVersion $phpVersion, private bool $genericPrototypeMessage) diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index f7a1a8120bc..1e9d6b1ba27 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -37,7 +37,7 @@ /** * @implements Rule */ -class MethodSignatureRule implements Rule +final class MethodSignatureRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/MethodVisibilityInInterfaceRule.php b/src/Rules/Methods/MethodVisibilityInInterfaceRule.php index 2bb1fb915d8..28dbd1e3683 100644 --- a/src/Rules/Methods/MethodVisibilityInInterfaceRule.php +++ b/src/Rules/Methods/MethodVisibilityInInterfaceRule.php @@ -10,7 +10,7 @@ use function sprintf; /** @implements Rule */ -class MethodVisibilityInInterfaceRule implements Rule +final class MethodVisibilityInInterfaceRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/MissingMagicSerializationMethodsRule.php b/src/Rules/Methods/MissingMagicSerializationMethodsRule.php index a169d867948..4f7df1a538f 100644 --- a/src/Rules/Methods/MissingMagicSerializationMethodsRule.php +++ b/src/Rules/Methods/MissingMagicSerializationMethodsRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class MissingMagicSerializationMethodsRule implements Rule +final class MissingMagicSerializationMethodsRule implements Rule { public function __construct(private PhpVersion $phpversion) diff --git a/src/Rules/Methods/MissingMethodImplementationRule.php b/src/Rules/Methods/MissingMethodImplementationRule.php index e7b6d2c3bb8..ec6d40f0a3f 100644 --- a/src/Rules/Methods/MissingMethodImplementationRule.php +++ b/src/Rules/Methods/MissingMethodImplementationRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class MissingMethodImplementationRule implements Rule +final class MissingMethodImplementationRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/NullsafeMethodCallRule.php b/src/Rules/Methods/NullsafeMethodCallRule.php index 008f4e7d08c..e950e4cb9ae 100644 --- a/src/Rules/Methods/NullsafeMethodCallRule.php +++ b/src/Rules/Methods/NullsafeMethodCallRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class NullsafeMethodCallRule implements Rule +final class NullsafeMethodCallRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 17c4f09990a..9d414f6f2b0 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -29,7 +29,7 @@ /** * @implements Rule */ -class OverridingMethodRule implements Rule +final class OverridingMethodRule implements Rule { public function __construct( diff --git a/src/Rules/Methods/ReturnTypeRule.php b/src/Rules/Methods/ReturnTypeRule.php index d1140961a5b..00753a620c3 100644 --- a/src/Rules/Methods/ReturnTypeRule.php +++ b/src/Rules/Methods/ReturnTypeRule.php @@ -26,7 +26,7 @@ /** * @implements Rule */ -class ReturnTypeRule implements Rule +final class ReturnTypeRule implements Rule { public function __construct(private FunctionReturnTypeCheck $returnTypeCheck) diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index d50f8dfd32c..6eca4510d93 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -32,7 +32,7 @@ use function sprintf; use function strtolower; -class StaticMethodCallCheck +final class StaticMethodCallCheck { public function __construct( diff --git a/src/Rules/Methods/StaticMethodCallableRule.php b/src/Rules/Methods/StaticMethodCallableRule.php index a9bae8d9d55..815fdce7939 100644 --- a/src/Rules/Methods/StaticMethodCallableRule.php +++ b/src/Rules/Methods/StaticMethodCallableRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class StaticMethodCallableRule implements Rule +final class StaticMethodCallableRule implements Rule { public function __construct(private StaticMethodCallCheck $methodCallCheck, private PhpVersion $phpVersion) diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 71102e4f235..a1f16bbd07d 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -24,7 +24,7 @@ /** * @implements Rule */ -class MissingReturnRule implements Rule +final class MissingReturnRule implements Rule { public function __construct( diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index ae407a2a30e..d6c55e62ec0 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -27,7 +27,7 @@ use function sprintf; use function strtolower; -class MissingTypehintCheck +final class MissingTypehintCheck { public const MISSING_ITERABLE_VALUE_TYPE_TIP = 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type'; diff --git a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php index b961e235fc5..68d8a954655 100644 --- a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ExistingNamesInGroupUseRule implements Rule +final class ExistingNamesInGroupUseRule implements Rule { public function __construct( diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index 92415f012c0..381a2e1de61 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ExistingNamesInUseRule implements Rule +final class ExistingNamesInUseRule implements Rule { public function __construct( diff --git a/src/Rules/NullsafeCheck.php b/src/Rules/NullsafeCheck.php index fd17b1f850e..b8ff7713bfa 100644 --- a/src/Rules/NullsafeCheck.php +++ b/src/Rules/NullsafeCheck.php @@ -4,7 +4,7 @@ use PhpParser\Node\Expr; -class NullsafeCheck +final class NullsafeCheck { public function containsNullSafe(Expr $expr): bool diff --git a/src/Rules/Operators/InvalidAssignVarRule.php b/src/Rules/Operators/InvalidAssignVarRule.php index 4f6e564849f..7b24c91c52d 100644 --- a/src/Rules/Operators/InvalidAssignVarRule.php +++ b/src/Rules/Operators/InvalidAssignVarRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class InvalidAssignVarRule implements Rule +final class InvalidAssignVarRule implements Rule { public function __construct(private NullsafeCheck $nullsafeCheck) diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 2c42f7be1e9..517c41b9092 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class InvalidBinaryOperationRule implements Rule +final class InvalidBinaryOperationRule implements Rule { public function __construct( diff --git a/src/Rules/Operators/InvalidComparisonOperationRule.php b/src/Rules/Operators/InvalidComparisonOperationRule.php index 43b87605035..8dc06429e54 100644 --- a/src/Rules/Operators/InvalidComparisonOperationRule.php +++ b/src/Rules/Operators/InvalidComparisonOperationRule.php @@ -23,7 +23,7 @@ /** * @implements Rule */ -class InvalidComparisonOperationRule implements Rule +final class InvalidComparisonOperationRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Operators/InvalidIncDecOperationRule.php b/src/Rules/Operators/InvalidIncDecOperationRule.php index 9cd551cbf37..6f3382d829d 100644 --- a/src/Rules/Operators/InvalidIncDecOperationRule.php +++ b/src/Rules/Operators/InvalidIncDecOperationRule.php @@ -24,7 +24,7 @@ /** * @implements Rule */ -class InvalidIncDecOperationRule implements Rule +final class InvalidIncDecOperationRule implements Rule { public function __construct( diff --git a/src/Rules/Operators/InvalidUnaryOperationRule.php b/src/Rules/Operators/InvalidUnaryOperationRule.php index 099c1d65070..9ea9c073423 100644 --- a/src/Rules/Operators/InvalidUnaryOperationRule.php +++ b/src/Rules/Operators/InvalidUnaryOperationRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class InvalidUnaryOperationRule implements Rule +final class InvalidUnaryOperationRule implements Rule { public function __construct( diff --git a/src/Rules/ParameterCastableToStringCheck.php b/src/Rules/ParameterCastableToStringCheck.php index e34f6fba220..2d4dea0dbd4 100644 --- a/src/Rules/ParameterCastableToStringCheck.php +++ b/src/Rules/ParameterCastableToStringCheck.php @@ -11,7 +11,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class ParameterCastableToStringCheck +final class ParameterCastableToStringCheck { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index e7d3f8e73b1..40551504faa 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -17,7 +17,7 @@ use function sprintf; use function substr; -class AssertRuleHelper +final class AssertRuleHelper { public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) diff --git a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php index 7ad14e65b69..eee8ad3281e 100644 --- a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php +++ b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php @@ -17,7 +17,7 @@ use function sprintf; use function substr; -class ConditionalReturnTypeRuleHelper +final class ConditionalReturnTypeRuleHelper { /** diff --git a/src/Rules/PhpDoc/FunctionAssertRule.php b/src/Rules/PhpDoc/FunctionAssertRule.php index 0995b7574a9..481d99f1655 100644 --- a/src/Rules/PhpDoc/FunctionAssertRule.php +++ b/src/Rules/PhpDoc/FunctionAssertRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class FunctionAssertRule implements Rule +final class FunctionAssertRule implements Rule { public function __construct(private AssertRuleHelper $helper) diff --git a/src/Rules/PhpDoc/FunctionConditionalReturnTypeRule.php b/src/Rules/PhpDoc/FunctionConditionalReturnTypeRule.php index 5610c04925e..56ed3c3cf78 100644 --- a/src/Rules/PhpDoc/FunctionConditionalReturnTypeRule.php +++ b/src/Rules/PhpDoc/FunctionConditionalReturnTypeRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class FunctionConditionalReturnTypeRule implements Rule +final class FunctionConditionalReturnTypeRule implements Rule { public function __construct(private ConditionalReturnTypeRuleHelper $helper) diff --git a/src/Rules/PhpDoc/GenericCallableRuleHelper.php b/src/Rules/PhpDoc/GenericCallableRuleHelper.php index 1fdb7a17c03..3e849cd1dc8 100644 --- a/src/Rules/PhpDoc/GenericCallableRuleHelper.php +++ b/src/Rules/PhpDoc/GenericCallableRuleHelper.php @@ -18,7 +18,7 @@ use function array_keys; use function sprintf; -class GenericCallableRuleHelper +final class GenericCallableRuleHelper { public function __construct( diff --git a/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php index 3b89aea5c3f..0008644c1af 100644 --- a/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class IncompatibleClassConstantPhpDocTypeRule implements Rule +final class IncompatibleClassConstantPhpDocTypeRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index e37db8ae895..131ee1ec911 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -24,7 +24,7 @@ /** * @implements Rule */ -class IncompatiblePhpDocTypeRule implements Rule +final class IncompatiblePhpDocTypeRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index 4713a0cd08b..184821b5a3f 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class IncompatiblePropertyPhpDocTypeRule implements Rule +final class IncompatiblePropertyPhpDocTypeRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index 22be14246c3..46164fff021 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class IncompatibleSelfOutTypeRule implements Rule +final class IncompatibleSelfOutTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index 5d4f65420c5..923d65e143d 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class InvalidPHPStanDocTagRule implements Rule +final class InvalidPHPStanDocTagRule implements Rule { private const POSSIBLE_PHPSTAN_TAGS = [ diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index 6734c54e4ce..fe40a1bd618 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class InvalidPhpDocTagValueRule implements Rule +final class InvalidPhpDocTagValueRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 029f558bb89..4a227ffd07f 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -23,7 +23,7 @@ /** * @implements Rule */ -class InvalidPhpDocVarTagTypeRule implements Rule +final class InvalidPhpDocVarTagTypeRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php index 1efb15ffaa4..087c89b6ed1 100644 --- a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php +++ b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class InvalidThrowsPhpDocValueRule implements Rule +final class InvalidThrowsPhpDocValueRule implements Rule { public function __construct(private FileTypeMapper $fileTypeMapper) diff --git a/src/Rules/PhpDoc/MethodAssertRule.php b/src/Rules/PhpDoc/MethodAssertRule.php index 5c24f9bca8a..d8be08408fc 100644 --- a/src/Rules/PhpDoc/MethodAssertRule.php +++ b/src/Rules/PhpDoc/MethodAssertRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class MethodAssertRule implements Rule +final class MethodAssertRule implements Rule { public function __construct(private AssertRuleHelper $helper) diff --git a/src/Rules/PhpDoc/MethodConditionalReturnTypeRule.php b/src/Rules/PhpDoc/MethodConditionalReturnTypeRule.php index 6925576665e..56746b7f7b4 100644 --- a/src/Rules/PhpDoc/MethodConditionalReturnTypeRule.php +++ b/src/Rules/PhpDoc/MethodConditionalReturnTypeRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class MethodConditionalReturnTypeRule implements Rule +final class MethodConditionalReturnTypeRule implements Rule { public function __construct(private ConditionalReturnTypeRuleHelper $helper) diff --git a/src/Rules/PhpDoc/PhpDocLineHelper.php b/src/Rules/PhpDoc/PhpDocLineHelper.php index d1d4b52e8ba..b008e634707 100644 --- a/src/Rules/PhpDoc/PhpDocLineHelper.php +++ b/src/Rules/PhpDoc/PhpDocLineHelper.php @@ -5,7 +5,7 @@ use PhpParser\Node as PhpParserNode; use PHPStan\PhpDocParser\Ast\Node as PhpDocNode; -class PhpDocLineHelper +final class PhpDocLineHelper { /** diff --git a/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php b/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php index 27d8b3d6a35..9540555e4ff 100644 --- a/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php +++ b/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class RequireExtendsDefinitionClassRule implements Rule +final class RequireExtendsDefinitionClassRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php index 174e7c9385e..de4be753d96 100644 --- a/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class RequireExtendsDefinitionTraitRule implements Rule +final class RequireExtendsDefinitionTraitRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionClassRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionClassRule.php index 03e9422e2a3..9a330444017 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionClassRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionClassRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class RequireImplementsDefinitionClassRule implements Rule +final class RequireImplementsDefinitionClassRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 2c18b2e3ef2..076ae342a0c 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class RequireImplementsDefinitionTraitRule implements Rule +final class RequireImplementsDefinitionTraitRule implements Rule { public function __construct( diff --git a/src/Rules/PhpDoc/UnresolvableTypeHelper.php b/src/Rules/PhpDoc/UnresolvableTypeHelper.php index 8b53af21d5e..25b485dfad0 100644 --- a/src/Rules/PhpDoc/UnresolvableTypeHelper.php +++ b/src/Rules/PhpDoc/UnresolvableTypeHelper.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; -class UnresolvableTypeHelper +final class UnresolvableTypeHelper { public function containsUnresolvableType(Type $type): bool diff --git a/src/Rules/PhpDoc/VarTagChangedExpressionTypeRule.php b/src/Rules/PhpDoc/VarTagChangedExpressionTypeRule.php index 3968f05792b..2ce5e70cce7 100644 --- a/src/Rules/PhpDoc/VarTagChangedExpressionTypeRule.php +++ b/src/Rules/PhpDoc/VarTagChangedExpressionTypeRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class VarTagChangedExpressionTypeRule implements Rule +final class VarTagChangedExpressionTypeRule implements Rule { public function __construct(private VarTagTypeRuleHelper $varTagTypeRuleHelper) diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index d01b17396b2..0070554896c 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -21,7 +21,7 @@ use function is_string; use function sprintf; -class VarTagTypeRuleHelper +final class VarTagTypeRuleHelper { public function __construct(private bool $checkTypeAgainstPhpDocType, private bool $strictWideningCheck) diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 031edf17433..9e4fc3ebf1b 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -31,7 +31,7 @@ /** * @implements Rule */ -class WrongVariableNameInVarTagRule implements Rule +final class WrongVariableNameInVarTagRule implements Rule { public function __construct( diff --git a/src/Rules/Playground/FunctionNeverRule.php b/src/Rules/Playground/FunctionNeverRule.php index 94171408dd4..84f6db5239a 100644 --- a/src/Rules/Playground/FunctionNeverRule.php +++ b/src/Rules/Playground/FunctionNeverRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class FunctionNeverRule implements Rule +final class FunctionNeverRule implements Rule { public function __construct(private NeverRuleHelper $helper) diff --git a/src/Rules/Playground/MethodNeverRule.php b/src/Rules/Playground/MethodNeverRule.php index 443a31112ff..d07066b4cc9 100644 --- a/src/Rules/Playground/MethodNeverRule.php +++ b/src/Rules/Playground/MethodNeverRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class MethodNeverRule implements Rule +final class MethodNeverRule implements Rule { public function __construct(private NeverRuleHelper $helper) diff --git a/src/Rules/Playground/NeverRuleHelper.php b/src/Rules/Playground/NeverRuleHelper.php index 9da99b88e90..520b4264249 100644 --- a/src/Rules/Playground/NeverRuleHelper.php +++ b/src/Rules/Playground/NeverRuleHelper.php @@ -7,7 +7,7 @@ use PHPStan\Type\NeverType; use PHPStan\Type\Type; -class NeverRuleHelper +final class NeverRuleHelper { /** diff --git a/src/Rules/Playground/NoPhpCodeRule.php b/src/Rules/Playground/NoPhpCodeRule.php index 2042177a2c2..c8d06460832 100644 --- a/src/Rules/Playground/NoPhpCodeRule.php +++ b/src/Rules/Playground/NoPhpCodeRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class NoPhpCodeRule implements Rule +final class NoPhpCodeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Playground/NotAnalysedTraitRule.php b/src/Rules/Playground/NotAnalysedTraitRule.php index 7efa8044424..c9ba6e0a24e 100644 --- a/src/Rules/Playground/NotAnalysedTraitRule.php +++ b/src/Rules/Playground/NotAnalysedTraitRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class NotAnalysedTraitRule implements Rule +final class NotAnalysedTraitRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php index d68fca010a1..548e176c1d4 100644 --- a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php +++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class AccessPrivatePropertyThroughStaticRule implements Rule +final class AccessPrivatePropertyThroughStaticRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Properties/AccessPropertiesInAssignRule.php b/src/Rules/Properties/AccessPropertiesInAssignRule.php index e5d3d08b4ed..5d7a9abcc4c 100644 --- a/src/Rules/Properties/AccessPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessPropertiesInAssignRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class AccessPropertiesInAssignRule implements Rule +final class AccessPropertiesInAssignRule implements Rule { public function __construct(private AccessPropertiesRule $accessPropertiesRule) diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 0a8d6cdbcff..773c715d04d 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -26,7 +26,7 @@ /** * @implements Rule */ -class AccessPropertiesRule implements Rule +final class AccessPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php b/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php index 21d6ff3e1ff..f9a6e616029 100644 --- a/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesInAssignRule.php @@ -10,7 +10,7 @@ /** * @implements Rule */ -class AccessStaticPropertiesInAssignRule implements Rule +final class AccessStaticPropertiesInAssignRule implements Rule { public function __construct(private AccessStaticPropertiesRule $accessStaticPropertiesRule) diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 9d929c39a2a..67a03643f9e 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -34,7 +34,7 @@ /** * @implements Rule */ -class AccessStaticPropertiesRule implements Rule +final class AccessStaticPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php index 8fd67c1e5fb..3076d2d3d86 100644 --- a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class DefaultValueTypesAssignedToPropertiesRule implements Rule +final class DefaultValueTypesAssignedToPropertiesRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php b/src/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php index 6ce75189d7f..0130c2f6336 100644 --- a/src/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php +++ b/src/Rules/Properties/DirectReadWritePropertiesExtensionProvider.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Properties; -class DirectReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider +final class DirectReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider { /** diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index 2b138d5bbd1..62678e3f141 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ExistingClassesInPropertiesRule implements Rule +final class ExistingClassesInPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 0132ac0d3bf..57577364cd3 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -10,7 +10,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -class FoundPropertyReflection implements PropertyReflection +final class FoundPropertyReflection implements PropertyReflection { public function __construct( diff --git a/src/Rules/Properties/InvalidCallablePropertyTypeRule.php b/src/Rules/Properties/InvalidCallablePropertyTypeRule.php index 2a8ed41bd89..4c4a2aa6554 100644 --- a/src/Rules/Properties/InvalidCallablePropertyTypeRule.php +++ b/src/Rules/Properties/InvalidCallablePropertyTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class InvalidCallablePropertyTypeRule implements Rule +final class InvalidCallablePropertyTypeRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php index 3f61fd0fb8f..f42cb7e7d5e 100644 --- a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php +++ b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider +final class LazyReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider { /** @var ReadWritePropertiesExtension[]|null */ diff --git a/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php index 72b30052e6b..bbb53f1089f 100644 --- a/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class MissingReadOnlyByPhpDocPropertyAssignRule implements Rule +final class MissingReadOnlyByPhpDocPropertyAssignRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php b/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php index 54d64878adf..de05aaaab95 100644 --- a/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/MissingReadOnlyPropertyAssignRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class MissingReadOnlyPropertyAssignRule implements Rule +final class MissingReadOnlyPropertyAssignRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/NullsafePropertyFetchRule.php b/src/Rules/Properties/NullsafePropertyFetchRule.php index 1195f470228..6b72cf6df7e 100644 --- a/src/Rules/Properties/NullsafePropertyFetchRule.php +++ b/src/Rules/Properties/NullsafePropertyFetchRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class NullsafePropertyFetchRule implements Rule +final class NullsafePropertyFetchRule implements Rule { public function __construct() diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index ca10cd44348..be6a9fca4f4 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class OverridingPropertyRule implements Rule +final class OverridingPropertyRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/PropertiesInInterfaceRule.php b/src/Rules/Properties/PropertiesInInterfaceRule.php index 35ce4c2c8c9..d9f62fa6187 100644 --- a/src/Rules/Properties/PropertiesInInterfaceRule.php +++ b/src/Rules/Properties/PropertiesInInterfaceRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class PropertiesInInterfaceRule implements Rule +final class PropertiesInInterfaceRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Properties/PropertyAttributesRule.php b/src/Rules/Properties/PropertyAttributesRule.php index b0d9cf2812b..375eb439cfe 100644 --- a/src/Rules/Properties/PropertyAttributesRule.php +++ b/src/Rules/Properties/PropertyAttributesRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class PropertyAttributesRule implements Rule +final class PropertyAttributesRule implements Rule { public function __construct(private AttributesCheck $attributesCheck) diff --git a/src/Rules/Properties/PropertyDescriptor.php b/src/Rules/Properties/PropertyDescriptor.php index e45011b3610..8588a1a57a7 100644 --- a/src/Rules/Properties/PropertyDescriptor.php +++ b/src/Rules/Properties/PropertyDescriptor.php @@ -9,7 +9,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class PropertyDescriptor +final class PropertyDescriptor { /** diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index 3f6cc8b000a..6cd33e10d58 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -11,7 +11,7 @@ use PHPStan\Type\Type; use function array_map; -class PropertyReflectionFinder +final class PropertyReflectionFinder { /** diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php index ce0b1d1f057..981415e915b 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ReadOnlyByPhpDocPropertyAssignRefRule implements Rule +final class ReadOnlyByPhpDocPropertyAssignRefRule implements Rule { public function __construct(private PropertyReflectionFinder $propertyReflectionFinder) diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index 9d72bb3895a..c71ce58bbcd 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ReadOnlyByPhpDocPropertyAssignRule implements Rule +final class ReadOnlyByPhpDocPropertyAssignRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyRule.php index a024d70b14c..93e7a0fb4d5 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ReadOnlyByPhpDocPropertyRule implements Rule +final class ReadOnlyByPhpDocPropertyRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php index b50e887e3b9..30f72336145 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ReadOnlyPropertyAssignRefRule implements Rule +final class ReadOnlyPropertyAssignRefRule implements Rule { public function __construct(private PropertyReflectionFinder $propertyReflectionFinder) diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php index 47d132b6399..2b5c7d010ad 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class ReadOnlyPropertyAssignRule implements Rule +final class ReadOnlyPropertyAssignRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/ReadOnlyPropertyRule.php b/src/Rules/Properties/ReadOnlyPropertyRule.php index a622964ed5c..5e777ae164b 100644 --- a/src/Rules/Properties/ReadOnlyPropertyRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ReadOnlyPropertyRule implements Rule +final class ReadOnlyPropertyRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php b/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php index 5e326c12a83..2d2ab20f6a8 100644 --- a/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php +++ b/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class ReadingWriteOnlyPropertiesRule implements Rule +final class ReadingWriteOnlyPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index 9e15b0b33fd..bb2c2e83c09 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class TypesAssignedToPropertiesRule implements Rule +final class TypesAssignedToPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/UninitializedPropertyRule.php b/src/Rules/Properties/UninitializedPropertyRule.php index a2995c38866..525a9ecbb69 100644 --- a/src/Rules/Properties/UninitializedPropertyRule.php +++ b/src/Rules/Properties/UninitializedPropertyRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class UninitializedPropertyRule implements Rule +final class UninitializedPropertyRule implements Rule { public function __construct( diff --git a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php index 5acc86ba91e..bfe8b1f7bf2 100644 --- a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php +++ b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class WritingToReadOnlyPropertiesRule implements Rule +final class WritingToReadOnlyPropertiesRule implements Rule { public function __construct( diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index 4e294dc0fe0..e70d2eb2927 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -20,7 +20,7 @@ use function lcfirst; use function sprintf; -class FunctionPurityCheck +final class FunctionPurityCheck { /** diff --git a/src/Rules/Pure/PureFunctionRule.php b/src/Rules/Pure/PureFunctionRule.php index 838e9bc67b1..2355503a93d 100644 --- a/src/Rules/Pure/PureFunctionRule.php +++ b/src/Rules/Pure/PureFunctionRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class PureFunctionRule implements Rule +final class PureFunctionRule implements Rule { public function __construct(private FunctionPurityCheck $check) diff --git a/src/Rules/Pure/PureMethodRule.php b/src/Rules/Pure/PureMethodRule.php index a0237201180..30685b4d6ea 100644 --- a/src/Rules/Pure/PureMethodRule.php +++ b/src/Rules/Pure/PureMethodRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class PureMethodRule implements Rule +final class PureMethodRule implements Rule { public function __construct(private FunctionPurityCheck $check) diff --git a/src/Rules/Regexp/RegularExpressionPatternRule.php b/src/Rules/Regexp/RegularExpressionPatternRule.php index d99fe4e4ec9..b19c3904bd5 100644 --- a/src/Rules/Regexp/RegularExpressionPatternRule.php +++ b/src/Rules/Regexp/RegularExpressionPatternRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class RegularExpressionPatternRule implements Rule +final class RegularExpressionPatternRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Regexp/RegularExpressionQuotingRule.php b/src/Rules/Regexp/RegularExpressionQuotingRule.php index 873209c00f6..4ffabf4ccdc 100644 --- a/src/Rules/Regexp/RegularExpressionQuotingRule.php +++ b/src/Rules/Regexp/RegularExpressionQuotingRule.php @@ -28,7 +28,7 @@ /** * @implements Rule */ -class RegularExpressionQuotingRule implements Rule +final class RegularExpressionQuotingRule implements Rule { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/RuleErrorBuilder.php b/src/Rules/RuleErrorBuilder.php index a8382008006..bf673c0fe83 100644 --- a/src/Rules/RuleErrorBuilder.php +++ b/src/Rules/RuleErrorBuilder.php @@ -13,6 +13,7 @@ /** * @api + * @final * @template-covariant T of RuleError */ class RuleErrorBuilder diff --git a/src/Rules/RuleErrors/RuleError1.php b/src/Rules/RuleErrors/RuleError1.php index ef7771dea36..c7a2338b301 100644 --- a/src/Rules/RuleErrors/RuleError1.php +++ b/src/Rules/RuleErrors/RuleError1.php @@ -7,7 +7,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError1 implements RuleError +final class RuleError1 implements RuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError101.php b/src/Rules/RuleErrors/RuleError101.php index cd6a40afe39..ff16ad53397 100644 --- a/src/Rules/RuleErrors/RuleError101.php +++ b/src/Rules/RuleErrors/RuleError101.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError101 implements RuleError, FileRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError101 implements RuleError, FileRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError103.php b/src/Rules/RuleErrors/RuleError103.php index a88780c2129..6c125de9b78 100644 --- a/src/Rules/RuleErrors/RuleError103.php +++ b/src/Rules/RuleErrors/RuleError103.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError103 implements RuleError, LineRuleError, FileRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError103 implements RuleError, LineRuleError, FileRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError105.php b/src/Rules/RuleErrors/RuleError105.php index 0fb7b8bc41a..a0b8945f522 100644 --- a/src/Rules/RuleErrors/RuleError105.php +++ b/src/Rules/RuleErrors/RuleError105.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError105 implements RuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError105 implements RuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError107.php b/src/Rules/RuleErrors/RuleError107.php index 35b081c092a..a0b9b85c84f 100644 --- a/src/Rules/RuleErrors/RuleError107.php +++ b/src/Rules/RuleErrors/RuleError107.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError107 implements RuleError, LineRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError107 implements RuleError, LineRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError109.php b/src/Rules/RuleErrors/RuleError109.php index 22ddff6f250..a4f81cce537 100644 --- a/src/Rules/RuleErrors/RuleError109.php +++ b/src/Rules/RuleErrors/RuleError109.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError109 implements RuleError, FileRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError109 implements RuleError, FileRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError11.php b/src/Rules/RuleErrors/RuleError11.php index 50d8bdb997a..be6bc0923a9 100644 --- a/src/Rules/RuleErrors/RuleError11.php +++ b/src/Rules/RuleErrors/RuleError11.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError11 implements RuleError, LineRuleError, TipRuleError +final class RuleError11 implements RuleError, LineRuleError, TipRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError111.php b/src/Rules/RuleErrors/RuleError111.php index 3024d5fdf67..ac0b980e01a 100644 --- a/src/Rules/RuleErrors/RuleError111.php +++ b/src/Rules/RuleErrors/RuleError111.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError111 implements RuleError, LineRuleError, FileRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError111 implements RuleError, LineRuleError, FileRuleError, TipRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError113.php b/src/Rules/RuleErrors/RuleError113.php index ee74c1f0ace..5d602a2fe5e 100644 --- a/src/Rules/RuleErrors/RuleError113.php +++ b/src/Rules/RuleErrors/RuleError113.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError113 implements RuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError113 implements RuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError115.php b/src/Rules/RuleErrors/RuleError115.php index 7b5f1af9e5b..f6d020a9e21 100644 --- a/src/Rules/RuleErrors/RuleError115.php +++ b/src/Rules/RuleErrors/RuleError115.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError115 implements RuleError, LineRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError115 implements RuleError, LineRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError117.php b/src/Rules/RuleErrors/RuleError117.php index 24928027991..80b8bd5fb25 100644 --- a/src/Rules/RuleErrors/RuleError117.php +++ b/src/Rules/RuleErrors/RuleError117.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError117 implements RuleError, FileRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError117 implements RuleError, FileRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError119.php b/src/Rules/RuleErrors/RuleError119.php index 6d6fb6b7a94..ddee0157522 100644 --- a/src/Rules/RuleErrors/RuleError119.php +++ b/src/Rules/RuleErrors/RuleError119.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError119 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError119 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError121.php b/src/Rules/RuleErrors/RuleError121.php index 2c8995a0b39..3a05d8d3c33 100644 --- a/src/Rules/RuleErrors/RuleError121.php +++ b/src/Rules/RuleErrors/RuleError121.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError121 implements RuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError121 implements RuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError123.php b/src/Rules/RuleErrors/RuleError123.php index fedd8de5d96..4bae22b6a51 100644 --- a/src/Rules/RuleErrors/RuleError123.php +++ b/src/Rules/RuleErrors/RuleError123.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError123 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError123 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError125.php b/src/Rules/RuleErrors/RuleError125.php index d64be7de954..a24ea70b44f 100644 --- a/src/Rules/RuleErrors/RuleError125.php +++ b/src/Rules/RuleErrors/RuleError125.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError125 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError125 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError127.php b/src/Rules/RuleErrors/RuleError127.php index dfb94ecc153..0c2dea58a73 100644 --- a/src/Rules/RuleErrors/RuleError127.php +++ b/src/Rules/RuleErrors/RuleError127.php @@ -13,7 +13,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError127 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError127 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError13.php b/src/Rules/RuleErrors/RuleError13.php index 3ecb8d62334..a606a29b872 100644 --- a/src/Rules/RuleErrors/RuleError13.php +++ b/src/Rules/RuleErrors/RuleError13.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError13 implements RuleError, FileRuleError, TipRuleError +final class RuleError13 implements RuleError, FileRuleError, TipRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError15.php b/src/Rules/RuleErrors/RuleError15.php index 956f7fe0f06..b952f9a3c50 100644 --- a/src/Rules/RuleErrors/RuleError15.php +++ b/src/Rules/RuleErrors/RuleError15.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError15 implements RuleError, LineRuleError, FileRuleError, TipRuleError +final class RuleError15 implements RuleError, LineRuleError, FileRuleError, TipRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError17.php b/src/Rules/RuleErrors/RuleError17.php index 827f6a87247..8cdf151a33c 100644 --- a/src/Rules/RuleErrors/RuleError17.php +++ b/src/Rules/RuleErrors/RuleError17.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError17 implements RuleError, IdentifierRuleError +final class RuleError17 implements RuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError19.php b/src/Rules/RuleErrors/RuleError19.php index 3732da9802a..d7a3a4388ba 100644 --- a/src/Rules/RuleErrors/RuleError19.php +++ b/src/Rules/RuleErrors/RuleError19.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError19 implements RuleError, LineRuleError, IdentifierRuleError +final class RuleError19 implements RuleError, LineRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError21.php b/src/Rules/RuleErrors/RuleError21.php index 3a6f7eb2d39..91516979e01 100644 --- a/src/Rules/RuleErrors/RuleError21.php +++ b/src/Rules/RuleErrors/RuleError21.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError21 implements RuleError, FileRuleError, IdentifierRuleError +final class RuleError21 implements RuleError, FileRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError23.php b/src/Rules/RuleErrors/RuleError23.php index 911a7a05fe0..4dcb3e0bae8 100644 --- a/src/Rules/RuleErrors/RuleError23.php +++ b/src/Rules/RuleErrors/RuleError23.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError23 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError +final class RuleError23 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError25.php b/src/Rules/RuleErrors/RuleError25.php index 1c5c6001e8d..429a1ed0efc 100644 --- a/src/Rules/RuleErrors/RuleError25.php +++ b/src/Rules/RuleErrors/RuleError25.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError25 implements RuleError, TipRuleError, IdentifierRuleError +final class RuleError25 implements RuleError, TipRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError27.php b/src/Rules/RuleErrors/RuleError27.php index e592c0d98e2..6910c787fd2 100644 --- a/src/Rules/RuleErrors/RuleError27.php +++ b/src/Rules/RuleErrors/RuleError27.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError27 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError +final class RuleError27 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError29.php b/src/Rules/RuleErrors/RuleError29.php index 68f71ae5c7c..85a6c859604 100644 --- a/src/Rules/RuleErrors/RuleError29.php +++ b/src/Rules/RuleErrors/RuleError29.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError29 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError +final class RuleError29 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError3.php b/src/Rules/RuleErrors/RuleError3.php index ce5c8fffcc8..17ab507d2a0 100644 --- a/src/Rules/RuleErrors/RuleError3.php +++ b/src/Rules/RuleErrors/RuleError3.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError3 implements RuleError, LineRuleError +final class RuleError3 implements RuleError, LineRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError31.php b/src/Rules/RuleErrors/RuleError31.php index 6402df18874..d9e7665e9b8 100644 --- a/src/Rules/RuleErrors/RuleError31.php +++ b/src/Rules/RuleErrors/RuleError31.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError31 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError +final class RuleError31 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError33.php b/src/Rules/RuleErrors/RuleError33.php index 0f37cede7c8..692da9a71a6 100644 --- a/src/Rules/RuleErrors/RuleError33.php +++ b/src/Rules/RuleErrors/RuleError33.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError33 implements RuleError, MetadataRuleError +final class RuleError33 implements RuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError35.php b/src/Rules/RuleErrors/RuleError35.php index 65868071a83..91c52036bbd 100644 --- a/src/Rules/RuleErrors/RuleError35.php +++ b/src/Rules/RuleErrors/RuleError35.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError35 implements RuleError, LineRuleError, MetadataRuleError +final class RuleError35 implements RuleError, LineRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError37.php b/src/Rules/RuleErrors/RuleError37.php index fe0d25a3f57..a92ded0e4f6 100644 --- a/src/Rules/RuleErrors/RuleError37.php +++ b/src/Rules/RuleErrors/RuleError37.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError37 implements RuleError, FileRuleError, MetadataRuleError +final class RuleError37 implements RuleError, FileRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError39.php b/src/Rules/RuleErrors/RuleError39.php index 1a50b299fc3..7b748007533 100644 --- a/src/Rules/RuleErrors/RuleError39.php +++ b/src/Rules/RuleErrors/RuleError39.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError39 implements RuleError, LineRuleError, FileRuleError, MetadataRuleError +final class RuleError39 implements RuleError, LineRuleError, FileRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError41.php b/src/Rules/RuleErrors/RuleError41.php index 528a20c7317..7cc55fdeb15 100644 --- a/src/Rules/RuleErrors/RuleError41.php +++ b/src/Rules/RuleErrors/RuleError41.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError41 implements RuleError, TipRuleError, MetadataRuleError +final class RuleError41 implements RuleError, TipRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError43.php b/src/Rules/RuleErrors/RuleError43.php index 9992c86c9d3..a251840dd91 100644 --- a/src/Rules/RuleErrors/RuleError43.php +++ b/src/Rules/RuleErrors/RuleError43.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError43 implements RuleError, LineRuleError, TipRuleError, MetadataRuleError +final class RuleError43 implements RuleError, LineRuleError, TipRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError45.php b/src/Rules/RuleErrors/RuleError45.php index d77f882c505..aceabd9f786 100644 --- a/src/Rules/RuleErrors/RuleError45.php +++ b/src/Rules/RuleErrors/RuleError45.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError45 implements RuleError, FileRuleError, TipRuleError, MetadataRuleError +final class RuleError45 implements RuleError, FileRuleError, TipRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError47.php b/src/Rules/RuleErrors/RuleError47.php index 3a1158c1761..866bbc3ddff 100644 --- a/src/Rules/RuleErrors/RuleError47.php +++ b/src/Rules/RuleErrors/RuleError47.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError47 implements RuleError, LineRuleError, FileRuleError, TipRuleError, MetadataRuleError +final class RuleError47 implements RuleError, LineRuleError, FileRuleError, TipRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError49.php b/src/Rules/RuleErrors/RuleError49.php index 6b8aa73a5bd..81b0015029d 100644 --- a/src/Rules/RuleErrors/RuleError49.php +++ b/src/Rules/RuleErrors/RuleError49.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError49 implements RuleError, IdentifierRuleError, MetadataRuleError +final class RuleError49 implements RuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError5.php b/src/Rules/RuleErrors/RuleError5.php index f8205fd24f7..0dbad8299ba 100644 --- a/src/Rules/RuleErrors/RuleError5.php +++ b/src/Rules/RuleErrors/RuleError5.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError5 implements RuleError, FileRuleError +final class RuleError5 implements RuleError, FileRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError51.php b/src/Rules/RuleErrors/RuleError51.php index a008143cd26..96d93510c9e 100644 --- a/src/Rules/RuleErrors/RuleError51.php +++ b/src/Rules/RuleErrors/RuleError51.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError51 implements RuleError, LineRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError51 implements RuleError, LineRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError53.php b/src/Rules/RuleErrors/RuleError53.php index cd8418f5b26..1e11f5e6410 100644 --- a/src/Rules/RuleErrors/RuleError53.php +++ b/src/Rules/RuleErrors/RuleError53.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError53 implements RuleError, FileRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError53 implements RuleError, FileRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError55.php b/src/Rules/RuleErrors/RuleError55.php index 4eb281839d1..3bf4a22ccf7 100644 --- a/src/Rules/RuleErrors/RuleError55.php +++ b/src/Rules/RuleErrors/RuleError55.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError55 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError55 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError57.php b/src/Rules/RuleErrors/RuleError57.php index 4fabd64eac3..22c77fc545d 100644 --- a/src/Rules/RuleErrors/RuleError57.php +++ b/src/Rules/RuleErrors/RuleError57.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError57 implements RuleError, TipRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError57 implements RuleError, TipRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError59.php b/src/Rules/RuleErrors/RuleError59.php index 837c62602d9..a7659febe12 100644 --- a/src/Rules/RuleErrors/RuleError59.php +++ b/src/Rules/RuleErrors/RuleError59.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError59 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError59 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError61.php b/src/Rules/RuleErrors/RuleError61.php index a861ab2f51d..723a0aa79b4 100644 --- a/src/Rules/RuleErrors/RuleError61.php +++ b/src/Rules/RuleErrors/RuleError61.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError61 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError61 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError63.php b/src/Rules/RuleErrors/RuleError63.php index 919587a2166..1c88f9fbc28 100644 --- a/src/Rules/RuleErrors/RuleError63.php +++ b/src/Rules/RuleErrors/RuleError63.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError63 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError +final class RuleError63 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, MetadataRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError65.php b/src/Rules/RuleErrors/RuleError65.php index 095f49475db..fc2593bbfa1 100644 --- a/src/Rules/RuleErrors/RuleError65.php +++ b/src/Rules/RuleErrors/RuleError65.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError65 implements RuleError, NonIgnorableRuleError +final class RuleError65 implements RuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError67.php b/src/Rules/RuleErrors/RuleError67.php index 08a214f997e..b2218268c3b 100644 --- a/src/Rules/RuleErrors/RuleError67.php +++ b/src/Rules/RuleErrors/RuleError67.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError67 implements RuleError, LineRuleError, NonIgnorableRuleError +final class RuleError67 implements RuleError, LineRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError69.php b/src/Rules/RuleErrors/RuleError69.php index 75cd512c3ec..7f5e130f095 100644 --- a/src/Rules/RuleErrors/RuleError69.php +++ b/src/Rules/RuleErrors/RuleError69.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError69 implements RuleError, FileRuleError, NonIgnorableRuleError +final class RuleError69 implements RuleError, FileRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError7.php b/src/Rules/RuleErrors/RuleError7.php index af9559cfaa7..203696b2fd4 100644 --- a/src/Rules/RuleErrors/RuleError7.php +++ b/src/Rules/RuleErrors/RuleError7.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError7 implements RuleError, LineRuleError, FileRuleError +final class RuleError7 implements RuleError, LineRuleError, FileRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError71.php b/src/Rules/RuleErrors/RuleError71.php index 652b0f1922a..d78d2813e3f 100644 --- a/src/Rules/RuleErrors/RuleError71.php +++ b/src/Rules/RuleErrors/RuleError71.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError71 implements RuleError, LineRuleError, FileRuleError, NonIgnorableRuleError +final class RuleError71 implements RuleError, LineRuleError, FileRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError73.php b/src/Rules/RuleErrors/RuleError73.php index 8cdaeaa36dd..fd81121a2dd 100644 --- a/src/Rules/RuleErrors/RuleError73.php +++ b/src/Rules/RuleErrors/RuleError73.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError73 implements RuleError, TipRuleError, NonIgnorableRuleError +final class RuleError73 implements RuleError, TipRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError75.php b/src/Rules/RuleErrors/RuleError75.php index 3195db74542..d249eef75e0 100644 --- a/src/Rules/RuleErrors/RuleError75.php +++ b/src/Rules/RuleErrors/RuleError75.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError75 implements RuleError, LineRuleError, TipRuleError, NonIgnorableRuleError +final class RuleError75 implements RuleError, LineRuleError, TipRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError77.php b/src/Rules/RuleErrors/RuleError77.php index 09edd26a3de..e0e8547a32f 100644 --- a/src/Rules/RuleErrors/RuleError77.php +++ b/src/Rules/RuleErrors/RuleError77.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError77 implements RuleError, FileRuleError, TipRuleError, NonIgnorableRuleError +final class RuleError77 implements RuleError, FileRuleError, TipRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError79.php b/src/Rules/RuleErrors/RuleError79.php index 3c1fcf4d236..3a07eea3962 100644 --- a/src/Rules/RuleErrors/RuleError79.php +++ b/src/Rules/RuleErrors/RuleError79.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError79 implements RuleError, LineRuleError, FileRuleError, TipRuleError, NonIgnorableRuleError +final class RuleError79 implements RuleError, LineRuleError, FileRuleError, TipRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError81.php b/src/Rules/RuleErrors/RuleError81.php index 1412335a8a5..fe96c09839e 100644 --- a/src/Rules/RuleErrors/RuleError81.php +++ b/src/Rules/RuleErrors/RuleError81.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError81 implements RuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError81 implements RuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError83.php b/src/Rules/RuleErrors/RuleError83.php index ceb21414561..9570715ebef 100644 --- a/src/Rules/RuleErrors/RuleError83.php +++ b/src/Rules/RuleErrors/RuleError83.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError83 implements RuleError, LineRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError83 implements RuleError, LineRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError85.php b/src/Rules/RuleErrors/RuleError85.php index a0d13d45def..5af535902ae 100644 --- a/src/Rules/RuleErrors/RuleError85.php +++ b/src/Rules/RuleErrors/RuleError85.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError85 implements RuleError, FileRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError85 implements RuleError, FileRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError87.php b/src/Rules/RuleErrors/RuleError87.php index 386b844a1be..44028fe427b 100644 --- a/src/Rules/RuleErrors/RuleError87.php +++ b/src/Rules/RuleErrors/RuleError87.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError87 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError87 implements RuleError, LineRuleError, FileRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError89.php b/src/Rules/RuleErrors/RuleError89.php index 3b207cf2ade..e69b4058a63 100644 --- a/src/Rules/RuleErrors/RuleError89.php +++ b/src/Rules/RuleErrors/RuleError89.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError89 implements RuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError89 implements RuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError9.php b/src/Rules/RuleErrors/RuleError9.php index e93934d20b9..c8454faf62c 100644 --- a/src/Rules/RuleErrors/RuleError9.php +++ b/src/Rules/RuleErrors/RuleError9.php @@ -8,7 +8,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError9 implements RuleError, TipRuleError +final class RuleError9 implements RuleError, TipRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError91.php b/src/Rules/RuleErrors/RuleError91.php index a0fc78df5ad..8c11c1816f9 100644 --- a/src/Rules/RuleErrors/RuleError91.php +++ b/src/Rules/RuleErrors/RuleError91.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError91 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError91 implements RuleError, LineRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError93.php b/src/Rules/RuleErrors/RuleError93.php index 88b7282eb29..8c5b9c5a64b 100644 --- a/src/Rules/RuleErrors/RuleError93.php +++ b/src/Rules/RuleErrors/RuleError93.php @@ -11,7 +11,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError93 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError93 implements RuleError, FileRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError95.php b/src/Rules/RuleErrors/RuleError95.php index 0fbb2a635b5..68b993db00c 100644 --- a/src/Rules/RuleErrors/RuleError95.php +++ b/src/Rules/RuleErrors/RuleError95.php @@ -12,7 +12,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError95 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError +final class RuleError95 implements RuleError, LineRuleError, FileRuleError, TipRuleError, IdentifierRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError97.php b/src/Rules/RuleErrors/RuleError97.php index da079022332..da13e04d888 100644 --- a/src/Rules/RuleErrors/RuleError97.php +++ b/src/Rules/RuleErrors/RuleError97.php @@ -9,7 +9,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError97 implements RuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError97 implements RuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleErrors/RuleError99.php b/src/Rules/RuleErrors/RuleError99.php index b26457a1e16..60c3af565fc 100644 --- a/src/Rules/RuleErrors/RuleError99.php +++ b/src/Rules/RuleErrors/RuleError99.php @@ -10,7 +10,7 @@ /** * @internal Use PHPStan\Rules\RuleErrorBuilder instead. */ -class RuleError99 implements RuleError, LineRuleError, MetadataRuleError, NonIgnorableRuleError +final class RuleError99 implements RuleError, LineRuleError, MetadataRuleError, NonIgnorableRuleError { public string $message; diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index c5a08ba5abe..80ef4bccdc1 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -28,7 +28,7 @@ use function sprintf; use function str_contains; -class RuleLevelHelper +final class RuleLevelHelper { public function __construct( diff --git a/src/Rules/RuleLevelHelperAcceptsResult.php b/src/Rules/RuleLevelHelperAcceptsResult.php index 201408f8f5b..e33db8f0dab 100644 --- a/src/Rules/RuleLevelHelperAcceptsResult.php +++ b/src/Rules/RuleLevelHelperAcceptsResult.php @@ -4,7 +4,7 @@ use function array_merge; -class RuleLevelHelperAcceptsResult +final class RuleLevelHelperAcceptsResult { /** diff --git a/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php index 2b5c809341a..74c5328ba42 100644 --- a/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class TooWideArrowFunctionReturnTypehintRule implements Rule +final class TooWideArrowFunctionReturnTypehintRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php index 77879f99a38..bf49b027655 100644 --- a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php @@ -16,7 +16,7 @@ /** * @implements Rule */ -class TooWideClosureReturnTypehintRule implements Rule +final class TooWideClosureReturnTypehintRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php b/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php index 8056f39420c..6c62fea6885 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class TooWideFunctionParameterOutTypeRule implements Rule +final class TooWideFunctionParameterOutTypeRule implements Rule { public function __construct( diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 9451787f4df..4f4aedec6db 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class TooWideFunctionReturnTypehintRule implements Rule +final class TooWideFunctionReturnTypehintRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php index e715ce7d917..b404ff34646 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php @@ -12,7 +12,7 @@ /** * @implements Rule */ -class TooWideMethodParameterOutTypeRule implements Rule +final class TooWideMethodParameterOutTypeRule implements Rule { public function __construct( diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 5e62501f724..0c74e65a023 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class TooWideMethodReturnTypehintRule implements Rule +final class TooWideMethodReturnTypehintRule implements Rule { public function __construct(private bool $checkProtectedAndPublicMethods, private bool $alwaysCheckFinal) diff --git a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php index 097812f918d..ceeb0712389 100644 --- a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php @@ -14,7 +14,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class TooWideParameterOutTypeCheck +final class TooWideParameterOutTypeCheck { /** diff --git a/src/Rules/Traits/ConflictingTraitConstantsRule.php b/src/Rules/Traits/ConflictingTraitConstantsRule.php index 73612f30747..4dd23f32bd0 100644 --- a/src/Rules/Traits/ConflictingTraitConstantsRule.php +++ b/src/Rules/Traits/ConflictingTraitConstantsRule.php @@ -19,7 +19,7 @@ /** * @implements Rule */ -class ConflictingTraitConstantsRule implements Rule +final class ConflictingTraitConstantsRule implements Rule { public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) diff --git a/src/Rules/Traits/ConstantsInTraitsRule.php b/src/Rules/Traits/ConstantsInTraitsRule.php index 69485685401..177af08c6ef 100644 --- a/src/Rules/Traits/ConstantsInTraitsRule.php +++ b/src/Rules/Traits/ConstantsInTraitsRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class ConstantsInTraitsRule implements Rule +final class ConstantsInTraitsRule implements Rule { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Rules/Traits/NotAnalysedTraitRule.php b/src/Rules/Traits/NotAnalysedTraitRule.php index 3ae9b74c461..6abc321550a 100644 --- a/src/Rules/Traits/NotAnalysedTraitRule.php +++ b/src/Rules/Traits/NotAnalysedTraitRule.php @@ -13,7 +13,7 @@ /** * @implements Rule */ -class NotAnalysedTraitRule implements Rule +final class NotAnalysedTraitRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Traits/TraitDeclarationCollector.php b/src/Rules/Traits/TraitDeclarationCollector.php index 7ce2cee84d5..5ccb33a7363 100644 --- a/src/Rules/Traits/TraitDeclarationCollector.php +++ b/src/Rules/Traits/TraitDeclarationCollector.php @@ -9,7 +9,7 @@ /** * @implements Collector */ -class TraitDeclarationCollector implements Collector +final class TraitDeclarationCollector implements Collector { public function getNodeType(): string diff --git a/src/Rules/Traits/TraitUseCollector.php b/src/Rules/Traits/TraitUseCollector.php index 1bd3f82cba3..cd97f3b9b44 100644 --- a/src/Rules/Traits/TraitUseCollector.php +++ b/src/Rules/Traits/TraitUseCollector.php @@ -11,7 +11,7 @@ /** * @implements Collector> */ -class TraitUseCollector implements Collector +final class TraitUseCollector implements Collector { public function getNodeType(): string @@ -19,7 +19,10 @@ public function getNodeType(): string return Node\Stmt\TraitUse::class; } - public function processNode(Node $node, Scope $scope) + /** + * @return list + */ + public function processNode(Node $node, Scope $scope): array { return array_values(array_map(static fn (Node\Name $traitName) => $traitName->toString(), $node->traits)); } diff --git a/src/Rules/Types/InvalidTypesInUnionRule.php b/src/Rules/Types/InvalidTypesInUnionRule.php index 88442c2bde3..ba531117608 100644 --- a/src/Rules/Types/InvalidTypesInUnionRule.php +++ b/src/Rules/Types/InvalidTypesInUnionRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class InvalidTypesInUnionRule implements Rule +final class InvalidTypesInUnionRule implements Rule { private const ONLY_STANDALONE_TYPES = [ diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 8da2427d31c..b85150e267f 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -13,7 +13,7 @@ use function is_string; use function sprintf; -class UnusedFunctionParametersCheck +final class UnusedFunctionParametersCheck { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Rules/Variables/DefinedVariableRule.php b/src/Rules/Variables/DefinedVariableRule.php index fbe15dcfbce..fc665ed9011 100644 --- a/src/Rules/Variables/DefinedVariableRule.php +++ b/src/Rules/Variables/DefinedVariableRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class DefinedVariableRule implements Rule +final class DefinedVariableRule implements Rule { public function __construct( diff --git a/src/Rules/Variables/EmptyRule.php b/src/Rules/Variables/EmptyRule.php index ca588bd7111..12d3fadf59b 100644 --- a/src/Rules/Variables/EmptyRule.php +++ b/src/Rules/Variables/EmptyRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class EmptyRule implements Rule +final class EmptyRule implements Rule { public function __construct(private IssetCheck $issetCheck) diff --git a/src/Rules/Variables/IssetRule.php b/src/Rules/Variables/IssetRule.php index f5c59363fff..69ed2634792 100644 --- a/src/Rules/Variables/IssetRule.php +++ b/src/Rules/Variables/IssetRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class IssetRule implements Rule +final class IssetRule implements Rule { public function __construct(private IssetCheck $issetCheck) diff --git a/src/Rules/Variables/NullCoalesceRule.php b/src/Rules/Variables/NullCoalesceRule.php index ef289640e38..563bec59f7f 100644 --- a/src/Rules/Variables/NullCoalesceRule.php +++ b/src/Rules/Variables/NullCoalesceRule.php @@ -11,7 +11,7 @@ /** * @implements Rule */ -class NullCoalesceRule implements Rule +final class NullCoalesceRule implements Rule { public function __construct(private IssetCheck $issetCheck) diff --git a/src/Rules/Variables/ParameterOutAssignedTypeRule.php b/src/Rules/Variables/ParameterOutAssignedTypeRule.php index 9c24597525d..0d4487d2173 100644 --- a/src/Rules/Variables/ParameterOutAssignedTypeRule.php +++ b/src/Rules/Variables/ParameterOutAssignedTypeRule.php @@ -20,7 +20,7 @@ /** * @implements Rule */ -class ParameterOutAssignedTypeRule implements Rule +final class ParameterOutAssignedTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php index 648781ccc5d..177079ac6cc 100644 --- a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php +++ b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php @@ -24,7 +24,7 @@ /** * @implements Rule */ -class ParameterOutExecutionEndTypeRule implements Rule +final class ParameterOutExecutionEndTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Variables/ThrowTypeRule.php b/src/Rules/Variables/ThrowTypeRule.php index 7678406a9ed..03a80e73bf8 100644 --- a/src/Rules/Variables/ThrowTypeRule.php +++ b/src/Rules/Variables/ThrowTypeRule.php @@ -17,7 +17,7 @@ /** * @implements Rule */ -class ThrowTypeRule implements Rule +final class ThrowTypeRule implements Rule { public function __construct( diff --git a/src/Rules/Variables/UnsetRule.php b/src/Rules/Variables/UnsetRule.php index 43efa6646c2..dc75eb024ec 100644 --- a/src/Rules/Variables/UnsetRule.php +++ b/src/Rules/Variables/UnsetRule.php @@ -14,7 +14,7 @@ /** * @implements Rule */ -class UnsetRule implements Rule +final class UnsetRule implements Rule { public function getNodeType(): string diff --git a/src/Rules/Variables/VariableCloningRule.php b/src/Rules/Variables/VariableCloningRule.php index 89aca44aa98..da72e99ebe6 100644 --- a/src/Rules/Variables/VariableCloningRule.php +++ b/src/Rules/Variables/VariableCloningRule.php @@ -18,7 +18,7 @@ /** * @implements Rule */ -class VariableCloningRule implements Rule +final class VariableCloningRule implements Rule { public function __construct(private RuleLevelHelper $ruleLevelHelper) diff --git a/src/Rules/Whitespace/FileWhitespaceRule.php b/src/Rules/Whitespace/FileWhitespaceRule.php index 7f3bcbdcfe1..d234baa6b1d 100644 --- a/src/Rules/Whitespace/FileWhitespaceRule.php +++ b/src/Rules/Whitespace/FileWhitespaceRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -class FileWhitespaceRule implements Rule +final class FileWhitespaceRule implements Rule { public function getNodeType(): string diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index fccedd9ec10..a4921c9201a 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -23,7 +23,7 @@ use function serialize; use function sha1; -class TestCaseSourceLocatorFactory +final class TestCaseSourceLocatorFactory { /** @var array> */ diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index df4e9f5218a..7e387901e04 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -10,6 +10,7 @@ /** * @api + * @final * @see https://phpstan.org/developing-extensions/trinary-logic */ class TrinaryLogic diff --git a/src/Type/AcceptsResult.php b/src/Type/AcceptsResult.php index e6e5df0f9bf..4cfecb05f60 100644 --- a/src/Type/AcceptsResult.php +++ b/src/Type/AcceptsResult.php @@ -9,7 +9,10 @@ use function array_unique; use function array_values; -/** @api */ +/** + * @api + * @final + */ class AcceptsResult { diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index eb58de74bb8..29f416d3aa4 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -9,7 +9,7 @@ use function count; use function sprintf; -class CallableTypeHelper +final class CallableTypeHelper { public static function isParametersAcceptorSuperTypeOf( diff --git a/src/Type/CircularTypeAliasDefinitionException.php b/src/Type/CircularTypeAliasDefinitionException.php index ad4bbfa25e0..d0502cb9a1b 100644 --- a/src/Type/CircularTypeAliasDefinitionException.php +++ b/src/Type/CircularTypeAliasDefinitionException.php @@ -4,7 +4,7 @@ use Exception; -class CircularTypeAliasDefinitionException extends Exception +final class CircularTypeAliasDefinitionException extends Exception { } diff --git a/src/Type/ClosureTypeFactory.php b/src/Type/ClosureTypeFactory.php index 07a395a5fef..a0e82946ff5 100644 --- a/src/Type/ClosureTypeFactory.php +++ b/src/Type/ClosureTypeFactory.php @@ -23,7 +23,10 @@ use function count; use function str_replace; -/** @api */ +/** + * @api + * @final + */ class ClosureTypeFactory { diff --git a/src/Type/Constant/ConstantArrayTypeAndMethod.php b/src/Type/Constant/ConstantArrayTypeAndMethod.php index e5bd1caf7b3..01e735d949b 100644 --- a/src/Type/Constant/ConstantArrayTypeAndMethod.php +++ b/src/Type/Constant/ConstantArrayTypeAndMethod.php @@ -6,7 +6,10 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -/** @api */ +/** + * @api + * @final + */ class ConstantArrayTypeAndMethod { diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 8547fa39a92..525b65caed1 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -22,7 +22,10 @@ use function min; use function range; -/** @api */ +/** + * @api + * @final + */ class ConstantArrayTypeBuilder { diff --git a/src/Type/Constant/OversizedArrayBuilder.php b/src/Type/Constant/OversizedArrayBuilder.php index 663f1c15130..02e1115e1db 100644 --- a/src/Type/Constant/OversizedArrayBuilder.php +++ b/src/Type/Constant/OversizedArrayBuilder.php @@ -18,7 +18,7 @@ use function array_values; use function count; -class OversizedArrayBuilder +final class OversizedArrayBuilder { /** diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 2a1f4cd4f7f..48cc18025b9 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -20,7 +20,10 @@ use function is_object; use function is_string; -/** @api */ +/** + * @api + * @final + */ class ConstantTypeHelper { diff --git a/src/Type/DirectTypeAliasResolverProvider.php b/src/Type/DirectTypeAliasResolverProvider.php index b64fe8f46da..f7fa61c09e4 100644 --- a/src/Type/DirectTypeAliasResolverProvider.php +++ b/src/Type/DirectTypeAliasResolverProvider.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class DirectTypeAliasResolverProvider implements TypeAliasResolverProvider +final class DirectTypeAliasResolverProvider implements TypeAliasResolverProvider { public function __construct(private TypeAliasResolver $typeAliasResolver) diff --git a/src/Type/DynamicReturnTypeExtensionRegistry.php b/src/Type/DynamicReturnTypeExtensionRegistry.php index ea3c27a13c6..002a87f66e0 100644 --- a/src/Type/DynamicReturnTypeExtensionRegistry.php +++ b/src/Type/DynamicReturnTypeExtensionRegistry.php @@ -8,7 +8,7 @@ use function array_merge; use function strtolower; -class DynamicReturnTypeExtensionRegistry +final class DynamicReturnTypeExtensionRegistry { /** @var DynamicMethodReturnTypeExtension[][]|null */ diff --git a/src/Type/ExpressionTypeResolverExtensionRegistry.php b/src/Type/ExpressionTypeResolverExtensionRegistry.php index 75c0d0b5ec9..1d9dc436c07 100644 --- a/src/Type/ExpressionTypeResolverExtensionRegistry.php +++ b/src/Type/ExpressionTypeResolverExtensionRegistry.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class ExpressionTypeResolverExtensionRegistry +final class ExpressionTypeResolverExtensionRegistry { /** diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 307be3446f6..2ba6b7ae90e 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -38,7 +38,7 @@ use function str_contains; use function strtolower; -class FileTypeMapper +final class FileTypeMapper { private const SKIP_NODE = 1; diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php index 0cce10ed32b..d69e030e2de 100644 --- a/src/Type/GeneralizePrecision.php +++ b/src/Type/GeneralizePrecision.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class GeneralizePrecision +final class GeneralizePrecision { private const LESS_SPECIFIC = 1; diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index 414ffc12aad..deeb9b6ddd0 100644 --- a/src/Type/Generic/TemplateTypeArgumentStrategy.php +++ b/src/Type/Generic/TemplateTypeArgumentStrategy.php @@ -12,7 +12,7 @@ /** * Template type strategy suitable for return type acceptance contexts */ -class TemplateTypeArgumentStrategy implements TemplateTypeStrategy +final class TemplateTypeArgumentStrategy implements TemplateTypeStrategy { public function accepts(TemplateType $left, Type $right, bool $strictTypes): AcceptsResult diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 166464a884d..a38d6565564 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -10,7 +10,7 @@ use PHPStan\Type\TypeTraverser; use PHPStan\Type\VerbosityLevel; -class TemplateTypeHelper +final class TemplateTypeHelper { /** diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index f807e3d65e2..00fbc34a1dc 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -9,7 +9,10 @@ use function array_key_exists; use function count; -/** @api */ +/** + * @api + * @final + */ class TemplateTypeMap { diff --git a/src/Type/Generic/TemplateTypeParameterStrategy.php b/src/Type/Generic/TemplateTypeParameterStrategy.php index 5ad77936653..1ec43f153af 100644 --- a/src/Type/Generic/TemplateTypeParameterStrategy.php +++ b/src/Type/Generic/TemplateTypeParameterStrategy.php @@ -9,7 +9,7 @@ /** * Template type strategy suitable for parameter type acceptance contexts */ -class TemplateTypeParameterStrategy implements TemplateTypeStrategy +final class TemplateTypeParameterStrategy implements TemplateTypeStrategy { public function accepts(TemplateType $left, Type $right, bool $strictTypes): AcceptsResult diff --git a/src/Type/Generic/TemplateTypeReference.php b/src/Type/Generic/TemplateTypeReference.php index 260abd3d939..0be67d5e08c 100644 --- a/src/Type/Generic/TemplateTypeReference.php +++ b/src/Type/Generic/TemplateTypeReference.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Generic; -class TemplateTypeReference +final class TemplateTypeReference { public function __construct(private TemplateType $type, private TemplateTypeVariance $positionVariance) diff --git a/src/Type/Generic/TemplateTypeScope.php b/src/Type/Generic/TemplateTypeScope.php index e40eab90d31..8cc0e54e5e0 100644 --- a/src/Type/Generic/TemplateTypeScope.php +++ b/src/Type/Generic/TemplateTypeScope.php @@ -4,7 +4,7 @@ use function sprintf; -class TemplateTypeScope +final class TemplateTypeScope { public static function createWithAnonymousFunction(): self diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index a3ab7a04bd2..786c61302c6 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -12,7 +12,10 @@ use PHPStan\Type\Type; use function sprintf; -/** @api */ +/** + * @api + * @final + */ class TemplateTypeVariance { diff --git a/src/Type/Generic/TemplateTypeVarianceMap.php b/src/Type/Generic/TemplateTypeVarianceMap.php index 55d3a18aa31..bcbb23ea424 100644 --- a/src/Type/Generic/TemplateTypeVarianceMap.php +++ b/src/Type/Generic/TemplateTypeVarianceMap.php @@ -4,7 +4,10 @@ use function array_key_exists; -/** @api */ +/** + * @api + * @final + */ class TemplateTypeVarianceMap { diff --git a/src/Type/Generic/TypeProjectionHelper.php b/src/Type/Generic/TypeProjectionHelper.php index 217103bd099..c311cde2f8a 100644 --- a/src/Type/Generic/TypeProjectionHelper.php +++ b/src/Type/Generic/TypeProjectionHelper.php @@ -6,7 +6,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; -class TypeProjectionHelper +final class TypeProjectionHelper { public static function describe( diff --git a/src/Type/GenericTypeVariableResolver.php b/src/Type/GenericTypeVariableResolver.php index 4f13c5eec97..8c3f7d2da58 100644 --- a/src/Type/GenericTypeVariableResolver.php +++ b/src/Type/GenericTypeVariableResolver.php @@ -4,7 +4,10 @@ use PHPStan\Type\Generic\TemplateTypeHelper; -/** @api */ +/** + * @api + * @final + */ class GenericTypeVariableResolver { diff --git a/src/Type/LazyTypeAliasResolverProvider.php b/src/Type/LazyTypeAliasResolverProvider.php index d270e691f27..0b0edeee19b 100644 --- a/src/Type/LazyTypeAliasResolverProvider.php +++ b/src/Type/LazyTypeAliasResolverProvider.php @@ -4,7 +4,7 @@ use PHPStan\DependencyInjection\Container; -class LazyTypeAliasResolverProvider implements TypeAliasResolverProvider +final class LazyTypeAliasResolverProvider implements TypeAliasResolverProvider { public function __construct(private Container $container) diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index a79f9244176..e7132ecfb39 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -8,7 +8,7 @@ use PHPStan\TrinaryLogic; use stdClass; -class ObjectShapePropertyReflection implements PropertyReflection +final class ObjectShapePropertyReflection implements PropertyReflection { public function __construct(private Type $type) diff --git a/src/Type/OperatorTypeSpecifyingExtensionRegistry.php b/src/Type/OperatorTypeSpecifyingExtensionRegistry.php index 654d7a1ea8e..809ba5bd202 100644 --- a/src/Type/OperatorTypeSpecifyingExtensionRegistry.php +++ b/src/Type/OperatorTypeSpecifyingExtensionRegistry.php @@ -7,7 +7,7 @@ use function array_filter; use function array_values; -class OperatorTypeSpecifyingExtensionRegistry +final class OperatorTypeSpecifyingExtensionRegistry { /** diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index 7299a08100b..e616fffc0ed 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -13,7 +13,7 @@ use function in_array; use function strtolower; -class ParserNodeTypeToPHPStanType +final class ParserNodeTypeToPHPStanType { /** diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index c4ab9e8b9c3..b2508600f43 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -13,7 +13,7 @@ use PHPStan\Type\TypeCombinator; use function array_key_exists; -class ArgumentBasedFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArgumentBasedFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const FUNCTION_NAMES = [ diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index a7edf8777c9..7bca91ccd97 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -21,7 +21,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php index 62e1d8435b7..3ca26e156e4 100644 --- a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php @@ -22,7 +22,7 @@ use PHPStan\Type\UnionType; use function count; -class ArrayCombineFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayCombineFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php index 3ca47235f87..9871a469c97 100644 --- a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayCurrentDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayCurrentDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index 2e85f590998..33ff1509e0b 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -20,7 +20,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ArrayFillFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFillFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const MAX_SIZE_USE_CONSTANT_ARRAY = 100; diff --git a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php index 785991b2c11..b8c7fdfe970 100644 --- a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use function count; -class ArrayFillKeysFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFillKeysFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php index d697380439d..d6f5a8251c7 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php @@ -37,7 +37,7 @@ use function strtolower; use function substr; -class ArrayFilterFunctionReturnTypeReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFilterFunctionReturnTypeReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php index 1beb76de40f..0d6342e5842 100644 --- a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use function count; -class ArrayFlipFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFlipFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php b/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php index 10335fa3e80..e7436d82752 100644 --- a/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php @@ -14,7 +14,7 @@ use function array_slice; use function count; -class ArrayIntersectKeyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayIntersectKeyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php index 16e343e12d0..fe418f4daa9 100644 --- a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayKeyDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayKeyDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index d2d5ed8360d..d255aa8c152 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -23,7 +23,7 @@ use function count; use function in_array; -class ArrayKeyExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class ArrayKeyExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index 06fcfb7c74a..bd9fdd6f0ab 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayKeyFirstDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayKeyFirstDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index c1684afa9d9..a13714293c9 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayKeyLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayKeyLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 2139222a9af..74a2903ea55 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -13,7 +13,7 @@ use function count; use function strtolower; -class ArrayKeysFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayKeysFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index af0f49d2318..24561b55982 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -20,7 +20,7 @@ use function array_slice; use function count; -class ArrayMapFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayMapFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 5bde2a454e0..5b177a9f686 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -21,7 +21,7 @@ use function count; use function in_array; -class ArrayMergeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayMergeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php index dff732d028e..0f33b7f532f 100644 --- a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\TypeCombinator; use function in_array; -class ArrayNextDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayNextDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index cd12f678d17..4e49cd465f9 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -12,7 +12,7 @@ use function count; use function in_array; -class ArrayPointerFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayPointerFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { /** @var string[] */ diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 64935edaea3..540dda82bb3 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayPopFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayPopFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php index 315acd84881..1d390b59d0d 100644 --- a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php @@ -16,7 +16,7 @@ use PHPStan\Type\UnionType; use function count; -class ArrayRandFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayRandFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php index 80bd5866ec4..970e2cb39f9 100644 --- a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php @@ -13,7 +13,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ArrayReduceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayReduceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php index 94584e35834..e68f0338f71 100644 --- a/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php @@ -13,7 +13,7 @@ use function count; use function strtolower; -class ArrayReplaceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayReplaceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index ef16c66fbfe..1a693eff8d6 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php index 8571c82ed33..4b974bbe1da 100644 --- a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php @@ -16,7 +16,7 @@ use PHPStan\Type\TypeCombinator; use function strtolower; -class ArraySearchFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class ArraySearchFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index 47552e875e9..cb6c35bbddc 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ArrayShiftFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayShiftFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php index 811727ae390..156a8c1a463 100644 --- a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ArraySliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArraySliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php index 45cff582a21..bf4c3abc6b9 100644 --- a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; -class ArraySpliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArraySpliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php index 4573e7d89dd..2378a291b3c 100644 --- a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php @@ -13,7 +13,7 @@ use function count; use function strtolower; -class ArrayValuesFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayValuesFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php index 413a1635355..d1458a27dd8 100644 --- a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php @@ -11,7 +11,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\FunctionTypeSpecifyingExtension; -class AssertFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class AssertFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/AssertThrowTypeExtension.php b/src/Type/Php/AssertThrowTypeExtension.php index 22bbbc20476..2cf42260472 100644 --- a/src/Type/Php/AssertThrowTypeExtension.php +++ b/src/Type/Php/AssertThrowTypeExtension.php @@ -11,7 +11,7 @@ use Throwable; use function count; -class AssertThrowTypeExtension implements DynamicFunctionThrowTypeExtension +final class AssertThrowTypeExtension implements DynamicFunctionThrowTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php b/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php index 8f4103018df..c80bb0950b4 100644 --- a/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php +++ b/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php @@ -14,7 +14,7 @@ use function count; use function in_array; -class BackedEnumFromMethodDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +final class BackedEnumFromMethodDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php index bb1ef074309..6e38a43a25f 100644 --- a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php @@ -13,7 +13,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; -class Base64DecodeDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class Base64DecodeDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php index c10cbe2e580..2651cb6b90f 100644 --- a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php +++ b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php @@ -21,7 +21,7 @@ use function in_array; use function is_numeric; -class BcMathStringOrNullReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class BcMathStringOrNullReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index 9367e20b1b7..8cf3d88c192 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -21,7 +21,7 @@ use function in_array; use function ltrim; -class ClassExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class ClassExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php index 754ac737c81..17e293973b6 100644 --- a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php +++ b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php @@ -18,7 +18,7 @@ use function count; use function in_array; -class ClassImplementsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ClassImplementsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php index 1d0e07a5006..719fd172d30 100644 --- a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\Type; -class ClosureBindDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +final class ClosureBindDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php index 73c34fa9ed3..9e495b3163e 100644 --- a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Type; -class ClosureBindToDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class ClosureBindToDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index ed2bd20acd3..45ff96e2c90 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ClosureFromCallableDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +final class ClosureFromCallableDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/CompactFunctionReturnTypeExtension.php b/src/Type/Php/CompactFunctionReturnTypeExtension.php index 4c634ad0d15..435d4b067df 100644 --- a/src/Type/Php/CompactFunctionReturnTypeExtension.php +++ b/src/Type/Php/CompactFunctionReturnTypeExtension.php @@ -13,7 +13,7 @@ use function array_merge; use function count; -class CompactFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class CompactFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private bool $checkMaybeUndefinedVariables) diff --git a/src/Type/Php/ConstantFunctionReturnTypeExtension.php b/src/Type/Php/ConstantFunctionReturnTypeExtension.php index 1e1075c812a..83cfb69ef57 100644 --- a/src/Type/Php/ConstantFunctionReturnTypeExtension.php +++ b/src/Type/Php/ConstantFunctionReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class ConstantFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ConstantFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private ConstantHelper $constantHelper) diff --git a/src/Type/Php/ConstantHelper.php b/src/Type/Php/ConstantHelper.php index 7f056f90ecc..edcbbf7b7fd 100644 --- a/src/Type/Php/ConstantHelper.php +++ b/src/Type/Php/ConstantHelper.php @@ -12,7 +12,7 @@ use function explode; use function ltrim; -class ConstantHelper +final class ConstantHelper { public function createExprFromConstantName(string $constantName): ?Expr diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index d4c995f933e..9368cb42799 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -12,7 +12,7 @@ use function in_array; use const COUNT_RECURSIVE; -class CountFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class CountFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index 5940c2d8033..03d938a7e46 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -14,7 +14,7 @@ use function count; use function in_array; -class CountFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class CountFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index 112ed30292f..7e705660448 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -21,7 +21,7 @@ use PHPStan\Type\UnionType; use function strtolower; -class CtypeDigitFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class CtypeDigitFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/CurlInitReturnTypeExtension.php b/src/Type/Php/CurlInitReturnTypeExtension.php index e9564278fee..d34d79059a6 100644 --- a/src/Type/Php/CurlInitReturnTypeExtension.php +++ b/src/Type/Php/CurlInitReturnTypeExtension.php @@ -12,7 +12,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class CurlInitReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class CurlInitReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/DateFormatFunctionReturnTypeExtension.php b/src/Type/Php/DateFormatFunctionReturnTypeExtension.php index bae299abaa5..1a404526f35 100644 --- a/src/Type/Php/DateFormatFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFormatFunctionReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use function count; -class DateFormatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class DateFormatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private DateFunctionReturnTypeHelper $dateFunctionReturnTypeHelper) @@ -26,7 +26,7 @@ public function getTypeFromFunctionCall( FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope, - ): ?Type + ): Type { if (count($functionCall->getArgs()) < 2) { return new StringType(); diff --git a/src/Type/Php/DateFormatMethodReturnTypeExtension.php b/src/Type/Php/DateFormatMethodReturnTypeExtension.php index f028fbea7b0..34854d28cc1 100644 --- a/src/Type/Php/DateFormatMethodReturnTypeExtension.php +++ b/src/Type/Php/DateFormatMethodReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\Type; use function count; -class DateFormatMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class DateFormatMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function __construct(private DateFunctionReturnTypeHelper $dateFunctionReturnTypeHelper) @@ -28,7 +28,7 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return $methodReflection->getName() === 'format'; } - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->getArgs()) === 0) { return new StringType(); diff --git a/src/Type/Php/DateFunctionReturnTypeExtension.php b/src/Type/Php/DateFunctionReturnTypeExtension.php index 41432d1c3a6..32113eb3124 100644 --- a/src/Type/Php/DateFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFunctionReturnTypeExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\Type; use function count; -class DateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class DateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private DateFunctionReturnTypeHelper $dateFunctionReturnTypeHelper) diff --git a/src/Type/Php/DateFunctionReturnTypeHelper.php b/src/Type/Php/DateFunctionReturnTypeHelper.php index d6cadad3846..5e154823362 100644 --- a/src/Type/Php/DateFunctionReturnTypeHelper.php +++ b/src/Type/Php/DateFunctionReturnTypeHelper.php @@ -17,10 +17,10 @@ use function str_pad; use const STR_PAD_LEFT; -class DateFunctionReturnTypeHelper +final class DateFunctionReturnTypeHelper { - public function getTypeFromFormatType(Type $formatType, bool $useMicrosec): ?Type + public function getTypeFromFormatType(Type $formatType, bool $useMicrosec): Type { $types = []; foreach ($formatType->getConstantStrings() as $formatString) { diff --git a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php index cd7f63662c1..04c356151de 100644 --- a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class DateIntervalConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class DateIntervalConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 1fdcbf49376..563ade35891 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -14,7 +14,7 @@ use function count; use function in_array; -class DateIntervalDynamicReturnTypeExtension +final class DateIntervalDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php index a492e793880..e20c503c05b 100644 --- a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -17,7 +17,7 @@ use PHPStan\Type\Type; use function strtolower; -class DatePeriodConstructorReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +final class DatePeriodConstructorReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php index 7b691b62570..6bde75bc6d2 100644 --- a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php @@ -16,7 +16,7 @@ use function count; use function in_array; -class DateTimeConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class DateTimeConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php index bd00c0f12aa..9e3e5c892dc 100644 --- a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php @@ -16,7 +16,7 @@ use function date_create; use function in_array; -class DateTimeCreateDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class DateTimeCreateDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php index b18c8534dfc..ef42505ede4 100644 --- a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -15,7 +15,7 @@ use function count; use function in_array; -class DateTimeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class DateTimeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/DateTimeModifyReturnTypeExtension.php b/src/Type/Php/DateTimeModifyReturnTypeExtension.php index 39a327157a8..6ab34811e5c 100644 --- a/src/Type/Php/DateTimeModifyReturnTypeExtension.php +++ b/src/Type/Php/DateTimeModifyReturnTypeExtension.php @@ -16,7 +16,7 @@ use Throwable; use function count; -class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnTypeExtension { /** @param class-string $dateTimeClass */ diff --git a/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php index b0eee10181e..e1b35ac7eeb 100644 --- a/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\TypeCombinator; use function count; -class DateTimeZoneConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class DateTimeZoneConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 5f996c55e1a..81a836f6202 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\FunctionTypeSpecifyingExtension; use function count; -class DefineConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class DefineConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php index 5f379952401..880a3c8c216 100644 --- a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\MixedType; use function count; -class DefinedConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class DefinedConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php index 0a96b8d73ff..5de459fb730 100644 --- a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class DioStatDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class DioStatDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index b69b412e583..9927cc252e5 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -22,7 +22,7 @@ use PHPStan\Type\TypeUtils; use function count; -class ExplodeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ExplodeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php index 0dd934cd321..1a04c7d5b94 100644 --- a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\Type; use function count; -class FilterInputDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class FilterInputDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper) diff --git a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php index caaca73501f..2eaa4308b4f 100644 --- a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php @@ -26,7 +26,7 @@ use function in_array; use function strtolower; -class FilterVarArrayDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class FilterVarArrayDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper, private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index 438d6440e97..26c27735557 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -10,7 +10,7 @@ use function count; use function strtolower; -class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private FilterFunctionReturnTypeHelper $filterFunctionReturnTypeHelper) diff --git a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php index de7e8dfe408..51bc6c4b2dd 100644 --- a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php @@ -18,7 +18,7 @@ use PHPStan\Type\FunctionTypeSpecifyingExtension; use function ltrim; -class FunctionExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class FunctionExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php index c570e8ec862..612c66d7862 100644 --- a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; -class GetCalledClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GetCalledClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index a6c2fdfdb59..bd1f73b5c27 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -23,7 +23,7 @@ use PHPStan\Type\UnionType; use function count; -class GetClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GetClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php index 936f5b5adf7..d5678343b5f 100644 --- a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php @@ -14,7 +14,7 @@ use function array_map; use function count; -class GetDebugTypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GetDebugTypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php index 3dc2dbba5be..b03a68655d8 100644 --- a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php @@ -18,7 +18,7 @@ use function array_map; use function count; -class GetParentClassDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GetParentClassDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php index eb396ad7976..8b6a6133cf0 100644 --- a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; -class GettimeofdayDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GettimeofdayDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/GettypeFunctionReturnTypeExtension.php b/src/Type/Php/GettypeFunctionReturnTypeExtension.php index 566faa3243b..875529ae473 100644 --- a/src/Type/Php/GettypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettypeFunctionReturnTypeExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\UnionType; use function count; -class GettypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class GettypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php index 47dc50fee98..fdbf7833781 100644 --- a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php @@ -16,7 +16,7 @@ use PHPStan\Type\TypeUtils; use function count; -class HrtimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class HrtimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 0daed61c52e..bb29690bd60 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -20,7 +20,7 @@ use function implode; use function in_array; -class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index a8d9c452761..216423696db 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -21,7 +21,7 @@ use function count; use function strtolower; -class InArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class InArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/IniGetReturnTypeExtension.php b/src/Type/Php/IniGetReturnTypeExtension.php index 4e4de99bc4e..15dc36a481a 100644 --- a/src/Type/Php/IniGetReturnTypeExtension.php +++ b/src/Type/Php/IniGetReturnTypeExtension.php @@ -13,7 +13,7 @@ use function array_key_exists; use function count; -class IniGetReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class IniGetReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/IntdivThrowTypeExtension.php b/src/Type/Php/IntdivThrowTypeExtension.php index aab1448733d..bab7bdcdebe 100644 --- a/src/Type/Php/IntdivThrowTypeExtension.php +++ b/src/Type/Php/IntdivThrowTypeExtension.php @@ -15,7 +15,7 @@ use function count; use const PHP_INT_MIN; -class IntdivThrowTypeExtension implements DynamicFunctionThrowTypeExtension +final class IntdivThrowTypeExtension implements DynamicFunctionThrowTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index 0eb26199df8..c4000b9aff0 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -15,7 +15,7 @@ use function count; use function strtolower; -class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index d05b999422e..20ca925c1c6 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\MixedType; use function strtolower; -class IsArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class IsArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index 38bb1f3044f..472683ffb1e 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -18,7 +18,7 @@ use function count; use function strtolower; -class IsCallableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class IsCallableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php index 470d1968941..c523fde2435 100644 --- a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\MixedType; use function strtolower; -class IsIterableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class IsIterableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 3864db74e5a..2d52ee99e1b 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -15,7 +15,7 @@ use function count; use function strtolower; -class IsSubclassOfFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class IsSubclassOfFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php index a06e2b33700..eda7d4df477 100644 --- a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php @@ -20,7 +20,7 @@ use function is_bool; use function json_decode; -class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { /** @var array */ diff --git a/src/Type/Php/JsonThrowTypeExtension.php b/src/Type/Php/JsonThrowTypeExtension.php index 29eaf4f2906..68e7855bb75 100644 --- a/src/Type/Php/JsonThrowTypeExtension.php +++ b/src/Type/Php/JsonThrowTypeExtension.php @@ -13,7 +13,7 @@ use PHPStan\Type\Type; use function in_array; -class JsonThrowTypeExtension implements DynamicFunctionThrowTypeExtension +final class JsonThrowTypeExtension implements DynamicFunctionThrowTypeExtension { private const ARGUMENTS_POSITIONS = [ diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 964d605b273..b8b5b38c6f7 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -12,7 +12,7 @@ use function count; use function ltrim; -class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index ae3aa752f99..dd46ed4c2ae 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; -class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/MbFunctionsReturnTypeExtension.php b/src/Type/Php/MbFunctionsReturnTypeExtension.php index 13f8d238b08..d6cb1445062 100644 --- a/src/Type/Php/MbFunctionsReturnTypeExtension.php +++ b/src/Type/Php/MbFunctionsReturnTypeExtension.php @@ -21,7 +21,7 @@ use function array_unique; use function count; -class MbFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MbFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { use MbFunctionsReturnTypeExtensionTrait; diff --git a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php index cb246f7e3d9..fe28eb34ca9 100644 --- a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php @@ -33,7 +33,7 @@ use function sprintf; use function var_export; -class MbStrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MbStrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const UNSUPPORTED_ENCODING = 'unsupported'; diff --git a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php index f5d0055d9b1..a782db66866 100644 --- a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php +++ b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php @@ -18,7 +18,7 @@ use function in_array; use function strtolower; -class MbSubstituteCharacterDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MbSubstituteCharacterDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 08a366a59b8..bdff31b8421 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -18,7 +18,7 @@ use PHPStan\Type\UnionType; use function count; -class MethodExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class MethodExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php index 422e1355c67..f0cc1561fb3 100644 --- a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\UnionType; use function count; -class MicrotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MicrotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index e1633c0b642..20128f0a927 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -20,7 +20,7 @@ use function count; use function in_array; -class MinMaxFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class MinMaxFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct( diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index cb7cf0eb07f..083914085a7 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -14,7 +14,7 @@ use function count; use function in_array; -class NonEmptyStringFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class NonEmptyStringFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index 11a5f747528..60fac483585 100644 --- a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php @@ -17,7 +17,7 @@ use function count; use function sprintf; -class PathinfoFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class PathinfoFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/PowFunctionReturnTypeExtension.php b/src/Type/Php/PowFunctionReturnTypeExtension.php index ea15dae2e88..6be372d38fc 100644 --- a/src/Type/Php/PowFunctionReturnTypeExtension.php +++ b/src/Type/Php/PowFunctionReturnTypeExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\Type; use function count; -class PowFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class PowFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php index 3e158d1bcc9..acd5ab309bb 100644 --- a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\UnionType; use function count; -class PregFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class PregFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index 6300d6887d7..d898085584b 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -20,7 +20,7 @@ use PHPStan\Type\TypeCombinator; use function strtolower; -class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct( diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 8907e48a661..70d08b9098f 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -19,7 +19,7 @@ use PHPStan\Type\ObjectWithoutClassType; use function count; -class PropertyExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class PropertyExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php index e7e17b90f61..3e0873ee118 100644 --- a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php +++ b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php @@ -17,7 +17,7 @@ use function max; use function min; -class RandomIntFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class RandomIntFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 8a67f1d6ab1..16c7a37788d 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -27,7 +27,7 @@ use function is_array; use function range; -class RangeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class RangeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const RANGE_LENGTH_THRESHOLD = 50; diff --git a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php index bbe3c9de626..487857213d9 100644 --- a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php @@ -13,7 +13,7 @@ use ReflectionClass; use function count; -class ReflectionClassConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class ReflectionClassConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function isStaticMethodSupported(MethodReflection $methodReflection): bool diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index 842821b1e2e..fb3e577d382 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\ObjectType; use ReflectionClass; -class ReflectionClassIsSubclassOfTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class ReflectionClassIsSubclassOfTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php index 56478c55c97..83df24904f3 100644 --- a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php @@ -14,7 +14,7 @@ use ReflectionFunction; use function count; -class ReflectionFunctionConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class ReflectionFunctionConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index bb11536c1be..621831d168e 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -13,7 +13,7 @@ use ReflectionAttribute; use function count; -class ReflectionGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class ReflectionGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { /** diff --git a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php index e6aa5823f5b..c0b0df2cf53 100644 --- a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php @@ -16,7 +16,7 @@ use ReflectionMethod; use function count; -class ReflectionMethodConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class ReflectionMethodConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php index 098b3f1567e..b988a7883a8 100644 --- a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php @@ -13,7 +13,7 @@ use ReflectionProperty; use function count; -class ReflectionPropertyConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class ReflectionPropertyConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function __construct(private ReflectionProvider $reflectionProvider) diff --git a/src/Type/Php/RegexCapturingGroup.php b/src/Type/Php/RegexCapturingGroup.php index e5ddac62e2c..b89989cb51f 100644 --- a/src/Type/Php/RegexCapturingGroup.php +++ b/src/Type/Php/RegexCapturingGroup.php @@ -4,7 +4,7 @@ use PHPStan\Type\Type; -class RegexCapturingGroup +final class RegexCapturingGroup { private bool $forceNonOptional = false; diff --git a/src/Type/Php/RegexNonCapturingGroup.php b/src/Type/Php/RegexNonCapturingGroup.php index 1f3fd26d4c5..71bf2d7ead3 100644 --- a/src/Type/Php/RegexNonCapturingGroup.php +++ b/src/Type/Php/RegexNonCapturingGroup.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Php; -class RegexNonCapturingGroup +final class RegexNonCapturingGroup { public function __construct( diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 1da17b48d89..f6af99e4d2c 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -18,7 +18,7 @@ use function array_key_exists; use function count; -class ReplaceFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ReplaceFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const FUNCTIONS_SUBJECT_POSITION = [ diff --git a/src/Type/Php/RoundFunctionReturnTypeExtension.php b/src/Type/Php/RoundFunctionReturnTypeExtension.php index e3e3441b3e9..8d064c1ef38 100644 --- a/src/Type/Php/RoundFunctionReturnTypeExtension.php +++ b/src/Type/Php/RoundFunctionReturnTypeExtension.php @@ -22,7 +22,7 @@ use function count; use function in_array; -class RoundFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class RoundFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index 70526414bba..934aece46e1 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -18,7 +18,7 @@ use function count; use function strtolower; -class SetTypeFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension +final class SetTypeFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php index e6c390cd227..a0f33f18419 100644 --- a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php @@ -14,7 +14,7 @@ use SimpleXMLElement; use function count; -class SimpleXMLElementAsXMLMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class SimpleXMLElementAsXMLMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php index dadfdaa4783..6b43f932c4f 100644 --- a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php +++ b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php @@ -10,7 +10,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; -class SimpleXMLElementClassPropertyReflectionExtension implements PropertiesClassReflectionExtension +final class SimpleXMLElementClassPropertyReflectionExtension implements PropertiesClassReflectionExtension { public function hasProperty(ClassReflection $classReflection, string $propertyName): bool diff --git a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php index ea5da838723..28995db8ea2 100644 --- a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php @@ -14,7 +14,7 @@ use function extension_loaded; use function libxml_use_internal_errors; -class SimpleXMLElementConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension +final class SimpleXMLElementConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { public function isStaticMethodSupported(MethodReflection $methodReflection): bool diff --git a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php index 5ce6d57598b..7255d648877 100644 --- a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php @@ -14,7 +14,7 @@ use SimpleXMLElement; use function extension_loaded; -class SimpleXMLElementXpathMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class SimpleXMLElementXpathMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 8f1782b0a58..8e53a957c4a 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -32,7 +32,7 @@ use function substr; use function vsprintf; -class SprintfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class SprintfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php index 5939bf7ced3..18a1275ca66 100644 --- a/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php @@ -21,7 +21,7 @@ use function in_array; use function preg_match_all; -class SscanfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class SscanfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StatDynamicReturnTypeExtension.php b/src/Type/Php/StatDynamicReturnTypeExtension.php index 7b8c08f1946..4a0141b1063 100644 --- a/src/Type/Php/StatDynamicReturnTypeExtension.php +++ b/src/Type/Php/StatDynamicReturnTypeExtension.php @@ -18,7 +18,7 @@ use SplFileObject; use function in_array; -class StatDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, DynamicMethodReturnTypeExtension +final class StatDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, DynamicMethodReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php index ebb693672db..39355b579ee 100644 --- a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php +++ b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php @@ -21,7 +21,7 @@ use function is_callable; use function mb_check_encoding; -class StrCaseFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrCaseFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { /** diff --git a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php index b739263e644..f44a76b71ce 100644 --- a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php @@ -23,7 +23,7 @@ use function str_split; use function stripos; -class StrIncrementDecrementFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrIncrementDecrementFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index 3b11f5130e4..fd98d36ae46 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -15,7 +15,7 @@ use PHPStan\Type\Type; use function count; -class StrPadFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrPadFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 3ba27a7f9e8..22c9714168c 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -22,7 +22,7 @@ use function str_repeat; use function strlen; -class StrRepeatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrRepeatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrTokFunctionReturnTypeExtension.php b/src/Type/Php/StrTokFunctionReturnTypeExtension.php index 1af80eafe6b..85efaa5ad2a 100644 --- a/src/Type/Php/StrTokFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrTokFunctionReturnTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\Type; use function count; -class StrTokFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrTokFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php index 96a532ceef3..33ed2457396 100644 --- a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php @@ -16,7 +16,7 @@ use PHPStan\Type\UnionType; use function count; -class StrWordCountFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrWordCountFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index e4a51cb8331..e50dc206762 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -24,7 +24,7 @@ use function sort; use function strlen; -class StrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php index ef5e1af7871..479adf4c435 100644 --- a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php @@ -21,7 +21,7 @@ use function min; use function strtotime; -class StrtotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrtotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php index 3328313d9a6..8ed6c637fcc 100644 --- a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php @@ -15,7 +15,7 @@ use function count; use function in_array; -class StrvalFamilyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class StrvalFamilyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { private const FUNCTIONS = [ diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index ae4bf10d5a7..e90116c57a8 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -22,7 +22,7 @@ use function mb_substr; use function substr; -class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php index 1df3f180e5e..c05c6f6cefd 100644 --- a/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php @@ -18,7 +18,7 @@ use const E_USER_NOTICE; use const E_USER_WARNING; -class TriggerErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class TriggerErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index 380cfb5b599..1f715dad2cf 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -15,7 +15,7 @@ use function count; use function in_array; -class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, TypeSpecifierAwareExtension +final class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, TypeSpecifierAwareExtension { private TypeSpecifier $typeSpecifier; diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index b8a77540cb3..9a5592bf406 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -15,7 +15,7 @@ use function count; use function version_compare; -class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php index 62e820b703e..26551ff8306 100644 --- a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php +++ b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php @@ -14,7 +14,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; -class XMLReaderOpenReturnTypeExtension implements DynamicMethodReturnTypeExtension, DynamicStaticMethodReturnTypeExtension +final class XMLReaderOpenReturnTypeExtension implements DynamicMethodReturnTypeExtension, DynamicStaticMethodReturnTypeExtension { private const XML_READER_CLASS = 'XMLReader'; diff --git a/src/Type/RecursionGuard.php b/src/Type/RecursionGuard.php index 8fd995882cd..aeecba0db76 100644 --- a/src/Type/RecursionGuard.php +++ b/src/Type/RecursionGuard.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class RecursionGuard +final class RecursionGuard { /** @var true[] */ diff --git a/src/Type/SimultaneousTypeTraverser.php b/src/Type/SimultaneousTypeTraverser.php index f0e0e3dfe71..046727de880 100644 --- a/src/Type/SimultaneousTypeTraverser.php +++ b/src/Type/SimultaneousTypeTraverser.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class SimultaneousTypeTraverser +final class SimultaneousTypeTraverser { /** @var callable(Type $left, Type $right, callable(Type, Type): Type $traverse): Type */ diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index ba99a36130d..382e4208ad9 100644 --- a/src/Type/StaticTypeFactory.php +++ b/src/Type/StaticTypeFactory.php @@ -8,7 +8,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -class StaticTypeFactory +final class StaticTypeFactory { public static function falsey(): Type diff --git a/src/Type/TypeAlias.php b/src/Type/TypeAlias.php index 10d6c099225..7dc7803af02 100644 --- a/src/Type/TypeAlias.php +++ b/src/Type/TypeAlias.php @@ -7,7 +7,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -class TypeAlias +final class TypeAlias { private ?Type $resolvedType = null; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 88d04af91f5..615c9b827a3 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -36,7 +36,10 @@ use function sprintf; use function usort; -/** @api */ +/** + * @api + * @final + */ class TypeCombinator { diff --git a/src/Type/TypeTraverser.php b/src/Type/TypeTraverser.php index 1507e8ce581..a95cf246c1e 100644 --- a/src/Type/TypeTraverser.php +++ b/src/Type/TypeTraverser.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class TypeTraverser +final class TypeTraverser { /** @var callable(Type $type, callable(Type): Type $traverse): Type */ diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index c16822e0a4f..92cc4343df1 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -15,7 +15,10 @@ use function array_unique; use function array_values; -/** @api */ +/** + * @api + * @final + */ class TypeUtils { diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 09e33f5c9b2..8f9f5616f33 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -19,7 +19,7 @@ use function str_ends_with; use function strtolower; -class TypehintHelper +final class TypehintHelper { private static function getTypeObjectFromTypehint(string $typeString, ClassReflection|string|null $selfClass): Type diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index a2c28b31531..7f82682dc53 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -12,7 +12,7 @@ use function usort; use const PHP_INT_MIN; -class UnionTypeHelper +final class UnionTypeHelper { /** diff --git a/src/Type/UsefulTypeAliasResolver.php b/src/Type/UsefulTypeAliasResolver.php index 191bad9794c..f4ad8abbb8c 100644 --- a/src/Type/UsefulTypeAliasResolver.php +++ b/src/Type/UsefulTypeAliasResolver.php @@ -10,7 +10,7 @@ use function array_key_exists; use function sprintf; -class UsefulTypeAliasResolver implements TypeAliasResolver +final class UsefulTypeAliasResolver implements TypeAliasResolver { /** @var array */ diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index c80bcbce998..d9724fc64d9 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; -class VerbosityLevel +final class VerbosityLevel { private const TYPE_ONLY = 1; From 5baa146510b56c9571b3d85eba71c02d86f683bb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 29 Jul 2024 08:48:11 +0200 Subject: [PATCH 0004/3097] A few more classes made final --- .../PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php | 2 +- src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 51874b7a238..22812240f48 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class ServiceLocatorDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension +final class ServiceLocatorDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension { public function getClass(): string diff --git a/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php index d47d7f39eba..5ce81dbe845 100644 --- a/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php @@ -9,7 +9,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\Type; -class AbsFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class AbsFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool From d631120bea6af099cdcc85e3e12dc9f26bf6f1f5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 29 Jul 2024 08:45:52 +0200 Subject: [PATCH 0005/3097] Internal PHPStan rule - class must be abstract or final --- build/PHPStan/Build/FinalClassRule.php | 64 ++++++++++++++++++++++++++ build/phpstan.neon | 4 ++ 2 files changed, 68 insertions(+) create mode 100644 build/PHPStan/Build/FinalClassRule.php diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php new file mode 100644 index 00000000000..c094aadda67 --- /dev/null +++ b/build/PHPStan/Build/FinalClassRule.php @@ -0,0 +1,64 @@ + + */ +final class FinalClassRule implements Rule +{ + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + if (!$classReflection->isClass()) { + return []; + } + if ($classReflection->isAbstract()) { + return []; + } + if ($classReflection->isFinal()) { + return []; + } + if ($classReflection->isSubclassOf(Type::class)) { + return []; + } + + // exceptions + if (in_array($classReflection->getName(), [ + FunctionVariant::class, + FunctionVariantWithPhpDocs::class, + DummyParameter::class, + PhpFunctionFromParserNodeReflection::class, + ], true)) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf('Class %s must be abstract or final.', $classReflection->getDisplayName()), + ) + ->identifier('phpstan.finalClass') + ->build(), + ]; + } + +} diff --git a/build/phpstan.neon b/build/phpstan.neon index 63e9a82ace1..bfb94931129 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -9,6 +9,7 @@ includes: - ../phpstan-baseline.php - ignore-by-php-version.neon.php - ignore-by-architecture.neon.php + parameters: level: 8 paths: @@ -98,6 +99,9 @@ parameters: - stubs/NetteDIContainer.stub - stubs/PhpParserName.stub +rules: + - PHPStan\Build\FinalClassRule + services: - class: PHPStan\Build\ServiceLocatorDynamicReturnTypeExtension From e99032101ef29b97bec9050f05cd2c717f78edd5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 29 Jul 2024 09:05:15 +0200 Subject: [PATCH 0006/3097] FinalClassRule - skip tests --- build/PHPStan/Build/FinalClassRule.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index c094aadda67..3479534345b 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -14,6 +14,7 @@ use PHPStan\Type\Type; use function in_array; use function sprintf; +use function str_starts_with; /** * @implements Rule @@ -52,6 +53,10 @@ public function processNode(Node $node, Scope $scope): array return []; } + if (str_starts_with($scope->getFile(), dirname(__DIR__, 3) . '/tests')) { + return []; + } + return [ RuleErrorBuilder::message( sprintf('Class %s must be abstract or final.', $classReflection->getDisplayName()), From fe503cad77b684f845a932644007c50684161b8d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 29 Jul 2024 09:08:13 +0200 Subject: [PATCH 0007/3097] Downgrade PHP files in build/PHPStan --- build/downgrade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/build/downgrade.php b/build/downgrade.php index a440588e852..7c117d13f56 100644 --- a/build/downgrade.php +++ b/build/downgrade.php @@ -2,6 +2,7 @@ return [ 'paths' => [ + __DIR__ . '/../build/PHPStan', __DIR__ . '/../src', __DIR__ . '/../tests/PHPStan', __DIR__ . '/../tests/e2e', From e2a2e1bc6519b77bd0a923bbb62cad43c7cb5727 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 29 Jul 2024 09:09:42 +0200 Subject: [PATCH 0008/3097] FinalClassRule - normalize paths for Windows --- build/PHPStan/Build/FinalClassRule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index 3479534345b..5918003a719 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\File\FileHelper; use PHPStan\Node\InClassNode; use PHPStan\Reflection\FunctionVariant; use PHPStan\Reflection\FunctionVariantWithPhpDocs; @@ -22,6 +23,10 @@ final class FinalClassRule implements Rule { + public function __construct(private FileHelper $fileHelper) + { + } + public function getNodeType(): string { return InClassNode::class; @@ -53,7 +58,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (str_starts_with($scope->getFile(), dirname(__DIR__, 3) . '/tests')) { + if (str_starts_with($this->fileHelper->normalizePath($scope->getFile()), $this->fileHelper->normalizePath(dirname(__DIR__, 3) . '/tests'))) { return []; } From bd2cec118592f7c66dff5a7ae28882654daf6468 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 1 Aug 2024 09:43:09 +0200 Subject: [PATCH 0009/3097] Precise type for `$matches` from `preg_match` generally available, out of bleeding edge --- conf/bleedingEdge.neon | 1 - conf/config.neon | 15 ++++++++++----- conf/parametersSchema.neon | 1 - 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 61482ac786b..b95ba0e92e6 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -54,7 +54,6 @@ parameters: paramOutType: true pure: true checkParameterCastableToStringFunctions: true - narrowPregMatches: true uselessReturnValue: true printfArrayParameters: true preciseMissingReturn: true diff --git a/conf/config.neon b/conf/config.neon index 2f5371bbfbc..df10aa7d4bf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -89,7 +89,6 @@ parameters: paramOutType: false pure: false checkParameterCastableToStringFunctions: false - narrowPregMatches: false uselessReturnValue: false printfArrayParameters: false preciseMissingReturn: false @@ -290,10 +289,6 @@ conditionalTags: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% PHPStan\Parser\TypeTraverserInstanceofVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.instanceofType% - PHPStan\Type\Php\PregMatchTypeSpecifyingExtension: - phpstan.typeSpecifier.functionTypeSpecifyingExtension: %featureToggles.narrowPregMatches% - PHPStan\Type\Php\PregMatchParameterOutTypeExtension: - phpstan.functionParameterOutTypeExtension: %featureToggles.narrowPregMatches% services: - @@ -1491,6 +1486,16 @@ services: - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension + - + class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + + - + class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension + tags: + - phpstan.functionParameterOutTypeExtension + - class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 94984336fac..ea4aa61efd6 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -84,7 +84,6 @@ parametersSchema: paramOutType: bool() pure: bool() checkParameterCastableToStringFunctions: bool() - narrowPregMatches: bool() uselessReturnValue: bool() printfArrayParameters: bool() preciseMissingReturn: bool() From 69555f3c790ac419d77f3dfdf77be02d802874ff Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 1 Aug 2024 15:41:15 +0200 Subject: [PATCH 0010/3097] Finalize classes --- src/DependencyInjection/InvalidExcludePathsException.php | 2 +- src/DependencyInjection/ValidateExcludePathsExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/InvalidExcludePathsException.php b/src/DependencyInjection/InvalidExcludePathsException.php index d230b211084..24ecfb9565b 100644 --- a/src/DependencyInjection/InvalidExcludePathsException.php +++ b/src/DependencyInjection/InvalidExcludePathsException.php @@ -5,7 +5,7 @@ use Exception; use function implode; -class InvalidExcludePathsException extends Exception +final class InvalidExcludePathsException extends Exception { /** diff --git a/src/DependencyInjection/ValidateExcludePathsExtension.php b/src/DependencyInjection/ValidateExcludePathsExtension.php index e98e512b315..5df6ca95e22 100644 --- a/src/DependencyInjection/ValidateExcludePathsExtension.php +++ b/src/DependencyInjection/ValidateExcludePathsExtension.php @@ -12,7 +12,7 @@ use function is_file; use function sprintf; -class ValidateExcludePathsExtension extends CompilerExtension +final class ValidateExcludePathsExtension extends CompilerExtension { /** From 82a38e1587138758f08bde08d00d7a075fded976 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 1 Aug 2024 18:07:41 +0200 Subject: [PATCH 0011/3097] Fix - deduplicate config --- conf/config.neon | 6 ------ 1 file changed, 6 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 8deb4638e16..548aa5974b0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1487,9 +1487,6 @@ services: tags: - phpstan.dynamicFunctionThrowTypeExtension - - - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension - - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension tags: @@ -1500,9 +1497,6 @@ services: tags: - phpstan.functionParameterOutTypeExtension - - - class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension - - class: PHPStan\Type\Php\RegexArrayShapeMatcher From aaa649c194400b222558f62356c75ee2ec2cf202 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 1 Aug 2024 18:09:33 +0200 Subject: [PATCH 0012/3097] Return narrowPregMatches into featureToggles schema to fix composer/pcre --- conf/config.neon | 1 + conf/parametersSchema.neon | 1 + 2 files changed, 2 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index 548aa5974b0..b82a722ab9a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -94,6 +94,7 @@ parameters: preciseMissingReturn: false validatePregQuote: false noImplicitWildcard: false + narrowPregMatches: true fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 176d083301c..7cfb651b7d9 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -89,6 +89,7 @@ parametersSchema: preciseMissingReturn: bool() validatePregQuote: bool() noImplicitWildcard: bool() + narrowPregMatches: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From d65138a11f0654b710a27b4b563bff2ccf0b2c1b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 4 Aug 2024 14:24:21 +0200 Subject: [PATCH 0013/3097] ExtendedPropertyReflection --- src/Analyser/MutatingScope.php | 3 +- src/Analyser/Scope.php | 4 +- .../AnnotationPropertyReflection.php | 4 +- ...ionsPropertiesClassReflectionExtension.php | 8 +- src/Reflection/ClassReflection.php | 15 +++- .../Dummy/ChangedTypePropertyReflection.php | 6 +- .../Dummy/DummyPropertyReflection.php | 4 +- src/Reflection/ExtendedPropertyReflection.php | 22 +++++ src/Reflection/Php/EnumPropertyReflection.php | 4 +- ...mUnresolvedPropertyPrototypeReflection.php | 6 +- .../Php/PhpClassReflectionExtension.php | 8 +- src/Reflection/Php/PhpPropertyReflection.php | 4 +- .../Php/SimpleXMLElementProperty.php | 4 +- .../Php/UniversalObjectCrateProperty.php | 4 +- ...endsPropertiesClassReflectionExtension.php | 6 +- src/Reflection/ResolvedPropertyReflection.php | 4 +- ...kUnresolvedPropertyPrototypeReflection.php | 12 +-- ...eUnresolvedPropertyPrototypeReflection.php | 12 +-- .../IntersectionTypePropertyReflection.php | 3 +- ...eUnresolvedPropertyPrototypeReflection.php | 7 +- .../Type/UnionTypePropertyReflection.php | 3 +- ...eUnresolvedPropertyPrototypeReflection.php | 7 +- .../UnresolvedPropertyPrototypeReflection.php | 6 +- .../WrappedExtendedPropertyReflection.php | 80 +++++++++++++++++++ src/Reflection/WrapperPropertyReflection.php | 4 +- src/Rules/Api/BcUncoveredInterface.php | 2 + .../Properties/FoundPropertyReflection.php | 6 +- src/Type/ObjectShapePropertyReflection.php | 4 +- src/Type/Type.php | 4 + 29 files changed, 193 insertions(+), 63 deletions(-) create mode 100644 src/Reflection/ExtendedPropertyReflection.php create mode 100644 src/Reflection/WrappedExtendedPropertyReflection.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index fca60cc8695..c3c3b2ee255 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -59,6 +59,7 @@ use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\Dummy\DummyConstructorReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -5687,7 +5688,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, } /** @api */ - public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection + public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection { if ($typeWithProperty instanceof UnionType) { $newTypes = []; diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index e26aa8853ae..53ceaa34437 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -10,12 +10,12 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\PropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; @@ -71,7 +71,7 @@ public function getDefinedVariables(): array; public function hasConstant(Name $name): bool; - public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?PropertyReflection; + public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection; diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index e6506a4a212..78b822ca3af 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -3,11 +3,11 @@ namespace PHPStan\Reflection\Annotations; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class AnnotationPropertyReflection implements PropertyReflection +final class AnnotationPropertyReflection implements ExtendedPropertyReflection { public function __construct( diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index 3a12dcdefb5..0d5cdf48987 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Annotations; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\Type\Generic\TemplateTypeHelper; @@ -12,7 +13,7 @@ final class AnnotationsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension { - /** @var PropertyReflection[][] */ + /** @var ExtendedPropertyReflection[][] */ private array $properties = []; public function hasProperty(ClassReflection $classReflection, string $propertyName): bool @@ -28,6 +29,9 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return isset($this->properties[$classReflection->getCacheKey()][$propertyName]); } + /** + * @return ExtendedPropertyReflection + */ public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection { return $this->properties[$classReflection->getCacheKey()][$propertyName]; @@ -37,7 +41,7 @@ private function findClassReflectionWithProperty( ClassReflection $classReflection, ClassReflection $declaringClass, string $propertyName, - ): ?PropertyReflection + ): ?ExtendedPropertyReflection { $propertyTags = $classReflection->getPropertyTags(); if (isset($propertyTags[$propertyName])) { diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 4c7410e2f0d..390092a857d 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -79,7 +79,7 @@ class ClassReflection /** @var ExtendedMethodReflection[] */ private array $methods = []; - /** @var PropertyReflection[] */ + /** @var ExtendedPropertyReflection[] */ private array $properties = []; /** @var ClassConstantReflection[] */ @@ -537,6 +537,15 @@ private function wrapExtendedMethod(MethodReflection $method): ExtendedMethodRef return new WrappedExtendedMethodReflection($method); } + private function wrapExtendedProperty(PropertyReflection $method): ExtendedPropertyReflection + { + if ($method instanceof ExtendedPropertyReflection) { + return $method; + } + + return new WrappedExtendedPropertyReflection($method); + } + public function hasNativeMethod(string $methodName): bool { return $this->getPhpExtension()->hasNativeMethod($this, $methodName); @@ -619,7 +628,7 @@ public function evictPrivateSymbols(): void $this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey()); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { if ($this->isEnum()) { return $this->getNativeProperty($propertyName); @@ -640,7 +649,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco continue; } - $property = $extension->getProperty($this, $propertyName); + $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName)); if ($scope->canAccessProperty($property)) { return $this->properties[$key] = $property; } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 73917153131..c4715616e74 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -3,7 +3,7 @@ namespace PHPStan\Reflection\Dummy; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\WrapperPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -11,7 +11,7 @@ final class ChangedTypePropertyReflection implements WrapperPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private PropertyReflection $reflection, private Type $readableType, private Type $writableType) + public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType) { } @@ -80,7 +80,7 @@ public function isInternal(): TrinaryLogic return $this->reflection->isInternal(); } - public function getOriginalReflection(): PropertyReflection + public function getOriginalReflection(): ExtendedPropertyReflection { return $this->reflection; } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 90ef29825ed..55addb6d907 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -3,14 +3,14 @@ namespace PHPStan\Reflection\Dummy; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use stdClass; -final class DummyPropertyReflection implements PropertyReflection +final class DummyPropertyReflection implements ExtendedPropertyReflection { public function getDeclaringClass(): ClassReflection diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php new file mode 100644 index 00000000000..fbeac4f3a75 --- /dev/null +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -0,0 +1,22 @@ +property; } - public function getTransformedProperty(): PropertyReflection + public function getTransformedProperty(): ExtendedPropertyReflection { return $this->property; } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index d99bbf89550..7887f431915 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -21,13 +21,13 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\FunctionSignature; use PHPStan\Reflection\SignatureMap\ParameterSignature; @@ -64,7 +64,7 @@ final class PhpClassReflectionExtension implements PropertiesClassReflectionExtension, MethodsClassReflectionExtension { - /** @var PropertyReflection[][] */ + /** @var ExtendedPropertyReflection[][] */ private array $propertiesIncludingAnnotations = []; /** @var PhpPropertyReflection[][] */ @@ -152,7 +152,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return $classReflection->getNativeReflection()->hasProperty($propertyName); } - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection { if (!isset($this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); @@ -176,7 +176,7 @@ private function createProperty( ClassReflection $classReflection, string $propertyName, bool $includingAnnotations, - ): PropertyReflection + ): ExtendedPropertyReflection { $propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName); $propertyName = $propertyReflection->getName(); diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 63d7eff0d8e..b1f2c186562 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -7,7 +7,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -17,7 +17,7 @@ * @api * @final */ -class PhpPropertyReflection implements PropertyReflection +class PhpPropertyReflection implements ExtendedPropertyReflection { private ?Type $finalNativeType = null; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index dbb69f8e9f4..4073809a23f 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -3,7 +3,7 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; @@ -12,7 +12,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -final class SimpleXMLElementProperty implements PropertyReflection +final class SimpleXMLElementProperty implements ExtendedPropertyReflection { public function __construct( diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index ad10221352a..ae1e86fe8cf 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -3,11 +3,11 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class UniversalObjectCrateProperty implements PropertyReflection +final class UniversalObjectCrateProperty implements ExtendedPropertyReflection { public function __construct( diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php index 600405d3638..294cc94b620 100644 --- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php @@ -4,6 +4,7 @@ use PHPStan\Analyser\OutOfClassScope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\ShouldNotHappenException; @@ -16,6 +17,9 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return $this->findProperty($classReflection, $propertyName) !== null; } + /** + * @return ExtendedPropertyReflection + */ public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection { $property = $this->findProperty($classReflection, $propertyName); @@ -26,7 +30,7 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa return $property; } - private function findProperty(ClassReflection $classReflection, string $propertyName): ?PropertyReflection + private function findProperty(ClassReflection $classReflection, string $propertyName): ?ExtendedPropertyReflection { if (!$classReflection->isInterface()) { return null; diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 3e111949cba..8e8447ecace 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -18,14 +18,14 @@ final class ResolvedPropertyReflection implements WrapperPropertyReflection private ?Type $writableType = null; public function __construct( - private PropertyReflection $reflection, + private ExtendedPropertyReflection $reflection, private TemplateTypeMap $templateTypeMap, private TemplateTypeVarianceMap $callSiteVarianceMap, ) { } - public function getOriginalReflection(): PropertyReflection + public function getOriginalReflection(): ExtendedPropertyReflection { return $this->reflection; } diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 38b4bae4e9c..151945e921f 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -4,7 +4,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Dummy\ChangedTypePropertyReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ResolvedPropertyReflection; use PHPStan\Type\Type; @@ -14,7 +14,7 @@ final class CallbackUnresolvedPropertyPrototypeReflection implements UnresolvedP /** @var callable(Type): Type */ private $transformStaticTypeCallback; - private ?PropertyReflection $transformedProperty = null; + private ?ExtendedPropertyReflection $transformedProperty = null; private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; @@ -22,7 +22,7 @@ final class CallbackUnresolvedPropertyPrototypeReflection implements UnresolvedP * @param callable(Type): Type $transformStaticTypeCallback */ public function __construct( - private PropertyReflection $propertyReflection, + private ExtendedPropertyReflection $propertyReflection, private ClassReflection $resolvedDeclaringClass, private bool $resolveTemplateTypeMapToBounds, callable $transformStaticTypeCallback, @@ -45,12 +45,12 @@ public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototy ); } - public function getNakedProperty(): PropertyReflection + public function getNakedProperty(): ExtendedPropertyReflection { return $this->propertyReflection; } - public function getTransformedProperty(): PropertyReflection + public function getTransformedProperty(): ExtendedPropertyReflection { if ($this->transformedProperty !== null) { return $this->transformedProperty; @@ -75,7 +75,7 @@ public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflect ); } - private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection + private function transformPropertyWithStaticType(ClassReflection $declaringClass, ExtendedPropertyReflection $property): ExtendedPropertyReflection { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 6582c358b75..4b843829ad0 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -4,7 +4,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Dummy\ChangedTypePropertyReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ResolvedPropertyReflection; use PHPStan\Type\StaticType; use PHPStan\Type\Type; @@ -13,12 +13,12 @@ final class CalledOnTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { - private ?PropertyReflection $transformedProperty = null; + private ?ExtendedPropertyReflection $transformedProperty = null; private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; public function __construct( - private PropertyReflection $propertyReflection, + private ExtendedPropertyReflection $propertyReflection, private ClassReflection $resolvedDeclaringClass, private bool $resolveTemplateTypeMapToBounds, private Type $fetchedOnType, @@ -40,12 +40,12 @@ public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototy ); } - public function getNakedProperty(): PropertyReflection + public function getNakedProperty(): ExtendedPropertyReflection { return $this->propertyReflection; } - public function getTransformedProperty(): PropertyReflection + public function getTransformedProperty(): ExtendedPropertyReflection { if ($this->transformedProperty !== null) { return $this->transformedProperty; @@ -70,7 +70,7 @@ public function withFechedOnType(Type $type): UnresolvedPropertyPrototypeReflect ); } - private function transformPropertyWithStaticType(ClassReflection $declaringClass, PropertyReflection $property): PropertyReflection + private function transformPropertyWithStaticType(ClassReflection $declaringClass, ExtendedPropertyReflection $property): ExtendedPropertyReflection { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index e81c0b22ddd..0db311786e7 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Type; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -11,7 +12,7 @@ use function count; use function implode; -final class IntersectionTypePropertyReflection implements PropertyReflection +final class IntersectionTypePropertyReflection implements ExtendedPropertyReflection { /** diff --git a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php index ac2f0b9bcd2..51e6ccaf469 100644 --- a/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/IntersectionTypeUnresolvedPropertyPrototypeReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Type; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\Type\Type; use function array_map; @@ -9,7 +10,7 @@ final class IntersectionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { - private ?PropertyReflection $transformedProperty = null; + private ?ExtendedPropertyReflection $transformedProperty = null; private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; @@ -32,12 +33,12 @@ public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototy return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static fn (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection => $prototype->doNotResolveTemplateTypeMapToBounds(), $this->propertyPrototypes)); } - public function getNakedProperty(): PropertyReflection + public function getNakedProperty(): ExtendedPropertyReflection { return $this->getTransformedProperty(); } - public function getTransformedProperty(): PropertyReflection + public function getTransformedProperty(): ExtendedPropertyReflection { if ($this->transformedProperty !== null) { return $this->transformedProperty; diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index f23f2f12343..91964e8eda1 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Type; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -11,7 +12,7 @@ use function count; use function implode; -final class UnionTypePropertyReflection implements PropertyReflection +final class UnionTypePropertyReflection implements ExtendedPropertyReflection { /** diff --git a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php index f3f2d389653..b28625e3c33 100644 --- a/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnionTypeUnresolvedPropertyPrototypeReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Type; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\Type\Type; use function array_map; @@ -9,7 +10,7 @@ final class UnionTypeUnresolvedPropertyPrototypeReflection implements UnresolvedPropertyPrototypeReflection { - private ?PropertyReflection $transformedProperty = null; + private ?ExtendedPropertyReflection $transformedProperty = null; private ?self $cachedDoNotResolveTemplateTypeMapToBounds = null; @@ -31,12 +32,12 @@ public function doNotResolveTemplateTypeMapToBounds(): UnresolvedPropertyPrototy return $this->cachedDoNotResolveTemplateTypeMapToBounds = new self($this->propertyName, array_map(static fn (UnresolvedPropertyPrototypeReflection $prototype): UnresolvedPropertyPrototypeReflection => $prototype->doNotResolveTemplateTypeMapToBounds(), $this->propertyPrototypes)); } - public function getNakedProperty(): PropertyReflection + public function getNakedProperty(): ExtendedPropertyReflection { return $this->getTransformedProperty(); } - public function getTransformedProperty(): PropertyReflection + public function getTransformedProperty(): ExtendedPropertyReflection { if ($this->transformedProperty !== null) { return $this->transformedProperty; diff --git a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php index 867f3c1ddac..441d4a36c36 100644 --- a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php @@ -2,7 +2,7 @@ namespace PHPStan\Reflection\Type; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Type\Type; interface UnresolvedPropertyPrototypeReflection @@ -10,9 +10,9 @@ interface UnresolvedPropertyPrototypeReflection public function doNotResolveTemplateTypeMapToBounds(): self; - public function getNakedProperty(): PropertyReflection; + public function getNakedProperty(): ExtendedPropertyReflection; - public function getTransformedProperty(): PropertyReflection; + public function getTransformedProperty(): ExtendedPropertyReflection; public function withFechedOnType(Type $type): self; diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php new file mode 100644 index 00000000000..9b941088bc7 --- /dev/null +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -0,0 +1,80 @@ +property->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->property->isStatic(); + } + + public function isPrivate(): bool + { + return $this->property->isPrivate(); + } + + public function isPublic(): bool + { + return $this->property->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->property->getDocComment(); + } + + public function getReadableType(): Type + { + return $this->property->getReadableType(); + } + + public function getWritableType(): Type + { + return $this->property->getWritableType(); + } + + public function canChangeTypeAfterAssignment(): bool + { + return $this->property->canChangeTypeAfterAssignment(); + } + + public function isReadable(): bool + { + return $this->property->isReadable(); + } + + public function isWritable(): bool + { + return $this->property->isWritable(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->property->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->property->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->property->isInternal(); + } + +} diff --git a/src/Reflection/WrapperPropertyReflection.php b/src/Reflection/WrapperPropertyReflection.php index c34a3c7cfb5..e7bb397ace8 100644 --- a/src/Reflection/WrapperPropertyReflection.php +++ b/src/Reflection/WrapperPropertyReflection.php @@ -2,9 +2,9 @@ namespace PHPStan\Reflection; -interface WrapperPropertyReflection extends PropertyReflection +interface WrapperPropertyReflection extends ExtendedPropertyReflection { - public function getOriginalReflection(): PropertyReflection; + public function getOriginalReflection(): ExtendedPropertyReflection; } diff --git a/src/Rules/Api/BcUncoveredInterface.php b/src/Rules/Api/BcUncoveredInterface.php index 9a78dc5cc70..7670f1962f2 100644 --- a/src/Rules/Api/BcUncoveredInterface.php +++ b/src/Rules/Api/BcUncoveredInterface.php @@ -5,6 +5,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; @@ -27,6 +28,7 @@ final class BcUncoveredInterface Scope::class, FunctionReflection::class, ExtendedMethodReflection::class, + ExtendedPropertyReflection::class, ParametersAcceptorWithPhpDocs::class, ParameterReflectionWithPhpDocs::class, CallableParametersAcceptor::class, diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 57577364cd3..a05182fa668 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -4,17 +4,17 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\PhpPropertyReflection; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\WrapperPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class FoundPropertyReflection implements PropertyReflection +final class FoundPropertyReflection implements ExtendedPropertyReflection { public function __construct( - private PropertyReflection $originalPropertyReflection, + private ExtendedPropertyReflection $originalPropertyReflection, private Scope $scope, private string $propertyName, private Type $readableType, diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index e7132ecfb39..ca96ebae940 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -3,12 +3,12 @@ namespace PHPStan\Type; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use stdClass; -final class ObjectShapePropertyReflection implements PropertyReflection +final class ObjectShapePropertyReflection implements ExtendedPropertyReflection { public function __construct(private Type $type) diff --git a/src/Type/Type.php b/src/Type/Type.php index 3ac0a41c24f..ec682c3e034 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -9,6 +9,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; @@ -85,6 +86,9 @@ public function canAccessProperties(): TrinaryLogic; public function hasProperty(string $propertyName): TrinaryLogic; + /** + * @return ExtendedPropertyReflection + */ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection; public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; From b3aefd0b314b8100482c6f281451d42545905c85 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 4 Aug 2024 14:49:20 +0200 Subject: [PATCH 0014/3097] Fix --- src/Reflection/Php/PhpClassReflectionExtension.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 7887f431915..b6ba30dbec7 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -28,6 +28,7 @@ use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; +use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\FunctionSignature; use PHPStan\Reflection\SignatureMap\ParameterSignature; @@ -152,7 +153,10 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return $classReflection->getNativeReflection()->hasProperty($propertyName); } - public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + /** + * @return ExtendedPropertyReflection + */ + public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection { if (!isset($this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); From 3fb919aa72428f285ee76f45f9d495011b1cf7f7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 10:44:19 +0200 Subject: [PATCH 0015/3097] Bleeding edge - more precise types for bcmath function parameters --- resources/functionMap_bleedingEdge.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/functionMap_bleedingEdge.php b/resources/functionMap_bleedingEdge.php index 9f2230dd6fb..11dd4fa7730 100644 --- a/resources/functionMap_bleedingEdge.php +++ b/resources/functionMap_bleedingEdge.php @@ -2,6 +2,15 @@ return [ 'new' => [ + 'bcadd' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], + 'bccomp' => ['int', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], + 'bcdiv' => ['numeric-string|null', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], + 'bcmod' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'numeric-string', 'scale='=>'int'], + 'bcmul' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], + 'bcpow' => ['numeric-string', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'scale='=>'int'], + 'bcpowmod' => ['numeric-string|null', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'modulus'=>'string', 'scale='=>'int'], + 'bcsqrt' => ['numeric-string', 'operand'=>'numeric-string', 'scale='=>'int'], + 'bcsub' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|class-string|\'static\'|null'], 'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|class-string|\'static\'|null'], 'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|2|3|4', 'destination='=>'string', 'extra_headers='=>'string'], From 74ba8c23696948f2647d880df72f375346f41010 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Aug 2024 10:53:35 +0200 Subject: [PATCH 0016/3097] Bleeding edge - enforce `@no-named-arguments` --- src/Analyser/ArgumentsNormalizer.php | 12 +++++-- src/Analyser/MutatingScope.php | 7 ++++ src/PhpDoc/TypeNodeResolver.php | 2 +- .../AnnotationMethodReflection.php | 5 +++ .../BetterReflectionProvider.php | 3 ++ .../CallableFunctionVariantWithPhpDocs.php | 6 ++++ .../Callables/CallableParametersAcceptor.php | 2 ++ .../Callables/FunctionCallableVariant.php | 5 +++ .../Dummy/ChangedTypeMethodReflection.php | 5 +++ .../Dummy/DummyConstructorReflection.php | 5 +++ .../Dummy/DummyMethodReflection.php | 5 +++ src/Reflection/ExtendedMethodReflection.php | 2 ++ src/Reflection/FunctionReflection.php | 2 ++ src/Reflection/FunctionReflectionFactory.php | 1 + .../GenericParametersAcceptorResolver.php | 1 + src/Reflection/InaccessibleMethod.php | 9 +++-- .../Native/NativeFunctionReflection.php | 6 ++++ .../Native/NativeMethodReflection.php | 6 ++++ src/Reflection/ParametersAcceptorSelector.php | 4 +++ .../Php/ClosureCallMethodReflection.php | 5 +++ .../Php/EnumCasesMethodReflection.php | 5 +++ .../Php/PhpClassReflectionExtension.php | 6 ++++ src/Reflection/Php/PhpFunctionReflection.php | 6 ++++ src/Reflection/Php/PhpMethodReflection.php | 6 ++++ .../Php/PhpMethodReflectionFactory.php | 1 + .../ResolvedFunctionVariantWithCallable.php | 6 ++++ src/Reflection/ResolvedMethodReflection.php | 5 +++ .../NativeFunctionReflectionProvider.php | 3 ++ src/Reflection/TrivialParametersAcceptor.php | 5 +++ .../Type/IntersectionTypeMethodReflection.php | 10 ++++++ .../Type/UnionTypeMethodReflection.php | 10 ++++++ .../WrappedExtendedMethodReflection.php | 5 +++ src/Rules/AttributesCheck.php | 2 ++ src/Rules/Classes/InstantiationRule.php | 2 ++ src/Rules/FunctionCallParametersCheck.php | 23 ++++++++++++- src/Rules/Functions/CallCallablesRule.php | 7 ++++ .../CallToFunctionParametersRule.php | 2 ++ src/Rules/Functions/CallUserFuncRule.php | 5 +-- src/Rules/Methods/CallMethodsRule.php | 2 ++ src/Rules/Methods/CallStaticMethodsRule.php | 2 ++ src/Rules/RuleLevelHelper.php | 3 ++ src/Type/CallableType.php | 5 +++ src/Type/ClosureType.php | 9 +++++ ...FromCallableDynamicReturnTypeExtension.php | 6 ++++ .../CallToFunctionParametersRuleTest.php | 26 ++++++++++++++ .../Rules/Functions/CallUserFuncRuleTest.php | 26 ++++++++++++++ .../no-named-arguments-call-user-func.php | 33 ++++++++++++++++++ .../Functions/data/no-named-arguments.php | 30 ++++++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 23 +++++++++++++ .../Rules/Methods/data/no-named-arguments.php | 34 +++++++++++++++++++ 50 files changed, 392 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/no-named-arguments-call-user-func.php create mode 100644 tests/PHPStan/Rules/Functions/data/no-named-arguments.php create mode 100644 tests/PHPStan/Rules/Methods/data/no-named-arguments.php diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index 6c6db632ac8..6baa6e0c6b3 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -27,7 +27,7 @@ final class ArgumentsNormalizer public const ORIGINAL_ARG_ATTRIBUTE = 'originalArg'; /** - * @return array{ParametersAcceptor, FuncCall}|null + * @return array{ParametersAcceptor, FuncCall, bool}|null */ public static function reorderCallUserFuncArguments( FuncCall $callUserFuncCall, @@ -65,18 +65,24 @@ public static function reorderCallUserFuncArguments( return null; } + $callableParametersAcceptors = $calledOnType->getCallableParametersAcceptors($scope); $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, $passThruArgs, - $calledOnType->getCallableParametersAcceptors($scope), + $callableParametersAcceptors, null, ); + $acceptsNamedArguments = true; + foreach ($callableParametersAcceptors as $callableParametersAcceptor) { + $acceptsNamedArguments = $acceptsNamedArguments && $callableParametersAcceptor->acceptsNamedArguments(); + } + return [$parametersAcceptor, new FuncCall( $callbackArg->value, $passThruArgs, $callUserFuncCall->getAttributes(), - )]; + ), $acceptsNamedArguments]; } public static function reorderFuncArguments( diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c3c3b2ee255..f10fc225081 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1361,6 +1361,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $cachedClosureData['impurePoints'], $cachedClosureData['invalidateExpressions'], $cachedClosureData['usedVariables'], + true, ); } if (self::$resolveClosureTypeDepth >= 2) { @@ -1575,6 +1576,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $impurePointsForClosureType, $invalidateExpressions, $usedVariables, + true, ); } elseif ($node instanceof New_) { if ($node->class instanceof Name) { @@ -2523,9 +2525,11 @@ private function createFirstClassCallable( $throwPoints = []; $impurePoints = []; + $acceptsNamedArguments = true; if ($variant instanceof CallableParametersAcceptor) { $throwPoints = $variant->getThrowPoints(); $impurePoints = $variant->getImpurePoints(); + $acceptsNamedArguments = $variant->acceptsNamedArguments(); } elseif ($function !== null) { $returnTypeForThrow = $variant->getReturnType(); $throwType = $function->getThrowType(); @@ -2549,6 +2553,8 @@ private function createFirstClassCallable( if ($impurePoint !== null) { $impurePoints[] = $impurePoint; } + + $acceptsNamedArguments = $function->acceptsNamedArguments(); } $parameters = $variant->getParameters(); @@ -2564,6 +2570,7 @@ private function createFirstClassCallable( $impurePoints, [], [], + $acceptsNamedArguments, ); } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 18db8040308..a52fde51f63 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -961,7 +961,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi ), ]); } elseif ($mainType instanceof ClosureType) { - $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], $mainType->getImpurePoints()); + $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], $mainType->getImpurePoints(), $mainType->getInvalidateExpressions(), $mainType->getUsedVariables(), $mainType->acceptsNamedArguments()); if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) { return new ErrorType(); } diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 79445fdbe12..0486d11048e 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -141,6 +141,11 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + return $this->declaringClass->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 8ec65fd9d22..984d3615b38 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -280,6 +280,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $isFinal = false; $isPure = null; $asserts = Assertions::createEmpty(); + $acceptsNamedArguments = true; $phpDocComment = null; $phpDocParameterOutTags = []; $phpDocParameterImmediatelyInvokedCallable = []; @@ -305,6 +306,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection if ($resolvedPhpDoc->hasPhpDocString()) { $phpDocComment = $resolvedPhpDoc->getPhpDocString(); } + $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); $phpDocParameterOutTags = $resolvedPhpDoc->getParamOutTags(); $phpDocParameterImmediatelyInvokedCallable = $resolvedPhpDoc->getParamsImmediatelyInvokedCallable(); $phpDocParameterClosureThisTypeTags = $resolvedPhpDoc->getParamClosureThisTags(); @@ -323,6 +325,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null, $isPure, $asserts, + $acceptsNamedArguments, $phpDocComment, array_map(static fn (ParamOutTag $paramOutTag): Type => $paramOutTag->getType(), $phpDocParameterOutTags), $phpDocParameterImmediatelyInvokedCallable, diff --git a/src/Reflection/CallableFunctionVariantWithPhpDocs.php b/src/Reflection/CallableFunctionVariantWithPhpDocs.php index 8b8aa99567e..9e6644c0f1e 100644 --- a/src/Reflection/CallableFunctionVariantWithPhpDocs.php +++ b/src/Reflection/CallableFunctionVariantWithPhpDocs.php @@ -35,6 +35,7 @@ public function __construct( private array $impurePoints, private array $invalidateExpressions, private array $usedVariables, + private bool $acceptsNamedArguments, ) { parent::__construct( @@ -74,4 +75,9 @@ public function getUsedVariables(): array return $this->usedVariables; } + public function acceptsNamedArguments(): bool + { + return $this->acceptsNamedArguments; + } + } diff --git a/src/Reflection/Callables/CallableParametersAcceptor.php b/src/Reflection/Callables/CallableParametersAcceptor.php index 62371a0e858..259ede81fab 100644 --- a/src/Reflection/Callables/CallableParametersAcceptor.php +++ b/src/Reflection/Callables/CallableParametersAcceptor.php @@ -19,6 +19,8 @@ public function getThrowPoints(): array; public function isPure(): TrinaryLogic; + public function acceptsNamedArguments(): bool; + /** * @return SimpleImpurePoint[] */ diff --git a/src/Reflection/Callables/FunctionCallableVariant.php b/src/Reflection/Callables/FunctionCallableVariant.php index 992d53d13ab..aef72101403 100644 --- a/src/Reflection/Callables/FunctionCallableVariant.php +++ b/src/Reflection/Callables/FunctionCallableVariant.php @@ -163,4 +163,9 @@ public function getUsedVariables(): array return []; } + public function acceptsNamedArguments(): bool + { + return $this->function->acceptsNamedArguments(); + } + } diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 351d90b467d..69d7a7a1a6d 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -107,6 +107,11 @@ public function getAsserts(): Assertions return $this->reflection->getAsserts(); } + public function acceptsNamedArguments(): bool + { + return $this->reflection->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return $this->reflection->getSelfOutType(); diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 4c98f8d5962..7f81cd13637 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -111,6 +111,11 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + return $this->declaringClass->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index d26ea0c4beb..79e85fda5bd 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -108,6 +108,11 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + return true; + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 0ff0f8f2de5..14f7061aa46 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -32,6 +32,8 @@ public function getVariants(): array; */ public function getNamedArgumentsVariants(): ?array; + public function acceptsNamedArguments(): bool; + public function getAsserts(): Assertions; public function getSelfOutType(): ?Type; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 91afcbaadb4..bdd5ed8d63f 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -23,6 +23,8 @@ public function getVariants(): array; */ public function getNamedArgumentsVariants(): ?array; + public function acceptsNamedArguments(): bool; + public function isDeprecated(): TrinaryLogic; public function getDeprecatedDescription(): ?string; diff --git a/src/Reflection/FunctionReflectionFactory.php b/src/Reflection/FunctionReflectionFactory.php index 67684abdabf..4333954ff32 100644 --- a/src/Reflection/FunctionReflectionFactory.php +++ b/src/Reflection/FunctionReflectionFactory.php @@ -29,6 +29,7 @@ public function create( ?string $filename, ?bool $isPure, Assertions $asserts, + bool $acceptsNamedArguments, ?string $phpDocComment, array $phpDocParameterOutTypes, array $phpDocParameterImmediatelyInvokedCallable, diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 09e9968a1a0..5aa65587de9 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -126,6 +126,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc $originalParametersAcceptor->getImpurePoints(), $originalParametersAcceptor->getInvalidateExpressions(), $originalParametersAcceptor->getUsedVariables(), + $originalParametersAcceptor->acceptsNamedArguments(), ); } diff --git a/src/Reflection/InaccessibleMethod.php b/src/Reflection/InaccessibleMethod.php index 58b63fe1c5c..eaf63ef8a1e 100644 --- a/src/Reflection/InaccessibleMethod.php +++ b/src/Reflection/InaccessibleMethod.php @@ -13,11 +13,11 @@ final class InaccessibleMethod implements CallableParametersAcceptor { - public function __construct(private MethodReflection $methodReflection) + public function __construct(private ExtendedMethodReflection $methodReflection) { } - public function getMethod(): MethodReflection + public function getMethod(): ExtendedMethodReflection { return $this->methodReflection; } @@ -86,4 +86,9 @@ public function getUsedVariables(): array return []; } + public function acceptsNamedArguments(): bool + { + return $this->methodReflection->acceptsNamedArguments(); + } + } diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 870f0b7c66f..2dd98f29517 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -29,6 +29,7 @@ public function __construct( ?Assertions $assertions = null, private ?string $phpDocComment = null, ?TrinaryLogic $returnsByReference = null, + private bool $acceptsNamedArguments = true, ) { $this->assertions = $assertions ?? Assertions::createEmpty(); @@ -132,4 +133,9 @@ public function returnsByReference(): TrinaryLogic return $this->returnsByReference; } + public function acceptsNamedArguments(): bool + { + return $this->acceptsNamedArguments; + } + } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 7c60edd6cc2..425f75edd6f 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -32,6 +32,7 @@ public function __construct( private TrinaryLogic $hasSideEffects, private ?Type $throwType, private Assertions $assertions, + private bool $acceptsNamedArguments, private ?Type $selfOutType, private ?string $phpDocComment, ) @@ -187,6 +188,11 @@ public function getAsserts(): Assertions return $this->assertions; } + public function acceptsNamedArguments(): bool + { + return $this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments; + } + public function getSelfOutType(): ?Type { return $this->selfOutType; diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 5cbdbd2907b..4d14c56ced8 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -606,6 +606,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $impurePoints = []; $invalidateExpressions = []; $usedVariables = []; + $acceptsNamedArguments = false; foreach ($acceptors as $acceptor) { $returnTypes[] = $acceptor->getReturnType(); @@ -621,6 +622,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $impurePoints = array_merge($impurePoints, $acceptor->getImpurePoints()); $invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions()); $usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables()); + $acceptsNamedArguments = $acceptsNamedArguments || $acceptor->acceptsNamedArguments(); } $isVariadic = $isVariadic || $acceptor->isVariadic(); @@ -722,6 +724,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $impurePoints, $invalidateExpressions, $usedVariables, + $acceptsNamedArguments, ); } @@ -757,6 +760,7 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ParametersAc $acceptor->getImpurePoints(), $acceptor->getInvalidateExpressions(), $acceptor->getUsedVariables(), + $acceptor->acceptsNamedArguments(), ); } diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index 7e2b402bf12..0f47e2c746a 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -150,6 +150,11 @@ public function getAsserts(): Assertions return $this->nativeMethodReflection->getAsserts(); } + public function acceptsNamedArguments(): bool + { + return $this->nativeMethodReflection->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return $this->nativeMethodReflection->getSelfOutType(); diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index b66c18b8058..ec9f1b3b9dc 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -120,6 +120,11 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + return $this->declaringClass->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index b6ba30dbec7..550be0da4f7 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -477,6 +477,7 @@ private function createMethod( $reflectionMethod = null; $throwType = null; $asserts = Assertions::createEmpty(); + $acceptsNamedArguments = true; $selfOutType = null; $phpDocComment = null; if ($classReflection->getNativeReflection()->hasMethod($methodReflection->getName())) { @@ -539,6 +540,7 @@ private function createMethod( } $asserts = Assertions::createFromResolvedPhpDocBlock($stubPhpDoc); + $acceptsNamedArguments = $stubPhpDoc->acceptsNamedArguments(); $selfOutTypeTag = $stubPhpDoc->getSelfOutTag(); if ($selfOutTypeTag !== null) { @@ -583,6 +585,7 @@ private function createMethod( $phpDocParameterTypes[$name] = $paramTag->getType(); } $asserts = Assertions::createFromResolvedPhpDocBlock($phpDocBlock); + $acceptsNamedArguments = $phpDocBlock->acceptsNamedArguments(); $selfOutTypeTag = $phpDocBlock->getSelfOutTag(); if ($selfOutTypeTag !== null) { @@ -625,6 +628,7 @@ private function createMethod( $hasSideEffects, $throwType, $asserts, + $acceptsNamedArguments, $selfOutType, $phpDocComment, ); @@ -773,6 +777,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $isFinal = $resolvedPhpDoc->isFinal(); $isPure = $resolvedPhpDoc->isPure(); $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); + $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; $phpDocComment = null; if ($resolvedPhpDoc->hasPhpDocString()) { @@ -793,6 +798,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $isFinal, $isPure, $asserts, + $acceptsNamedArguments, $selfOutType, $phpDocComment, $phpDocParameterOutTypes, diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index ed044586682..5303d38b0fd 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -60,6 +60,7 @@ public function __construct( private ?string $filename, private ?bool $isPure, private Assertions $asserts, + private bool $acceptsNamedArguments, private ?string $phpDocComment, private array $phpDocParameterOutTypes, private array $phpDocParameterImmediatelyInvokedCallable, @@ -297,4 +298,9 @@ public function returnsByReference(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } + public function acceptsNamedArguments(): bool + { + return $this->acceptsNamedArguments; + } + } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index ce6306bada4..5a75f85a8ac 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -87,6 +87,7 @@ public function __construct( private bool $isFinal, private ?bool $isPure, private Assertions $asserts, + private bool $acceptsNamedArguments, private ?Type $selfOutType, private ?string $phpDocComment, private array $phpDocParameterOutTypes, @@ -456,6 +457,11 @@ public function getAsserts(): Assertions return $this->asserts; } + public function acceptsNamedArguments(): bool + { + return $this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments; + } + public function getSelfOutType(): ?Type { return $this->selfOutType; diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 8da0dcb5c6e..0745deee78e 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -31,6 +31,7 @@ public function create( bool $isFinal, ?bool $isPure, Assertions $asserts, + bool $acceptsNamedArguments, ?Type $selfOutType, ?string $phpDocComment, array $phpDocParameterOutTypes, diff --git a/src/Reflection/ResolvedFunctionVariantWithCallable.php b/src/Reflection/ResolvedFunctionVariantWithCallable.php index c84670be53b..ab121486a14 100644 --- a/src/Reflection/ResolvedFunctionVariantWithCallable.php +++ b/src/Reflection/ResolvedFunctionVariantWithCallable.php @@ -27,6 +27,7 @@ public function __construct( private array $impurePoints, private array $invalidateExpressions, private array $usedVariables, + private bool $acceptsNamedArguments, ) { } @@ -111,4 +112,9 @@ public function getUsedVariables(): array return $this->usedVariables; } + public function acceptsNamedArguments(): bool + { + return $this->acceptsNamedArguments; + } + } diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 0f4cd81a03b..69863e86665 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -170,6 +170,11 @@ public function getAsserts(): Assertions )); } + public function acceptsNamedArguments(): bool + { + return $this->reflection->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { if ($this->selfOutType === false) { diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 4b980e21d7a..32a484c3c93 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -51,6 +51,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $asserts = Assertions::createEmpty(); $docComment = null; $returnsByReference = TrinaryLogic::createMaybe(); + $acceptsNamedArguments = true; try { $reflectionFunction = $this->reflector->reflectFunction($functionName); $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); @@ -84,6 +85,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef } $asserts = Assertions::createFromResolvedPhpDocBlock($phpDoc); $phpDocReturnType = $this->getReturnTypeFromPhpDoc($phpDoc); + $acceptsNamedArguments = $phpDoc->acceptsNamedArguments(); } $variantsByType = ['positional' => []]; @@ -148,6 +150,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $asserts, $docComment, $returnsByReference, + $acceptsNamedArguments, ); $this->functionMap[$lowerCasedFunctionName] = $functionReflection; diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index 51a89fc3293..b6e638c9792 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -94,4 +94,9 @@ public function getUsedVariables(): array return []; } + public function acceptsNamedArguments(): bool + { + return true; + } + } diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index 8698e6196ca..c4478755802 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -187,6 +187,16 @@ public function getAsserts(): Assertions return $assertions; } + public function acceptsNamedArguments(): bool + { + $accepts = true; + foreach ($this->methods as $method) { + $accepts = $accepts && $method->acceptsNamedArguments(); + } + + return $accepts; + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 2982f71a624..3b2598c3687 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -169,6 +169,16 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + $accepts = true; + foreach ($this->methods as $method) { + $accepts = $accepts && $method->acceptsNamedArguments(); + } + + return $accepts; + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index 2a953cf36b9..78f31cdce6f 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -137,6 +137,11 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } + public function acceptsNamedArguments(): bool + { + return $this->getDeclaringClass()->acceptsNamedArguments(); + } + public function getSelfOutType(): ?Type { return null; diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index cd549f67e3a..e04381033e0 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -151,8 +151,10 @@ public function check( 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', + 'Attribute class ' . $attributeClassName . ' constructorinvoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'attribute', + $attributeConstructor->acceptsNamedArguments(), ); foreach ($parameterErrors as $error) { diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index e90aee6b80f..8994a4754b1 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -216,8 +216,10 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', 'Parameter %s of class ' . $classDisplayName . ' constructor contains unresolvable type.', + 'Class ' . $classDisplayName . ' constructor invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'new', + $constructorReflection->acceptsNamedArguments(), )); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 1925232132b..4f0d2ae447e 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -53,7 +53,7 @@ public function __construct( /** * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall - * @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string} $messages + * @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string, 14?: string} $messages * @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType * @return list */ @@ -64,6 +64,7 @@ public function check( $funcCall, array $messages, string $nodeType = 'function', + bool $acceptsNamedArguments = true, ): array { $functionParametersMinCount = 0; @@ -289,6 +290,26 @@ public function check( } } + if (!$acceptsNamedArguments && $this->checkUnresolvableParameterTypes && isset($messages[14])) { + if ($argumentName !== null) { + $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('named argument $%s', $argumentName))) + ->identifier('argument.named') + ->line($argumentLine) + ->nonIgnorable() + ->build(); + } elseif ($unpack) { + $unpackedArrayType = $scope->getType($argumentValue); + $hasStringKey = $unpackedArrayType->getIterableKeyType()->isString(); + if (!$hasStringKey->no()) { + $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('unpacked array with %s', $hasStringKey->yes() ? 'string key' : 'possibly string key'))) + ->identifier('argument.named') + ->line($argumentLine) + ->nonIgnorable() + ->build(); + } + } + } + if ($this->checkArgumentTypes) { $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index c21790b738f..d8fb352e529 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -79,6 +79,11 @@ public function processNode( $parametersAcceptors = $type->getCallableParametersAcceptors($scope); $messages = []; + $acceptsNamedArguments = true; + foreach ($parametersAcceptors as $parametersAcceptor) { + $acceptsNamedArguments = $acceptsNamedArguments && $parametersAcceptor->acceptsNamedArguments(); + } + if ( count($parametersAcceptors) === 1 && $parametersAcceptors[0] instanceof InaccessibleMethod @@ -127,8 +132,10 @@ public function processNode( 'Unknown parameter $%s in call to ' . $callableDescription . '.', 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', + ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'callable', + $acceptsNamedArguments, ), ); } diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 24dc76e1868..d1ca216791e 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -64,8 +64,10 @@ public function processNode(Node $node, Scope $scope): array 'Unknown parameter $%s in call to function ' . $functionName . '.', 'Return type of call to function ' . $functionName . ' contains unresolvable type.', 'Parameter %s of function ' . $functionName . ' contains unresolvable type.', + 'Function ' . $functionName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'function', + $function->acceptsNamedArguments(), ); } diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index 415e04b748b..c4030961cf1 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -56,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array if ($result === null) { return []; } - [$parametersAcceptor, $funcCall] = $result; + [$parametersAcceptor, $funcCall, $acceptsNamedArguments] = $result; $callableDescription = 'callable passed to call_user_func()'; @@ -75,7 +75,8 @@ public function processNode(Node $node, Scope $scope): array 'Unknown parameter $%s in call to ' . $callableDescription . '.', 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', - ]); + ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', + ], 'function', $acceptsNamedArguments); } } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 67eee301571..4f45dbf9fdc 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -70,8 +70,10 @@ public function processNode(Node $node, Scope $scope): array 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', 'Parameter %s of method ' . $messagesMethodName . ' contains unresolvable type.', + 'Method ' . $messagesMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'method', + $methodReflection->acceptsNamedArguments(), )); } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 1706bdb76e6..33612ff02cc 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -78,8 +78,10 @@ public function processNode(Node $node, Scope $scope): array 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', 'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.', 'Parameter %s of ' . $lowercasedMethodName . ' contains unresolvable type.', + $displayMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'staticMethod', + $method->acceptsNamedArguments(), )); return $errors; diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 80ef4bccdc1..0f156836060 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -124,6 +124,9 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType): $acceptedType->getTemplateTags(), $acceptedType->getThrowPoints(), $acceptedType->getImpurePoints(), + $acceptedType->getInvalidateExpressions(), + $acceptedType->getUsedVariables(), + $acceptedType->acceptsNamedArguments(), ); } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index e51cf45631f..ce30d8983c3 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -294,6 +294,11 @@ public function getUsedVariables(): array return []; } + public function acceptsNamedArguments(): bool + { + return true; + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 93b99891027..6d987f63421 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -99,6 +99,7 @@ public function __construct( ?array $impurePoints = null, private array $invalidateExpressions = [], private array $usedVariables = [], + private bool $acceptsNamedArguments = true, ) { $this->parameters = $parameters ?? []; @@ -407,6 +408,11 @@ public function getUsedVariables(): array return $this->usedVariables; } + public function acceptsNamedArguments(): bool + { + return $this->acceptsNamedArguments; + } + public function isCloneable(): TrinaryLogic { return TrinaryLogic::createYes(); @@ -562,6 +568,7 @@ public function traverse(callable $cb): Type $this->impurePoints, $this->invalidateExpressions, $this->usedVariables, + $this->acceptsNamedArguments, ); } @@ -611,6 +618,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type $this->impurePoints, $this->invalidateExpressions, $this->usedVariables, + $this->acceptsNamedArguments, ); } @@ -780,6 +788,7 @@ public static function __set_state(array $properties): Type $properties['impurePoints'], $properties['invalidateExpressions'], $properties['usedVariables'], + $properties['acceptsNamedArguments'], ); } diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index 45ff96e2c90..d392b20500e 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -48,6 +48,12 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $variant->getTemplateTypeMap(), $variant->getResolvedTemplateTypeMap(), $variant instanceof ParametersAcceptorWithPhpDocs ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + [], + $variant->getThrowPoints(), + $variant->getImpurePoints(), + $variant->getInvalidateExpressions(), + $variant->getUsedVariables(), + $variant->acceptsNamedArguments(), ); } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 90859848d0b..e1c0c8db098 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1716,4 +1716,30 @@ public function testCountArrayShift(): void $this->analyse([__DIR__ . '/data/count-array-shift.php'], $errors); } + public function testNoNamedArguments(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/no-named-arguments.php'], [ + [ + 'Function NoNamedArgumentsFunction\\foo invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 14, + ], + [ + 'Function NoNamedArgumentsFunction\foo invoked with unpacked array with string key, but it\'s not allowed because of @no-named-arguments.', + 24, + ], + [ + 'Function NoNamedArgumentsFunction\foo invoked with unpacked array with possibly string key, but it\'s not allowed because of @no-named-arguments.', + 25, + ], + [ + 'Function NoNamedArgumentsFunction\\foo invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 29, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index 9eed7393998..d10eee8e48c 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -83,4 +83,30 @@ public function testBug7057(): void $this->analyse([__DIR__ . '/data/bug-7057.php'], []); } + public function testNoNamedArguments(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/no-named-arguments-call-user-func.php'], [ + [ + 'Callable passed to call_user_func() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 29, + ], + [ + 'Callable passed to call_user_func() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 30, + ], + [ + 'Callable passed to call_user_func() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 31, + ], + [ + 'Callable passed to call_user_func() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 32, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/no-named-arguments-call-user-func.php b/tests/PHPStan/Rules/Functions/data/no-named-arguments-call-user-func.php new file mode 100644 index 00000000000..d385b739c9e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/no-named-arguments-call-user-func.php @@ -0,0 +1,33 @@ += 8.1 + +namespace NoNamedArgumentsCallUserFunc; + +use function call_user_func; + +/** + * @no-named-arguments + */ +function foo(int $i): void +{ + +} + +class Foo +{ + + /** + * @no-named-arguments + */ + public function doFoo(int $i): void + { + + } + +} + +function (Foo $f): void { + call_user_func(foo(...), i: 1); + call_user_func('NoNamedArgumentsCallUserFunc\\foo', i: 1); + call_user_func([$f, 'doFoo'], i: 1); + call_user_func($f->doFoo(...), i: 1); +}; diff --git a/tests/PHPStan/Rules/Functions/data/no-named-arguments.php b/tests/PHPStan/Rules/Functions/data/no-named-arguments.php new file mode 100644 index 00000000000..843530132ea --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/no-named-arguments.php @@ -0,0 +1,30 @@ += 8.0 + +namespace NoNamedArgumentsFunction; + +/** + * @no-named-arguments + */ +function foo(int $i): void +{ + +} + +function (): void { + foo(i: 5); +}; + +/** + * @param array $a + * @param array $b + * @param array $c + */ +function bar(array $a, array $b, array $c): void +{ + foo(...$a); + foo(...$b); + foo(...$c); + + foo(...[0 => 1]); + foo(...['i' => 1]); +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 8877e53e313..255d9f9c029 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3322,4 +3322,27 @@ public function testClosureParameterGenerics(): void $this->analyse([__DIR__ . '/data/closure-parameter-generics.php'], []); } + public function testNoNamedArguments(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/no-named-arguments.php'], [ + [ + 'Method NoNamedArgumentsMethod\Foo::doFoo() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 32, + ], + [ + 'Method NoNamedArgumentsMethod\Bar::doFoo() invoked with named argument $i, but it\'s not allowed because of @no-named-arguments.', + 33, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/no-named-arguments.php b/tests/PHPStan/Rules/Methods/data/no-named-arguments.php new file mode 100644 index 00000000000..e28e91d1d82 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/no-named-arguments.php @@ -0,0 +1,34 @@ += 8.0 + +namespace NoNamedArgumentsMethod; + +class Foo +{ + + /** + * @no-named-arguments + */ + public function doFoo(int $i): void + { + + } + +} + +/** + * @no-named-arguments + */ +class Bar +{ + + public function doFoo(int $i): void + { + + } + +} + +function (Foo $f, Bar $b): void { + $f->doFoo(i: 1); + $b->doFoo(i: 1); +}; From 374799174253f03c90ba1695b8a502330b03c6ff Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 19 Aug 2024 16:59:41 +0200 Subject: [PATCH 0017/3097] PhpMethodReflectionFactory - move `$acceptsNamedArguments` for backward compatibility --- src/Reflection/Php/PhpClassReflectionExtension.php | 2 +- src/Reflection/Php/PhpMethodReflectionFactory.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 550be0da4f7..9b9a9132e1a 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -798,12 +798,12 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $isFinal, $isPure, $asserts, - $acceptsNamedArguments, $selfOutType, $phpDocComment, $phpDocParameterOutTypes, $immediatelyInvokedCallableParameters, $closureThisParameters, + $acceptsNamedArguments, ); } diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 0745deee78e..2aa2aca526b 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -31,12 +31,12 @@ public function create( bool $isFinal, ?bool $isPure, Assertions $asserts, - bool $acceptsNamedArguments, ?Type $selfOutType, ?string $phpDocComment, array $phpDocParameterOutTypes, array $immediatelyInvokedCallableParameters = [], array $phpDocClosureThisTypeParameters = [], + bool $acceptsNamedArguments = true, ): PhpMethodReflection; } From 7453f4f75fae3d635063589467842aae29d88b54 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 Aug 2024 13:30:18 +0200 Subject: [PATCH 0018/3097] Bleeding edge - check too wide private property type --- conf/bleedingEdge.neon | 1 + conf/config.level4.neon | 5 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + src/Analyser/NodeScopeResolver.php | 2 +- src/Node/ClassPropertiesNode.php | 11 ++ src/Node/ClassStatementsGatherer.php | 13 ++ src/Node/Property/PropertyAssign.php | 31 ++++ .../TooWidePropertyTypeRule.php | 132 ++++++++++++++++++ .../TooWidePropertyTypeRuleTest.php | 59 ++++++++ .../data/too-wide-property-type.php | 87 ++++++++++++ 11 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 src/Node/Property/PropertyAssign.php create mode 100644 src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php create mode 100644 tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/too-wide-property-type.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 84fd7a8252d..fb6a5ad968f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -59,5 +59,6 @@ parameters: preciseMissingReturn: true validatePregQuote: true noImplicitWildcard: true + tooWidePropertyType: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.level4.neon b/conf/config.level4.neon index cb79c9cca5f..8f3ecc86bcc 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -54,6 +54,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureStaticCallCollector: phpstan.collector: %featureToggles.pure% + PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule: + phpstan.rules.rule: %featureToggles.tooWidePropertyType% parameters: checkAdvancedIsset: true @@ -313,3 +315,6 @@ services: - class: PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule + + - + class: PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule diff --git a/conf/config.neon b/conf/config.neon index 517728b3cd8..d347559d7f7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -95,6 +95,7 @@ parameters: validatePregQuote: false noImplicitWildcard: false narrowPregMatches: true + tooWidePropertyType: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 7cfb651b7d9..66fc2ae4275 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -90,6 +90,7 @@ parametersSchema: validatePregQuote: bool() noImplicitWildcard: bool() narrowPregMatches: bool() + tooWidePropertyType: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c48b2e439be..f1c6b0725f6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -870,7 +870,7 @@ private function processStmtNode( $this->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $classStatementsGatherer); $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer, $context); - $nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classReflection), $classScope); + $nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope); $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope); $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope); $classReflection->evictPrivateSymbols(); diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 302dab88f69..7afc4bc874b 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\PropertyInitializationExpr; use PHPStan\Node\Method\MethodCall; +use PHPStan\Node\Property\PropertyAssign; use PHPStan\Node\Property\PropertyRead; use PHPStan\Node\Property\PropertyWrite; use PHPStan\Reflection\ClassReflection; @@ -40,6 +41,7 @@ class ClassPropertiesNode extends NodeAbstract implements VirtualNode * @param array $propertyUsages * @param array $methodCalls * @param array $returnStatementNodes + * @param list $propertyAssigns */ public function __construct( private ClassLike $class, @@ -48,6 +50,7 @@ public function __construct( private array $propertyUsages, private array $methodCalls, private array $returnStatementNodes, + private array $propertyAssigns, private ClassReflection $classReflection, ) { @@ -404,4 +407,12 @@ private function getInitializedProperties(Scope $scope, array $initialInitialize return $initialInitializedProperties; } + /** + * @return list + */ + public function getPropertyAssigns(): array + { + return $this->propertyAssigns; + } + } diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index a344e4349fd..55ef9077995 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -13,6 +13,7 @@ use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; use PHPStan\Node\Constant\ClassConstantFetch; +use PHPStan\Node\Property\PropertyAssign; use PHPStan\Node\Property\PropertyRead; use PHPStan\Node\Property\PropertyWrite; use PHPStan\Reflection\ClassReflection; @@ -55,6 +56,9 @@ final class ClassStatementsGatherer /** @var array */ private array $returnStatementNodes = []; + /** @var list */ + private array $propertyAssigns = []; + /** * @param callable(Node $node, Scope $scope): void $nodeCallback */ @@ -122,6 +126,14 @@ public function getReturnStatementsNodes(): array return $this->returnStatementNodes; } + /** + * @return list + */ + public function getPropertyAssigns(): array + { + return $this->propertyAssigns; + } + public function __invoke(Node $node, Scope $scope): void { $nodeCallback = $this->nodeCallback; @@ -189,6 +201,7 @@ private function gatherNodes(Node $node, Scope $scope): void } if ($node instanceof PropertyAssignNode) { $this->propertyUsages[] = new PropertyWrite($node->getPropertyFetch(), $scope, false); + $this->propertyAssigns[] = new PropertyAssign($node, $scope); return; } if (!$node instanceof Expr) { diff --git a/src/Node/Property/PropertyAssign.php b/src/Node/Property/PropertyAssign.php new file mode 100644 index 00000000000..a88d384a736 --- /dev/null +++ b/src/Node/Property/PropertyAssign.php @@ -0,0 +1,31 @@ +assign; + } + + public function getScope(): Scope + { + return $this->scope; + } + +} diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php new file mode 100644 index 00000000000..68a513f2955 --- /dev/null +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -0,0 +1,132 @@ + + */ +final class TooWidePropertyTypeRule implements Rule +{ + + public function __construct( + private ReadWritePropertiesExtensionProvider $extensionProvider, + private PropertyReflectionFinder $propertyReflectionFinder, + ) + { + } + + public function getNodeType(): string + { + return ClassPropertiesNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + $classReflection = $node->getClassReflection(); + + foreach ($node->getProperties() as $property) { + if (!$property->isPrivate()) { + continue; + } + if ($property->isDeclaredInTrait()) { + continue; + } + if ($property->isPromoted()) { + continue; + } + $propertyName = $property->getName(); + if (!$classReflection->hasNativeProperty($propertyName)) { + continue; + } + + $propertyReflection = $classReflection->getNativeProperty($propertyName); + $propertyType = $propertyReflection->getWritableType(); + if (!$propertyType instanceof UnionType) { + continue; + } + foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($extension->isAlwaysWritten($propertyReflection, $propertyName)) { + continue 2; + } + if ($extension->isInitialized($propertyReflection, $propertyName)) { + continue 2; + } + } + + $assignedTypes = []; + foreach ($node->getPropertyAssigns() as $assign) { + $assignNode = $assign->getAssign(); + $assignPropertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($assignNode->getPropertyFetch(), $assign->getScope()); + foreach ($assignPropertyReflections as $assignPropertyReflection) { + if ($propertyName !== $assignPropertyReflection->getName()) { + continue; + } + if ($propertyReflection->getDeclaringClass()->getName() !== $assignPropertyReflection->getDeclaringClass()->getName()) { + continue; + } + + $assignedTypes[] = $assignPropertyReflection->getScope()->getType($assignNode->getAssignedExpr()); + } + } + + if ($property->getDefault() !== null) { + $assignedTypes[] = $scope->getType($property->getDefault()); + } + + if (count($assignedTypes) === 0) { + continue; + } + + $assignedType = TypeCombinator::union(...$assignedTypes); + $propertyDescription = $this->describePropertyByName($propertyReflection, $propertyName); + $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedType); + foreach ($propertyType->getTypes() as $type) { + if (!$type->isSuperTypeOf($assignedType)->no()) { + continue; + } + + if ($property->getNativeType() === null && (new NullType())->isSuperTypeOf($type)->yes()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + '%s (%s) is never assigned %s so it can be removed from the property type.', + $propertyDescription, + $propertyType->describe($verbosityLevel), + $type->describe($verbosityLevel), + )) + ->identifier('property.unusedType') + ->line($property->getStartLine()) + ->build(); + } + + } + return $errors; + } + + private function describePropertyByName(PropertyReflection $property, string $propertyName): string + { + if (!$property->isStatic()) { + return sprintf('Property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); + } + + return sprintf('Static property %s::$%s', $property->getDeclaringClass()->getDisplayName(), $propertyName); + } + +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php new file mode 100644 index 00000000000..f512d22fc73 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -0,0 +1,59 @@ + + */ +class TooWidePropertyTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new TooWidePropertyTypeRule( + new DirectReadWritePropertiesExtensionProvider([]), + new PropertyReflectionFinder(), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/too-wide-property-type.php'], [ + [ + 'Property TooWidePropertyType\Foo::$foo (int|string) is never assigned string so it can be removed from the property type.', + 9, + ], + /*[ + 'Property TooWidePropertyType\Foo::$barr (int|null) is never assigned null so it can be removed from the property type.', + 15, + ], + [ + 'Property TooWidePropertyType\Foo::$barrr (int|null) is never assigned null so it can be removed from the property type.', + 18, + ],*/ + [ + 'Property TooWidePropertyType\Foo::$baz (int|null) is never assigned null so it can be removed from the property type.', + 20, + ], + [ + 'Property TooWidePropertyType\Bar::$c (int|null) is never assigned int so it can be removed from the property type.', + 45, + ], + [ + 'Property TooWidePropertyType\Bar::$d (int|null) is never assigned null so it can be removed from the property type.', + 47, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/too-wide-property-type.php b/tests/PHPStan/Rules/TooWideTypehints/data/too-wide-property-type.php new file mode 100644 index 00000000000..b304e89340b --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/too-wide-property-type.php @@ -0,0 +1,87 @@ += 8.0 + +namespace TooWidePropertyType; + +class Foo +{ + + /** @var int|string */ + private $foo; + + /** @var int|null */ + private $bar; // do not report "null" as not assigned + + /** @var int|null */ + private $barr = 1; // report "null" as not assigned + + /** @var int|null */ + private $barrr; // assigned in constructor - report "null" as not assigned + + private int|null $baz; // report "null" as not assigned + + public function __construct() + { + $this->barrr = 1; + } + + public function doFoo(): void + { + $this->foo = 1; + $this->bar = 1; + $this->barr = 1; + $this->barrr = 1; + $this->baz = 1; + } + +} + +class Bar +{ + + private ?int $a = null; + + private ?int $b = 1; + + private ?int $c = null; + + private ?int $d = 1; + + public function doFoo(): void + { + $this->a = 1; + $this->b = null; + } + +} + +class Baz +{ + + private ?int $a = null; + + public function doFoo(): self + { + $s = new self(); + $s->a = 1; + + return $s; + } + +} + +class Lorem +{ + + public function __construct( + private ?int $a = null + ) + { + + } + + public function doFoo(): void + { + $this->a = 1; + } + +} From 1fcae1cd7946c3497da700feb5c7c3ec07ed8e79 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 Aug 2024 16:17:29 +0200 Subject: [PATCH 0019/3097] TooWidePropertyTypeRule - ignore properties covered by ReadWritePropertiesExtension --- src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 68a513f2955..8d77cb01b4d 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -61,6 +61,9 @@ public function processNode(Node $node, Scope $scope): array continue; } foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($extension->isAlwaysRead($propertyReflection, $propertyName)) { + continue 2; + } if ($extension->isAlwaysWritten($propertyReflection, $propertyName)) { continue 2; } From 22eef6d5ab9a4afafb2305258fea273be6cc06e4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 Aug 2024 20:41:09 +0200 Subject: [PATCH 0020/3097] Bleeding edge - consider implicit throw points when the only explicit one is Throw_ --- conf/bleedingEdge.neon | 1 + conf/config.neon | 2 + conf/parametersSchema.neon | 1 + src/Analyser/NodeScopeResolver.php | 15 +++++- src/Testing/RuleTestCase.php | 1 + src/Testing/TypeInferenceTestCase.php | 1 + tests/PHPStan/Analyser/AnalyserTest.php | 1 + tests/PHPStan/Analyser/nsrt/bug-4879.php | 2 +- .../PHPStan/Analyser/nsrt/explicit-throws.php | 53 +++++++++++++++++++ .../Analyser/nsrt/throw-points/try-catch.php | 10 ++-- 10 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/explicit-throws.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index fb6a5ad968f..00e7b08f18e 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -60,5 +60,6 @@ parameters: validatePregQuote: true noImplicitWildcard: true tooWidePropertyType: true + explicitThrow: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.neon b/conf/config.neon index d347559d7f7..3842191a094 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -96,6 +96,7 @@ parameters: noImplicitWildcard: false narrowPregMatches: true tooWidePropertyType: false + explicitThrow: false fileExtensions: - php checkAdvancedIsset: false @@ -538,6 +539,7 @@ services: universalObjectCratesClasses: %universalObjectCratesClasses% paramOutType: %featureToggles.paramOutType% preciseMissingReturn: %featureToggles.preciseMissingReturn% + explicitThrow: %featureToggles.explicitThrow% - class: PHPStan\Analyser\ConstantResolver diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 66fc2ae4275..3518d5ba4aa 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -91,6 +91,7 @@ parametersSchema: noImplicitWildcard: bool() narrowPregMatches: bool() tooWidePropertyType: bool() + explicitThrow: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f1c6b0725f6..d81f6151c29 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -262,6 +262,7 @@ public function __construct( private readonly bool $detectDeadTypeInMultiCatch, private readonly bool $paramOutType, private readonly bool $preciseMissingReturn, + private readonly bool $explicitThrow, ) { $earlyTerminatingMethodNames = []; @@ -1545,6 +1546,7 @@ private function processStmtNode( } // explicit only + $onlyExplicitIsThrow = true; if (count($matchingThrowPoints) === 0) { foreach ($throwPoints as $throwPointIndex => $throwPoint) { foreach ($catchTypes as $catchTypeIndex => $catchTypeItem) { @@ -1556,13 +1558,24 @@ private function processStmtNode( if (!$throwPoint->isExplicit()) { continue; } + $throwNode = $throwPoint->getNode(); + if ( + !$throwNode instanceof Throw_ + && !$throwNode instanceof Expr\Throw_ + && !($throwNode instanceof Node\Stmt\Expression && $throwNode->expr instanceof Expr\Throw_) + ) { + $onlyExplicitIsThrow = false; + } $matchingThrowPoints[$throwPointIndex] = $throwPoint; } } } // implicit only - if (count($matchingThrowPoints) === 0) { + if ( + count($matchingThrowPoints) === 0 + || ($this->explicitThrow && $onlyExplicitIsThrow) + ) { foreach ($throwPoints as $throwPointIndex => $throwPoint) { if ($throwPoint->isExplicit()) { continue; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 3b4330a2979..b507b05ae60 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -107,6 +107,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], + self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index c9c32db584e..4194d4f4d3e 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -87,6 +87,7 @@ public static function processFile( self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], + self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index b149c39ab0a..f820c64aff0 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -743,6 +743,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], + self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); $fileAnalyser = new FileAnalyser( diff --git a/tests/PHPStan/Analyser/nsrt/bug-4879.php b/tests/PHPStan/Analyser/nsrt/bug-4879.php index 1c6c9536c42..26d74b1c738 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4879.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4879.php @@ -33,7 +33,7 @@ public function sayHello2(bool $bool1): void $this->test(); } catch (\Exception $ex) { - assertVariableCertainty(TrinaryLogic::createNo(), $var); + assertVariableCertainty(TrinaryLogic::createMaybe(), $var); } } diff --git a/tests/PHPStan/Analyser/nsrt/explicit-throws.php b/tests/PHPStan/Analyser/nsrt/explicit-throws.php new file mode 100644 index 00000000000..e37d6be94a2 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/explicit-throws.php @@ -0,0 +1,53 @@ +throwInvalidArgument(); + } catch (\InvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $a); + } + } + + public function doBaz(): void + { + try { + doFoo(); + $a = 1; + $this->throwInvalidArgument(); + throw new \InvalidArgumentException(); + } catch (\InvalidArgumentException $e) { + assertVariableCertainty(TrinaryLogic::createYes(), $a); + } + } + + /** + * @throws \InvalidArgumentException + */ + private function throwInvalidArgument(): void + { + + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php b/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php index 57faddd9bb8..f39b3e4fac3 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php @@ -65,15 +65,15 @@ function (): void { $bar = 1; maybeThrows(); } catch (\InvalidArgumentException $e) { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); assertType('1|2', $foo); - assertVariableCertainty(TrinaryLogic::createNo(), $bar); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); assertVariableCertainty(TrinaryLogic::createNo(), $baz); } catch (\RuntimeException $e) { assertVariableCertainty(TrinaryLogic::createNo(), $foo); - assertVariableCertainty(TrinaryLogic::createNo(), $bar); - assertVariableCertainty(TrinaryLogic::createYes(), $baz); + assertVariableCertainty(TrinaryLogic::createMaybe(), $bar); + assertVariableCertainty(TrinaryLogic::createMaybe(), $baz); assertType('1|2', $baz); } catch (\Throwable $e) { assertType('Throwable~InvalidArgumentException|RuntimeException', $e); @@ -99,7 +99,7 @@ function (): void { throw new \InvalidArgumentException(); } catch (\InvalidArgumentException $e) { assertType('1', $foo); - assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); } }; From 30d3c0ac6018afdcf0edc5e6c7166bb6244b2c47 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 Aug 2024 09:25:32 +0200 Subject: [PATCH 0021/3097] Fix build --- src/Parser/AnonymousClassVisitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/AnonymousClassVisitor.php b/src/Parser/AnonymousClassVisitor.php index 3a179206baa..4181f6fa866 100644 --- a/src/Parser/AnonymousClassVisitor.php +++ b/src/Parser/AnonymousClassVisitor.php @@ -6,7 +6,7 @@ use PhpParser\NodeVisitorAbstract; use function count; -class AnonymousClassVisitor extends NodeVisitorAbstract +final class AnonymousClassVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_ANONYMOUS_CLASS = 'anonymousClass'; From 30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 Aug 2024 10:22:23 +0200 Subject: [PATCH 0022/3097] Bleeding edge - check existing classes in `@param-out` --- conf/bleedingEdge.neon | 1 + conf/config.neon | 2 ++ conf/parametersSchema.neon | 1 + src/Rules/FunctionDefinitionCheck.php | 7 +++++ ...lassesInArrowFunctionTypehintsRuleTest.php | 1 + ...stingClassesInClosureTypehintsRuleTest.php | 1 + .../ExistingClassesInTypehintsRuleTest.php | 19 ++++++++++++ .../Functions/data/param-out-classes.php | 23 +++++++++++++++ .../ExistingClassesInTypehintsRuleTest.php | 19 ++++++++++++ .../Rules/Methods/data/param-out-classes.php | 29 +++++++++++++++++++ 10 files changed, 103 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/param-out-classes.php create mode 100644 tests/PHPStan/Rules/Methods/data/param-out-classes.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 00e7b08f18e..a9f20432b22 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -61,5 +61,6 @@ parameters: noImplicitWildcard: true tooWidePropertyType: true explicitThrow: true + absentTypeChecks: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.neon b/conf/config.neon index bb384e9511d..4097e0d85d9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -97,6 +97,7 @@ parameters: narrowPregMatches: true tooWidePropertyType: false explicitThrow: false + absentTypeChecks: false fileExtensions: - php checkAdvancedIsset: false @@ -970,6 +971,7 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% + absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\FunctionReturnTypeCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3518d5ba4aa..2f3ef3b668c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -92,6 +92,7 @@ parametersSchema: narrowPregMatches: bool() tooWidePropertyType: bool() explicitThrow: bool() + absentTypeChecks: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index fbedaffbd7a..50a0c99e85d 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -52,6 +52,7 @@ public function __construct( private PhpVersion $phpVersion, private bool $checkClassCaseSensitivity, private bool $checkThisOnly, + private bool $absentTypeChecks, ) { } @@ -583,9 +584,15 @@ private function getParameterReferencedClasses(ParameterReflection $parameter): return $parameter->getNativeType()->getReferencedClasses(); } + $outTypeClasses = []; + if ($parameter->getOutType() !== null && $this->absentTypeChecks) { + $outTypeClasses = $parameter->getOutType()->getReferencedClasses(); + } + return array_merge( $parameter->getNativeType()->getReferencedClasses(), $parameter->getPhpDocType()->getReferencedClasses(), + $outTypeClasses, ); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index 2b2cec639bd..2417944d1af 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, + true, ), new PhpVersion(PHP_VERSION_ID), ); diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 439f5bdd711..86f87255730 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, + true, ), ); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 227044f2d7b..5cbe1c95eb0 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, + true, ), ); } @@ -451,4 +452,22 @@ public function testTemplateInParamOut(): void ]); } + public function testParamOutClasses(): void + { + $this->analyse([__DIR__ . '/data/param-out-classes.php'], [ + [ + 'Parameter $p of function ParamOutClasses\doFoo() has invalid type ParamOutClasses\Nonexistent.', + 20, + ], + [ + 'Parameter $q of function ParamOutClasses\doFoo() has invalid type ParamOutClasses\FooTrait.', + 20, + ], + [ + 'Class ParamOutClasses\Foo referenced with incorrect case: ParamOutClasses\fOO.', + 20, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/param-out-classes.php b/tests/PHPStan/Rules/Functions/data/param-out-classes.php new file mode 100644 index 00000000000..8bb5c9da963 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-out-classes.php @@ -0,0 +1,23 @@ +phpVersionId), true, false, + true, ), ); } @@ -471,4 +472,22 @@ public function testTemplateInParamOut(): void ]); } + public function testParamOutClasses(): void + { + $this->analyse([__DIR__ . '/data/param-out-classes.php'], [ + [ + 'Parameter $p of method ParamOutClassesMethods\Bar::doFoo() has invalid type ParamOutClassesMethods\Nonexistent.', + 23, + ], + [ + 'Parameter $q of method ParamOutClassesMethods\Bar::doFoo() has invalid type ParamOutClassesMethods\FooTrait.', + 23, + ], + [ + 'Class ParamOutClassesMethods\Foo referenced with incorrect case: ParamOutClassesMethods\fOO.', + 23, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/param-out-classes.php b/tests/PHPStan/Rules/Methods/data/param-out-classes.php new file mode 100644 index 00000000000..8141c6b1f8d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/param-out-classes.php @@ -0,0 +1,29 @@ + Date: Thu, 22 Aug 2024 22:26:13 +0200 Subject: [PATCH 0023/3097] pr-base-on-previous-branch.yml - do not comment on 1.12.x anymore --- .github/workflows/pr-base-on-previous-branch.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-base-on-previous-branch.yml b/.github/workflows/pr-base-on-previous-branch.yml index 84015d215a7..85b8974449b 100644 --- a/.github/workflows/pr-base-on-previous-branch.yml +++ b/.github/workflows/pr-base-on-previous-branch.yml @@ -7,7 +7,7 @@ on: types: - opened branches: - - '1.12.x' + - '2.0.x' jobs: @@ -19,6 +19,6 @@ jobs: - name: Comment PR uses: peter-evans/create-or-update-comment@v4 with: - body: "You've opened the pull request against the latest branch 1.12.x. If your code is relevant on 1.11.x and you want it to be released sooner, please rebase your pull request and change its target to 1.11.x." + body: "You've opened the pull request against the latest branch 2.0.x. If your code is relevant on 1.12.x and you want it to be released sooner, please rebase your pull request and change its target to 1.12.x." token: ${{ secrets.PHPSTAN_BOT_TOKEN }} issue-number: ${{ github.event.pull_request.number }} From 2fa539a39e06bcc3155b109fd8d246703ceb176d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 09:47:25 +0200 Subject: [PATCH 0024/3097] Bleeding edge - check existing classes in `@param-closure-this` --- src/Rules/FunctionDefinitionCheck.php | 13 +++++--- .../ExistingClassesInTypehintsRuleTest.php | 18 +++++++++++ .../data/param-closure-this-classes.php | 27 ++++++++++++++++ .../ExistingClassesInTypehintsRuleTest.php | 18 +++++++++++ .../data/param-closure-this-classes.php | 32 +++++++++++++++++++ 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/param-closure-this-classes.php create mode 100644 tests/PHPStan/Rules/Methods/data/param-closure-this-classes.php diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 50a0c99e85d..c49d6ce4ea2 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -584,15 +584,20 @@ private function getParameterReferencedClasses(ParameterReflection $parameter): return $parameter->getNativeType()->getReferencedClasses(); } - $outTypeClasses = []; - if ($parameter->getOutType() !== null && $this->absentTypeChecks) { - $outTypeClasses = $parameter->getOutType()->getReferencedClasses(); + $moreClasses = []; + if ($this->absentTypeChecks) { + if ($parameter->getOutType() !== null) { + $moreClasses = array_merge($moreClasses, $parameter->getOutType()->getReferencedClasses()); + } + if ($parameter->getClosureThisType() !== null) { + $moreClasses = array_merge($moreClasses, $parameter->getClosureThisType()->getReferencedClasses()); + } } return array_merge( $parameter->getNativeType()->getReferencedClasses(), $parameter->getPhpDocType()->getReferencedClasses(), - $outTypeClasses, + $moreClasses, ); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 5cbe1c95eb0..6dc1acf00c2 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -470,4 +470,22 @@ public function testParamOutClasses(): void ]); } + public function testParamClosureThisClasses(): void + { + $this->analyse([__DIR__ . '/data/param-closure-this-classes.php'], [ + [ + 'Parameter $a of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\Nonexistent.', + 24, + ], + [ + 'Parameter $b of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\FooTrait.', + 25, + ], + [ + 'Class ParamClosureThisClasses\Foo referenced with incorrect case: ParamClosureThisClasses\fOO.', + 26, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/param-closure-this-classes.php b/tests/PHPStan/Rules/Functions/data/param-closure-this-classes.php new file mode 100644 index 00000000000..d1652c132b9 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-closure-this-classes.php @@ -0,0 +1,27 @@ +analyse([__DIR__ . '/data/param-closure-this-classes.php'], [ + [ + 'Parameter $a of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\Nonexistent.', + 24, + ], + [ + 'Parameter $b of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\FooTrait.', + 25, + ], + [ + 'Class ParamClosureThisClasses\Foo referenced with incorrect case: ParamClosureThisClasses\fOO.', + 26, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/param-closure-this-classes.php b/tests/PHPStan/Rules/Methods/data/param-closure-this-classes.php new file mode 100644 index 00000000000..f36ffbfb1f7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/param-closure-this-classes.php @@ -0,0 +1,32 @@ + Date: Fri, 23 Aug 2024 09:59:06 +0200 Subject: [PATCH 0025/3097] Fix build --- .../Functions/ExistingClassesInTypehintsRuleTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 6dc1acf00c2..78415d46167 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -474,16 +474,16 @@ public function testParamClosureThisClasses(): void { $this->analyse([__DIR__ . '/data/param-closure-this-classes.php'], [ [ - 'Parameter $a of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\Nonexistent.', - 24, + 'Parameter $a of function ParamClosureThisClassesFunctions\doFoo() has invalid type ParamClosureThisClassesFunctions\Nonexistent.', + 21, ], [ - 'Parameter $b of method ParamClosureThisClasses\Bar::doFoo() has invalid type ParamClosureThisClasses\FooTrait.', - 25, + 'Parameter $b of function ParamClosureThisClassesFunctions\doFoo() has invalid type ParamClosureThisClassesFunctions\FooTrait.', + 22, ], [ - 'Class ParamClosureThisClasses\Foo referenced with incorrect case: ParamClosureThisClasses\fOO.', - 26, + 'Class ParamClosureThisClassesFunctions\Foo referenced with incorrect case: ParamClosureThisClassesFunctions\fOO.', + 23, ], ]); } From 95c0a5806c65c975201b9d3a464873f75a04c8b8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 10:12:35 +0200 Subject: [PATCH 0026/3097] Check invalid `@param-closure-this` --- src/PhpDoc/PhpDocNodeResolver.php | 8 ++- .../PhpDoc/IncompatiblePhpDocTypeRule.php | 61 ++++++++++------ .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 38 ++++++++++ .../Rules/PhpDoc/data/param-closure-this.php | 72 +++++++++++++++++++ 4 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/param-closure-this.php diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 6d52d72f950..58a63f6e853 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -36,6 +36,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function array_key_exists; @@ -421,7 +422,12 @@ public function resolveParamClosureThisTags(PhpDocNode $phpDocNode, NameScope $n foreach (['@param-closure-this', '@phpstan-param-closure-this'] as $tagName) { foreach ($phpDocNode->getParamClosureThisTagValues($tagName) as $tagValue) { $parameterName = substr($tagValue->parameterName, 1); - $closureThisTypes[$parameterName] = new ParamClosureThisTag($this->typeNodeResolver->resolve($tagValue->type, $nameScope)); + $closureThisTypes[$parameterName] = new ParamClosureThisTag( + TypeCombinator::intersect( + $this->typeNodeResolver->resolve($tagValue->type, $nameScope), + new ObjectWithoutClassType(), + ), + ); } } diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index 131ee1ec911..acdbeef79f2 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -12,11 +12,13 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\ClosureType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function array_merge; +use function in_array; use function is_string; use function sprintf; use function trim; @@ -68,10 +70,9 @@ public function processNode(Node $node, Scope $scope): array $errors = []; - foreach ([$resolvedPhpDoc->getParamTags(), $resolvedPhpDoc->getParamOutTags()] as $parameters) { + foreach (['@param' => $resolvedPhpDoc->getParamTags(), '@param-out' => $resolvedPhpDoc->getParamOutTags(), '@param-closure-this' => $resolvedPhpDoc->getParamClosureThisTags()] as $tagName => $parameters) { foreach ($parameters as $parameterName => $phpDocParamTag) { $phpDocParamType = $phpDocParamTag->getType(); - $tagName = $phpDocParamTag instanceof ParamTag ? '@param' : '@param-out'; if (!isset($nativeParameterTypes[$parameterName])) { $errors[] = RuleErrorBuilder::message(sprintf( @@ -99,7 +100,6 @@ public function processNode(Node $node, Scope $scope): array ) { $phpDocParamType = $phpDocParamType->getIterableValueType(); } - $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); $escapedTagName = SprintfHelper::escapeFormatString($tagName); @@ -160,28 +160,43 @@ public function processNode(Node $node, Scope $scope): array continue; } - if ($isParamSuperType->no()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', - $tagName, - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()), - ))->identifier('parameter.phpDocType')->build(); - - } elseif ($isParamSuperType->maybe()) { - $errorBuilder = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', - $tagName, - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()), - ))->identifier('parameter.phpDocType'); - if ($phpDocParamType instanceof TemplateType) { - $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); + if (in_array($tagName, ['@param', '@param-out'], true)) { + $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); + if ($isParamSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', + $tagName, + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('parameter.phpDocType')->build(); + + } elseif ($isParamSuperType->maybe()) { + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', + $tagName, + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('parameter.phpDocType'); + if ($phpDocParamType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); + } + + $errors[] = $errorBuilder->build(); } + } - $errors[] = $errorBuilder->build(); + if ($tagName === '@param-closure-this') { + $isNonClosure = (new ClosureType())->isSuperTypeOf($nativeParamType)->no(); + if ($isNonClosure) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s is for parameter $%s with non-Closure type %s.', + $tagName, + $parameterName, + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('paramClosureThis.nonClosure')->build(); + } } } } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index 223fd616b06..ace29c0b955 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -442,4 +442,42 @@ public function testBug10622B(): void $this->analyse([__DIR__ . '/data/bug-10622b.php'], []); } + public function testParamClosureThis(): void + { + $this->analyse([__DIR__ . '/data/param-closure-this.php'], [ + [ + 'PHPDoc tag @param-closure-this references unknown parameter: $b', + 20, + ], + [ + 'PHPDoc tag @param-closure-this for parameter $i contains unresolvable type.', + 27, + ], + [ + 'PHPDoc tag @param-closure-this for parameter $i contains unresolvable type.', + 34, + ], + [ + 'PHPDoc tag @param-closure-this is for parameter $i with non-Closure type string.', + 41, + ], + [ + 'PHPDoc tag @param-closure-this for parameter $i contains generic type Exception but class Exception is not generic.', + 48, + ], + [ + 'Generic type ParamClosureThisPhpDocRule\FooBar in PHPDoc tag @param-closure-this for parameter $i does not specify all template types of class ParamClosureThisPhpDocRule\FooBar: T, TT', + 55, + ], + [ + 'Type mixed in generic type ParamClosureThisPhpDocRule\FooBar in PHPDoc tag @param-closure-this for parameter $i is not subtype of template type T of int of class ParamClosureThisPhpDocRule\FooBar.', + 55, + ], + [ + 'Generic type ParamClosureThisPhpDocRule\FooBar in PHPDoc tag @param-closure-this for parameter $i does not specify all template types of class ParamClosureThisPhpDocRule\FooBar: T, TT', + 62, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/param-closure-this.php b/tests/PHPStan/Rules/PhpDoc/data/param-closure-this.php new file mode 100644 index 00000000000..46ad5bde9c7 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/param-closure-this.php @@ -0,0 +1,72 @@ + $i + */ +function invalidParamClosureThisGeneric(callable $i) { + +} + +/** + * @param-closure-this FooBar $i + */ +function invalidParamClosureThisWrongGenericParams(callable $i) { + +} + +/** + * @param-closure-this FooBar $i + */ +function invalidParamClosureThisNotAllGenericParams(callable $i) { + +} + +/** + * @template T of int + * @template TT of string + */ +class FooBar { + +} From 580a6add422f4e34191df9e7a77ba1655e914bda Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 10:52:50 +0200 Subject: [PATCH 0027/3097] Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` --- conf/config.level2.neon | 1 + src/PhpDoc/StubValidator.php | 2 + ...bleParamImmediatelyInvokedCallableRule.php | 94 +++++++++++++++++++ ...aramImmediatelyInvokedCallableRuleTest.php | 60 ++++++++++++ ...ble-param-immediately-invoked-callable.php | 80 ++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 src/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRule.php create mode 100644 tests/PHPStan/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRuleTest.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/incompatible-param-immediately-invoked-callable.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 907c83e394e..347bdb26294 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -40,6 +40,7 @@ rules: - PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule - PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule + - PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule - PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule - PHPStan\Rules\Classes\RequireImplementsRule - PHPStan\Rules\Classes\RequireExtendsRule diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 91ff9d9360d..8489946fa76 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -54,6 +54,7 @@ use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; +use PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule; use PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule; use PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule; @@ -197,6 +198,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $container->getParameter('featureToggles')['allInvalidPhpDocs'], $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), + new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), new InvalidThrowsPhpDocValueRule($fileTypeMapper), // level 6 diff --git a/src/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRule.php b/src/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRule.php new file mode 100644 index 00000000000..fa6f36463ce --- /dev/null +++ b/src/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRule.php @@ -0,0 +1,94 @@ + + */ +final class IncompatibleParamImmediatelyInvokedCallableRule implements Rule +{ + + public function __construct( + private FileTypeMapper $fileTypeMapper, + ) + { + } + + public function getNodeType(): string + { + return FunctionLike::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof Node\Stmt\ClassMethod) { + $functionName = $node->name->name; + } elseif ($node instanceof Node\Stmt\Function_) { + $functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); + } else { + return []; + } + + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $functionName, + $docComment->getText(), + ); + $nativeParameterTypes = []; + foreach ($node->getParams() as $parameter) { + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new ShouldNotHappenException(); + } + $nativeParameterTypes[$parameter->var->name] = $scope->getFunctionType( + $parameter->type, + $scope->isParameterValueNullable($parameter), + false, + ); + } + + $errors = []; + foreach ($resolvedPhpDoc->getParamsImmediatelyInvokedCallable() as $parameterName => $immediately) { + $tagName = $immediately ? '@param-immediately-invoked-callable' : '@param-later-invoked-callable'; + if (!isset($nativeParameterTypes[$parameterName])) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s references unknown parameter: $%s', + $tagName, + $parameterName, + ))->identifier('parameter.notFound')->build(); + } elseif ($nativeParameterTypes[$parameterName]->isCallable()->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s is for parameter $%s with non-callable type %s.', + $tagName, + $parameterName, + $nativeParameterTypes[$parameterName]->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf( + '%s.nonCallable', + $immediately ? 'paramImmediatelyInvokedCallable' : 'paramLaterInvokedCallable', + ))->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRuleTest.php new file mode 100644 index 00000000000..d19443e6443 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRuleTest.php @@ -0,0 +1,60 @@ + + */ +class IncompatibleParamImmediatelyInvokedCallableRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new IncompatibleParamImmediatelyInvokedCallableRule( + self::getContainer()->getByType(FileTypeMapper::class), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/incompatible-param-immediately-invoked-callable.php'], [ + [ + 'PHPDoc tag @param-immediately-invoked-callable references unknown parameter: $b', + 21, + ], + [ + 'PHPDoc tag @param-later-invoked-callable references unknown parameter: $c', + 21, + ], + [ + 'PHPDoc tag @param-immediately-invoked-callable is for parameter $b with non-callable type int.', + 30, + ], + [ + 'PHPDoc tag @param-later-invoked-callable is for parameter $b with non-callable type int.', + 39, + ], + [ + 'PHPDoc tag @param-immediately-invoked-callable references unknown parameter: $b', + 59, + ], + [ + 'PHPDoc tag @param-later-invoked-callable references unknown parameter: $c', + 59, + ], + [ + 'PHPDoc tag @param-immediately-invoked-callable is for parameter $b with non-callable type int.', + 68, + ], + [ + 'PHPDoc tag @param-later-invoked-callable is for parameter $b with non-callable type int.', + 77, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-param-immediately-invoked-callable.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-param-immediately-invoked-callable.php new file mode 100644 index 00000000000..79cd19f1160 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-param-immediately-invoked-callable.php @@ -0,0 +1,80 @@ + Date: Fri, 23 Aug 2024 11:12:21 +0200 Subject: [PATCH 0028/3097] StubValidator - added missing rules --- src/PhpDoc/StubValidator.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 8489946fa76..b8396455d86 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -54,9 +54,11 @@ use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; +use PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule; use PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule; +use PHPStan\Rules\PhpDoc\IncompatibleSelfOutTypeRule; use PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule; use PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule; use PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule; @@ -199,6 +201,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), + new IncompatibleSelfOutTypeRule(), + new IncompatibleClassConstantPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper), new InvalidThrowsPhpDocValueRule($fileTypeMapper), // level 6 From 6838669976bf20232abde36ecdd52b1770fa50c9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 11:35:13 +0200 Subject: [PATCH 0029/3097] Bleeding edge - check existing classes in `@phpstan-self-out` --- src/Rules/FunctionDefinitionCheck.php | 35 +++++++++++++++++- .../ExistingClassesInTypehintsRuleTest.php | 18 +++++++++ tests/PHPStan/Rules/Methods/data/self-out.php | 37 +++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/self-out.php diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index c49d6ce4ea2..a26ea1cb84e 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -246,7 +246,7 @@ public function checkClassMethod( /** @var ParametersAcceptorWithPhpDocs $parametersAcceptor */ $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - return $this->checkParametersAcceptor( + $errors = $this->checkParametersAcceptor( $parametersAcceptor, $methodNode, $parameterMessage, @@ -256,6 +256,39 @@ public function checkClassMethod( $unresolvableParameterTypeMessage, $unresolvableReturnTypeMessage, ); + + $selfOutType = $methodReflection->getSelfOutType(); + if ($selfOutType !== null && $this->absentTypeChecks) { + $selfOutTypeReferencedClasses = $selfOutType->getReferencedClasses(); + + foreach ($selfOutTypeReferencedClasses as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class)) + ->line($methodNode->getStartLine()) + ->identifier('class.notFound') + ->build(); + continue; + } + if (!$this->reflectionProvider->getClass($class)->isTrait()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class)) + ->line($methodNode->getStartLine()) + ->identifier('selfOut.trait') + ->build(); + } + + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames( + array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $methodNode), $selfOutTypeReferencedClasses), + $this->checkClassCaseSensitivity, + ), + ); + } + + return $errors; } /** diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 0d13ec6d876..f33e974e08d 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -508,4 +508,22 @@ public function testParamClosureThisClasses(): void ]); } + public function testSelfOut(): void + { + $this->analyse([__DIR__ . '/data/self-out.php'], [ + [ + 'Method SelfOutClasses\Foo::doFoo() has invalid return type SelfOutClasses\Nonexistent.', + 16, + ], + [ + 'Method SelfOutClasses\Foo::doBar() has invalid return type SelfOutClasses\FooTrait.', + 24, + ], + [ + 'Class SelfOutClasses\Foo referenced with incorrect case: SelfOutClasses\fOO.', + 32, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/self-out.php b/tests/PHPStan/Rules/Methods/data/self-out.php new file mode 100644 index 00000000000..887ee2fbb0f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/self-out.php @@ -0,0 +1,37 @@ + Date: Fri, 23 Aug 2024 12:56:21 +0200 Subject: [PATCH 0030/3097] Fix `@phpstan-self-out` error message --- src/Rules/FunctionDefinitionCheck.php | 5 +++-- src/Rules/Methods/ExistingClassesInTypehintsRule.php | 5 +++++ .../Rules/Methods/ExistingClassesInTypehintsRuleTest.php | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index a26ea1cb84e..19a76e042c2 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -241,6 +241,7 @@ public function checkClassMethod( string $templateTypeMissingInParameterMessage, string $unresolvableParameterTypeMessage, string $unresolvableReturnTypeMessage, + string $selfOutMessage, ): array { /** @var ParametersAcceptorWithPhpDocs $parametersAcceptor */ @@ -263,7 +264,7 @@ public function checkClassMethod( foreach ($selfOutTypeReferencedClasses as $class) { if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class)) + $errors[] = RuleErrorBuilder::message(sprintf($selfOutMessage, $class)) ->line($methodNode->getStartLine()) ->identifier('class.notFound') ->build(); @@ -273,7 +274,7 @@ public function checkClassMethod( continue; } - $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $class)) + $errors[] = RuleErrorBuilder::message(sprintf($selfOutMessage, $class)) ->line($methodNode->getStartLine()) ->identifier('selfOut.trait') ->build(); diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 702fc0f299e..fcc46717085 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -56,6 +56,11 @@ public function processNode(Node $node, Scope $scope): array $className, $methodName, ), + sprintf( + 'Method %s::%s() has invalid @phpstan-self-out type %%s.', + $className, + $methodName, + ), ); } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index f33e974e08d..b86302f4536 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -512,11 +512,11 @@ public function testSelfOut(): void { $this->analyse([__DIR__ . '/data/self-out.php'], [ [ - 'Method SelfOutClasses\Foo::doFoo() has invalid return type SelfOutClasses\Nonexistent.', + 'Method SelfOutClasses\Foo::doFoo() has invalid @phpstan-self-out type SelfOutClasses\Nonexistent.', 16, ], [ - 'Method SelfOutClasses\Foo::doBar() has invalid return type SelfOutClasses\FooTrait.', + 'Method SelfOutClasses\Foo::doBar() has invalid @phpstan-self-out type SelfOutClasses\FooTrait.', 24, ], [ From 0dfd8217699fc1c4796bcafbf2f6e04137938365 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 12:59:01 +0200 Subject: [PATCH 0031/3097] Do not allow `@phpstan-self-out` above static method --- src/Analyser/NodeScopeResolver.php | 2 +- .../PhpDoc/IncompatibleSelfOutTypeRule.php | 21 ++++++++++++------- tests/PHPStan/Analyser/nsrt/self-out.php | 11 ++++++++++ .../IncompatibleSelfOutTypeRuleTest.php | 4 ++++ .../data/incompatible-self-out-type.php | 14 +++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a085f8f07b2..185c2c55308 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2649,7 +2649,7 @@ static function (): void { $nodeCallback(new InvalidateExprNode($expr->var), $scope); $scope = $scope->invalidateExpression($expr->var, true); } - if ($parametersAcceptor !== null) { + if ($parametersAcceptor !== null && !$methodReflection->isStatic()) { $selfOutType = $methodReflection->getSelfOutType(); if ($selfOutType !== null) { $scope = $scope->assignExpression( diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index 46164fff021..b3092b7a4aa 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -34,19 +34,24 @@ public function processNode(Node $node, Scope $scope): array $classReflection = $method->getDeclaringClass(); $classType = new ObjectType($classReflection->getName(), null, $classReflection); - if ($classType->isSuperTypeOf($selfOutType)->yes()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( + $errors = []; + if (!$classType->isSuperTypeOf($selfOutType)->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( 'Self-out type %s of method %s::%s is not subtype of %s.', $selfOutType->describe(VerbosityLevel::precise()), $classReflection->getName(), $method->getName(), $classType->describe(VerbosityLevel::precise()), - ))->identifier('selfOut.type')->build(), - ]; + ))->identifier('selfOut.type')->build(); + } + + if ($method->isStatic()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-self-out is not supported above static method %s::%s().', $classReflection->getName(), $method->getName())) + ->identifier('selfOut.static') + ->build(); + } + + return $errors; } } diff --git a/tests/PHPStan/Analyser/nsrt/self-out.php b/tests/PHPStan/Analyser/nsrt/self-out.php index d4de8dbf840..fa623d2d5a8 100644 --- a/tests/PHPStan/Analyser/nsrt/self-out.php +++ b/tests/PHPStan/Analyser/nsrt/self-out.php @@ -50,6 +50,14 @@ public function setData($data) { */ public function test(): void { } + + /** + * @phpstan-self-out self + */ + public static function selfOutWithStaticMethod(): void + { + + } } /** @@ -94,4 +102,7 @@ function () { $i->setData(true); assertType('SelfOut\\a', $i); + + $i->selfOutWithStaticMethod(); + assertType('SelfOut\\a', $i); }; diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php index 96ea8862571..dae30a10398 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php @@ -27,6 +27,10 @@ public function testRule(): void 'Self-out type IncompatibleSelfOutType\A|null of method IncompatibleSelfOutType\A::four is not subtype of IncompatibleSelfOutType\A.', 28, ], + [ + 'PHPDoc tag @phpstan-self-out is not supported above static method IncompatibleSelfOutType\Foo::selfOutStatic().', + 38, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php index a0c4e977a05..018b6b1c98e 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php @@ -27,3 +27,17 @@ public function three(); */ public function four(); } + +/** + * @template T + */ +class Foo +{ + + /** @phpstan-self-out self */ + public static function selfOutStatic(): void + { + + } + +} From e182c0662df24e57c81b1d49e22963cad5ff5d13 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 13:13:07 +0200 Subject: [PATCH 0032/3097] Check unresolvable types in `@phpstan-self-out` --- src/PhpDoc/StubValidator.php | 2 +- src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php | 14 +++++++++++++- .../PhpDoc/IncompatibleSelfOutTypeRuleTest.php | 10 +++++++++- .../PhpDoc/data/incompatible-self-out-type.php | 16 ++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index b8396455d86..190d3e296c1 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -201,7 +201,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), - new IncompatibleSelfOutTypeRule(), + new IncompatibleSelfOutTypeRule($unresolvableTypeHelper), new IncompatibleClassConstantPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper), new InvalidThrowsPhpDocValueRule($fileTypeMapper), diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index b3092b7a4aa..e81665cf8b3 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -17,6 +17,10 @@ final class IncompatibleSelfOutTypeRule implements Rule { + public function __construct(private UnresolvableTypeHelper $unresolvableTypeHelper) + { + } + public function getNodeType(): string { return InClassMethodNode::class; @@ -39,7 +43,7 @@ public function processNode(Node $node, Scope $scope): array $errors[] = RuleErrorBuilder::message(sprintf( 'Self-out type %s of method %s::%s is not subtype of %s.', $selfOutType->describe(VerbosityLevel::precise()), - $classReflection->getName(), + $classReflection->getDisplayName(), $method->getName(), $classType->describe(VerbosityLevel::precise()), ))->identifier('selfOut.type')->build(); @@ -51,6 +55,14 @@ public function processNode(Node $node, Scope $scope): array ->build(); } + if ($this->unresolvableTypeHelper->containsUnresolvableType($selfOutType)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @phpstan-self-out for method %s::%s() contains unresolvable type.', + $classReflection->getDisplayName(), + $method->getName(), + ))->identifier('parameter.unresolvableType')->build(); + } + return $errors; } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php index dae30a10398..efb2481bb6f 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php @@ -13,7 +13,7 @@ class IncompatibleSelfOutTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IncompatibleSelfOutTypeRule(); + return new IncompatibleSelfOutTypeRule(new UnresolvableTypeHelper()); } public function testRule(): void @@ -31,6 +31,14 @@ public function testRule(): void 'PHPDoc tag @phpstan-self-out is not supported above static method IncompatibleSelfOutType\Foo::selfOutStatic().', 38, ], + [ + 'PHPDoc tag @phpstan-self-out for method IncompatibleSelfOutType\Foo::doFoo() contains unresolvable type.', + 46, + ], + [ + 'PHPDoc tag @phpstan-self-out for method IncompatibleSelfOutType\Foo::doBar() contains unresolvable type.', + 54, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php index 018b6b1c98e..6cf1e7057ae 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php @@ -40,4 +40,20 @@ public static function selfOutStatic(): void } + /** + * @phpstan-self-out int&string + */ + public function doFoo(): void + { + + } + + /** + * @phpstan-self-out self + */ + public function doBar(): void + { + + } + } From 9ebc315589ba2086279dd4c404ef77a33f8b43a7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 16:51:39 +0200 Subject: [PATCH 0033/3097] Call GenericObjectTypeCheck from IncompatibleSelfOutTypeRule --- src/PhpDoc/StubValidator.php | 2 +- .../PhpDoc/IncompatibleSelfOutTypeRule.php | 38 ++++++++++++++- .../IncompatibleSelfOutTypeRuleTest.php | 19 +++++++- .../data/incompatible-self-out-type.php | 46 +++++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 190d3e296c1..fb5a46f209a 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -201,7 +201,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), - new IncompatibleSelfOutTypeRule($unresolvableTypeHelper), + new IncompatibleSelfOutTypeRule($unresolvableTypeHelper, $genericObjectTypeCheck), new IncompatibleClassConstantPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper), new InvalidThrowsPhpDocValueRule($fileTypeMapper), diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index e81665cf8b3..ca8bceb0fb2 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -4,11 +4,14 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassMethodNode; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function array_merge; use function sprintf; /** @@ -17,7 +20,10 @@ final class IncompatibleSelfOutTypeRule implements Rule { - public function __construct(private UnresolvableTypeHelper $unresolvableTypeHelper) + public function __construct( + private UnresolvableTypeHelper $unresolvableTypeHelper, + private GenericObjectTypeCheck $genericObjectTypeCheck, + ) { } @@ -63,7 +69,35 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('parameter.unresolvableType')->build(); } - return $errors; + $escapedTagName = SprintfHelper::escapeFormatString('@phpstan-self-out'); + + return array_merge($errors, $this->genericObjectTypeCheck->check( + $selfOutType, + sprintf( + 'PHPDoc tag %s contains generic type %%s but %%s %%s is not generic.', + $escapedTagName, + ), + sprintf( + 'Generic type %%s in PHPDoc tag %s does not specify all template types of %%s %%s: %%s', + $escapedTagName, + ), + sprintf( + 'Generic type %%s in PHPDoc tag %s specifies %%d template types, but %%s %%s supports only %%d: %%s', + $escapedTagName, + ), + sprintf( + 'Type %%s in generic type %%s in PHPDoc tag %s is not subtype of template type %%s of %%s %%s.', + $escapedTagName, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s is in conflict with %%s template type %%s of %%s %%s.', + $escapedTagName, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s is redundant, template type %%s of %%s %%s has the same variance.', + $escapedTagName, + ), + )); } } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php index efb2481bb6f..2ab4973cefb 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleSelfOutTypeRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -13,7 +14,7 @@ class IncompatibleSelfOutTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IncompatibleSelfOutTypeRule(new UnresolvableTypeHelper()); + return new IncompatibleSelfOutTypeRule(new UnresolvableTypeHelper(), new GenericObjectTypeCheck()); } public function testRule(): void @@ -39,6 +40,22 @@ public function testRule(): void 'PHPDoc tag @phpstan-self-out for method IncompatibleSelfOutType\Foo::doBar() contains unresolvable type.', 54, ], + [ + 'PHPDoc tag @phpstan-self-out contains generic type IncompatibleSelfOutType\GenericCheck but class IncompatibleSelfOutType\GenericCheck is not generic.', + 67, + ], + [ + 'Generic type IncompatibleSelfOutType\GenericCheck2 in PHPDoc tag @phpstan-self-out does not specify all template types of class IncompatibleSelfOutType\GenericCheck2: T, U', + 84, + ], + [ + 'Generic type IncompatibleSelfOutType\GenericCheck2, string> in PHPDoc tag @phpstan-self-out specifies 3 template types, but class IncompatibleSelfOutType\GenericCheck2 supports only 2: T, U', + 92, + ], + [ + 'Type string in generic type IncompatibleSelfOutType\GenericCheck2 in PHPDoc tag @phpstan-self-out is not subtype of template type U of int of class IncompatibleSelfOutType\GenericCheck2.', + 100, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php index 6cf1e7057ae..c60ff3ce6c0 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-self-out-type.php @@ -57,3 +57,49 @@ public function doBar(): void } } + +class GenericCheck +{ + + /** + * @phpstan-self-out self + */ + public function doFoo(): void + { + + } + +} + +/** + * @template T of \Exception + * @template U of int + */ +class GenericCheck2 +{ + + /** + * @phpstan-self-out self<\InvalidArgumentException> + */ + public function doFoo(): void + { + + } + + /** + * @phpstan-self-out self<\InvalidArgumentException, positive-int, string> + */ + public function doFoo2(): void + { + + } + + /** + * @phpstan-self-out self<\InvalidArgumentException, string> + */ + public function doFoo3(): void + { + + } + +} From 892b319f25f04bc1b55c3d0063b607909612fe6d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Aug 2024 17:14:49 +0200 Subject: [PATCH 0034/3097] Bleeding edge - MissingMethodSelfOutTypeRule --- conf/config.level6.neon | 7 ++ src/PhpDoc/StubValidator.php | 5 ++ .../Methods/MissingMethodSelfOutTypeRule.php | 85 +++++++++++++++++++ .../MissingMethodSelfOutTypeRuleTest.php | 39 +++++++++ .../data/missing-method-self-out-type.php | 35 ++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/Rules/Methods/MissingMethodSelfOutTypeRule.php create mode 100644 tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php diff --git a/conf/config.level6.neon b/conf/config.level6.neon index 545fac6ad21..1029bcdba03 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -13,6 +13,10 @@ rules: - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule - PHPStan\Rules\Properties\MissingPropertyTypehintRule +conditionalTags: + PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + services: - class: PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule @@ -27,3 +31,6 @@ services: paramOut: %featureToggles.paramOutType% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index fb5a46f209a..8d61831c381 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -51,6 +51,7 @@ use PHPStan\Rules\Methods\MethodSignatureRule; use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule; use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule; +use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule; use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; @@ -228,6 +229,10 @@ private function getRuleRegistry(Container $container): RuleRegistry ); } + if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { + $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); + } + return new DirectRuleRegistry($rules); } diff --git a/src/Rules/Methods/MissingMethodSelfOutTypeRule.php b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php new file mode 100644 index 00000000000..4b602b5fa14 --- /dev/null +++ b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php @@ -0,0 +1,85 @@ + + */ +final class MissingMethodSelfOutTypeRule implements Rule +{ + + public function __construct( + private MissingTypehintCheck $missingTypehintCheck, + ) + { + } + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $methodReflection = $node->getMethodReflection(); + $selfOutType = $methodReflection->getSelfOutType(); + + if ($selfOutType === null) { + return []; + } + + $classReflection = $methodReflection->getDeclaringClass(); + $phpDocTagMessage = 'PHPDoc tag @phpstan-self-out'; + + $messages = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($selfOutType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with no value type specified in iterable type %s.', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($selfOutType) as [$name, $genericTypeNames]) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with generic %s but does not specify its types: %s', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($selfOutType) as $callableType) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() has %s with no signature specified for %s.', + $classReflection->getDisplayName(), + $methodReflection->getName(), + $phpDocTagMessage, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + + return $messages; + } + +} diff --git a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php new file mode 100644 index 00000000000..373e46494a5 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php @@ -0,0 +1,39 @@ + + */ +class MissingMethodSelfOutTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, true, true, [])); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-method-self-out-type.php'], [ + [ + 'Method MissingMethodSelfOutType\Foo::doFoo() has PHPDoc tag @phpstan-self-out with no value type specified in iterable type array.', + 14, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Method MissingMethodSelfOutType\Foo::doFoo2() has PHPDoc tag @phpstan-self-out with generic class MissingMethodSelfOutType\Foo but does not specify its types: T', + 22, + ], + [ + 'Method MissingMethodSelfOutType\Foo::doFoo3() has PHPDoc tag @phpstan-self-out with no signature specified for callable.', + 30, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php b/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php new file mode 100644 index 00000000000..a0c83d7e3fe --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/missing-method-self-out-type.php @@ -0,0 +1,35 @@ + + */ + public function doFoo(): void + { + + } + + /** + * @phpstan-self-out self + */ + public function doFoo2(): void + { + + } + + /** + * @phpstan-self-out Foo&callable + */ + public function doFoo3(): void + { + + } + +} From ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 09:48:26 +0200 Subject: [PATCH 0035/3097] Bleeding edge - check missing types in LocalTypeAliasesCheck --- conf/config.neon | 2 + src/Rules/Classes/LocalTypeAliasesCheck.php | 46 +++++++++++++++++++ .../Classes/LocalTypeAliasesRuleTest.php | 17 +++++++ .../Classes/LocalTypeTraitAliasesRuleTest.php | 5 ++ .../Rules/Classes/data/local-type-aliases.php | 12 ++++- .../Classes/data/local-type-trait-aliases.php | 10 +++- 6 files changed, 90 insertions(+), 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 4097e0d85d9..081dc1a4a6d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -913,6 +913,8 @@ services: class: PHPStan\Rules\Classes\LocalTypeAliasesCheck arguments: globalTypeAliases: %typeAliases% + checkMissingTypehints: %checkMissingTypehints% + absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index a8d1b1e476b..74c7f5507bb 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -8,12 +8,14 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\CircularTypeAliasErrorType; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; +use PHPStan\Type\VerbosityLevel; use function array_key_exists; use function in_array; use function sprintf; @@ -28,6 +30,9 @@ public function __construct( private array $globalTypeAliases, private ReflectionProvider $reflectionProvider, private TypeNodeResolver $typeNodeResolver, + private MissingTypehintCheck $missingTypehintCheck, + private bool $checkMissingTypehints, + private bool $absentTypeChecks, ) { } @@ -169,6 +174,47 @@ public function check(ClassReflection $reflection): array return $traverse($type); }); + + if ($this->absentTypeChecks && !$foundError) { + if ($this->checkMissingTypehints) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no value type specified in iterable type %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with generic %s but does not specify its types: %s', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no signature specified for %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + } } return $errors; diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index e55f4293172..b3b8689b84e 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -20,6 +21,9 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), + new MissingTypehintCheck(true, true, true, true, []), + true, + true, ), ); } @@ -91,6 +95,19 @@ public function testRule(): void 'Invalid type definition detected in type alias InvalidTypeAlias.', 62, ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoIterableValue with no value type specified in iterable type array.', + 77, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoGenerics with generic class LocalTypeAliases\Generic but does not specify its types: T', + 77, + ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.', + 77, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 49d73e9c5c8..ba18681762e 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -91,6 +91,11 @@ public function testRule(): void 'Invalid type definition detected in type alias InvalidTypeAlias.', 62, ], + [ + 'Trait LocalTypeTraitAliases\MissingType has type alias NoIterablueValue with no value type specified in iterable type array.', + 69, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index a70cac8f8e1..9dde0cdd297 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -5,7 +5,7 @@ class ExistingClassAlias {} /** - * @phpstan-type ExportedTypeAlias \Countable&\Traversable + * @phpstan-type ExportedTypeAlias \Countable&\Traversable */ class Foo { @@ -68,3 +68,13 @@ class InvalidTypeDefinitionToIgnoreBecauseItsAParseErrorAlreadyReportedInInvalid { } + +/** + * @phpstan-type NoIterableValue = array + * @phpstan-type NoGenerics = Generic + * @phpstan-type NoCallable = array + */ +class MissingTypehints +{ + +} diff --git a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php index d8c16b3e0ec..6aaa554d523 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php @@ -5,7 +5,7 @@ class ExistingClassAlias {} /** - * @phpstan-type ExportedTypeAlias \Countable&\Traversable + * @phpstan-type ExportedTypeAlias \Countable&\Traversable */ trait Foo { @@ -62,3 +62,11 @@ trait Generic trait Invalid { } + +/** + * @phpstan-type NoIterablueValue = array + */ +trait MissingType +{ + +} From 2485b2e9c129e789ec3b2d7db81ca30f87c63911 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 10:02:48 +0200 Subject: [PATCH 0036/3097] Bleeding edge - check nonexistent classes in LocalTypeAliasesCheck --- conf/config.neon | 1 + src/Rules/Classes/LocalTypeAliasesCheck.php | 27 ++++++++++++++++++- src/Rules/Classes/LocalTypeAliasesRule.php | 2 +- .../Classes/LocalTypeTraitAliasesRule.php | 2 +- .../Classes/LocalTypeAliasesRuleTest.php | 23 ++++++++++++++++ .../Classes/LocalTypeTraitAliasesRuleTest.php | 14 ++++++++++ .../Rules/Classes/data/local-type-aliases.php | 10 +++++++ 7 files changed, 76 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 081dc1a4a6d..688c2a4f5c3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -914,6 +914,7 @@ services: arguments: globalTypeAliases: %typeAliases% checkMissingTypehints: %checkMissingTypehints% + checkClassCaseSensitivity: %checkClassCaseSensitivity% absentTypeChecks: %featureToggles.absentTypeChecks% - diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 74c7f5507bb..a5606a5b209 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -2,11 +2,14 @@ namespace PHPStan\Rules\Classes; +use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\NameScope; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\RuleErrorBuilder; @@ -31,7 +34,9 @@ public function __construct( private ReflectionProvider $reflectionProvider, private TypeNodeResolver $typeNodeResolver, private MissingTypehintCheck $missingTypehintCheck, + private ClassNameCheck $classCheck, private bool $checkMissingTypehints, + private bool $checkClassCaseSensitivity, private bool $absentTypeChecks, ) { @@ -40,7 +45,7 @@ public function __construct( /** * @return list */ - public function check(ClassReflection $reflection): array + public function check(ClassReflection $reflection, ClassLike $node): array { $phpDoc = $reflection->getResolvedPhpDoc(); if ($phpDoc === null) { @@ -214,6 +219,26 @@ public function check(ClassReflection $reflection): array ))->identifier('missingType.callable')->build(); } } + + foreach ($resolvedType->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class)) + ->identifier('typeAlias.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + } } } diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index 7fb999403c0..cfb270cadcb 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection()); + return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/src/Rules/Classes/LocalTypeTraitAliasesRule.php b/src/Rules/Classes/LocalTypeTraitAliasesRule.php index 241cef7c6c4..58d72696ad3 100644 --- a/src/Rules/Classes/LocalTypeTraitAliasesRule.php +++ b/src/Rules/Classes/LocalTypeTraitAliasesRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($this->reflectionProvider->getClass($traitName->toString())); + return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); } } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index b3b8689b84e..7cdffa7704d 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -3,6 +3,9 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -16,12 +19,19 @@ class LocalTypeAliasesRuleTest extends RuleTestCase protected function getRule(): Rule { + $reflectionProvider = $this->createReflectionProvider(); + return new LocalTypeAliasesRule( new LocalTypeAliasesCheck( ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), new MissingTypehintCheck(true, true, true, true, []), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, true, true, ), @@ -108,6 +118,19 @@ public function testRule(): void 'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.', 77, ], + [ + 'Type alias A contains unknown class LocalTypeAliases\Nonexistent.', + 87, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Type alias B contains invalid type LocalTypeTraitAliases\Foo.', + 87, + ], + [ + 'Class LocalTypeAliases\Foo referenced with incorrect case: LocalTypeAliases\fOO.', + 87, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index ba18681762e..7106d65997f 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -3,6 +3,10 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -14,11 +18,21 @@ class LocalTypeTraitAliasesRuleTest extends RuleTestCase protected function getRule(): Rule { + $reflectionProvider = $this->createReflectionProvider(); + return new LocalTypeTraitAliasesRule( new LocalTypeAliasesCheck( ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), + new MissingTypehintCheck(true, true, true, true, []), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, + true, + true, ), $this->createReflectionProvider(), ); diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 9dde0cdd297..ba4c47f5764 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -78,3 +78,13 @@ class MissingTypehints { } + +/** + * @phpstan-type A = Nonexistent + * @phpstan-type B = \LocalTypeTraitAliases\Foo + * @phpstan-type C = fOO + */ +class NonexistentClasses +{ + +} From 82f7e149365b97064c8ba219db06fc32952f55b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 10:10:54 +0200 Subject: [PATCH 0037/3097] Fix CS --- src/Rules/Classes/LocalTypeAliasesCheck.php | 120 +++++++++++--------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index a5606a5b209..f71222e4435 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -20,6 +20,8 @@ use PHPStan\Type\TypeTraverser; use PHPStan\Type\VerbosityLevel; use function array_key_exists; +use function array_merge; +use function implode; use function in_array; use function sprintf; @@ -180,64 +182,70 @@ public function check(ClassReflection $reflection, ClassLike $node): array return $traverse($type); }); - if ($this->absentTypeChecks && !$foundError) { - if ($this->checkMissingTypehints) { - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with no value type specified in iterable type %s.', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $iterableTypeDescription, - )) - ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) - ->identifier('missingType.iterableValue') - ->build(); - } - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with generic %s but does not specify its types: %s', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $name, - implode(', ', $genericTypeNames), - )) - ->identifier('missingType.generics') - ->build(); - } - - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with no signature specified for %s.', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $callableType->describe(VerbosityLevel::typeOnly()), - ))->identifier('missingType.callable')->build(); - } + if ($foundError) { + continue; + } + + if (!$this->absentTypeChecks) { + continue; + } + + if ($this->checkMissingTypehints) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no value type specified in iterable type %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); } - foreach ($resolvedType->getReferencedClasses() as $class) { - if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); - } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class)) - ->identifier('typeAlias.trait') - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames([ - new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), - ); - } + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with generic %s but does not specify its types: %s', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no signature specified for %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + + foreach ($resolvedType->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class)) + ->identifier('typeAlias.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); } } } From 5f7d12b2fb2809525ab0e96eeae95093204ea4d3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 10:17:34 +0200 Subject: [PATCH 0038/3097] Bleeding edge - check unresolvable types in LocalTypeAliasesCheck --- src/Rules/Classes/LocalTypeAliasesCheck.php | 8 ++++++++ tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php | 6 ++++++ .../Rules/Classes/LocalTypeTraitAliasesRuleTest.php | 2 ++ tests/PHPStan/Rules/Classes/data/local-type-aliases.php | 8 ++++++++ 4 files changed, 24 insertions(+) diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index f71222e4435..cd6362f8dd1 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -12,6 +12,7 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\CircularTypeAliasErrorType; use PHPStan\Type\ErrorType; @@ -37,6 +38,7 @@ public function __construct( private TypeNodeResolver $typeNodeResolver, private MissingTypehintCheck $missingTypehintCheck, private ClassNameCheck $classCheck, + private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkMissingTypehints, private bool $checkClassCaseSensitivity, private bool $absentTypeChecks, @@ -248,6 +250,12 @@ public function check(ClassReflection $reflection, ClassLike $node): array ); } } + + if ($this->unresolvableTypeHelper->containsUnresolvableType($resolvedType)) { + $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unresolvable type.', $aliasName)) + ->identifier('typeAlias.unresolvableType') + ->build(); + } } return $errors; diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 7cdffa7704d..089014a7aef 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -7,6 +7,7 @@ use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\MissingTypehintCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -31,6 +32,7 @@ protected function getRule(): Rule new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + new UnresolvableTypeHelper(), true, true, true, @@ -131,6 +133,10 @@ public function testRule(): void 'Class LocalTypeAliases\Foo referenced with incorrect case: LocalTypeAliases\fOO.', 87, ], + [ + 'Type alias A contains unresolvable type.', + 95, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 7106d65997f..1f53ff745ca 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -7,6 +7,7 @@ use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\MissingTypehintCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -30,6 +31,7 @@ protected function getRule(): Rule new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index ba4c47f5764..5aa52e90821 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -88,3 +88,11 @@ class NonexistentClasses { } + +/** + * @phpstan-type A = string&int + */ +class UnresolvableExample +{ + +} From 5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 10:23:05 +0200 Subject: [PATCH 0039/3097] Bleeding edge - call GenericObjectTypeCheck from LocalTypeAliasesCheck --- src/Rules/Classes/LocalTypeAliasesCheck.php | 32 +++++++++++++++++++ .../Classes/LocalTypeAliasesRuleTest.php | 6 ++++ .../Classes/LocalTypeTraitAliasesRuleTest.php | 2 ++ .../Rules/Classes/data/local-type-aliases.php | 8 +++++ 4 files changed, 48 insertions(+) diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index cd6362f8dd1..71e807ecd72 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -4,12 +4,14 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\NameScope; +use PHPStan\Internal\SprintfHelper; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -39,6 +41,7 @@ public function __construct( private MissingTypehintCheck $missingTypehintCheck, private ClassNameCheck $classCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, + private GenericObjectTypeCheck $genericObjectTypeCheck, private bool $checkMissingTypehints, private bool $checkClassCaseSensitivity, private bool $absentTypeChecks, @@ -256,6 +259,35 @@ public function check(ClassReflection $reflection, ClassLike $node): array ->identifier('typeAlias.unresolvableType') ->build(); } + + $escapedTypeAlias = SprintfHelper::escapeFormatString($aliasName); + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $resolvedType, + sprintf( + 'Type alias %s contains generic type %%s but %%s %%s is not generic.', + $escapedTypeAlias, + ), + sprintf( + 'Generic type %%s in type alias %s does not specify all template types of %%s %%s: %%s', + $escapedTypeAlias, + ), + sprintf( + 'Generic type %%s in type alias %s specifies %%d template types, but %%s %%s supports only %%d: %%s', + $escapedTypeAlias, + ), + sprintf( + 'Type %%s in generic type %%s in type alias %s is not subtype of template type %%s of %%s %%s.', + $escapedTypeAlias, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in type alias %s is in conflict with %%s template type %%s of %%s %%s.', + $escapedTypeAlias, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in type alias %s is redundant, template type %%s of %%s %%s has the same variance.', + $escapedTypeAlias, + ), + )); } return $errors; diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 089014a7aef..e8c07ca171b 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; @@ -33,6 +34,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new UnresolvableTypeHelper(), + new GenericObjectTypeCheck(), true, true, true, @@ -137,6 +139,10 @@ public function testRule(): void 'Type alias A contains unresolvable type.', 95, ], + [ + 'Type alias A contains generic type Exception but class Exception is not generic.', + 103, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 1f53ff745ca..1501b40f797 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; @@ -32,6 +33,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new UnresolvableTypeHelper(), + new GenericObjectTypeCheck(), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 5aa52e90821..152e77d8d7b 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -96,3 +96,11 @@ class UnresolvableExample { } + +/** + * @phpstan-type A = \Exception + */ +class GenericsCheck +{ + +} From 3175c81f26fd5bcb4a161b24e774921870ed2533 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 12:33:05 +0200 Subject: [PATCH 0040/3097] Bleeding edge - add missing MissingTypehintCheck calls --- conf/config.level2.neon | 1 + src/Rules/Classes/MixinRule.php | 25 +++++++++++++++++++ tests/PHPStan/Rules/Classes/MixinRuleTest.php | 10 ++++++++ tests/PHPStan/Rules/Classes/data/mixin.php | 16 ++++++++++++ 4 files changed, 52 insertions(+) diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 347bdb26294..72ff318df06 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -71,6 +71,7 @@ services: class: PHPStan\Rules\Classes\MixinRule arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + absentTypeChecks: %featureToggles.absentTypeChecks% tags: - phpstan.rules.rule diff --git a/src/Rules/Classes/MixinRule.php b/src/Rules/Classes/MixinRule.php index 07e39383c77..56f19a7203f 100644 --- a/src/Rules/Classes/MixinRule.php +++ b/src/Rules/Classes/MixinRule.php @@ -31,6 +31,7 @@ public function __construct( private MissingTypehintCheck $missingTypehintCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, + private bool $absentTypeChecks, ) { } @@ -83,6 +84,30 @@ public function processNode(Node $node, Scope $scope): array ->build(); } + if ($this->absentTypeChecks) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no signature specified for %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index a3962451461..1d93a8a299c 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): Rule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ); } @@ -97,6 +98,15 @@ public function testRule(): void 116, 'You can safely remove the call-site variance annotation.', ], + [ + 'Class MixinRule\NoIterableValue has PHPDoc tag @mixin with no value type specified in iterable type array.', + 124, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Class MixinRule\NoCallableSignature has PHPDoc tag @mixin with no signature specified for callable.', + 132, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/mixin.php b/tests/PHPStan/Rules/Classes/data/mixin.php index d5a3fafd670..b6ab1b092b1 100644 --- a/tests/PHPStan/Rules/Classes/data/mixin.php +++ b/tests/PHPStan/Rules/Classes/data/mixin.php @@ -117,3 +117,19 @@ class Elit2 { } + +/** + * @mixin Dolor + */ +class NoIterableValue +{ + +} + +/** + * @mixin Dolor + */ +class NoCallableSignature +{ + +} From 55ea2ae516df22a071ab873fdd6f748a3af0520e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 Aug 2024 13:45:46 +0200 Subject: [PATCH 0041/3097] Bleeding edge - check type in `@property` tags --- conf/config.level2.neon | 10 + conf/config.neon | 5 + src/Reflection/ClassReflection.php | 2 +- src/Rules/Classes/PropertyTagCheck.php | 174 ++++++++++++++++++ src/Rules/Classes/PropertyTagRule.php | 30 +++ src/Rules/Classes/PropertyTagTraitRule.php | 39 ++++ .../Rules/Classes/PropertyTagRuleTest.php | 139 ++++++++++++++ .../Classes/PropertyTagTraitRuleTest.php | 51 +++++ .../Rules/Classes/data/property-tag-trait.php | 11 ++ .../Rules/Classes/data/property-tag.php | 93 ++++++++++ 10 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Classes/PropertyTagCheck.php create mode 100644 src/Rules/Classes/PropertyTagRule.php create mode 100644 src/Rules/Classes/PropertyTagTraitRule.php create mode 100644 tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/property-tag-trait.php create mode 100644 tests/PHPStan/Rules/Classes/data/property-tag.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 72ff318df06..0d9fd56ff54 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -49,6 +49,10 @@ rules: - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionTraitRule conditionalTags: + PHPStan\Rules\Classes\PropertyTagRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\PropertyTagTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: @@ -75,6 +79,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Classes\PropertyTagRule + + - + class: PHPStan\Rules\Classes\PropertyTagTraitRule + - class: PHPStan\Rules\PhpDoc\RequireExtendsCheck arguments: diff --git a/conf/config.neon b/conf/config.neon index 688c2a4f5c3..6ece4804c13 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -917,6 +917,11 @@ services: checkClassCaseSensitivity: %checkClassCaseSensitivity% absentTypeChecks: %featureToggles.absentTypeChecks% + - + class: PHPStan\Rules\Classes\PropertyTagCheck + arguments: + checkClassCaseSensitivity: %checkClassCaseSensitivity% + - class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper arguments: diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 390092a857d..68752bfab17 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1732,7 +1732,7 @@ public function getRequireImplementsTags(): array } /** - * @return array + * @return array */ public function getPropertyTags(): array { diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php new file mode 100644 index 00000000000..abbc2746984 --- /dev/null +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -0,0 +1,174 @@ + + */ + public function check( + ClassReflection $classReflection, + ClassLike $node, + ): array + { + $errors = []; + foreach ($classReflection->getPropertyTags() as $propertyName => $propertyTag) { + $readableType = $propertyTag->getReadableType(); + $writableType = $propertyTag->getWritableType(); + + $types = []; + $tagName = '@property'; + if ($readableType !== null) { + if ($writableType !== null) { + if ($writableType->equals($readableType)) { + $types[] = $readableType; + } else { + $types[] = $readableType; + $types[] = $writableType; + } + } else { + $tagName = '@property-read'; + $types[] = $readableType; + } + } elseif ($writableType !== null) { + $tagName = '@property-write'; + $types[] = $writableType; + } else { + throw new ShouldNotHappenException(); + } + + foreach ($types as $type) { + foreach ($this->checkPropertyType($classReflection, $propertyName, $tagName, $type, $node) as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } + + /** + * @return list + */ + private function checkPropertyType(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type, ClassLike $node): array + { + if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for property %s::$%s contains unresolvable type.', + $tagName, + $classReflection->getDisplayName(), + $propertyName, + ))->identifier('propertyTag.unresolvableType') + ->build(), + ]; + } + + $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); + $escapedPropertyName = SprintfHelper::escapeFormatString($propertyName); + $escapedTagName = SprintfHelper::escapeFormatString($tagName); + + $errors = $this->genericObjectTypeCheck->check( + $type, + sprintf('PHPDoc tag %s for property %s::$%s contains generic type %%s but %%s %%s is not generic.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s does not specify all template types of %%s %%s: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Type %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is not subtype of template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is in conflict with %%s template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is redundant, template type %%s of %%s %%s has the same variance.', $escapedTagName, $escapedClassName, $escapedPropertyName), + ); + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for property %s::$%s contains generic %s but does not specify its types: %s', + $tagName, + $classReflection->getDisplayName(), + $propertyName, + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag %s for property $%s with no value type specified in iterable type %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $tagName, + $propertyName, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag %s for property $%s with no signature specified for %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $tagName, + $propertyName, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + + foreach ($type->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains unknown class %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains invalid type %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) + ->identifier('propertyTag.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + } + + return $errors; + } + +} diff --git a/src/Rules/Classes/PropertyTagRule.php b/src/Rules/Classes/PropertyTagRule.php new file mode 100644 index 00000000000..c1f002c3b3d --- /dev/null +++ b/src/Rules/Classes/PropertyTagRule.php @@ -0,0 +1,30 @@ + + */ +final class PropertyTagRule implements Rule +{ + + public function __construct(private PropertyTagCheck $check) + { + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + } + +} diff --git a/src/Rules/Classes/PropertyTagTraitRule.php b/src/Rules/Classes/PropertyTagTraitRule.php new file mode 100644 index 00000000000..cd3a54c9fd1 --- /dev/null +++ b/src/Rules/Classes/PropertyTagTraitRule.php @@ -0,0 +1,39 @@ + + */ +final class PropertyTagTraitRule implements Rule +{ + + public function __construct(private PropertyTagCheck $check, private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); + } + +} diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php new file mode 100644 index 00000000000..b5718fb844a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -0,0 +1,139 @@ + + */ +class PropertyTagRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new PropertyTagRule( + new PropertyTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + ); + } + + public function testRule(): void + { + $tipText = 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + $fooClassLine = 23; + + $this->analyse([__DIR__ . '/data/property-tag.php'], [ + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$a contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$b contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$c contains unknown class PropertyTag\stringg.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$c contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$d contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$e contains unknown class PropertyTag\stringg.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Foo::$e contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property-read for property PropertyTag\Foo::$f contains unknown class PropertyTag\intt.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property-write for property PropertyTag\Foo::$g contains unknown class PropertyTag\stringg.', + $fooClassLine, + $tipText, + ], + [ + 'PHPDoc tag @property for property PropertyTag\Bar::$unresolvable contains unresolvable type.', + 31, + ], + [ + 'PHPDoc tag @property for property PropertyTag\TestGenerics::$a contains generic type Exception but class Exception is not generic.', + 51, + ], + [ + 'Generic type PropertyTag\Generic in PHPDoc tag @property for property PropertyTag\TestGenerics::$b does not specify all template types of class PropertyTag\Generic: T, U', + 51, + ], + [ + 'Generic type PropertyTag\Generic in PHPDoc tag @property for property PropertyTag\TestGenerics::$c specifies 3 template types, but class PropertyTag\Generic supports only 2: T, U', + 51, + ], + [ + 'Type string in generic type PropertyTag\Generic in PHPDoc tag @property for property PropertyTag\TestGenerics::$d is not subtype of template type T of int of class PropertyTag\Generic.', + 51, + ], + [ + 'PHPDoc tag @property for property PropertyTag\MissingGenerics::$a contains generic class PropertyTag\Generic but does not specify its types: T, U', + 59, + ], + [ + 'Class PropertyTag\MissingIterableValue has PHPDoc tag @property for property $a with no value type specified in iterable type array.', + 67, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Class PropertyTag\MissingCallableSignature has PHPDoc tag @property for property $a with no signature specified for callable.', + 75, + ], + [ + 'PHPDoc tag @property for property PropertyTag\NonexistentClasses::$a contains unknown class PropertyTag\Nonexistent.', + 85, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @property for property PropertyTag\NonexistentClasses::$b contains invalid type PropertyTagTrait\Foo.', + 85, + ], + [ + 'Class PropertyTag\Foo referenced with incorrect case: PropertyTag\fOO.', + 85, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php new file mode 100644 index 00000000000..c6e140604c6 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -0,0 +1,51 @@ + + */ +class PropertyTagTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new PropertyTagTraitRule( + new PropertyTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/property-tag-trait.php'], [ + [ + 'PHPDoc tag @property for property PropertyTagTrait\Foo::$foo contains unknown class PropertyTagTrait\intt.', + 8, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/property-tag-trait.php b/tests/PHPStan/Rules/Classes/data/property-tag-trait.php new file mode 100644 index 00000000000..c5a50f30dd3 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/property-tag-trait.php @@ -0,0 +1,11 @@ + $a + * @property Generic $b + * @property Generic $c + * @property Generic $d + */ +class TestGenerics +{ + +} + +/** + * @property Generic $a + */ +class MissingGenerics +{ + +} + +/** + * @property Generic $a + */ +class MissingIterableValue +{ + +} + +/** + * @property Generic $a + */ +class MissingCallableSignature +{ + +} + +/** + * @property Nonexistent $a + * @property \PropertyTagTrait\Foo $b + * @property fOO $c + */ +class NonexistentClasses +{ + +} + + +// todo nonexistent class +// todo trait +// todo class name case From bf43ef327a023ce9ba92076cf9cbacfe00bc0f89 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 10:36:21 +0200 Subject: [PATCH 0042/3097] Missing rules in StubValidator --- src/PhpDoc/StubValidator.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 8d61831c381..0310ae11f80 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -26,6 +26,10 @@ use PHPStan\Rules\Classes\LocalTypeAliasesCheck; use PHPStan\Rules\Classes\LocalTypeAliasesRule; use PHPStan\Rules\Classes\LocalTypeTraitAliasesRule; +use PHPStan\Rules\Classes\MixinRule; +use PHPStan\Rules\Classes\PropertyTagCheck; +use PHPStan\Rules\Classes\PropertyTagRule; +use PHPStan\Rules\Classes\PropertyTagTraitRule; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; use PHPStan\Rules\FunctionDefinitionCheck; @@ -231,6 +235,11 @@ private function getRuleRegistry(Container $container): RuleRegistry if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); + + $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); + $rules[] = new PropertyTagRule($propertyTagCheck); + $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); + $rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); } return new DirectRuleRegistry($rules); From e5600f15170ca99e7ed3007a4d8a5502f8349139 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 10:38:35 +0200 Subject: [PATCH 0043/3097] Fix identifier --- src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index ca8bceb0fb2..f7907395ad4 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -66,7 +66,7 @@ public function processNode(Node $node, Scope $scope): array 'PHPDoc tag @phpstan-self-out for method %s::%s() contains unresolvable type.', $classReflection->getDisplayName(), $method->getName(), - ))->identifier('parameter.unresolvableType')->build(); + ))->identifier('selfOut.unresolvableType')->build(); } $escapedTagName = SprintfHelper::escapeFormatString('@phpstan-self-out'); From 2bb528233edb75312614166e282776f279cf2018 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 13:39:47 +0200 Subject: [PATCH 0044/3097] Bleeding edge - GenericAncestorsCheck looks for unresolvable types --- conf/config.neon | 1 + src/Rules/Generics/ClassAncestorsRule.php | 2 ++ src/Rules/Generics/EnumAncestorsRule.php | 2 ++ src/Rules/Generics/GenericAncestorsCheck.php | 12 ++++++++++++ src/Rules/Generics/InterfaceAncestorsRule.php | 2 ++ src/Rules/Generics/UsedTraitsRule.php | 1 + .../Rules/Generics/ClassAncestorsRuleTest.php | 13 +++++++++++++ .../Rules/Generics/EnumAncestorsRuleTest.php | 3 +++ .../Generics/InterfaceAncestorsRuleTest.php | 3 +++ .../Rules/Generics/UsedTraitsRuleTest.php | 3 +++ .../PHPStan/Rules/Generics/data/bug-11552.php | 19 +++++++++++++++++++ 11 files changed, 61 insertions(+) create mode 100644 tests/PHPStan/Rules/Generics/data/bug-11552.php diff --git a/conf/config.neon b/conf/config.neon index 6ece4804c13..732370c728a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -994,6 +994,7 @@ services: arguments: checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% + absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\Generics\GenericObjectTypeCheck diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index 9c1efe6c4a3..a0a07a2c20e 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -49,6 +49,7 @@ public function processNode(Node $node, Scope $scope): array $originalNode->extends !== null ? [$originalNode->extends] : [], array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()), sprintf('Class %s @extends tag contains incompatible type %%s.', $escapedClassName), + sprintf('Class %s @extends tag contains unresolvable type.', $className), sprintf('Class %s has @extends tag, but does not extend any class.', $escapedClassName), sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $escapedClassName), 'PHPDoc tag @extends contains generic type %s but %s %s is not generic.', @@ -65,6 +66,7 @@ public function processNode(Node $node, Scope $scope): array $originalNode->implements, array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()), sprintf('Class %s @implements tag contains incompatible type %%s.', $escapedClassName), + sprintf('Class %s @implements tag contains unresolvable type.', $className), sprintf('Class %s has @implements tag, but does not implement any interface.', $escapedClassName), sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $escapedClassName), 'PHPDoc tag @implements contains generic type %s but %s %s is not generic.', diff --git a/src/Rules/Generics/EnumAncestorsRule.php b/src/Rules/Generics/EnumAncestorsRule.php index 2cb7788d592..71daff135b8 100644 --- a/src/Rules/Generics/EnumAncestorsRule.php +++ b/src/Rules/Generics/EnumAncestorsRule.php @@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array [], array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()), sprintf('Enum %s @extends tag contains incompatible type %%s.', $escapedEnumName), + sprintf('Enum %s @extends tag contains unresolvable type.', $enumName), sprintf('Enum %s has @extends tag, but cannot extend anything.', $escapedEnumName), '', '', @@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array $originalNode->implements, array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()), sprintf('Enum %s @implements tag contains incompatible type %%s.', $escapedEnumName), + sprintf('Enum %s @implements tag contains unresolvable type.', $enumName), sprintf('Enum %s has @implements tag, but does not implement any interface.', $escapedEnumName), sprintf('The @implements tag of eunm %s describes %%s but the enum implements: %%s', $escapedEnumName), 'PHPDoc tag @implements contains generic type %s but %s %s is not generic.', diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index fd60ba01a4d..f5140a487c0 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -6,6 +6,7 @@ use PhpParser\Node\Name; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -31,8 +32,10 @@ public function __construct( private ReflectionProvider $reflectionProvider, private GenericObjectTypeCheck $genericObjectTypeCheck, private VarianceCheck $varianceCheck, + private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkGenericClassInNonGenericObjectType, private array $skipCheckGenericClasses, + private bool $absentTypeChecks, ) { } @@ -46,6 +49,7 @@ public function check( array $nameNodes, array $ancestorTypes, string $incompatibleTypeMessage, + string $unresolvableTypeMessage, string $noNamesMessage, string $noRelatedNameMessage, string $classNotGenericMessage, @@ -99,6 +103,14 @@ public function check( ); $messages = array_merge($messages, $genericObjectTypeCheckMessages); + if ($this->absentTypeChecks) { + if ($this->unresolvableTypeHelper->containsUnresolvableType($ancestorType)) { + $messages[] = RuleErrorBuilder::message($unresolvableTypeMessage) + ->identifier('generics.unresolvable') + ->build(); + } + } + foreach ($ancestorType->getReferencedClasses() as $referencedClass) { if ($this->reflectionProvider->hasClass($referencedClass)) { continue; diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index c26dd6f290b..c270de36260 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array $originalNode->extends, array_map(static fn (ExtendsTag $tag): Type => $tag->getType(), $classReflection->getExtendsTags()), sprintf('Interface %s @extends tag contains incompatible type %%s.', $escapedInterfaceName), + sprintf('Interface %s @extends tag contains unresolvable type.', $interfaceName), sprintf('Interface %s has @extends tag, but does not extend any interface.', $escapedInterfaceName), sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $escapedInterfaceName), 'PHPDoc tag @extends contains generic type %s but %s %s is not generic.', @@ -63,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array [], array_map(static fn (ImplementsTag $tag): Type => $tag->getType(), $classReflection->getImplementsTags()), sprintf('Interface %s @implements tag contains incompatible type %%s.', $escapedInterfaceName), + sprintf('Interface %s @implements tag contains unresolvable type.', $interfaceName), sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $escapedInterfaceName), '', '', diff --git a/src/Rules/Generics/UsedTraitsRule.php b/src/Rules/Generics/UsedTraitsRule.php index 72914da2e78..625c3509f7e 100644 --- a/src/Rules/Generics/UsedTraitsRule.php +++ b/src/Rules/Generics/UsedTraitsRule.php @@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array $node->traits, array_map(static fn (UsesTag $tag): Type => $tag->getType(), $useTags), sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)), + sprintf('%s @use tag contains unresolvable type.', ucfirst($description)), sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)), sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription), 'PHPDoc tag @use contains generic type %s but %s %s is not generic.', diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index eb34897e107..5126d4bf154 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -18,8 +19,10 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(true, true), + new UnresolvableTypeHelper(), true, [], + true, ), new CrossCheckInterfacesHelper(), ); @@ -269,4 +272,14 @@ public function testBug8473(): void $this->analyse([__DIR__ . '/data/bug-8473.php'], []); } + public function testBug11552(): void + { + $this->analyse([__DIR__ . '/data/bug-11552.php'], [ + [ + 'Class Bug11552\SomeResult @extends tag contains unresolvable type.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php index a7851f6c52c..017065a4a88 100644 --- a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -19,8 +20,10 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(true, true), + new UnresolvableTypeHelper(), true, [], + true, ), new CrossCheckInterfacesHelper(), ); diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index c57774efedf..3a9d430ec07 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -18,8 +19,10 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(true, true), + new UnresolvableTypeHelper(), true, [], + true, ), new CrossCheckInterfacesHelper(), ); diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 2101cedb5ca..196e663c7b4 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; @@ -20,8 +21,10 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(true, true), + new UnresolvableTypeHelper(), true, [], + true, ), ); } diff --git a/tests/PHPStan/Rules/Generics/data/bug-11552.php b/tests/PHPStan/Rules/Generics/data/bug-11552.php new file mode 100644 index 00000000000..04a04e28629 --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/bug-11552.php @@ -0,0 +1,19 @@ + + */ +class SomeResult extends Result { + +} From 4ffbb3b126d3c98fad4ad0906c76d24febdb89ed Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 13:52:19 +0200 Subject: [PATCH 0045/3097] Fix description escaping in UsedTraitsRule --- src/Rules/Generics/UsedTraitsRule.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Rules/Generics/UsedTraitsRule.php b/src/Rules/Generics/UsedTraitsRule.php index 625c3509f7e..ef698a40610 100644 --- a/src/Rules/Generics/UsedTraitsRule.php +++ b/src/Rules/Generics/UsedTraitsRule.php @@ -64,21 +64,25 @@ public function processNode(Node $node, Scope $scope): array $description = sprintf('%s %s', $typeDescription, SprintfHelper::escapeFormatString($traitName)); } + $escapedDescription = SprintfHelper::escapeFormatString($description); + $upperCaseDescription = ucfirst($description); + $escapedUpperCaseDescription = SprintfHelper::escapeFormatString($upperCaseDescription); + return $this->genericAncestorsCheck->check( $node->traits, array_map(static fn (UsesTag $tag): Type => $tag->getType(), $useTags), - sprintf('%s @use tag contains incompatible type %%s.', ucfirst($description)), - sprintf('%s @use tag contains unresolvable type.', ucfirst($description)), - sprintf('%s has @use tag, but does not use any trait.', ucfirst($description)), - sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $description, $typeDescription), + sprintf('%s @use tag contains incompatible type %%s.', $escapedUpperCaseDescription), + sprintf('%s @use tag contains unresolvable type.', $upperCaseDescription), + sprintf('%s has @use tag, but does not use any trait.', $upperCaseDescription), + sprintf('The @use tag of %s describes %%s but the %s uses %%s.', $escapedDescription, $typeDescription), 'PHPDoc tag @use contains generic type %s but %s %s is not generic.', 'Generic type %s in PHPDoc tag @use does not specify all template types of %s %s: %s', 'Generic type %s in PHPDoc tag @use specifies %d template types, but %s %s supports only %d: %s', 'Type %s in generic type %s in PHPDoc tag @use is not subtype of template type %s of %s %s.', 'Call-site variance annotation of %s in generic type %s in PHPDoc tag @use is not allowed.', 'PHPDoc tag @use has invalid type %s.', - sprintf('%s uses generic trait %%s but does not specify its types: %%s', ucfirst($description)), - sprintf('in used type %%s of %s', $description), + sprintf('%s uses generic trait %%s but does not specify its types: %%s', $escapedUpperCaseDescription), + sprintf('in used type %%s of %s', $escapedDescription), ); } From bfbc401c1664b392362292b74270246be1a2cba9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 13:58:07 +0200 Subject: [PATCH 0046/3097] Bleeding edge - GenericAncestorsCheck - do not allow trait --- src/Rules/Generics/GenericAncestorsCheck.php | 20 +++++++++++++++++-- .../Rules/Generics/ClassAncestorsRuleTest.php | 4 ++++ .../Generics/data/class-ancestors-extends.php | 13 ++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index f5140a487c0..ef9ce469b57 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -112,12 +112,28 @@ public function check( } foreach ($ancestorType->getReferencedClasses() as $referencedClass) { - if ($this->reflectionProvider->hasClass($referencedClass)) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + $messages[] = RuleErrorBuilder::message(sprintf($invalidTypeMessage, $referencedClass)) + ->identifier('class.notFound') + ->build(); + continue; + } + + if (!$this->absentTypeChecks) { + continue; + } + + if ($referencedClass === $ancestorType->getClassName()) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->isTrait()) { continue; } $messages[] = RuleErrorBuilder::message(sprintf($invalidTypeMessage, $referencedClass)) - ->identifier('class.notFound') + ->identifier('generics.trait') ->build(); } diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 5126d4bf154..05963f5576b 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -127,6 +127,10 @@ public function testRuleExtends(): void 'Call-site variance annotation of covariant Throwable in generic type ClassAncestorsExtends\FooGeneric in PHPDoc tag @extends is not allowed.', 246, ], + [ + 'PHPDoc tag @extends has invalid type ClassAncestorsExtends\FooTrait.', + 259, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php index bb942838ed2..e66591168ed 100644 --- a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php +++ b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php @@ -247,3 +247,16 @@ class FooTypeProjection extends FooGeneric { } + +trait FooTrait +{ + +} + +/** + * @extends FooGeneric + */ +class TraitInExtends extends FooGeneric +{ + +} From b5dc72cb059120b8adc4ce6574cc841b2402b250 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:05:05 +0200 Subject: [PATCH 0047/3097] Renovate - use 1.12.x branch --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 8bafa45fd15..5cb51461c6a 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -6,7 +6,7 @@ "dependencyDashboard": true, "rangeStrategy": "update-lockfile", "rebaseWhen": "conflicted", - "baseBranches": ["1.11.x"], + "baseBranches": ["1.12.x"], "packageRules": [ { "matchPackagePatterns": ["*"], From 524913e1b70062c8e340c9ffd1af48af94be6a38 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:05:44 +0200 Subject: [PATCH 0048/3097] Update PhpStorm stubs - use 1.12.x branch --- .github/workflows/update-phpstorm-stubs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-phpstorm-stubs.yml b/.github/workflows/update-phpstorm-stubs.yml index f0fc56a9b1b..c559efc2086 100644 --- a/.github/workflows/update-phpstorm-stubs.yml +++ b/.github/workflows/update-phpstorm-stubs.yml @@ -16,7 +16,7 @@ jobs: - name: "Checkout" uses: actions/checkout@v4 with: - ref: 1.11.x + ref: 1.12.x fetch-depth: '0' token: ${{ secrets.PHPSTAN_BOT_TOKEN }} - name: "Install PHP" From 3e51899dd7ed0e2785846f8ec820b4cd8214b993 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:26:38 +0200 Subject: [PATCH 0049/3097] ConstExprNodeResolver - support ConstFetchNode for class constants --- src/PhpDoc/ConstExprNodeResolver.php | 80 +++++++++++++++++++++++++--- src/PhpDoc/PhpDocNodeResolver.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 1 + 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/PhpDoc/ConstExprNodeResolver.php b/src/PhpDoc/ConstExprNodeResolver.php index 9e3e86f1877..257883af2ce 100644 --- a/src/PhpDoc/ConstExprNodeResolver.php +++ b/src/PhpDoc/ConstExprNodeResolver.php @@ -2,6 +2,7 @@ namespace PHPStan\PhpDoc; +use PHPStan\Analyser\NameScope; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; @@ -10,22 +11,35 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; +use PHPStan\Reflection\InitializerExprContext; +use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\MixedType; +use PHPStan\Type\Enum\EnumCaseObjectType; +use PHPStan\Type\ErrorType; use PHPStan\Type\NullType; use PHPStan\Type\Type; +use function strtolower; final class ConstExprNodeResolver { - public function resolve(ConstExprNode $node): Type + public function __construct( + private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, + private InitializerExprTypeResolver $initializerExprTypeResolver, + ) + { + } + + public function resolve(ConstExprNode $node, NameScope $nameScope): Type { if ($node instanceof ConstExprArrayNode) { - return $this->resolveArrayNode($node); + return $this->resolveArrayNode($node, $nameScope); } if ($node instanceof ConstExprFalseNode) { @@ -52,22 +66,74 @@ public function resolve(ConstExprNode $node): Type return new ConstantStringType($node->value); } - return new MixedType(); + if ($node instanceof ConstFetchNode) { + if ($nameScope->getClassName() !== null) { + switch (strtolower($node->className)) { + case 'static': + case 'self': + $className = $nameScope->getClassName(); + break; + + case 'parent': + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + if ($classReflection->getParentClass() === null) { + return new ErrorType(); + + } + + $className = $classReflection->getParentClass()->getName(); + } + break; + } + } + if (!isset($className)) { + $className = $nameScope->resolveStringName($node->className); + } + if (!$this->getReflectionProvider()->hasClass($className)) { + return new ErrorType(); + } + $classReflection = $this->getReflectionProvider()->getClass($className); + if (!$classReflection->hasConstant($node->name)) { + return new ErrorType(); + } + if ($classReflection->isEnum() && $classReflection->hasEnumCase($node->name)) { + return new EnumCaseObjectType($classReflection->getName(), $node->name); + } + + $reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($node->name); + if ($reflectionConstant === false) { + return new ErrorType(); + } + $declaringClass = $reflectionConstant->getDeclaringClass(); + + return $this->initializerExprTypeResolver->getType( + $reflectionConstant->getValueExpression(), + InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null), + ); + } + + return new ErrorType(); } - private function resolveArrayNode(ConstExprArrayNode $node): Type + private function resolveArrayNode(ConstExprArrayNode $node, NameScope $nameScope): Type { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach ($node->items as $item) { if ($item->key === null) { $key = null; } else { - $key = $this->resolve($item->key); + $key = $this->resolve($item->key, $nameScope); } - $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value)); + $arrayBuilder->setOffsetValueType($key, $this->resolve($item->value, $nameScope)); } return $arrayBuilder->getArray(); } + private function getReflectionProvider(): ReflectionProvider + { + return $this->reflectionProviderProvider->getReflectionProvider(); + } + } diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 58a63f6e853..402f8c7dd1f 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -196,7 +196,7 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope): } $defaultValue = null; if ($parameterNode->defaultValue !== null) { - $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue); + $defaultValue = $this->constExprNodeResolver->resolve($parameterNode->defaultValue, $nameScope); } $parameters[$parameterName] = new MethodTagParameter( diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index a52fde51f63..c0fa6c55857 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1059,6 +1059,7 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc $className = $classReflection->getParentClass()->getName(); } + break; } } From 5b7e474680eaf33874b7ed6a227677adcbed9ca5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 Aug 2024 17:23:41 +0200 Subject: [PATCH 0050/3097] Bleeding edge - check types in `@method` tags --- conf/config.level2.neon | 10 ++ conf/config.neon | 5 + src/PhpDoc/StubValidator.php | 7 + src/Reflection/ClassReflection.php | 2 +- src/Rules/Classes/MethodTagCheck.php | 164 ++++++++++++++++++ src/Rules/Classes/MethodTagRule.php | 30 ++++ src/Rules/Classes/MethodTagTraitRule.php | 39 +++++ .../Rules/Classes/MethodTagRuleTest.php | 106 +++++++++++ .../Rules/Classes/MethodTagTraitRuleTest.php | 65 +++++++ .../Rules/Classes/data/method-tag-trait.php | 23 +++ .../PHPStan/Rules/Classes/data/method-tag.php | 76 ++++++++ .../Rules/Classes/data/property-tag.php | 5 - 12 files changed, 526 insertions(+), 6 deletions(-) create mode 100644 src/Rules/Classes/MethodTagCheck.php create mode 100644 src/Rules/Classes/MethodTagRule.php create mode 100644 src/Rules/Classes/MethodTagTraitRule.php create mode 100644 tests/PHPStan/Rules/Classes/MethodTagRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/method-tag-trait.php create mode 100644 tests/PHPStan/Rules/Classes/data/method-tag.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 0d9fd56ff54..72d297bfb36 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -49,6 +49,10 @@ rules: - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionTraitRule conditionalTags: + PHPStan\Rules\Classes\MethodTagRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MethodTagTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagTraitRule: @@ -79,6 +83,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Classes\MethodTagRule + + - + class: PHPStan\Rules\Classes\MethodTagTraitRule + - class: PHPStan\Rules\Classes\PropertyTagRule diff --git a/conf/config.neon b/conf/config.neon index 732370c728a..38a1518a30f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -917,6 +917,11 @@ services: checkClassCaseSensitivity: %checkClassCaseSensitivity% absentTypeChecks: %featureToggles.absentTypeChecks% + - + class: PHPStan\Rules\Classes\MethodTagCheck + arguments: + checkClassCaseSensitivity: %checkClassCaseSensitivity% + - class: PHPStan\Rules\Classes\PropertyTagCheck arguments: diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 0310ae11f80..a1409ca9171 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -26,6 +26,9 @@ use PHPStan\Rules\Classes\LocalTypeAliasesCheck; use PHPStan\Rules\Classes\LocalTypeAliasesRule; use PHPStan\Rules\Classes\LocalTypeTraitAliasesRule; +use PHPStan\Rules\Classes\MethodTagCheck; +use PHPStan\Rules\Classes\MethodTagRule; +use PHPStan\Rules\Classes\MethodTagTraitRule; use PHPStan\Rules\Classes\MixinRule; use PHPStan\Rules\Classes\PropertyTagCheck; use PHPStan\Rules\Classes\PropertyTagRule; @@ -236,6 +239,10 @@ private function getRuleRegistry(Container $container): RuleRegistry if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); + $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); + $rules[] = new MethodTagRule($methodTagCheck); + $rules[] = new MethodTagTraitRule($methodTagCheck, $reflectionProvider); + $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); $rules[] = new PropertyTagRule($propertyTagCheck); $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 68752bfab17..cc687fdfcc8 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1745,7 +1745,7 @@ public function getPropertyTags(): array } /** - * @return array + * @return array */ public function getMethodTags(): array { diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php new file mode 100644 index 00000000000..1c61442c9df --- /dev/null +++ b/src/Rules/Classes/MethodTagCheck.php @@ -0,0 +1,164 @@ + + */ + public function check( + ClassReflection $classReflection, + ClassLike $node, + ): array + { + $errors = []; + foreach ($classReflection->getMethodTags() as $methodName => $methodTag) { + $i = 0; + foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { + $i++; + $parameterDescription = sprintf('parameter #%d $%s', $i, $parameterName); + foreach ($this->checkMethodType($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { + $errors[] = $error; + } + + if ($parameterTag->getDefaultValue() === null) { + continue; + } + + foreach ($this->checkMethodType($classReflection, $methodName, sprintf('%s default value', $parameterDescription), $parameterTag->getDefaultValue(), $node) as $error) { + $errors[] = $error; + } + } + + foreach ($this->checkMethodType($classReflection, $methodName, 'return type', $methodTag->getReturnType(), $node) as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + /** + * @return list + */ + private function checkMethodType(ClassReflection $classReflection, string $methodName, string $description, Type $type, ClassLike $node): array + { + if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @method for method %s::%s() %s contains unresolvable type.', + $classReflection->getDisplayName(), + $methodName, + $description, + ))->identifier('methodTag.unresolvableType') + ->build(), + ]; + } + + $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); + $escapedMethodName = SprintfHelper::escapeFormatString($methodName); + $escapedDescription = SprintfHelper::escapeFormatString($description); + + $errors = $this->genericObjectTypeCheck->check( + $type, + sprintf('PHPDoc tag @method for method %s::%s() %s contains generic type %%s but %%s %%s is not generic.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s does not specify all template types of %%s %%s: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Type %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is not subtype of template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is in conflict with %%s template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is redundant, template type %%s of %%s %%s has the same variance.', $escapedClassName, $escapedMethodName, $escapedDescription), + ); + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @method for method %s::%s() %s contains generic %s but does not specify its types: %s', + $classReflection->getDisplayName(), + $methodName, + $description, + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @method for method %s() %s with no value type specified in iterable type %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $methodName, + $description, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @method for method %s() %s with no signature specified for %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $methodName, + $description, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + + foreach ($type->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains unknown class %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains invalid type %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) + ->identifier('methodTag.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + } + + return $errors; + } + +} diff --git a/src/Rules/Classes/MethodTagRule.php b/src/Rules/Classes/MethodTagRule.php new file mode 100644 index 00000000000..cdfc6759e7a --- /dev/null +++ b/src/Rules/Classes/MethodTagRule.php @@ -0,0 +1,30 @@ + + */ +final class MethodTagRule implements Rule +{ + + public function __construct(private MethodTagCheck $check) + { + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + } + +} diff --git a/src/Rules/Classes/MethodTagTraitRule.php b/src/Rules/Classes/MethodTagTraitRule.php new file mode 100644 index 00000000000..57f84a39419 --- /dev/null +++ b/src/Rules/Classes/MethodTagTraitRule.php @@ -0,0 +1,39 @@ + + */ +final class MethodTagTraitRule implements Rule +{ + + public function __construct(private MethodTagCheck $check, private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); + } + +} diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php new file mode 100644 index 00000000000..9ff4c1337ad --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -0,0 +1,106 @@ + + */ +class MethodTagRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MethodTagRule( + new MethodTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + ); + } + + public function testRule(): void + { + $fooClassLine = 12; + $this->analyse([__DIR__ . '/data/method-tag.php'], [ + [ + 'PHPDoc tag @method for method MethodTag\Foo::doFoo() return type contains unknown class MethodTag\intt.', + $fooClassLine, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @method for method MethodTag\Foo::doBar() parameter #1 $a contains unresolvable type.', + $fooClassLine, + ], + [ + 'PHPDoc tag @method for method MethodTag\Foo::doBaz2() parameter #1 $a default value contains unresolvable type.', + 12, + ], + [ + 'Class MethodTag\Foo has PHPDoc tag @method for method doMissingIterablueValue() return type with no value type specified in iterable type array.', + 12, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'PHPDoc tag @method for method MethodTag\TestGenerics::doA() return type contains generic type Exception but class Exception is not generic.', + 39, + ], + [ + 'Generic type MethodTag\Generic in PHPDoc tag @method for method MethodTag\TestGenerics::doB() return type does not specify all template types of class MethodTag\Generic: T, U', + 39, + ], + [ + 'Generic type MethodTag\Generic in PHPDoc tag @method for method MethodTag\TestGenerics::doC() return type specifies 3 template types, but class MethodTag\Generic supports only 2: T, U', + 39, + ], + [ + 'Type string in generic type MethodTag\Generic in PHPDoc tag @method for method MethodTag\TestGenerics::doD() return type is not subtype of template type T of int of class MethodTag\Generic.', + 39, + ], + [ + 'PHPDoc tag @method for method MethodTag\MissingGenerics::doA() return type contains generic class MethodTag\Generic but does not specify its types: T, U', + 47, + ], + [ + 'Class MethodTag\MissingIterableValue has PHPDoc tag @method for method doA() return type with no value type specified in iterable type array.', + 55, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Class MethodTag\MissingCallableSignature has PHPDoc tag @method for method doA() return type with no signature specified for callable.', + 63, + ], + [ + 'PHPDoc tag @method for method MethodTag\NonexistentClasses::doA() return type contains unknown class MethodTag\Nonexistent.', + 73, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @method for method MethodTag\NonexistentClasses::doB() return type contains invalid type PropertyTagTrait\Foo.', + 73, + ], + [ + 'Class MethodTag\Foo referenced with incorrect case: MethodTag\fOO.', + 73, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php new file mode 100644 index 00000000000..543e0d9d70b --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -0,0 +1,65 @@ + + */ +class MethodTagTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MethodTagTraitRule( + new MethodTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $fooTraitLine = 12; + $this->analyse([__DIR__ . '/data/method-tag-trait.php'], [ + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doFoo() return type contains unknown class MethodTagTrait\intt.', + $fooTraitLine, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doBar() parameter #1 $a contains unresolvable type.', + $fooTraitLine, + ], + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doBaz2() parameter #1 $a default value contains unresolvable type.', + $fooTraitLine, + ], + [ + 'Trait MethodTagTrait\Foo has PHPDoc tag @method for method doMissingIterablueValue() return type with no value type specified in iterable type array.', + $fooTraitLine, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/method-tag-trait.php b/tests/PHPStan/Rules/Classes/data/method-tag-trait.php new file mode 100644 index 00000000000..149d43a8542 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/method-tag-trait.php @@ -0,0 +1,23 @@ + doA() + * @method Generic doB() + * @method Generic doC() + * @method Generic doD() + */ +class TestGenerics +{ + +} + +/** + * @method Generic doA() + */ +class MissingGenerics +{ + +} + +/** + * @method Generic doA() + */ +class MissingIterableValue +{ + +} + +/** + * @method Generic doA() + */ +class MissingCallableSignature +{ + +} + +/** + * @method Nonexistent doA() + * @method \PropertyTagTrait\Foo doB() + * @method fOO doC() + */ +class NonexistentClasses +{ + +} diff --git a/tests/PHPStan/Rules/Classes/data/property-tag.php b/tests/PHPStan/Rules/Classes/data/property-tag.php index 253ce691748..f6f76e56101 100644 --- a/tests/PHPStan/Rules/Classes/data/property-tag.php +++ b/tests/PHPStan/Rules/Classes/data/property-tag.php @@ -86,8 +86,3 @@ class NonexistentClasses { } - - -// todo nonexistent class -// todo trait -// todo class name case From 2671bb64ab2037f7429bf335f20b48947c3b527a Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:06:26 +0000 Subject: [PATCH 0051/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 58a4c837ce1..c05c3fd4ce7 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.13", - "phpstan/php-8-stubs": "0.3.95", + "phpstan/php-8-stubs": "0.3.97", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index cbed841d934..06578e01d35 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db22a3caebfecfcc88bd82e014d25b19", + "content-hash": "7ca1dce24a4867287a0ea250e10ef4e2", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.95", + "version": "0.3.97", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "1e2422fdfc9da3e96bc1038eaf42728025d24756" + "reference": "cf1c7eedf0be83dabf3a8694556445966a1f57f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1e2422fdfc9da3e96bc1038eaf42728025d24756", - "reference": "1e2422fdfc9da3e96bc1038eaf42728025d24756", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/cf1c7eedf0be83dabf3a8694556445966a1f57f0", + "reference": "cf1c7eedf0be83dabf3a8694556445966a1f57f0", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.95" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.97" }, - "time": "2024-08-12T00:18:17+00:00" + "time": "2024-08-26T12:05:47+00:00" }, { "name": "phpstan/phpdoc-parser", From 8b5a27a79bf621413798aa0afd486264f17a48a0 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Mon, 26 Aug 2024 20:24:05 +0000 Subject: [PATCH 0052/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c05c3fd4ce7..a252b221974 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.13", + "ondrejmirtes/better-reflection": "6.25.0.15", "phpstan/php-8-stubs": "0.3.97", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 06578e01d35..33d5165355d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ca1dce24a4867287a0ea250e10ef4e2", + "content-hash": "5f42c6fc299ea8b76be8124ba1f01218", "packages": [ { "name": "clue/ndjson-react", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.13", + "version": "6.25.0.15", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "ee473c36242850418a8bf372961ab3d9ec0ca234" + "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/ee473c36242850418a8bf372961ab3d9ec0ca234", - "reference": "ee473c36242850418a8bf372961ab3d9ec0ca234", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/248261ac2f7ec04fcf7cec5e1c815303368f6d0e", + "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.13" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.15" }, - "time": "2024-08-03T11:36:12+00:00" + "time": "2024-08-26T20:22:03+00:00" }, { "name": "phpstan/php-8-stubs", From 3491ea33a6a271d8ccaa6e9187f9f70dbcc05054 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:45:51 +0200 Subject: [PATCH 0053/3097] Allow running on PHP 8.4 --- conf/parametersSchema.neon | 2 +- src/Php/PhpVersionFactory.php | 2 +- tests/PHPStan/Php/PhpVersionFactoryTest.php | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 2f3ef3b668c..84a40065666 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -145,7 +145,7 @@ parametersSchema: minimumNumberOfJobsPerProcess: int(), buffer: int() ]) - phpVersion: schema(anyOf(schema(int(), min(70100), max(80399))), nullable()) + phpVersion: schema(anyOf(schema(int(), min(70100), max(80499))), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() propertyAlwaysWrittenTags: listOf(string()) diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index ef1e244ac42..d926420e773 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -26,7 +26,7 @@ public function create(): PhpVersion $parts = explode('.', $this->composerPhpVersion); $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); $tmp = max($tmp, 70100); - $versionId = min($tmp, 80399); + $versionId = min($tmp, 80499); $source = PhpVersion::SOURCE_COMPOSER_PLATFORM_PHP; } else { $versionId = PHP_VERSION_ID; diff --git a/tests/PHPStan/Php/PhpVersionFactoryTest.php b/tests/PHPStan/Php/PhpVersionFactoryTest.php index f84fe900ce7..d8f30898fc4 100644 --- a/tests/PHPStan/Php/PhpVersionFactoryTest.php +++ b/tests/PHPStan/Php/PhpVersionFactoryTest.php @@ -74,8 +74,14 @@ public function dataCreate(): array [ null, '8.4', - 80399, - '8.3.99', + 80400, + '8.4', + ], + [ + null, + '8.5', + 80499, + '8.4.99', ], [ null, From 6f11b499954f4a9b7ea453e6889d844d00961ae1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 13:48:19 +0200 Subject: [PATCH 0054/3097] Test on PHP 8.4 --- .github/workflows/lint.yml | 3 ++- .github/workflows/reflection-golden-test.yml | 5 +++-- .github/workflows/static-analysis.yml | 4 +++- .github/workflows/tests.yml | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b0393de1099..03420615cde 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,6 +32,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: "Checkout" @@ -50,7 +51,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - name: "Lint" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 3c13f9205b6..6d16f21aaa0 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -71,6 +71,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - uses: Wandalen/wretry.action@v3.5.0 @@ -101,7 +102,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" @@ -120,7 +121,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d38dd2726ea..450717dbff8 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -38,6 +38,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" operating-system: [ubuntu-latest, windows-latest] steps: @@ -56,7 +57,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" @@ -85,6 +86,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: "Checkout" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e86c7738fb3..d1c6348bc24 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,6 +41,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" operating-system: [ ubuntu-latest, windows-latest ] steps: @@ -61,7 +62,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" From 11853e15073adb588477b107ea5012438703b7bd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 14:14:09 +0200 Subject: [PATCH 0055/3097] Update react/socket --- composer.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.lock b/composer.lock index 33d5165355d..db6184e8783 100644 --- a/composer.lock +++ b/composer.lock @@ -3013,31 +3013,31 @@ }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", + "react/async": "^4.3 || ^3.3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -3081,7 +3081,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { @@ -3089,7 +3089,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { "name": "react/stream", From f6526cc12fbebf8c4c6a4d7d21a28b7227b3ee5f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 14:24:01 +0200 Subject: [PATCH 0056/3097] Exit as function --- src/Php/PhpVersion.php | 5 + .../BetterReflectionProvider.php | 14 ++ src/Reflection/Php/ExitFunctionReflection.php | 140 ++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 src/Reflection/Php/ExitFunctionReflection.php diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 14f81570cad..e08bad14329 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -338,4 +338,9 @@ public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool return $this->versionId < 80000; } + public function hasExitAsFunction(): bool + { + return $this->versionId >= 80400; + } + } diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 0f8e5ba4633..b25b7b1e0f2 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -40,6 +40,7 @@ use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\NamespaceAnswerer; +use PHPStan\Reflection\Php\ExitFunctionReflection; use PHPStan\Reflection\Php\PhpFunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider; @@ -52,6 +53,7 @@ use function array_key_exists; use function array_map; use function base64_decode; +use function in_array; use function sprintf; use function strtolower; use const PHP_VERSION_ID; @@ -264,6 +266,12 @@ public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return $this->functionReflections[$lowerCasedFunctionName]; } + if ($this->phpVersion->hasExitAsFunction()) { + if (in_array($lowerCasedFunctionName, ['exit', 'die'], true)) { + return $this->functionReflections[$lowerCasedFunctionName] = new ExitFunctionReflection($lowerCasedFunctionName); + } + } + $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); if ($nativeFunctionReflection !== null) { $this->functionReflections[$lowerCasedFunctionName] = $nativeFunctionReflection; @@ -343,6 +351,12 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string { + if ($this->phpVersion->hasExitAsFunction()) { + $name = $nameNode->toLowerString(); + if (in_array($name, ['exit', 'die'], true)) { + return $name; + } + } return $this->resolveName($nameNode, function (string $name): bool { try { $this->reflector->reflectFunction($name); diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php new file mode 100644 index 00000000000..e343d801b1c --- /dev/null +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -0,0 +1,140 @@ +name; + } + + public function getFileName(): ?string + { + return null; + } + + public function getVariants(): array + { + $parameterType = new UnionType([ + new StringType(), + new IntegerType(), + ]); + return [ + new FunctionVariantWithPhpDocs( + TemplateTypeMap::createEmpty(), + TemplateTypeMap::createEmpty(), + [ + new DummyParameterWithPhpDocs( + 'status', + $parameterType, + true, + PassedByReference::createNo(), + false, + new ConstantIntegerType(0), + $parameterType, + new MixedType(), + null, + TrinaryLogic::createNo(), + null, + ), + ], + false, + new NeverType(true), + new MixedType(), + new NeverType(true), + TemplateTypeVarianceMap::createEmpty(), + ), + ]; + } + + /** + * @return ParametersAcceptorWithPhpDocs[] + */ + public function getNamedArgumentsVariants(): array + { + return $this->getVariants(); + } + + public function acceptsNamedArguments(): bool + { + return true; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isBuiltin(): bool + { + return true; + } + + public function getAsserts(): Assertions + { + return Assertions::createEmpty(); + } + + public function getDocComment(): ?string + { + return null; + } + + public function returnsByReference(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isPure(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + +} From dbb6bea2e03660f825d7f625065b40d8078b383e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 14:37:12 +0200 Subject: [PATCH 0057/3097] Update react/child-process --- composer.json | 2 +- composer.lock | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index a252b221974..2ec74216d0d 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", "react/async": "^3", - "react/child-process": "^0.6.4", + "react/child-process": "^0.7", "react/dns": "^1.10", "react/event-loop": "^1.2", "react/http": "^1.1", diff --git a/composer.lock b/composer.lock index db6184e8783..3de95f5a0f3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f42c6fc299ea8b76be8124ba1f01218", + "content-hash": "07d048440a600861774ae079e2896460", "packages": [ { "name": "clue/ndjson-react", @@ -2622,33 +2622,34 @@ }, { "name": "react/child-process", - "version": "v0.6.5", + "version": "0.7.x-dev", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + "reference": "ce2654d21d2a749e0a6142d00432e65ba003a2d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/ce2654d21d2a749e0a6142d00432e65ba003a2d9", + "reference": "ce2654d21d2a749e0a6142d00432e65ba003a2d9", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", "react/event-loop": "^1.2", - "react/stream": "^1.2" + "react/stream": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/socket": "^1.8", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { - "React\\ChildProcess\\": "src" + "React\\ChildProcess\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2685,19 +2686,15 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.5" + "source": "https://github.com/reactphp/child-process/tree/0.7.x" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-09-16T13:41:56+00:00" + "time": "2024-08-04T20:30:51+00:00" }, { "name": "react/dns", From a40b5d6a1cd4d41a0aff1cb2140f9467523a7133 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 14:41:34 +0200 Subject: [PATCH 0058/3097] Update PHPUnit to 9.6 in CI --- .github/workflows/static-analysis.yml | 7 +++++++ .github/workflows/tests.yml | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 450717dbff8..b19b6c10f7f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -70,6 +70,10 @@ jobs: if: matrix.php-version == '7.2' run: "composer require --dev phpunit/phpunit:^8.5.31 brianium/paratest:^4.0 composer/semver:^1.2 --update-with-dependencies --ignore-platform-reqs" + - name: "Update PHPUnit" + if: matrix.php-version != '7.2' && matrix.php-version != '7.3' + run: "composer update phpunit/phpunit -W" + - name: "PHPStan" run: "make phpstan" @@ -103,6 +107,9 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" + - name: "Update PHPUnit" + run: "composer update phpunit/phpunit -W" + - name: "Cache Result cache" uses: actions/cache@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d1c6348bc24..59d834e9c70 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,6 +66,10 @@ jobs: shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + - name: "Update PHPUnit" + if: matrix.php-version != '7.3' + run: "composer update phpunit/phpunit -W" + - name: "Tests" run: "make tests" From ae3c3987b555445c3a6273091e4117a4b1fcf79a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 14:52:13 +0200 Subject: [PATCH 0059/3097] Update nette/neon --- composer.json | 5 +++- composer.lock | 14 +++++------ patches/NetteNeonStringNode.patch | 40 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 patches/NetteNeonStringNode.patch diff --git a/composer.json b/composer.json index 2ec74216d0d..fcf036b4585 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "jetbrains/phpstorm-stubs": "dev-master#bdc8acd7c04c0c87197849c7cdd27e44b67b56c7", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", - "nette/neon": "^3.3.1", + "nette/neon": "3.3.3", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", @@ -109,6 +109,9 @@ ], "nette/di": [ "patches/DependencyChecker.patch" + ], + "nette/neon": [ + "patches/NetteNeonStringNode.patch" ] } }, diff --git a/composer.lock b/composer.lock index 3de95f5a0f3..350eeb5e8c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07d048440a600861774ae079e2896460", + "content-hash": "79f7f79bd158fd9c4955861df0778f30", "packages": [ { "name": "clue/ndjson-react", @@ -1698,16 +1698,16 @@ }, { "name": "nette/neon", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2" + "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/54b287d8c2cdbe577b02e28ca1713e275b05ece2", - "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2", + "url": "https://api.github.com/repos/nette/neon/zipball/22e384da162fab42961d48eb06c06d3ad0c11b95", + "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95", "shasum": "" }, "require": { @@ -1760,9 +1760,9 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.3.2" + "source": "https://github.com/nette/neon/tree/v3.3.3" }, - "time": "2021-11-25T15:57:41+00:00" + "time": "2022-03-10T02:04:26+00:00" }, { "name": "nette/php-generator", diff --git a/patches/NetteNeonStringNode.patch b/patches/NetteNeonStringNode.patch new file mode 100644 index 00000000000..ff7332693fa --- /dev/null +++ b/patches/NetteNeonStringNode.patch @@ -0,0 +1,40 @@ +--- src/Neon/Node/StringNode.php 2022-03-10 03:04:26 ++++ src/Neon/Node/StringNode.php 2024-08-26 14:53:45 +@@ -79,27 +79,18 @@ + + public function toString(): string + { +- if (strpos($this->value, "\n") === false) { +- return "'" . str_replace("'", "''", $this->value) . "'"; ++ $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ++ if ($res === false) { ++ throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); ++ } + +- } elseif (preg_match('~\n[\t ]+\'{3}~', $this->value)) { +- $s = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +- $s = preg_replace_callback( +- '#[^\\\\]|\\\\(.)#s', +- function ($m) { +- return ['n' => "\n", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; +- }, +- substr($s, 1, -1) +- ); +- $s = str_replace('"""', '""\"', $s); +- $delim = '"""'; +- +- } else { +- $s = $this->value; +- $delim = "'''"; ++ if (strpos($this->value, "\n") !== false) { ++ $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { ++ return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; ++ }, $res); ++ $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; + } + +- $s = preg_replace('#^(?=.)#m', "\t", $s); +- return $delim . "\n" . $s . "\n" . $delim; ++ return $res; + } + } From 847a947baaaffaca3685913b31b21637c04800d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 21:58:02 +0200 Subject: [PATCH 0060/3097] Patch nette/neon --- composer.json | 3 ++- composer.lock | 2 +- patches/NeonParser.patch | 11 +++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 patches/NeonParser.patch diff --git a/composer.json b/composer.json index fcf036b4585..fdfd4e753df 100644 --- a/composer.json +++ b/composer.json @@ -111,7 +111,8 @@ "patches/DependencyChecker.patch" ], "nette/neon": [ - "patches/NetteNeonStringNode.patch" + "patches/NetteNeonStringNode.patch", + "patches/NeonParser.patch" ] } }, diff --git a/composer.lock b/composer.lock index 350eeb5e8c0..6af4110a978 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "79f7f79bd158fd9c4955861df0778f30", + "content-hash": "a55bb8ed92edb521d2583b129375410b", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/NeonParser.patch b/patches/NeonParser.patch new file mode 100644 index 00000000000..9ff7a2b3dea --- /dev/null +++ b/patches/NeonParser.patch @@ -0,0 +1,11 @@ +--- src/Neon/Parser.php 2022-03-10 03:04:26 ++++ src/Neon/Parser.php 2024-08-26 21:57:02 +@@ -236,7 +236,7 @@ + } + + +- private function injectPos(Node $node, int $start = null, int $end = null): Node ++ private function injectPos(Node $node, ?int $start = null, ?int $end = null): Node + { + $node->startTokenPos = $start ?? $this->tokens->getPos(); + $node->startLine = $this->posToLine[$node->startTokenPos]; From b275771a7938c346674af475d2dc4d5feb7028b8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 22:31:40 +0200 Subject: [PATCH 0061/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index fdfd4e753df..4f382b64525 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.15", + "ondrejmirtes/better-reflection": "6.25.0.16", "phpstan/php-8-stubs": "0.3.97", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 6af4110a978..a6da61b2a5c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a55bb8ed92edb521d2583b129375410b", + "content-hash": "c5e031ebd915a5356fd5a10fac39f871", "packages": [ { "name": "clue/ndjson-react", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.15", + "version": "6.25.0.16", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e" + "reference": "8a117c1a435a58295548f5c7a740da800bb01d61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/248261ac2f7ec04fcf7cec5e1c815303368f6d0e", - "reference": "248261ac2f7ec04fcf7cec5e1c815303368f6d0e", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/8a117c1a435a58295548f5c7a740da800bb01d61", + "reference": "8a117c1a435a58295548f5c7a740da800bb01d61", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.15" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.16" }, - "time": "2024-08-26T20:22:03+00:00" + "time": "2024-08-26T20:30:37+00:00" }, { "name": "phpstan/php-8-stubs", From 1330b19a5cdc21be6c638a2978a5f8508182f532 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 22:47:57 +0200 Subject: [PATCH 0062/3097] Update nette/php-generator --- build/composer-dependency-analyser.php | 1 + composer.json | 1 + composer.lock | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/build/composer-dependency-analyser.php b/build/composer-dependency-analyser.php index de637bfc799..723a3ece2e9 100644 --- a/build/composer-dependency-analyser.php +++ b/build/composer-dependency-analyser.php @@ -19,6 +19,7 @@ 'symfony/process', 'symfony/service-contracts', 'symfony/string', + 'nette/php-generator', ]; return $config diff --git a/composer.json b/composer.json index 4f382b64525..ed74282aee2 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.3", + "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", diff --git a/composer.lock b/composer.lock index a6da61b2a5c..988ff4494d6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5e031ebd915a5356fd5a10fac39f871", + "content-hash": "d2b3ff31349073202fa407bce5fa46b0", "packages": [ { "name": "clue/ndjson-react", @@ -1766,21 +1766,21 @@ }, { "name": "nette/php-generator", - "version": "v3.6.5", + "version": "v3.6.9", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82" + "reference": "d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/9370403f9d9c25b51c4596ded1fbfe70347f7c82", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82", + "url": "https://api.github.com/repos/nette/php-generator/zipball/d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6", + "reference": "d31782f7bd2ae84ad06f863391ec3fb77ca4d0a6", "shasum": "" }, "require": { "nette/utils": "^3.1.2", - "php": ">=7.2 <8.2" + "php": ">=7.2 <8.3" }, "require-dev": { "nette/tester": "^2.4", @@ -1828,9 +1828,9 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v3.6.5" + "source": "https://github.com/nette/php-generator/tree/v3.6.9" }, - "time": "2021-11-24T16:23:44+00:00" + "time": "2022-10-04T11:49:47+00:00" }, { "name": "nette/robot-loader", From 2fdef47ed7fb0caf4653c663d2d8f6c9098293d0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 22:48:19 +0200 Subject: [PATCH 0063/3097] Update BetterReflection again --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ed74282aee2..d31bac1381a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.16", + "ondrejmirtes/better-reflection": "6.25.0.17", "phpstan/php-8-stubs": "0.3.97", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 988ff4494d6..31c6ca2800c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d2b3ff31349073202fa407bce5fa46b0", + "content-hash": "bd2a93888cc6505be9390abef749c447", "packages": [ { "name": "clue/ndjson-react", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.16", + "version": "6.25.0.17", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "8a117c1a435a58295548f5c7a740da800bb01d61" + "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/8a117c1a435a58295548f5c7a740da800bb01d61", - "reference": "8a117c1a435a58295548f5c7a740da800bb01d61", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", + "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.16" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.17" }, - "time": "2024-08-26T20:30:37+00:00" + "time": "2024-08-26T20:47:13+00:00" }, { "name": "phpstan/php-8-stubs", From ef44f28c7a905f585ddac5936a0efa8133218160 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 22:54:59 +0200 Subject: [PATCH 0064/3097] Fix tests --- .../PHPStan/Analyser/nsrt/bug-7341-php-84.php | 29 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-7341.php | 2 +- .../RegularExpressionPatternRuleTest.php | 3 ++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php b/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php new file mode 100644 index 00000000000..a756e468d56 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7341-php-84.php @@ -0,0 +1,29 @@ += 8.4 + +namespace Bug7341Php84; + +use function PHPStan\Testing\assertType; + +final class CsvWriterTerminate extends \php_user_filter +{ + /** + * @param resource $in + * @param resource $out + * @param int $consumed + * @param bool $closing + */ + public function filter($in, $out, &$consumed, $closing): int + { + while ($bucket = stream_bucket_make_writeable($in)) { + assertType('StreamBucket', $bucket); + + if (isset($this->params['terminate'])) { + $bucket->data = preg_replace('/([^\r])\n/', '$1'.$this->params['terminate'], $bucket->data); + } + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return \PSFS_PASS_ON; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-7341.php b/tests/PHPStan/Analyser/nsrt/bug-7341.php index a227b7cf3cb..45b3efe97bb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7341.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7341.php @@ -1,4 +1,4 @@ -= 80200) { $messagePart = 'alphanumeric, backslash, or NUL'; } + if (PHP_VERSION_ID >= 80400) { + $messagePart = 'alphanumeric, backslash, or NUL byte'; + } $this->analyse( [__DIR__ . '/data/valid-regex-pattern.php'], From f6702882ff9409195ed609ee803c61f2d2bd202c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 23:06:26 +0200 Subject: [PATCH 0065/3097] Patch hoa --- composer.json | 11 ++++++++++- composer.lock | 2 +- patches/File.patch | 11 +++++++++++ patches/Idle.patch | 11 +++++++++++ patches/Invocation.patch | 11 +++++++++++ patches/Read.patch | 11 +++++++++++ patches/Stream.patch | 13 +++++++++++-- patches/TreeNode.patch | 14 ++++++++++++++ 8 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 patches/File.patch create mode 100644 patches/Idle.patch create mode 100644 patches/Invocation.patch create mode 100644 patches/Read.patch create mode 100644 patches/TreeNode.patch diff --git a/composer.json b/composer.json index d31bac1381a..e5f77ae753f 100644 --- a/composer.json +++ b/composer.json @@ -82,14 +82,23 @@ "composer/ca-bundle": [ "patches/cloudflare-ca.patch" ], + "hoa/exception": [ + "patches/Idle.patch" + ], + "hoa/file": [ + "patches/File.patch", + "patches/Read.patch" + ], "hoa/iterator": [ "patches/Buffer.patch", "patches/Lookahead.patch" ], "hoa/compiler": [ "patches/HoaException.patch", + "patches/Invocation.patch", "patches/Rule.patch", - "patches/Lexer.patch" + "patches/Lexer.patch", + "patches/TreeNode.patch" ], "hoa/consistency": [ "patches/Consistency.patch" diff --git a/composer.lock b/composer.lock index 31c6ca2800c..b50891bf2ca 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bd2a93888cc6505be9390abef749c447", + "content-hash": "758196456ce51136032914c3ba937aff", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/File.patch b/patches/File.patch new file mode 100644 index 00000000000..0732eb0e555 --- /dev/null +++ b/patches/File.patch @@ -0,0 +1,11 @@ +--- File.php 2017-07-11 09:42:15 ++++ File.php 2024-08-26 23:13:27 +@@ -192,7 +192,7 @@ + * @throws \Hoa\File\Exception\FileDoesNotExist + * @throws \Hoa\File\Exception + */ +- protected function &_open($streamName, Stream\Context $context = null) ++ protected function &_open($streamName, ?Stream\Context $context = null) + { + if (substr($streamName, 0, 4) == 'file' && + false === is_dir(dirname($streamName))) { diff --git a/patches/Idle.patch b/patches/Idle.patch new file mode 100644 index 00000000000..6f6f0dbe29b --- /dev/null +++ b/patches/Idle.patch @@ -0,0 +1,11 @@ +--- Idle.php 2017-01-16 08:53:27 ++++ Idle.php 2024-08-26 23:18:04 +@@ -100,7 +100,7 @@ + $message, + $code = 0, + $arguments = [], +- \Exception $previous = null ++ ?\Exception $previous = null + ) { + $this->_tmpArguments = $arguments; + parent::__construct($message, $code, $previous); diff --git a/patches/Invocation.patch b/patches/Invocation.patch new file mode 100644 index 00000000000..a3e9e10965d --- /dev/null +++ b/patches/Invocation.patch @@ -0,0 +1,11 @@ +--- Llk/Rule/Invocation.php 2017-08-08 09:44:07 ++++ Llk/Rule/Invocation.php 2024-08-26 23:11:25 +@@ -95,7 +95,7 @@ + public function __construct( + $rule, + $data, +- array $todo = null, ++ ?array $todo = null, + $depth = -1 + ) { + $this->_rule = $rule; diff --git a/patches/Read.patch b/patches/Read.patch new file mode 100644 index 00000000000..ad9b64c4458 --- /dev/null +++ b/patches/Read.patch @@ -0,0 +1,11 @@ +--- Read.php 2017-07-11 09:42:15 ++++ Read.php 2024-08-26 23:09:54 +@@ -77,7 +77,7 @@ + * @throws \Hoa\File\Exception\FileDoesNotExist + * @throws \Hoa\File\Exception + */ +- protected function &_open($streamName, Stream\Context $context = null) ++ protected function &_open($streamName, ?Stream\Context $context = null) + { + static $createModes = [ + parent::MODE_READ diff --git a/patches/Stream.patch b/patches/Stream.patch index daf6990e1bf..8dbb2e108e9 100644 --- a/patches/Stream.patch +++ b/patches/Stream.patch @@ -1,5 +1,5 @@ ---- Stream.php 2017-02-21 17:01:06.000000000 +0100 -+++ Stream.php 2021-04-19 17:10:20.000000000 +0200 +--- Stream.php 2024-08-26 23:05:49 ++++ Stream.php 2024-08-26 23:01:08 @@ -192,7 +192,7 @@ * @return array * @throws \Hoa\Stream\Exception @@ -9,6 +9,15 @@ $streamName, Stream $handler, $context = null +@@ -250,7 +250,7 @@ + * @return resource + * @throws \Hoa\Exception\Exception + */ +- abstract protected function &_open($streamName, Context $context = null); ++ abstract protected function &_open($streamName, ?Context $context = null); + + /** + * Close the current stream. @@ -687,11 +687,6 @@ Consistency::flexEntity('Hoa\Stream\Stream'); diff --git a/patches/TreeNode.patch b/patches/TreeNode.patch new file mode 100644 index 00000000000..39e7917defc --- /dev/null +++ b/patches/TreeNode.patch @@ -0,0 +1,14 @@ +--- Llk/TreeNode.php 2017-08-08 09:44:07 ++++ Llk/TreeNode.php 2024-08-26 23:07:29 +@@ -95,9 +95,9 @@ + */ + public function __construct( + $id, +- array $value = null, ++ ?array $value = null, + array $children = [], +- self $parent = null ++ ?self $parent = null + ) { + $this->setId($id); + From bb4fdfcd7004d0532e13f5f6b9615bacf05fe7bf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 23:23:23 +0200 Subject: [PATCH 0066/3097] Replace highlight_string() stub with a return type extension --- conf/config.neon | 5 ++ src/Php/PhpVersion.php | 5 ++ ...hlightStringDynamicReturnTypeExtension.php | 51 +++++++++++++++++++ stubs/core.stub | 6 --- .../PHPStan/Analyser/nsrt/pr-1244-php-84.php | 35 +++++++++++++ tests/PHPStan/Analyser/nsrt/pr-1244.php | 2 +- 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/Type/Php/HighlightStringDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/pr-1244-php-84.php diff --git a/conf/config.neon b/conf/config.neon index 38a1518a30f..4e3d04027e4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1497,6 +1497,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\HighlightStringDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\IntdivThrowTypeExtension tags: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index e08bad14329..ff3f1966e2b 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -343,4 +343,9 @@ public function hasExitAsFunction(): bool return $this->versionId >= 80400; } + public function highlightStringDoesNotReturnFalse(): bool + { + return $this->versionId >= 80400; + } + } diff --git a/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..12236c20cf1 --- /dev/null +++ b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php @@ -0,0 +1,51 @@ +getName() === 'highlight_string'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $args = $functionCall->getArgs(); + if (count($args) < 2) { + if ($this->phpVersion->highlightStringDoesNotReturnFalse()) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + + $returnType = $scope->getType($args[1]->value); + if ($returnType->isTrue()->yes()) { + return new StringType(); + } + + if ($this->phpVersion->highlightStringDoesNotReturnFalse()) { + return new ConstantBooleanType(true); + } + + return new BooleanType(); + } + +} diff --git a/stubs/core.stub b/stubs/core.stub index 2cb6f294483..e3a0b751ee9 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -35,12 +35,6 @@ function highlight_file($var, bool $return = false) {} */ function show_source($var, bool $return = false) {} -/** - * @param mixed $var - * @return ($return is true ? string : bool) - */ -function highlight_string($var, bool $return = false) {} - /** * @param mixed $var * @param bool $return diff --git a/tests/PHPStan/Analyser/nsrt/pr-1244-php-84.php b/tests/PHPStan/Analyser/nsrt/pr-1244-php-84.php new file mode 100644 index 00000000000..17bf3d44c14 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pr-1244-php-84.php @@ -0,0 +1,35 @@ += 8.4 + +namespace Pr1244Php84; + +use function PHPStan\Testing\assertType; + +function foo() { + /** @var string $string */ + $string = doFoo(); + + assertType('null', var_export()); + assertType('null', var_export($string)); + assertType('null', var_export($string, false)); + assertType('string', var_export($string, true)); + + assertType('true', highlight_string()); + assertType('true', highlight_string($string)); + assertType('true', highlight_string($string, false)); + assertType('string', highlight_string($string, true)); + + assertType('bool', highlight_file()); + assertType('bool', highlight_file($string)); + assertType('bool', highlight_file($string, false)); + assertType('string', highlight_file($string, true)); + + assertType('bool', show_source()); + assertType('bool', show_source($string)); + assertType('bool', show_source($string, false)); + assertType('string', show_source($string, true)); + + assertType('true', print_r()); + assertType('true', print_r($string)); + assertType('true', print_r($string, false)); + assertType('string', print_r($string, true)); +} diff --git a/tests/PHPStan/Analyser/nsrt/pr-1244.php b/tests/PHPStan/Analyser/nsrt/pr-1244.php index 6aec5018a07..a8c0fc19aed 100644 --- a/tests/PHPStan/Analyser/nsrt/pr-1244.php +++ b/tests/PHPStan/Analyser/nsrt/pr-1244.php @@ -1,4 +1,4 @@ - Date: Mon, 26 Aug 2024 23:40:49 +0200 Subject: [PATCH 0067/3097] Issue bot - test PHP 8.4 --- issue-bot/src/Console/DownloadCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/issue-bot/src/Console/DownloadCommand.php b/issue-bot/src/Console/DownloadCommand.php index 2b702bd9a2e..ab3bce12d26 100644 --- a/issue-bot/src/Console/DownloadCommand.php +++ b/issue-bot/src/Console/DownloadCommand.php @@ -96,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $matrix = []; - foreach ([70200, 70300, 70400, 80000, 80100, 80200, 80300] as $phpVersion) { + foreach ([70200, 70300, 70400, 80000, 80100, 80200, 80300, 80400] as $phpVersion) { $phpVersionHashes = []; foreach ($cachedResults as $hash => $result) { $resultPhpVersions = array_keys($result->getVersionedErrors()); @@ -113,6 +113,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!in_array(80300, $resultPhpVersions, true)) { $resultPhpVersions[] = 80300; } + if (!in_array(80400, $resultPhpVersions, true)) { + $resultPhpVersions[] = 80400; + } if (!in_array($phpVersion, $resultPhpVersions, true)) { continue; From 7d85a3aabd641f4d7d8295a7006cdaca478931c7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Aug 2024 23:58:19 +0200 Subject: [PATCH 0068/3097] Fix --- src/Php/PhpVersion.php | 5 ----- .../BetterReflection/BetterReflectionProvider.php | 15 ++++++--------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index ff3f1966e2b..817316d16f9 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -338,11 +338,6 @@ public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool return $this->versionId < 80000; } - public function hasExitAsFunction(): bool - { - return $this->versionId >= 80400; - } - public function highlightStringDoesNotReturnFalse(): bool { return $this->versionId >= 80400; diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index b25b7b1e0f2..6ae28ec9c62 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -266,10 +266,8 @@ public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return $this->functionReflections[$lowerCasedFunctionName]; } - if ($this->phpVersion->hasExitAsFunction()) { - if (in_array($lowerCasedFunctionName, ['exit', 'die'], true)) { - return $this->functionReflections[$lowerCasedFunctionName] = new ExitFunctionReflection($lowerCasedFunctionName); - } + if (in_array($lowerCasedFunctionName, ['exit', 'die'], true)) { + return $this->functionReflections[$lowerCasedFunctionName] = new ExitFunctionReflection($lowerCasedFunctionName); } $nativeFunctionReflection = $this->nativeFunctionReflectionProvider->findFunctionReflection($lowerCasedFunctionName); @@ -351,12 +349,11 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string { - if ($this->phpVersion->hasExitAsFunction()) { - $name = $nameNode->toLowerString(); - if (in_array($name, ['exit', 'die'], true)) { - return $name; - } + $name = $nameNode->toLowerString(); + if (in_array($name, ['exit', 'die'], true)) { + return $name; } + return $this->resolveName($nameNode, function (string $name): bool { try { $this->reflector->reflectFunction($name); From 4f7beffdf4c30c49d7ece03f7131f0cb0518d4ab Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 27 Aug 2024 00:18:28 +0000 Subject: [PATCH 0069/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e5f77ae753f..d033daaf218 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", - "phpstan/php-8-stubs": "0.3.97", + "phpstan/php-8-stubs": "0.3.98", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index b50891bf2ca..46635833baa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "758196456ce51136032914c3ba937aff", + "content-hash": "5658482bd1bfa5d86d2ae312865bfd24", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.97", + "version": "0.3.98", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "cf1c7eedf0be83dabf3a8694556445966a1f57f0" + "reference": "5a2c1686edf1908e536dbd6d4789aad26794431c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/cf1c7eedf0be83dabf3a8694556445966a1f57f0", - "reference": "cf1c7eedf0be83dabf3a8694556445966a1f57f0", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/5a2c1686edf1908e536dbd6d4789aad26794431c", + "reference": "5a2c1686edf1908e536dbd6d4789aad26794431c", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.97" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.98" }, - "time": "2024-08-26T12:05:47+00:00" + "time": "2024-08-27T00:17:50+00:00" }, { "name": "phpstan/phpdoc-parser", From db38ed7ef6f4f824592627091d59ad4289503225 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 28 Aug 2024 09:01:10 +0200 Subject: [PATCH 0070/3097] Added regression test --- tests/PHPStan/Analyser/nsrt/bug7856.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug7856.php diff --git a/tests/PHPStan/Analyser/nsrt/bug7856.php b/tests/PHPStan/Analyser/nsrt/bug7856.php new file mode 100644 index 00000000000..0b48d36ebc8 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug7856.php @@ -0,0 +1,16 @@ +modify(array_shift($intervals)); + } while (count($intervals) > 0 && $periodEnd->format('U') < $endDate); +} From 7a939397f9e4e3fcaf434a4db28cac9972de7f6a Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:18:06 +0000 Subject: [PATCH 0071/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index d033daaf218..cb9caba6f19 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", - "phpstan/php-8-stubs": "0.3.98", + "phpstan/php-8-stubs": "0.3.99", "phpstan/phpdoc-parser": "1.29.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 46635833baa..fc9f3d1ef04 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5658482bd1bfa5d86d2ae312865bfd24", + "content-hash": "90a2d079aaedb41e51a4f7b16bacbb85", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.98", + "version": "0.3.99", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "5a2c1686edf1908e536dbd6d4789aad26794431c" + "reference": "9bd59d79824c80d7613c8acc70542c42a8cac7f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/5a2c1686edf1908e536dbd6d4789aad26794431c", - "reference": "5a2c1686edf1908e536dbd6d4789aad26794431c", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/9bd59d79824c80d7613c8acc70542c42a8cac7f7", + "reference": "9bd59d79824c80d7613c8acc70542c42a8cac7f7", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.98" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.99" }, - "time": "2024-08-27T00:17:50+00:00" + "time": "2024-08-28T00:17:29+00:00" }, { "name": "phpstan/phpdoc-parser", From 760f86f9b3a5898b68f97317edfc2d575b6cdc41 Mon Sep 17 00:00:00 2001 From: Martin Bruna Date: Wed, 28 Aug 2024 09:34:27 +0200 Subject: [PATCH 0072/3097] Fix array_filter with callback optional persistance --- ...FilterFunctionReturnTypeReturnTypeExtension.php | 3 +++ tests/PHPStan/Analyser/nsrt/bug-11570.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11570.php diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php index d6f5a8251c7..c44d17a250a 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php @@ -33,6 +33,7 @@ use PHPStan\Type\TypeUtils; use function array_map; use function count; +use function in_array; use function is_string; use function strtolower; use function substr; @@ -192,9 +193,11 @@ private function filterByTruthyValue(Scope $scope, Error|Variable|null $itemVar, $results = []; foreach ($constantArrays as $constantArray) { $builder = ConstantArrayTypeBuilder::createEmpty(); + $optionalKeys = $constantArray->getOptionalKeys(); foreach ($constantArray->getKeyTypes() as $i => $keyType) { $itemType = $constantArray->getValueTypes()[$i]; [$newKeyType, $newItemType, $optional] = $this->processKeyAndItemType($scope, $keyType, $itemType, $itemVar, $keyVar, $expr); + $optional = $optional || in_array($i, $optionalKeys, true); if ($newKeyType instanceof NeverType || $newItemType instanceof NeverType) { continue; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11570.php b/tests/PHPStan/Analyser/nsrt/bug-11570.php new file mode 100644 index 00000000000..7a970620782 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11570.php @@ -0,0 +1,14 @@ + $var !== null); + assertType("array{one?: string, two?: string, three?: string}", $data); +} From 40513a05eb2896f1bed2ad72208d38dcd217d9e8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 28 Aug 2024 15:29:41 +0200 Subject: [PATCH 0073/3097] RegexArrayShapeMatcher - infer constant string types in alternations --- src/Type/Regex/RegexExpressionHelper.php | 3 ++ src/Type/Regex/RegexGroupParser.php | 21 ++++++++++- .../PHPStan/Analyser/nsrt/bug-11311-php72.php | 4 +-- tests/PHPStan/Analyser/nsrt/bug-11311.php | 4 +-- .../Analyser/nsrt/preg_match_shapes.php | 36 ++++++++++++------- 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/Type/Regex/RegexExpressionHelper.php b/src/Type/Regex/RegexExpressionHelper.php index 2b94fda6e2a..0334aead777 100644 --- a/src/Type/Regex/RegexExpressionHelper.php +++ b/src/Type/Regex/RegexExpressionHelper.php @@ -11,6 +11,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function array_key_exists; +use function ltrim; use function strrpos; use function substr; @@ -147,6 +148,8 @@ public function getPatternDelimiters(Concat $concat, Scope $scope): array private function getPatternDelimiter(string $regex): ?string { + $regex = ltrim($regex); + if ($regex === '') { return null; } diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 6938ff80001..8f282899ede 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -20,6 +20,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use function array_merge; use function count; use function in_array; use function is_int; @@ -432,7 +433,7 @@ private function walkGroupAst( $isNonEmpty = TrinaryLogic::createYes(); } } - } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing'], true)) { + } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing', '#alternation'], true)) { $onlyLiterals = null; } @@ -447,6 +448,7 @@ private function walkGroupAst( $isNumeric = TrinaryLogic::createNo(); } + $alternativeLiterals = []; foreach ($children as $child) { $this->walkGroupAst( $child, @@ -459,7 +461,24 @@ private function walkGroupAst( $inClass, $patternModifiers, ); + + if ($ast->getId() !== '#alternation') { + continue; + } + + if ($onlyLiterals !== null && $alternativeLiterals !== null) { + $alternativeLiterals = array_merge($alternativeLiterals, $onlyLiterals); + $onlyLiterals = []; + } else { + $alternativeLiterals = null; + } } + + if ($alternativeLiterals === null || $alternativeLiterals === []) { + return; + } + + $onlyLiterals = $alternativeLiterals; } private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php b/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php index 40e6d99d9f8..957b3557de8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php @@ -22,9 +22,9 @@ function doUnmatchedAsNull(string $s): void { // see https://3v4l.org/VeDob#veol function unmatchedAsNullWithOptionalGroup(string $s): void { if (preg_match('/Price: (£|€)?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{0: string, 1?: non-empty-string}", $matches); + assertType("array{0: string, 1?: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{0: string, 1?: non-empty-string}", $matches); + assertType("array{}|array{0: string, 1?: '£'|'€'}", $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index 3a01594ded5..0d2eaec66dd 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -23,11 +23,11 @@ function doUnmatchedAsNull(string $s): void { function unmatchedAsNullWithOptionalGroup(string $s): void { if (preg_match('/Price: (£|€)?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { // with PREG_UNMATCHED_AS_NULL the offset 1 will always exist. It is correct that it's nullable because it's optional though - assertType('array{string, non-empty-string|null}', $matches); + assertType("array{string, '£'|'€'|null}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{string, non-empty-string|null}', $matches); + assertType("array{}|array{string, '£'|'€'|null}", $matches); } function bug11331a(string $url):void { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 001ec26d21b..655c9d7d107 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -12,11 +12,11 @@ function doMatch(string $s): void { assertType('array{}|array{string}', $matches); if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType("array{string, '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{string, non-empty-string}', $matches); + assertType("array{}|array{string, '£'|'€'}", $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { assertType('array{string, non-empty-string, numeric-string}', $matches); @@ -54,9 +54,9 @@ function doMatch(string $s): void { assertType("array{}|array{0: string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { - assertType('array{0: string, 1?: non-empty-string}', $matches); + assertType("array{0: string, 1?: 'a'|'b'}", $matches); } - assertType('array{}|array{0: string, 1?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1?: 'a'|'b'}", $matches); if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { assertType("array{string, 'foo', 'bar', non-falsy-string}", $matches); @@ -356,30 +356,30 @@ function bug11291(string $s): void { function bug11323a(string $s): void { if (preg_match('/Price: (?P£|€)\d+/', $s, $matches)) { - assertType('array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function bug11323b(string $s): void { if (preg_match('/Price: (?£|€)\d+/', $s, $matches)) { - assertType('array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function unmatchedAsNullWithMandatoryGroup(string $s): void { if (preg_match('/Price: (?£|€)\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{0: string, currency: non-empty-string, 1: non-empty-string}', $matches); + assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function (string $s): void { @@ -608,17 +608,29 @@ function (string $s): void { }; function (string $s): void { - if (preg_match('/Price: (a|0)/', $s, $matches)) { + if (preg_match('/Price: (a|bc?)/', $s, $matches)) { assertType("array{string, non-empty-string}", $matches); } }; function (string $s): void { - if (preg_match('/Price: (aa|0)/', $s, $matches)) { + if (preg_match('/Price: (a|\d)/', $s, $matches)) { assertType("array{string, non-empty-string}", $matches); } }; +function (string $s): void { + if (preg_match('/Price: (a|0)/', $s, $matches)) { + assertType("array{string, '0'|'a'}", $matches); + } +}; + +function (string $s): void { + if (preg_match('/Price: (aa|0)/', $s, $matches)) { + assertType("array{string, '0'|'aa'}", $matches); + } +}; + function (string $s): void { if (preg_match('/( \d+ )/x', $s, $matches)) { assertType('array{string, numeric-string}', $matches); From 637fe4df77a70174cc2709abb4716a4a9d14704e Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:58:04 +0000 Subject: [PATCH 0074/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index cb9caba6f19..860315948ba 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", "phpstan/php-8-stubs": "0.3.99", - "phpstan/phpdoc-parser": "1.29.1", + "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index fc9f3d1ef04..8fe97b97d3b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "90a2d079aaedb41e51a4f7b16bacbb85", + "content-hash": "c0ecdd5ce5efc07d867dd44ff7c821a7", "packages": [ { "name": "clue/ndjson-react", @@ -2280,16 +2280,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.30.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", + "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", "shasum": "" }, "require": { @@ -2321,9 +2321,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-08-29T09:54:52+00:00" }, { "name": "psr/container", From ad3286151a8edd63ccd7b51d2396f691ab0e0a5a Mon Sep 17 00:00:00 2001 From: Aggelos Bellos Date: Thu, 29 Aug 2024 16:18:17 +0300 Subject: [PATCH 0075/3097] Bleeding edge - check if required file exists --- conf/bleedingEdge.neon | 1 + conf/config.level0.neon | 6 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + src/Rules/Keywords/RequireFileExistsRule.php | 138 ++++++++++++++++++ .../Keywords/RequireFileExistsRuleTest.php | 136 +++++++++++++++++ .../data/include-me-to-prove-you-work.txt | 0 .../data/require-file-conditionally.php | 12 ++ .../data/require-file-relative-path.php | 11 ++ .../data/require-file-simple-case.php | 14 ++ 10 files changed, 320 insertions(+) create mode 100644 src/Rules/Keywords/RequireFileExistsRule.php create mode 100644 tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php create mode 100644 tests/PHPStan/Rules/Keywords/data/include-me-to-prove-you-work.txt create mode 100644 tests/PHPStan/Rules/Keywords/data/require-file-conditionally.php create mode 100644 tests/PHPStan/Rules/Keywords/data/require-file-relative-path.php create mode 100644 tests/PHPStan/Rules/Keywords/data/require-file-simple-case.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index a9f20432b22..8fb6e4da6a1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -62,5 +62,6 @@ parameters: tooWidePropertyType: true explicitThrow: true absentTypeChecks: true + requireFileExists: true stubFiles: - ../stubs/bleedingEdge/Rule.stub diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 8052e5f5de5..d23705fa92e 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -30,6 +30,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.printfArrayParameters% PHPStan\Rules\Regexp\RegularExpressionQuotingRule: phpstan.rules.rule: %featureToggles.validatePregQuote% + PHPStan\Rules\Keywords\RequireFileExistsRule: + phpstan.rules.rule: %featureToggles.requireFileExists% rules: - PHPStan\Rules\Api\ApiInstantiationRule @@ -309,3 +311,7 @@ services: - class: PHPStan\Rules\Regexp\RegularExpressionQuotingRule + - + class: PHPStan\Rules\Keywords\RequireFileExistsRule + arguments: + currentWorkingDirectory: %currentWorkingDirectory% diff --git a/conf/config.neon b/conf/config.neon index 4e3d04027e4..32b0127eb19 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -94,6 +94,7 @@ parameters: preciseMissingReturn: false validatePregQuote: false noImplicitWildcard: false + requireFileExists: false narrowPregMatches: true tooWidePropertyType: false explicitThrow: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 84a40065666..05d6d79f0cf 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -93,6 +93,7 @@ parametersSchema: tooWidePropertyType: bool() explicitThrow: bool() absentTypeChecks: bool() + requireFileExists: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/Keywords/RequireFileExistsRule.php b/src/Rules/Keywords/RequireFileExistsRule.php new file mode 100644 index 00000000000..f2a9af9989b --- /dev/null +++ b/src/Rules/Keywords/RequireFileExistsRule.php @@ -0,0 +1,138 @@ + + */ +final class RequireFileExistsRule implements Rule +{ + + public function __construct(private string $currentWorkingDirectory) + { + } + + public function getNodeType(): string + { + return Include_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + $paths = $this->resolveFilePaths($node, $scope); + + foreach ($paths as $path) { + if ($this->doesFileExist($path, $scope)) { + continue; + } + + $errors[] = $this->getErrorMessage($node, $path); + } + + return $errors; + } + + /** + * We cannot use `stream_resolve_include_path` as it works based on the calling script. + * This method simulates the behavior of `stream_resolve_include_path` but for the given scope. + * The priority order is the following: + * 1. The current working directory. + * 2. The include path. + * 3. The path of the script that is being executed. + */ + private function doesFileExist(string $path, Scope $scope): bool + { + $directories = array_merge( + [$this->currentWorkingDirectory], + explode(PATH_SEPARATOR, get_include_path()), + [dirname($scope->getFile())], + ); + + foreach ($directories as $directory) { + if ($this->doesFileExistForDirectory($path, $directory)) { + return true; + } + } + + return false; + } + + private function doesFileExistForDirectory(string $path, string $workingDirectory): bool + { + $fileHelper = new FileHelper($workingDirectory); + $normalisedPath = $fileHelper->normalizePath($path); + $absolutePath = $fileHelper->absolutizePath($normalisedPath); + + return is_file($absolutePath); + } + + private function getErrorMessage(Include_ $node, string $filePath): IdentifierRuleError + { + $message = 'Path in %s() "%s" is not a file or it does not exist.'; + + switch ($node->type) { + case Include_::TYPE_REQUIRE: + $type = 'require'; + $identifierType = 'require'; + break; + case Include_::TYPE_REQUIRE_ONCE: + $type = 'require_once'; + $identifierType = 'requireOnce'; + break; + case Include_::TYPE_INCLUDE: + $type = 'include'; + $identifierType = 'include'; + break; + case Include_::TYPE_INCLUDE_ONCE: + $type = 'include_once'; + $identifierType = 'includeOnce'; + break; + default: + throw new ShouldNotHappenException('Rule should have already validated the node type.'); + } + + $identifier = sprintf('%s.fileNotFound', $identifierType); + + return RuleErrorBuilder::message( + sprintf( + $message, + $type, + $filePath, + ), + )->identifier($identifier)->build(); + } + + /** + * @return array + */ + private function resolveFilePaths(Include_ $node, Scope $scope): array + { + $paths = []; + $type = $scope->getType($node->expr); + $constantStrings = $type->getConstantStrings(); + + foreach ($constantStrings as $constantString) { + $paths[] = $constantString->getValue(); + } + + return $paths; + } + +} diff --git a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php new file mode 100644 index 00000000000..287df686345 --- /dev/null +++ b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php @@ -0,0 +1,136 @@ + + */ +class RequireFileExistsRuleTest extends RuleTestCase +{ + + private RequireFileExistsRule $rule; + + public function setUp(): void + { + parent::setUp(); + + $this->rule = $this->getDefaultRule(); + } + + protected function getRule(): Rule + { + return $this->rule; + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../Analyser/usePathConstantsAsConstantString.neon', + ]; + } + + private function getDefaultRule(): RequireFileExistsRule + { + return new RequireFileExistsRule(__DIR__ . '/../'); + } + + public function testBasicCase(): void + { + $this->analyse([__DIR__ . '/data/require-file-simple-case.php'], [ + [ + 'Path in include() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 11, + ], + [ + 'Path in include_once() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 12, + ], + [ + 'Path in require() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 13, + ], + [ + 'Path in require_once() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 14, + ], + ]); + } + + public function testFileDoesNotExistConditionally(): void + { + $this->analyse([__DIR__ . '/data/require-file-conditionally.php'], [ + [ + 'Path in include() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 9, + ], + [ + 'Path in include_once() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 10, + ], + [ + 'Path in require() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 11, + ], + [ + 'Path in require_once() "a-file-that-does-not-exist.php" is not a file or it does not exist.', + 12, + ], + ]); + } + + public function testRelativePath(): void + { + $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], [ + [ + 'Path in include() "data/include-me-to-prove-you-work.txt" is not a file or it does not exist.', + 8, + ], + [ + 'Path in include_once() "data/include-me-to-prove-you-work.txt" is not a file or it does not exist.', + 9, + ], + [ + 'Path in require() "data/include-me-to-prove-you-work.txt" is not a file or it does not exist.', + 10, + ], + [ + 'Path in require_once() "data/include-me-to-prove-you-work.txt" is not a file or it does not exist.', + 11, + ], + ]); + } + + public function testRelativePathWithIncludePath(): void + { + $includePaths = [realpath(__DIR__)]; + $includePaths[] = get_include_path(); + + set_include_path(implode(PATH_SEPARATOR, $includePaths)); + + try { + $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], []); + } finally { + set_include_path($includePaths[1]); + } + } + + public function testRelativePathWithSameWorkingDirectory(): void + { + $this->rule = new RequireFileExistsRule(__DIR__); + + try { + $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], []); + } finally { + $this->rule = $this->getDefaultRule(); + } + } + +} diff --git a/tests/PHPStan/Rules/Keywords/data/include-me-to-prove-you-work.txt b/tests/PHPStan/Rules/Keywords/data/include-me-to-prove-you-work.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/PHPStan/Rules/Keywords/data/require-file-conditionally.php b/tests/PHPStan/Rules/Keywords/data/require-file-conditionally.php new file mode 100644 index 00000000000..2d18f0066e4 --- /dev/null +++ b/tests/PHPStan/Rules/Keywords/data/require-file-conditionally.php @@ -0,0 +1,12 @@ + Date: Thu, 29 Aug 2024 21:34:15 +0200 Subject: [PATCH 0076/3097] Error on offset assignment to specialized strings --- .../Accessory/AccessoryLiteralStringType.php | 6 +++ .../Accessory/AccessoryNonEmptyStringType.php | 6 +++ .../Accessory/AccessoryNonFalsyStringType.php | 6 +++ .../Accessory/AccessoryNumericStringType.php | 6 +++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 14 ++++++ .../Arrays/OffsetAccessAssignmentRuleTest.php | 39 +++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-11572.php | 47 +++++++++++++++++++ 7 files changed, 124 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11572.php diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index da1a00a16aa..dd4af579acd 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -153,6 +153,12 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + if ($valueType->isLiteralString()->yes()) { return $this; } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 8e98af9d78d..16566ee5308 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -159,6 +159,12 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + return $this; } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 460137066c5..dacccd3e31c 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -155,6 +155,12 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + if ($valueType->isNonFalsyString()->yes()) { return $this; } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 292d00f7a0f..9d376256125 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -158,6 +158,12 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + return $this; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index deacff9371e..ffc6aa26d5d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -864,4 +864,18 @@ public function testBug10997(): void ]); } + public function testBug11572(): void + { + $this->analyse([__DIR__ . '/data/bug-11572.php'], [ + [ + 'Cannot access an offset on int.', + 45, + ], + [ + 'Cannot access an offset on int<3, 4>.', + 46, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index bf704ac7252..86420630c2e 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -156,4 +156,43 @@ public function testBug8015(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8015.php'], []); } + public function testBug11572(): void + { + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-11572.php'], [ + [ + 'Cannot assign new offset to string.', + 15, + ], + [ + 'Cannot assign new offset to string.', + 16, + ], + [ + 'Cannot assign new offset to string.', + 17, + ], + [ + 'Cannot assign new offset to string.', + 18, + ], + [ + 'Cannot assign new offset to string.', + 19, + ], + [ + 'Cannot assign new offset to string.', + 20, + ], + [ + 'Cannot assign new offset to string.', + 24, + ], + [ + 'Cannot assign new offset to string.', + 36, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11572.php b/tests/PHPStan/Rules/Arrays/data/bug-11572.php new file mode 100644 index 00000000000..43480baf5f4 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11572.php @@ -0,0 +1,47 @@ + $range + */ +function doInt(int $i, $range): void +{ + $i[] = 1; + $range[] = 1; +} From 562b7303f06c16cf3c4452ad425d224819c08bad Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 29 Aug 2024 21:42:07 +0200 Subject: [PATCH 0077/3097] Narrow string on strlen() == and === comparison with integer-range --- src/Analyser/TypeSpecifier.php | 64 +++++++++++------- tests/PHPStan/Analyser/nsrt/count-type.php | 46 +++++++++++++ .../Analyser/nsrt/strlen-int-range.php | 65 ++++++++++++++++++- 3 files changed, 151 insertions(+), 24 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5601c024af5..e5bd2bbd573 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1056,13 +1056,19 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, private function specifyTypesForConstantBinaryExpression( Expr $exprNode, - ConstantScalarType $constantType, + Type $constantType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr, ): ?SpecifiedTypes { - if (!$context->null() && $constantType->getValue() === false) { + $scalarValues = $constantType->getConstantScalarValues(); + if (count($scalarValues) !== 1) { + return null; + } + $constValue = $scalarValues[0]; + + if (!$context->null() && $constValue === false) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; @@ -1076,7 +1082,7 @@ private function specifyTypesForConstantBinaryExpression( )); } - if (!$context->null() && $constantType->getValue() === true) { + if (!$context->null() && $constValue === true) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; @@ -1090,10 +1096,6 @@ private function specifyTypesForConstantBinaryExpression( )); } - if ($constantType->getValue() === null) { - return $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - } - if ( !$context->null() && $exprNode instanceof FuncCall @@ -1102,6 +1104,10 @@ private function specifyTypesForConstantBinaryExpression( && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) && $constantType instanceof ConstantIntegerType ) { + if ($constantType->getValue() < 0) { + return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + } + $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType instanceof UnionType) { @@ -1146,6 +1152,10 @@ private function specifyTypesForConstantBinaryExpression( && in_array(strtolower((string) $exprNode->name), ['strlen', 'mb_strlen'], true) && $constantType instanceof ConstantIntegerType ) { + if ($constantType->getValue() < 0) { + return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + } + if ($context->truthy() || $constantType->getValue() === 0) { $newContext = $context; if ($constantType->getValue() === 0) { @@ -1172,12 +1182,18 @@ private function specifyTypesForConstantBinaryExpression( private function specifyTypesForConstantStringBinaryExpression( Expr $exprNode, - ConstantStringType $constantType, + Type $constantType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr, ): ?SpecifiedTypes { + $scalarValues = $constantType->getConstantScalarValues(); + if (count($scalarValues) !== 1 || !is_string($scalarValues[0])) { + return null; + } + $constantStringValue = $scalarValues[0]; + if ( $context->truthy() && $exprNode instanceof FuncCall @@ -1188,12 +1204,12 @@ private function specifyTypesForConstantStringBinaryExpression( 'ucwords', 'mb_convert_case', 'mb_convert_kana', ], true) && isset($exprNode->getArgs()[0]) - && $constantType->getValue() !== '' + && $constantStringValue !== '' ) { $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType->isString()->yes()) { - if ($constantType->getValue() !== '0') { + if ($constantStringValue !== '0') { return $this->create( $exprNode->getArgs()[0]->value, TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), @@ -1220,28 +1236,28 @@ private function specifyTypesForConstantStringBinaryExpression( && isset($exprNode->getArgs()[0]) ) { $type = null; - if ($constantType->getValue() === 'string') { + if ($constantStringValue === 'string') { $type = new StringType(); } - if ($constantType->getValue() === 'array') { + if ($constantStringValue === 'array') { $type = new ArrayType(new MixedType(), new MixedType()); } - if ($constantType->getValue() === 'boolean') { + if ($constantStringValue === 'boolean') { $type = new BooleanType(); } - if (in_array($constantType->getValue(), ['resource', 'resource (closed)'], true)) { + if (in_array($constantStringValue, ['resource', 'resource (closed)'], true)) { $type = new ResourceType(); } - if ($constantType->getValue() === 'integer') { + if ($constantStringValue === 'integer') { $type = new IntegerType(); } - if ($constantType->getValue() === 'double') { + if ($constantStringValue === 'double') { $type = new FloatType(); } - if ($constantType->getValue() === 'NULL') { + if ($constantStringValue === 'NULL') { $type = new NullType(); } - if ($constantType->getValue() === 'object') { + if ($constantStringValue === 'object') { $type = new ObjectWithoutClassType(); } @@ -1260,7 +1276,7 @@ private function specifyTypesForConstantStringBinaryExpression( && isset($exprNode->getArgs()[0]) ) { $argType = $scope->getType($exprNode->getArgs()[0]->value); - $objectType = new ObjectType($constantType->getValue()); + $objectType = new ObjectType($constantStringValue); $classStringType = new GenericClassStringType($objectType); if ($argType->isString()->yes()) { @@ -2149,10 +2165,14 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } - if (count($rightType->getConstantStrings()) > 0) { + if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) { $types = null; - foreach ($rightType->getConstantStrings() as $constantString) { - $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $constantString, $context, $scope, $rootExpr); + foreach ($rightType->getFiniteTypes() as $finiteType) { + if ($finiteType->isString()->yes()) { + $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); + } else { + $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); + } if ($specifiedType === null) { continue; } diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 58dadbdd4ab..09114d90f89 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -18,4 +18,50 @@ public function doFoo( assertType('int<1, max>', sizeof($nonEmpty)); } + /** + * @param int<3,5> $range + * @param int<0,5> $maybeZero + * @param int<-10,-5> $negative + */ + public function doFooBar( + array $arr, + int $range, + int $maybeZero, + int $negative + ) + { + if (count($arr) == $range) { + assertType('non-empty-array', $arr); + } else { + assertType('array', $arr); + } + if (count($arr) === $range) { + assertType('non-empty-array', $arr); + } else { + assertType('array', $arr); + } + + if (count($arr) == $maybeZero) { + assertType('array', $arr); + } else { + assertType('non-empty-array', $arr); + } + if (count($arr) === $maybeZero) { + assertType('array', $arr); + } else { + assertType('non-empty-array', $arr); + } + + if (count($arr) == $negative) { + assertType('*NEVER*', $arr); + } else { + assertType('array', $arr); + } + if (count($arr) === $negative) { + assertType('*NEVER*', $arr); + } else { + assertType('array', $arr); + } + } + } diff --git a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php index 540b932531b..7a4e2172875 100644 --- a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php +++ b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php @@ -9,9 +9,11 @@ * @param int<2, 3> $twoOrThree * @param int<2, max> $twoOrMore * @param int $maxThree - * @param int<10, 11> $tenOrEleven + * @param 10|11 $tenOrEleven + * @param 0|11 $zeroOrEleven + * @param int<-10,-5> $negative */ -function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven): void +function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven, $zeroOrEleven, int $negative): void { if (strlen($s) >= $zeroToThree) { assertType('string', $s); @@ -51,4 +53,63 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, if (strlen($s) > $tenOrEleven) { assertType('non-falsy-string', $s); } + + if (strlen($s) == $zeroToThree) { + assertType('string', $s); + } + if (strlen($s) === $zeroToThree) { + assertType('string', $s); + } + + if (strlen($s) == $twoOrThree) { + assertType('non-falsy-string', $s); + } + if (strlen($s) === $twoOrThree) { + assertType('non-falsy-string', $s); + } + + if (strlen($s) == $oneOrMore) { + assertType('string', $s); // could be non-empty-string + } + if (strlen($s) === $oneOrMore) { + assertType('string', $s); // could be non-empty-string + } + + if (strlen($s) == $tenOrEleven) { + assertType('non-falsy-string', $s); + } + if (strlen($s) === $tenOrEleven) { + assertType('non-falsy-string', $s); + } + if ($tenOrEleven == strlen($s)) { + assertType('non-falsy-string', $s); + } + if ($tenOrEleven === strlen($s)) { + assertType('non-falsy-string', $s); + } + + if (strlen($s) == $maxThree) { + assertType('string', $s); + } + if (strlen($s) === $maxThree) { + assertType('string', $s); + } + + if (strlen($s) == $zeroOrEleven) { + assertType('string', $s); + } + if (strlen($s) === $zeroOrEleven) { + assertType('string', $s); + } + + if (strlen($s) == $negative) { + assertType('*NEVER*', $s); + } else { + assertType('string', $s); + } + if (strlen($s) === $negative) { + assertType('*NEVER*', $s); + } else { + assertType('string', $s); + } } From d95005a488eab892c56d7c112a7b69d368dc852b Mon Sep 17 00:00:00 2001 From: Sven Reichel Date: Fri, 30 Aug 2024 15:41:03 +0200 Subject: [PATCH 0078/3097] Allow dot-prefixed config files --- src/Command/CommandHelper.php | 10 ++++++- tests/PHPStan/Command/AnalyseCommandTest.php | 28 +++++++++++++++---- .../.phpstan.dist.neon} | 0 .../.phpstan.neon.dist} | 0 .../.phpstan.neon} | 0 .../phpstan.dist.neon | 4 +++ .../phpstan.neon.dist | 4 +++ .../test-autodiscover-no-dot/phpstan.neon | 4 +++ .../.phpstan.neon | 4 +++ .../phpstan.neon | 4 +++ 10 files changed, 51 insertions(+), 7 deletions(-) rename tests/PHPStan/Command/{test-autodiscover-dist-dot-neon/phpstan.dist.neon => test-autodiscover-dot-dist-dot-neon/.phpstan.dist.neon} (100%) rename tests/PHPStan/Command/{test-autodiscover-dist/phpstan.neon.dist => test-autodiscover-dot-dist/.phpstan.neon.dist} (100%) rename tests/PHPStan/Command/{test-autodiscover/phpstan.neon => test-autodiscover-dot/.phpstan.neon} (100%) create mode 100644 tests/PHPStan/Command/test-autodiscover-no-dot-dist-dot-neon/phpstan.dist.neon create mode 100644 tests/PHPStan/Command/test-autodiscover-no-dot-dist/phpstan.neon.dist create mode 100644 tests/PHPStan/Command/test-autodiscover-no-dot/phpstan.neon create mode 100644 tests/PHPStan/Command/test-autodiscover-priority-dot/.phpstan.neon create mode 100644 tests/PHPStan/Command/test-autodiscover-priority-dot/phpstan.neon diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 931f3bfe024..e5dd75c186f 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -179,7 +179,15 @@ public static function begin( })($autoloadFile); } if ($projectConfigFile === null) { - foreach (['phpstan.neon', 'phpstan.neon.dist', 'phpstan.dist.neon'] as $discoverableConfigName) { + $discoverableConfigNames = [ + '.phpstan.neon', + '.phpstan.neon.dist', + '.phpstan.dist.neon', + 'phpstan.neon', + 'phpstan.neon.dist', + 'phpstan.dist.neon', + ]; + foreach ($discoverableConfigNames as $discoverableConfigName) { $discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName; if (is_file($discoverableConfigFile)) { $projectConfigFile = $discoverableConfigFile; diff --git a/tests/PHPStan/Command/AnalyseCommandTest.php b/tests/PHPStan/Command/AnalyseCommandTest.php index 2bec6dfa218..f47edea747c 100644 --- a/tests/PHPStan/Command/AnalyseCommandTest.php +++ b/tests/PHPStan/Command/AnalyseCommandTest.php @@ -78,16 +78,28 @@ public static function autoDiscoveryPathsProvider(): array { return [ [ - __DIR__ . '/test-autodiscover', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover' . DIRECTORY_SEPARATOR . 'phpstan.neon', + __DIR__ . '/test-autodiscover-dot', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dot' . DIRECTORY_SEPARATOR . '.phpstan.neon', ], [ - __DIR__ . '/test-autodiscover-dist', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dist' . DIRECTORY_SEPARATOR . 'phpstan.neon.dist', + __DIR__ . '/test-autodiscover-dot-dist', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dot-dist' . DIRECTORY_SEPARATOR . '.phpstan.neon.dist', ], [ - __DIR__ . '/test-autodiscover-dist-dot-neon', - __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dist-dot-neon' . DIRECTORY_SEPARATOR . 'phpstan.dist.neon', + __DIR__ . '/test-autodiscover-dot-dist-dot-neon', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-dot-dist-dot-neon' . DIRECTORY_SEPARATOR . '.phpstan.dist.neon', + ], + [ + __DIR__ . '/test-autodiscover-no-dot', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-no-dot' . DIRECTORY_SEPARATOR . 'phpstan.neon', + ], + [ + __DIR__ . '/test-autodiscover-no-dot-dist', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-no-dot-dist' . DIRECTORY_SEPARATOR . 'phpstan.neon.dist', + ], + [ + __DIR__ . '/test-autodiscover-no-dot-dist-dot-neon', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-no-dot-dist-dot-neon' . DIRECTORY_SEPARATOR . 'phpstan.dist.neon', ], [ __DIR__ . '/test-autodiscover-priority', @@ -97,6 +109,10 @@ public static function autoDiscoveryPathsProvider(): array __DIR__ . '/test-autodiscover-priority-dist-dot-neon', __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-priority-dist-dot-neon' . DIRECTORY_SEPARATOR . 'phpstan.neon', ], + [ + __DIR__ . '/test-autodiscover-priority-dot', + __DIR__ . DIRECTORY_SEPARATOR . 'test-autodiscover-priority-dot' . DIRECTORY_SEPARATOR . '.phpstan.neon', + ], ]; } diff --git a/tests/PHPStan/Command/test-autodiscover-dist-dot-neon/phpstan.dist.neon b/tests/PHPStan/Command/test-autodiscover-dot-dist-dot-neon/.phpstan.dist.neon similarity index 100% rename from tests/PHPStan/Command/test-autodiscover-dist-dot-neon/phpstan.dist.neon rename to tests/PHPStan/Command/test-autodiscover-dot-dist-dot-neon/.phpstan.dist.neon diff --git a/tests/PHPStan/Command/test-autodiscover-dist/phpstan.neon.dist b/tests/PHPStan/Command/test-autodiscover-dot-dist/.phpstan.neon.dist similarity index 100% rename from tests/PHPStan/Command/test-autodiscover-dist/phpstan.neon.dist rename to tests/PHPStan/Command/test-autodiscover-dot-dist/.phpstan.neon.dist diff --git a/tests/PHPStan/Command/test-autodiscover/phpstan.neon b/tests/PHPStan/Command/test-autodiscover-dot/.phpstan.neon similarity index 100% rename from tests/PHPStan/Command/test-autodiscover/phpstan.neon rename to tests/PHPStan/Command/test-autodiscover-dot/.phpstan.neon diff --git a/tests/PHPStan/Command/test-autodiscover-no-dot-dist-dot-neon/phpstan.dist.neon b/tests/PHPStan/Command/test-autodiscover-no-dot-dist-dot-neon/phpstan.dist.neon new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-no-dot-dist-dot-neon/phpstan.dist.neon @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: diff --git a/tests/PHPStan/Command/test-autodiscover-no-dot-dist/phpstan.neon.dist b/tests/PHPStan/Command/test-autodiscover-no-dot-dist/phpstan.neon.dist new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-no-dot-dist/phpstan.neon.dist @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: diff --git a/tests/PHPStan/Command/test-autodiscover-no-dot/phpstan.neon b/tests/PHPStan/Command/test-autodiscover-no-dot/phpstan.neon new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-no-dot/phpstan.neon @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: diff --git a/tests/PHPStan/Command/test-autodiscover-priority-dot/.phpstan.neon b/tests/PHPStan/Command/test-autodiscover-priority-dot/.phpstan.neon new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-priority-dot/.phpstan.neon @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: diff --git a/tests/PHPStan/Command/test-autodiscover-priority-dot/phpstan.neon b/tests/PHPStan/Command/test-autodiscover-priority-dot/phpstan.neon new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-priority-dot/phpstan.neon @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: From 95f82af82ee696d7e4f513cbf30ca3b8ce0f7dc7 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sat, 31 Aug 2024 00:18:44 +0000 Subject: [PATCH 0079/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 860315948ba..9c2e3570b97 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", - "phpstan/php-8-stubs": "0.3.99", + "phpstan/php-8-stubs": "0.3.100", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 8fe97b97d3b..cbfc81e3da2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c0ecdd5ce5efc07d867dd44ff7c821a7", + "content-hash": "b8e13e2eb99acb0933ff8956a498ffe4", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.99", + "version": "0.3.100", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "9bd59d79824c80d7613c8acc70542c42a8cac7f7" + "reference": "7f7ad3739b0dac07d74be9c998a72fc2427fcba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/9bd59d79824c80d7613c8acc70542c42a8cac7f7", - "reference": "9bd59d79824c80d7613c8acc70542c42a8cac7f7", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/7f7ad3739b0dac07d74be9c998a72fc2427fcba1", + "reference": "7f7ad3739b0dac07d74be9c998a72fc2427fcba1", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.99" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.100" }, - "time": "2024-08-28T00:17:29+00:00" + "time": "2024-08-31T00:18:01+00:00" }, { "name": "phpstan/phpdoc-parser", From 1968aa97f0579be3260f4f08bb886dc9c8a2b3f5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 30 Aug 2024 23:25:58 +0200 Subject: [PATCH 0080/3097] RegexArrayShapeMatcher - improve type inference in alternations --- src/Type/Regex/RegexGroupParser.php | 51 ++++++++++++------- .../Analyser/nsrt/preg_match_shapes.php | 22 +++++++- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 8f282899ede..0cfbb26e2e4 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -20,7 +20,6 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use function array_merge; use function count; use function in_array; use function is_int; @@ -295,6 +294,16 @@ private function getQuantificationRange(TreeNode $node): array private function createGroupType(TreeNode $group, bool $maybeConstant, string $patternModifiers): Type { + $rootAlternation = $this->getRootAlternation($group); + if ($rootAlternation !== null) { + $types = []; + foreach ($rootAlternation->getChildren() as $alternative) { + $types[] = $this->createGroupType($alternative, $maybeConstant, $patternModifiers); + } + + return TypeCombinator::union(...$types); + } + $isNonEmpty = TrinaryLogic::createMaybe(); $isNonFalsy = TrinaryLogic::createMaybe(); $isNumeric = TrinaryLogic::createMaybe(); @@ -345,6 +354,28 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p return new StringType(); } + private function getRootAlternation(TreeNode $group): ?TreeNode + { + if ( + $group->getId() === '#capturing' + && count($group->getChildren()) === 1 + && $group->getChild(0)->getId() === '#alternation' + ) { + return $group->getChild(0); + } + + // 1st token within a named capturing group is a token holding the group-name + if ( + $group->getId() === '#namedcapturing' + && count($group->getChildren()) === 2 + && $group->getChild(1)->getId() === '#alternation' + ) { + return $group->getChild(1); + } + + return null; + } + /** * @param array|null $onlyLiterals */ @@ -448,7 +479,6 @@ private function walkGroupAst( $isNumeric = TrinaryLogic::createNo(); } - $alternativeLiterals = []; foreach ($children as $child) { $this->walkGroupAst( $child, @@ -461,24 +491,7 @@ private function walkGroupAst( $inClass, $patternModifiers, ); - - if ($ast->getId() !== '#alternation') { - continue; - } - - if ($onlyLiterals !== null && $alternativeLiterals !== null) { - $alternativeLiterals = array_merge($alternativeLiterals, $onlyLiterals); - $onlyLiterals = []; - } else { - $alternativeLiterals = null; - } - } - - if ($alternativeLiterals === null || $alternativeLiterals === []) { - return; } - - $onlyLiterals = $alternativeLiterals; } private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 655c9d7d107..f4ed3b7ffe6 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -561,7 +561,7 @@ function (string $s): void { } if (preg_match($p, $s, $matches)) { - assertType("array{0: string, 1: non-empty-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); + assertType("array{0: string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); } }; @@ -609,13 +609,31 @@ function (string $s): void { function (string $s): void { if (preg_match('/Price: (a|bc?)/', $s, $matches)) { + assertType("array{string, non-falsy-string}", $matches); + } +}; + +function (string $s): void { + if (preg_match('/Price: (?a|bc?)/', $s, $matches)) { + assertType("array{0: string, named: non-falsy-string, 1: non-falsy-string}", $matches); + } +}; + +function (string $s): void { + if (preg_match('/Price: (a|0c?)/', $s, $matches)) { assertType("array{string, non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (a|\d)/', $s, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{string, 'a'|numeric-string}", $matches); + } +}; + +function (string $s): void { + if (preg_match('/Price: (?a|\d)/', $s, $matches)) { + assertType("array{0: string, named: 'a'|numeric-string, 1: 'a'|numeric-string}", $matches); } }; From d5282281a439116b0a2bf261ffd749234fa8eff3 Mon Sep 17 00:00:00 2001 From: Patrick Kusebauch Date: Sat, 31 Aug 2024 11:38:46 +0200 Subject: [PATCH 0081/3097] Fix `get_debug_type` produces wrong type for anonymous classes with parent --- .../Php/GetDebugTypeFunctionReturnTypeExtension.php | 13 ++++++++++++- tests/PHPStan/Analyser/nsrt/get-debug-type.php | 9 +++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php index d5678343b5f..fa22bc9c061 100644 --- a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php @@ -11,6 +11,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use function array_key_first; use function array_map; use function count; @@ -37,6 +38,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, /** * @see https://www.php.net/manual/en/function.get-debug-type.php#refsect1-function.get-debug-type-returnvalues + * @see https://github.com/php/php-src/commit/ef0e4478c51540510b67f7781ad240f5e0592ee4 */ private static function resolveOneType(Type $type): Type { @@ -71,7 +73,16 @@ private static function resolveOneType(Type $type): Type } if ($reflection->isAnonymous()) { // phpcs:ignore - $types[] = new ConstantStringType('class@anonymous'); + $parentClass = $reflection->getParentClass(); + $implementedInterfaces = $reflection->getImmediateInterfaces(); + if ($parentClass !== null) { + $types[] = new ConstantStringType($parentClass->getName() . '@anonymous'); + } elseif ($implementedInterfaces !== []) { + $firstInterface = $implementedInterfaces[array_key_first($implementedInterfaces)]; + $types[] = new ConstantStringType($firstInterface->getName() . '@anonymous'); + } else { + $types[] = new ConstantStringType('class@anonymous'); + } } } diff --git a/tests/PHPStan/Analyser/nsrt/get-debug-type.php b/tests/PHPStan/Analyser/nsrt/get-debug-type.php index 975ea626139..322c33547fd 100644 --- a/tests/PHPStan/Analyser/nsrt/get-debug-type.php +++ b/tests/PHPStan/Analyser/nsrt/get-debug-type.php @@ -5,6 +5,9 @@ use function PHPStan\Testing\assertType; final class A {} +interface B {} +interface C {} +class D {} /** * @param double $d @@ -18,6 +21,9 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr $o = new \stdClass(); $A = new A(); $anonymous = new class {}; + $anonymousImplements = new class implements B, C {}; + $anonymousExtends = new class extends D {}; + $anonymousExtendsImplements = new class extends D implements B, C {}; assertType("'bool'", get_debug_type($b)); assertType("'bool'", get_debug_type(true)); @@ -35,6 +41,9 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr assertType("'int'|'string'", get_debug_type($intOrString)); assertType("'array'|'GetDebugType\\\\A'", get_debug_type($arrayOrObject)); assertType("'class@anonymous'", get_debug_type($anonymous)); + assertType("'GetDebugType\\\\B@anonymous'", get_debug_type($anonymousImplements)); + assertType("'GetDebugType\\\\D@anonymous'", get_debug_type($anonymousExtends)); + assertType("'GetDebugType\\\\D@anonymous'", get_debug_type($anonymousExtendsImplements)); } /** From 2e2757395b0c3bd9a845e265f62803008b751bbe Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 31 Aug 2024 14:45:18 +0200 Subject: [PATCH 0082/3097] Update PhpStorm stubs + refactor WithoutSideEffectsRule classes Co-authored-by: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> --- composer.json | 2 +- composer.lock | 18 +++---- ...tructorStatementWithoutSideEffectsRule.php | 38 ++++++-------- ...oMethodStatementWithoutSideEffectsRule.php | 49 ++++++++----------- ...cMethodStatementWithoutSideEffectsRule.php | 42 ++++++---------- ...hodStatementWithoutSideEffectsRuleTest.php | 4 -- 6 files changed, 61 insertions(+), 92 deletions(-) diff --git a/composer.json b/composer.json index 9c2e3570b97..3a722a9282c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#bdc8acd7c04c0c87197849c7cdd27e44b67b56c7", + "jetbrains/phpstorm-stubs": "dev-master#5686f9ceebde3d9338bea53b78d70ebde5fb5710", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.3", diff --git a/composer.lock b/composer.lock index cbfc81e3da2..7039f2bc07c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b8e13e2eb99acb0933ff8956a498ffe4", + "content-hash": "8d65a8ad1ba3923e57e72376e8dfefed", "packages": [ { "name": "clue/ndjson-react", @@ -1434,19 +1434,19 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "bdc8acd7c04c0c87197849c7cdd27e44b67b56c7" + "reference": "5686f9ceebde3d9338bea53b78d70ebde5fb5710" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/bdc8acd7c04c0c87197849c7cdd27e44b67b56c7", - "reference": "bdc8acd7c04c0c87197849c7cdd27e44b67b56c7", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/5686f9ceebde3d9338bea53b78d70ebde5fb5710", + "reference": "5686f9ceebde3d9338bea53b78d70ebde5fb5710", "shasum": "" }, "require-dev": { - "friendsofphp/php-cs-fixer": "v3.46.0", - "nikic/php-parser": "v5.0.0", - "phpdocumentor/reflection-docblock": "5.3.0", - "phpunit/phpunit": "10.5.5" + "friendsofphp/php-cs-fixer": "v3.61.1", + "nikic/php-parser": "v5.1.0", + "phpdocumentor/reflection-docblock": "5.4.1", + "phpunit/phpunit": "11.3.0" }, "default-branch": true, "type": "library", @@ -1474,7 +1474,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-07-05T11:52:49+00:00" + "time": "2024-07-24T19:11:43+00:00" }, { "name": "nette/bootstrap", diff --git a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php index 00a2352c996..c50d21d8780 100644 --- a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\NoopExpressionNode; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -11,7 +12,7 @@ use function sprintf; /** - * @implements Rule + * @implements Rule */ final class CallToConstructorStatementWithoutSideEffectsRule implements Rule { @@ -25,16 +26,16 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt\Expression::class; + return NoopExpressionNode::class; } public function processNode(Node $node, Scope $scope): array { - if (!$node->expr instanceof Node\Expr\New_) { + $instantiation = $node->getOriginalExpr(); + if (!$instantiation instanceof Node\Expr\New_) { return []; } - $instantiation = $node->expr; if (!$instantiation->class instanceof Node\Name) { return []; } @@ -59,27 +60,18 @@ public function processNode(Node $node, Scope $scope): array } $constructor = $classReflection->getConstructor(); - if ($constructor->hasSideEffects()->no()) { - $throwsType = $constructor->getThrowType(); - if ($throwsType !== null && !$throwsType->isVoid()->yes()) { - return []; - } - - $methodResult = $scope->getType($instantiation); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s::%s() on a separate line has no effect.', - $classReflection->getDisplayName(), - $constructor->getName(), - ))->identifier('new.resultUnused')->build(), - ]; + $methodResult = $scope->getType($instantiation); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; } - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s::%s() on a separate line has no effect.', + $classReflection->getDisplayName(), + $constructor->getName(), + ))->identifier('new.resultUnused')->build(), + ]; } } diff --git a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index ed78c46f170..c8ead1d217f 100644 --- a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; +use PHPStan\Node\NoopExpressionNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -14,7 +15,7 @@ use function sprintf; /** - * @implements Rule + * @implements Rule */ final class CallToMethodStatementWithoutSideEffectsRule implements Rule { @@ -25,18 +26,18 @@ public function __construct(private RuleLevelHelper $ruleLevelHelper) public function getNodeType(): string { - return Node\Stmt\Expression::class; + return NoopExpressionNode::class; } public function processNode(Node $node, Scope $scope): array { - if ($node->expr instanceof Node\Expr\NullsafeMethodCall) { - $scope = $scope->filterByTruthyValue(new Node\Expr\BinaryOp\NotIdentical($node->expr->var, new Node\Expr\ConstFetch(new Node\Name('null')))); - } elseif (!$node->expr instanceof Node\Expr\MethodCall) { + $methodCall = $node->getOriginalExpr(); + if ($methodCall instanceof Node\Expr\NullsafeMethodCall) { + $scope = $scope->filterByTruthyValue(new Node\Expr\BinaryOp\NotIdentical($methodCall->var, new Node\Expr\ConstFetch(new Node\Name('null')))); + } elseif (!$methodCall instanceof Node\Expr\MethodCall) { return []; } - $methodCall = $node->expr; if (!$methodCall->name instanceof Node\Identifier) { return []; } @@ -60,31 +61,21 @@ public function processNode(Node $node, Scope $scope): array return []; } - $method = $calledOnType->getMethod($methodName, $scope); - if ($method->hasSideEffects()->no() || $node->expr->isFirstClassCallable()) { - if (!$node->expr->isFirstClassCallable()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType->isVoid()->yes()) { - return []; - } - } - - $methodResult = $scope->getType($methodCall); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s::%s() on a separate line has no effect.', - $method->isStatic() ? 'static method' : 'method', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - ))->identifier('method.resultUnused')->build(), - ]; + $methodResult = $scope->getType($methodCall); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; } - return []; + $method = $calledOnType->getMethod($methodName, $scope); + + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s::%s() on a separate line has no effect.', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + ))->identifier('method.resultUnused')->build(), + ]; } } diff --git a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index 8f1828cbe41..550ea9e0194 100644 --- a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; +use PHPStan\Node\NoopExpressionNode; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -17,7 +18,7 @@ use function strtolower; /** - * @implements Rule + * @implements Rule */ final class CallToStaticMethodStatementWithoutSideEffectsRule implements Rule { @@ -31,16 +32,16 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt\Expression::class; + return NoopExpressionNode::class; } public function processNode(Node $node, Scope $scope): array { - if (!$node->expr instanceof Node\Expr\StaticCall) { + $staticCall = $node->getOriginalExpr(); + if (!$staticCall instanceof Node\Expr\StaticCall) { return []; } - $staticCall = $node->expr; if (!$staticCall->name instanceof Node\Identifier) { return []; } @@ -84,30 +85,19 @@ public function processNode(Node $node, Scope $scope): array return []; } - if ($method->hasSideEffects()->no() || $node->expr->isFirstClassCallable()) { - if (!$node->expr->isFirstClassCallable()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType->isVoid()->yes()) { - return []; - } - } - - $methodResult = $scope->getType($staticCall); - if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s::%s() on a separate line has no effect.', - $method->isStatic() ? 'static method' : 'method', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - ))->identifier('staticMethod.resultUnused')->build(), - ]; + $methodResult = $scope->getType($staticCall); + if ($methodResult instanceof NeverType && $methodResult->isExplicit()) { + return []; } - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s::%s() on a separate line has no effect.', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + ))->identifier('staticMethod.resultUnused')->build(), + ]; } } diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index 03cbdaa49d3..953ca5f9823 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -28,10 +28,6 @@ public function testRule(): void 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', 12, ], - [ - 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', - 13, - ], [ 'Call to method DateTime::format() on a separate line has no effect.', 23, From 24bae9445c3d7839c4dad2dbc572c8843ddfccb3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 31 Aug 2024 19:50:38 +0200 Subject: [PATCH 0083/3097] Respect dist order over dot order --- src/Command/CommandHelper.php | 4 ++-- .../.phpstan.dist.neon | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Command/test-autodiscover-priority-dist-dot-neon/.phpstan.dist.neon diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index e5dd75c186f..55926064a2a 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -181,10 +181,10 @@ public static function begin( if ($projectConfigFile === null) { $discoverableConfigNames = [ '.phpstan.neon', - '.phpstan.neon.dist', - '.phpstan.dist.neon', 'phpstan.neon', + '.phpstan.neon.dist', 'phpstan.neon.dist', + '.phpstan.dist.neon', 'phpstan.dist.neon', ]; foreach ($discoverableConfigNames as $discoverableConfigName) { diff --git a/tests/PHPStan/Command/test-autodiscover-priority-dist-dot-neon/.phpstan.dist.neon b/tests/PHPStan/Command/test-autodiscover-priority-dist-dot-neon/.phpstan.dist.neon new file mode 100644 index 00000000000..f242b77eeb4 --- /dev/null +++ b/tests/PHPStan/Command/test-autodiscover-priority-dist-dot-neon/.phpstan.dist.neon @@ -0,0 +1,4 @@ +includes: + - ../../../../conf/bleedingEdge.neon + +parameters: From 39892b63e1f36ff6e58c4fcb003cf4d9d13225cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 1 Sep 2024 20:55:13 +0200 Subject: [PATCH 0084/3097] TypeSpecifier: Narrow `(bool) $expr` like `$expr == true` --- src/Analyser/TypeSpecifier.php | 2 ++ .../Analyser/LegacyNodeScopeResolverTest.php | 4 +-- tests/PHPStan/Analyser/nsrt/bug-10528.php | 17 +++++++++++ tests/PHPStan/Analyser/nsrt/bug-6006.php | 19 ++++++++++++ tests/PHPStan/Analyser/nsrt/bug-7685.php | 17 +++++++++++ .../Analyser/nsrt/narrow-bool-cast.php | 29 +++++++++++++++++++ .../Rules/Functions/ReturnTypeRuleTest.php | 7 +++++ .../PHPStan/Rules/Functions/data/bug-8881.php | 13 +++++++++ 8 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10528.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6006.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-7685.php create mode 100644 tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-8881.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index e5bd2bbd573..b2791859069 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -196,6 +196,8 @@ public function specifyTypesInCondition( $context, $rootExpr, ); + } elseif ($expr instanceof Expr\Cast\Bool_) { + return $this->resolveEqual(new Expr\BinaryOp\Equal($expr->expr, new ConstFetch(new Name\FullyQualified('true'))), $scope, $context, $rootExpr); } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { return $this->resolveEqual($expr, $scope, $context, $rootExpr); } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 4d0ca33581a..1b67568efab 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -355,7 +355,7 @@ public function dataAssignInIf(): array $testScope, 'matches3', TrinaryLogic::createYes(), - 'array{0?: string}', + 'array{}|array{string}', ], [ $testScope, @@ -415,7 +415,7 @@ public function dataAssignInIf(): array $testScope, 'ternaryMatches', TrinaryLogic::createYes(), - 'array{0?: string}', + 'array{string}', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/nsrt/bug-10528.php b/tests/PHPStan/Analyser/nsrt/bug-10528.php new file mode 100644 index 00000000000..07fe77ade01 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10528.php @@ -0,0 +1,17 @@ +', $pos); + + $sub = substr($string, 0, $pos); + assert($pos !== FALSE); + $sub = substr($string, 0, $pos); +} + diff --git a/tests/PHPStan/Analyser/nsrt/bug-6006.php b/tests/PHPStan/Analyser/nsrt/bug-6006.php new file mode 100644 index 00000000000..e9ad4e24642 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6006.php @@ -0,0 +1,19 @@ + $data */ + $data = [ + 'name' => 'John', + 'dob' => null, + ]; + + $data = array_filter($data, fn(?string $input): bool => (bool)$input); + + assertType('array', $data); +} + + diff --git a/tests/PHPStan/Analyser/nsrt/bug-7685.php b/tests/PHPStan/Analyser/nsrt/bug-7685.php new file mode 100644 index 00000000000..580ad7f33f4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7685.php @@ -0,0 +1,17 @@ +getFilePath(); + if (false !== (bool) $filePath) { + assertType('non-falsy-string', $filePath); + } +} + diff --git a/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php new file mode 100644 index 00000000000..582062deb7d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php @@ -0,0 +1,29 @@ + $arr */ +function doFoo(string $x, array $arr): void { + if ((bool) strlen($x)) { + assertType('string', $x); // could be non-empty-string + } else { + assertType('string', $x); + } + assertType('string', $x); + + if ((bool) array_search($x, $arr, true)) { + assertType('non-empty-array', $arr); + } else { + assertType('array', $arr); + } + assertType('string', $x); + + if ((bool) preg_match('~.*~', $x, $matches)) { + assertType('array{string}', $matches); + } else { + assertType('array{}', $matches); + } + assertType('array{}|array{string}', $matches); +} diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 45ddca17674..f16d288869e 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -290,4 +290,11 @@ public function testBug11518(): void $this->analyse([__DIR__ . '/data/bug-11518.php'], []); } + public function testBug8881(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-8881.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-8881.php b/tests/PHPStan/Rules/Functions/data/bug-8881.php new file mode 100644 index 00000000000..85c59c4fae7 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-8881.php @@ -0,0 +1,13 @@ + Date: Mon, 2 Sep 2024 09:44:20 +0200 Subject: [PATCH 0085/3097] TypeSpecifier: Narrow `(string) $expr` like `$expr != false` --- src/Analyser/TypeSpecifier.php | 7 +++++++ .../Analyser/nsrt/narrow-bool-cast.php | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index b2791859069..66a60954b6f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -196,6 +196,13 @@ public function specifyTypesInCondition( $context, $rootExpr, ); + } elseif ($expr instanceof Expr\Cast\String_) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BinaryOp\NotEqual($expr->expr, new ConstFetch(new Name\FullyQualified('false'))), + $context, + $rootExpr, + ); } elseif ($expr instanceof Expr\Cast\Bool_) { return $this->resolveEqual(new Expr\BinaryOp\Equal($expr->expr, new ConstFetch(new Name\FullyQualified('true'))), $scope, $context, $rootExpr); } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { diff --git a/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php index 582062deb7d..ea9486a77d9 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php @@ -27,3 +27,24 @@ function doFoo(string $x, array $arr): void { } assertType('array{}|array{string}', $matches); } + +/** @param int<-5, 5> $x */ +function castString($x, string $s, bool $b) { + if ((string) $x) { + assertType('int<-5, -1>|int<1, 5>', $x); + } else { + assertType('0', $x); + } + + if ((string) $b) { + assertType('true', $b); + } else { + assertType('false', $b); + } + + if ((string) strrchr($s, 'xy')) { + assertType('string', $s); // could be non-empty-string + } else { + assertType('string', $s); + } +} From 910ce0ad4dcbd7ffa577d3bad5b0354a0263c676 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:31:31 +0000 Subject: [PATCH 0086/3097] Update issue-bot --- issue-bot/composer.lock | 239 ++++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 119 deletions(-) diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index f2a1652e428..af4a00fca88 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -74,16 +74,16 @@ }, { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -143,28 +143,28 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2022-10-27T11:44:00+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -175,9 +175,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -255,7 +255,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -271,20 +271,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", "shasum": "" }, "require": { @@ -292,7 +292,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -338,7 +338,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.3" }, "funding": [ { @@ -354,20 +354,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-07-18T10:29:17+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -382,8 +382,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -454,7 +454,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -470,7 +470,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "knplabs/github-api", @@ -562,16 +562,16 @@ }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -584,8 +584,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -607,7 +607,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -664,7 +664,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -1716,20 +1716,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -1753,7 +1753,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1765,9 +1765,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -1868,16 +1868,16 @@ }, { "name": "symfony/console", - "version": "v6.4.9", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9" + "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", - "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", + "url": "https://api.github.com/repos/symfony/console/zipball/42686880adaacdad1835ee8fc2a9ec5b7bd63998", + "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998", "shasum": "" }, "require": { @@ -1942,7 +1942,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.9" + "source": "https://github.com/symfony/console/tree/v6.4.11" }, "funding": [ { @@ -1958,7 +1958,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:49:33+00:00" + "time": "2024-08-15T22:48:29+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2029,16 +2029,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.8", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", + "url": "https://api.github.com/repos/symfony/finder/zipball/d7eb6daf8cd7e9ac4976e9576b32042ef7253453", + "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453", "shasum": "" }, "require": { @@ -2073,7 +2073,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.8" + "source": "https://github.com/symfony/finder/tree/v6.4.11" }, "funding": [ { @@ -2089,7 +2089,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-08-13T14:27:37+00:00" }, { "name": "symfony/options-resolver", @@ -2641,16 +2641,16 @@ }, { "name": "symfony/string", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -2708,7 +2708,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.2" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -2724,7 +2724,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:27:18+00:00" + "time": "2024-08-12T09:59:40+00:00" } ], "packages-dev": [ @@ -2800,16 +2800,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -2817,11 +2817,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -2847,7 +2848,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -2855,20 +2856,20 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -2879,7 +2880,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -2911,9 +2912,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -3035,35 +3036,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -3072,7 +3073,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -3101,7 +3102,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -3109,7 +3110,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3354,45 +3355,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -3437,7 +3438,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -3453,7 +3454,7 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "sebastian/cli-parser", From ab787e252170ff255c9ab99a5868f3acb353e047 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 00:31:20 +0000 Subject: [PATCH 0087/3097] Update crate-ci/typos action to v1.24.3 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index c8125243ef7..d91ab1ff1f3 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.23.6" + uses: "crate-ci/typos@v1.24.3" with: files: "README.md src/" From 09749db03132c93dbdd2d8cae93ae2381e90d285 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 2 Sep 2024 19:25:04 +0200 Subject: [PATCH 0088/3097] Fix preserving list when setting union offset type to a ConstantArrayTypeBuilder --- .../Constant/ConstantArrayTypeBuilder.php | 4 +-- .../Analyser/nsrt/array-is-list-offset.php | 19 +++++++++++ .../Constant/ConstantArrayTypeBuilderTest.php | 34 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/array-is-list-offset.php diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 525b65caed1..a306833e8d0 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -199,8 +199,6 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt return; } - $this->isList = TrinaryLogic::createNo(); - $scalarTypes = $offsetType->getConstantScalarTypes(); if (count($scalarTypes) === 0) { $integerRanges = TypeUtils::getIntegerRanges($offsetType); @@ -257,6 +255,8 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt return; } } + + $this->isList = TrinaryLogic::createNo(); } if ($offsetType === null) { diff --git a/tests/PHPStan/Analyser/nsrt/array-is-list-offset.php b/tests/PHPStan/Analyser/nsrt/array-is-list-offset.php new file mode 100644 index 00000000000..911490fac7e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-is-list-offset.php @@ -0,0 +1,19 @@ + $key + */ + public function test(array $array, int $key) { + assertType('int<0, 1>', $key); + assertType('true', array_is_list($array)); + + $array[$key] = false; + assertType('true', array_is_list($array)); + } +} diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeBuilderTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeBuilderTest.php index 0b8bff2efbf..2d1aaa237c8 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeBuilderTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeBuilderTest.php @@ -5,6 +5,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; use PHPUnit\Framework\TestCase; @@ -128,4 +129,37 @@ public function testIsList(): void $this->assertFalse($builder->isList()); } + public function testIsListWithUnion(): void + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + + $builder->setOffsetValueType(null, new ConstantIntegerType(0)); + $this->assertTrue($builder->isList()); + + $builder->setOffsetValueType(new ConstantIntegerType(0), new NullType()); + $this->assertTrue($builder->isList()); + + $builder->setOffsetValueType(new ConstantIntegerType(1), new NullType()); + $this->assertTrue($builder->isList()); + + $builder->setOffsetValueType(new ConstantIntegerType(2), new NullType()); + $this->assertTrue($builder->isList()); + + $oneOrZero = TypeCombinator::union( + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ); + + $builder->setOffsetValueType($oneOrZero, new NullType()); + $this->assertTrue($builder->isList()); + + $oneOrFour = TypeCombinator::union( + new ConstantIntegerType(1), + new ConstantIntegerType(4), + ); + + $builder->setOffsetValueType($oneOrFour, new NullType()); + $this->assertFalse($builder->isList()); + } + } From f4c887035fc7d5ca4c73c102ca37facd79a03bfe Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:25:47 +0000 Subject: [PATCH 0089/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 3a722a9282c..11803e18a36 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", - "phpstan/php-8-stubs": "0.3.100", + "phpstan/php-8-stubs": "0.3.101", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 7039f2bc07c..71beaf624c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8d65a8ad1ba3923e57e72376e8dfefed", + "content-hash": "f5af1898ab9d95520d1511334b2000c0", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.100", + "version": "0.3.101", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "7f7ad3739b0dac07d74be9c998a72fc2427fcba1" + "reference": "07a716723f30182d2f77c49426cc08327e5e9df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/7f7ad3739b0dac07d74be9c998a72fc2427fcba1", - "reference": "7f7ad3739b0dac07d74be9c998a72fc2427fcba1", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/07a716723f30182d2f77c49426cc08327e5e9df1", + "reference": "07a716723f30182d2f77c49426cc08327e5e9df1", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.100" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.101" }, - "time": "2024-08-31T00:18:01+00:00" + "time": "2024-09-02T17:25:11+00:00" }, { "name": "phpstan/phpdoc-parser", From 319e98b9193af745290673026cbb8dca3e21bafb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 1 Sep 2024 23:06:03 +0200 Subject: [PATCH 0090/3097] TypeSpecifier: Narrow `(int) $expr` like `$expr != false` --- src/Analyser/TypeSpecifier.php | 8 ++++--- .../{narrow-bool-cast.php => narrow-cast.php} | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) rename tests/PHPStan/Analyser/nsrt/{narrow-bool-cast.php => narrow-cast.php} (70%) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 66a60954b6f..69b72a1772e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -196,15 +196,17 @@ public function specifyTypesInCondition( $context, $rootExpr, ); - } elseif ($expr instanceof Expr\Cast\String_) { + } elseif ( + $expr instanceof Expr\Cast\String_ + || $expr instanceof Expr\Cast\Int_ + || $expr instanceof Expr\Cast\Bool_ + ) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BinaryOp\NotEqual($expr->expr, new ConstFetch(new Name\FullyQualified('false'))), $context, $rootExpr, ); - } elseif ($expr instanceof Expr\Cast\Bool_) { - return $this->resolveEqual(new Expr\BinaryOp\Equal($expr->expr, new ConstFetch(new Name\FullyQualified('true'))), $scope, $context, $rootExpr); } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { return $this->resolveEqual($expr, $scope, $context, $rootExpr); } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { diff --git a/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-cast.php similarity index 70% rename from tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php rename to tests/PHPStan/Analyser/nsrt/narrow-cast.php index ea9486a77d9..0d883cd6ba9 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-bool-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-cast.php @@ -48,3 +48,24 @@ function castString($x, string $s, bool $b) { assertType('string', $s); } } + +/** @param int<-5, 5> $x */ +function castInt($x, string $s, bool $b) { + if ((int) $x) { + assertType('int<-5, -1>|int<1, 5>', $x); + } else { + assertType('0', $x); + } + + if ((int) $b) { + assertType('true', $b); + } else { + assertType('false', $b); + } + + if ((int) strpos($s, 'xy')) { + assertType('non-falsy-string', $s); + } else { + assertType('string', $s); + } +} From 0c9487d4d815c0f99e29da8ed6cac0560ebdad84 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 22 Aug 2024 15:28:35 +0200 Subject: [PATCH 0091/3097] Fix preg_replace() return type --- ...aceFunctionsDynamicReturnTypeExtension.php | 41 +++++++++++- .../Analyser/LegacyNodeScopeResolverTest.php | 6 +- tests/PHPStan/Analyser/nsrt/bug-11547.php | 62 +++++++++++++++++++ 3 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11547.php diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index b297f3dc72f..18a66da75c2 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; @@ -17,6 +18,7 @@ use PHPStan\Type\TypeUtils; use function array_key_exists; use function count; +use function in_array; final class ReplaceFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -101,9 +103,30 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( if ($compareSuperTypes === $isStringSuperType) { return new StringType(); } elseif ($compareSuperTypes === $isArraySuperType) { - if (count($subjectArgumentType->getArrays()) > 0) { + $subjectArrays = $subjectArgumentType->getArrays(); + if (count($subjectArrays) > 0) { $result = []; - foreach ($subjectArgumentType->getArrays() as $arrayType) { + foreach ($subjectArrays as $arrayType) { + $constantArrays = $arrayType->getConstantArrays(); + + if ( + $constantArrays !== [] + && in_array($functionReflection->getName(), ['preg_replace', 'preg_replace_callback', 'preg_replace_callback_array'], true) + ) { + foreach ($constantArrays as $constantArray) { + $generalizedArray = $constantArray->generalizeValues(); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + // turn all keys optional + foreach ($constantArray->getKeyTypes() as $keyType) { + $builder->setOffsetValueType($keyType, $generalizedArray->getOffsetValueType($keyType), true); + } + $result[] = $builder->getArray(); + } + + continue; + } + $result[] = $arrayType->generalizeValues(); } @@ -134,6 +157,20 @@ private function canReturnNull( Scope $scope, ): bool { + if ( + in_array($functionReflection->getName(), ['preg_replace', 'preg_replace_callback', 'preg_replace_callback_array'], true) + && count($functionCall->getArgs()) > 0 + ) { + $subjectArgumentType = $this->getSubjectType($functionReflection, $functionCall, $scope); + + if ( + $subjectArgumentType !== null + && $subjectArgumentType->isArray()->yes() + ) { + return false; + } + } + $possibleTypes = ParametersAcceptorSelector::selectFromArgs( $scope, $functionCall->getArgs(), diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 1b67568efab..1d17f9ee483 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -7426,11 +7426,11 @@ public function dataReplaceFunctions(): array '$expectedArray', ], [ - 'array{a: string, b: string}|null', + 'array{a?: string, b?: string}', '$expectedArray2', ], [ - 'array{a: string, b: string}|null', + 'array{a?: string, b?: string}', '$anotherExpectedArray', ], [ @@ -7450,7 +7450,7 @@ public function dataReplaceFunctions(): array '$anotherExpectedArrayOrString', ], [ - 'array{a: string, b: string}|null', + 'array{a?: string, b?: string}', 'preg_replace_callback_array($callbacks, $array)', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/bug-11547.php b/tests/PHPStan/Analyser/nsrt/bug-11547.php new file mode 100644 index 00000000000..3acb253f492 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11547.php @@ -0,0 +1,62 @@ + Date: Tue, 3 Sep 2024 08:36:53 +0200 Subject: [PATCH 0092/3097] TypeSpecifier: Narrow `(float) $expr` like `$expr != false` --- src/Analyser/TypeSpecifier.php | 1 + tests/PHPStan/Analyser/nsrt/narrow-cast.php | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 69b72a1772e..9bf24f318d4 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -198,6 +198,7 @@ public function specifyTypesInCondition( ); } elseif ( $expr instanceof Expr\Cast\String_ + || $expr instanceof Expr\Cast\Double || $expr instanceof Expr\Cast\Int_ || $expr instanceof Expr\Cast\Bool_ ) { diff --git a/tests/PHPStan/Analyser/nsrt/narrow-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-cast.php index 0d883cd6ba9..afd5224ae0e 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-cast.php @@ -69,3 +69,24 @@ function castInt($x, string $s, bool $b) { assertType('string', $s); } } + +/** @param int<-5, 5> $x */ +function castFloat($x, string $s, bool $b) { + if ((float) $x) { + assertType('int<-5, -1>|int<1, 5>', $x); + } else { + assertType('0', $x); + } + + if ((float) $b) { + assertType('true', $b); + } else { + assertType('false', $b); + } + + if ((float) $s) { + assertType('non-falsy-string', $s); + } else { + assertType("''|'0'", $s); + } +} From 0ee67d58c66bf5c603cb4ba60baa91f3c5030f33 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 3 Sep 2024 08:58:03 +0200 Subject: [PATCH 0093/3097] Detect function variadic-ness anywhere deep in the declaration file --- src/Reflection/Php/PhpFunctionReflection.php | 60 ++++++++------ .../CallToFunctionParametersRuleTest.php | 19 +++++ .../Rules/Functions/data/bug-11559.php | 41 ++++++++++ .../Rules/Functions/data/bug-11559b.php | 82 +++++++++++++++++++ 4 files changed, 178 insertions(+), 24 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11559.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11559b.php diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 5303d38b0fd..a52f3829f8f 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -3,10 +3,7 @@ namespace PHPStan\Reflection\Php; use PhpParser\Node; -use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\Declare_; use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Namespace_; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\Cache\Cache; @@ -27,6 +24,7 @@ use function array_key_exists; use function array_map; use function filemtime; +use function is_array; use function is_file; use function sprintf; use function time; @@ -149,12 +147,12 @@ private function isVariadic(): bool if ($modifiedTime === false) { $modifiedTime = time(); } - $variableCacheKey = sprintf('%d-v3', $modifiedTime); + $variableCacheKey = sprintf('%d-v4', $modifiedTime); $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null) { $nodes = $this->parser->parseFile($fileName); - $result = $this->callsFuncGetArgs($nodes); + $result = !$this->containsVariadicFunction($nodes)->no(); $this->cache->save($key, $variableCacheKey, $result); return $result; } @@ -167,41 +165,40 @@ private function isVariadic(): bool } /** - * @param Node[] $nodes + * @param Node[]|scalar[]|Node $node */ - private function callsFuncGetArgs(array $nodes): bool + private function containsVariadicFunction(array|Node $node): TrinaryLogic { - foreach ($nodes as $node) { + $result = TrinaryLogic::createMaybe(); + + if ($node instanceof Node) { if ($node instanceof Function_) { $functionName = (string) $node->namespacedName; if ($functionName === $this->reflection->getName()) { - return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; + return TrinaryLogic::createFromBoolean($this->isFunctionNodeVariadic($node)); } - - continue; } - if ($node instanceof ClassLike) { - continue; - } - - if ($node instanceof Namespace_) { - if ($this->callsFuncGetArgs($node->stmts)) { - return true; + foreach ($node->getSubNodeNames() as $subNodeName) { + $innerNode = $node->{$subNodeName}; + if (!$innerNode instanceof Node && !is_array($innerNode)) { + continue; } - } - if (!$node instanceof Declare_ || $node->stmts === null) { - continue; + $result = $result->and($this->containsVariadicFunction($innerNode)); } + } elseif (is_array($node)) { + foreach ($node as $subNode) { + if (!$subNode instanceof Node) { + continue; + } - if ($this->callsFuncGetArgs($node->stmts)) { - return true; + $result = $result->and($this->containsVariadicFunction($subNode)); } } - return false; + return $result; } private function getReturnType(): Type @@ -303,4 +300,19 @@ public function acceptsNamedArguments(): bool return $this->acceptsNamedArguments; } + private function isFunctionNodeVariadic(Function_ $node): bool + { + foreach ($node->params as $parameter) { + if ($parameter->variadic) { + return true; + } + } + + if ($this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null) { + return true; + } + + return false; + } + } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 49b56548cca..d49987e3c58 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1742,4 +1742,23 @@ public function testBug11506(): void $this->analyse([__DIR__ . '/data/bug-11506.php'], []); } + public function testBug11559(): void + { + $this->analyse([__DIR__ . '/data/bug-11559.php'], []); + } + + public function testBug11559b(): void + { + $this->analyse([__DIR__ . '/data/bug-11559b.php'], [ + [ + 'Function Bug11559b\maybe_variadic_fn invoked with 5 parameters, 0 required.', + 14, + ], + [ + 'Function Bug11559b\maybe_variadic_fn4 invoked with 2 parameters, 0 required.', + 65, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11559.php b/tests/PHPStan/Rules/Functions/data/bug-11559.php new file mode 100644 index 00000000000..05e4d85fff2 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11559.php @@ -0,0 +1,41 @@ + Date: Tue, 3 Sep 2024 09:13:07 +0200 Subject: [PATCH 0094/3097] Simplify specifyTypesForConstantBinaryExpression --- src/Analyser/TypeSpecifier.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9bf24f318d4..356d0b30bef 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1074,13 +1074,7 @@ private function specifyTypesForConstantBinaryExpression( ?Expr $rootExpr, ): ?SpecifiedTypes { - $scalarValues = $constantType->getConstantScalarValues(); - if (count($scalarValues) !== 1) { - return null; - } - $constValue = $scalarValues[0]; - - if (!$context->null() && $constValue === false) { + if (!$context->null() && $constantType->isFalse()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; @@ -1094,7 +1088,7 @@ private function specifyTypesForConstantBinaryExpression( )); } - if (!$context->null() && $constValue === true) { + if (!$context->null() && $constantType->isTrue()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; From 9c4bee937661a01285f5cad2e3116b5deaf01e9d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 3 Sep 2024 10:21:26 +0200 Subject: [PATCH 0095/3097] Add DateTimeSubMethodThrowTypeExtension --- conf/config.neon | 5 +++ .../DateTimeSubMethodThrowTypeExtension.php | 43 +++++++++++++++++++ ...hodStatementWithoutSideEffectsRuleTest.php | 22 ++++++++++ .../PHPStan/Rules/Methods/data/bug-11503.php | 19 ++++++++ 4 files changed, 89 insertions(+) create mode 100644 src/Type/Php/DateTimeSubMethodThrowTypeExtension.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11503.php diff --git a/conf/config.neon b/conf/config.neon index 32b0127eb19..dc3e3b85f4b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1421,6 +1421,11 @@ services: tags: - phpstan.dynamicMethodThrowTypeExtension + - + class: PHPStan\Type\Php\DateTimeSubMethodThrowTypeExtension + tags: + - phpstan.dynamicMethodThrowTypeExtension + - class: PHPStan\Type\Php\DateTimeZoneConstructorThrowTypeExtension tags: diff --git a/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php b/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php new file mode 100644 index 00000000000..ce6d31b581a --- /dev/null +++ b/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php @@ -0,0 +1,43 @@ +getName() === 'sub' + && in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true); + } + + public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->getArgs()) === 0) { + return null; + } + + if (!$this->phpVersion->hasDateTimeExceptions()) { + return null; + } + + return new ObjectType('DateInvalidOperationException'); + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php index f1d3d5902d5..a75873ffd52 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php @@ -5,6 +5,8 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use function array_merge; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -89,6 +91,26 @@ public function testBug4455(): void $this->analyse([__DIR__ . '/data/bug-4455.php'], []); } + public function testBug11503(): void + { + $errors = [ + ['Call to method DateTimeImmutable::add() on a separate line has no effect.', 10], + ['Call to method DateTimeImmutable::modify() on a separate line has no effect.', 11], + ['Call to method DateTimeImmutable::setDate() on a separate line has no effect.', 12], + ['Call to method DateTimeImmutable::setISODate() on a separate line has no effect.', 13], + ['Call to method DateTimeImmutable::setTime() on a separate line has no effect.', 14], + ['Call to method DateTimeImmutable::setTimestamp() on a separate line has no effect.', 15], + ['Call to method DateTimeImmutable::setTimezone() on a separate line has no effect.', 17], + ]; + if (PHP_VERSION_ID < 80300) { + $errors = array_merge([ + ['Call to method DateTimeImmutable::sub() on a separate line has no effect.', 9], + ], $errors); + } + + $this->analyse([__DIR__ . '/data/bug-11503.php'], $errors); + } + public function testFirstClassCallables(): void { $this->analyse([__DIR__ . '/data/first-class-callable-method-without-side-effect.php'], [ diff --git a/tests/PHPStan/Rules/Methods/data/bug-11503.php b/tests/PHPStan/Rules/Methods/data/bug-11503.php new file mode 100644 index 00000000000..d37f48cf073 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11503.php @@ -0,0 +1,19 @@ +sub($interval); + $date->add($interval); + $date->modify('+1 day'); + $date->setDate(2024, 8, 13); + $date->setISODate(2024, 1); + $date->setTime(0, 0, 0, 0); + $date->setTimestamp(1); + $zone = new \DateTimeZone('UTC'); + $date->setTimezone($zone); + } +} From 777a82a0dc9d6a64a709c30a2e5bdb030b634464 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 09:18:12 +0200 Subject: [PATCH 0096/3097] Do not report `static` in PHPDoc tags above traits as an error --- conf/config.level0.neon | 5 + conf/config.level2.neon | 10 + src/Analyser/NodeScopeResolver.php | 5 +- src/Node/InTraitNode.php | 7 +- src/PhpDoc/StubValidator.php | 6 + src/Reflection/ClassReflection.php | 27 +++ src/Rules/Classes/LocalTypeAliasesCheck.php | 182 ++++++++++++------ .../Classes/LocalTypeTraitAliasesRule.php | 2 +- .../Classes/LocalTypeTraitUseAliasesRule.php | 34 ++++ src/Rules/Classes/MethodTagCheck.php | 150 ++++++++++++--- src/Rules/Classes/MethodTagRule.php | 5 +- src/Rules/Classes/MethodTagTraitRule.php | 2 +- src/Rules/Classes/MethodTagTraitUseRule.php | 34 ++++ src/Rules/Classes/PropertyTagCheck.php | 159 ++++++++++----- src/Rules/Classes/PropertyTagTraitRule.php | 2 +- src/Rules/Classes/PropertyTagTraitUseRule.php | 34 ++++ .../Analyser/NodeScopeResolverTest.php | 3 + .../Classes/LocalTypeTraitAliasesRuleTest.php | 5 + .../LocalTypeTraitUseAliasesRuleTest.php | 78 ++++++++ .../Rules/Classes/MethodTagTraitRuleTest.php | 21 +- .../Classes/MethodTagTraitUseRuleTest.php | 64 ++++++ .../Classes/PropertyTagTraitRuleTest.php | 11 +- .../Classes/PropertyTagTraitUseRuleTest.php | 55 ++++++ .../Classes/data/bug-11591-method-tag.php | 32 +++ .../Classes/data/bug-11591-property-tag.php | 26 +++ .../PHPStan/Rules/Classes/data/bug-11591.php | 44 +++++ .../Classes/data/local-type-trait-aliases.php | 13 ++ .../data/local-type-trait-use-aliases.php | 26 +++ .../Rules/Classes/data/method-tag-trait.php | 7 + .../Rules/Classes/data/property-tag-trait.php | 8 + 30 files changed, 898 insertions(+), 159 deletions(-) create mode 100644 src/Rules/Classes/LocalTypeTraitUseAliasesRule.php create mode 100644 src/Rules/Classes/MethodTagTraitUseRule.php create mode 100644 src/Rules/Classes/PropertyTagTraitUseRule.php create mode 100644 tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11591-method-tag.php create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11591-property-tag.php create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11591.php create mode 100644 tests/PHPStan/Rules/Classes/data/local-type-trait-use-aliases.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d23705fa92e..1382d99ee16 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -32,6 +32,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.validatePregQuote% PHPStan\Rules\Keywords\RequireFileExistsRule: phpstan.rules.rule: %featureToggles.requireFileExists% + PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% rules: - PHPStan\Rules\Api\ApiInstantiationRule @@ -148,6 +150,9 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + - + class: PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule + - class: PHPStan\Rules\Exceptions\CaughtExceptionExistenceRule tags: diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 72d297bfb36..c60247afb1d 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -53,10 +53,14 @@ conditionalTags: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\MethodTagTraitRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MethodTagTraitUseRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagTraitRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\PropertyTagTraitUseRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: @@ -89,12 +93,18 @@ services: - class: PHPStan\Rules\Classes\MethodTagTraitRule + - + class: PHPStan\Rules\Classes\MethodTagTraitUseRule + - class: PHPStan\Rules\Classes\PropertyTagRule - class: PHPStan\Rules\Classes\PropertyTagTraitRule + - + class: PHPStan\Rules\Classes\PropertyTagTraitUseRule + - class: PHPStan\Rules\PhpDoc\RequireExtendsCheck arguments: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7c2118d058b..a885d648b00 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5788,8 +5788,11 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection $methodAst->name = $methodNames[$methodName]; } + if (!$scope->isInClass()) { + throw new ShouldNotHappenException(); + } $traitScope = $scope->enterTrait($traitReflection); - $nodeCallback(new InTraitNode($node, $traitReflection), $traitScope); + $nodeCallback(new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope); $this->processStmtNodes($node, $stmts, $traitScope, $nodeCallback, StatementContext::createTopLevel()); return; } diff --git a/src/Node/InTraitNode.php b/src/Node/InTraitNode.php index 39b0dc509b3..2a3a810fb5d 100644 --- a/src/Node/InTraitNode.php +++ b/src/Node/InTraitNode.php @@ -12,7 +12,7 @@ class InTraitNode extends Node\Stmt implements VirtualNode { - public function __construct(private Node\Stmt\Trait_ $originalNode, private ClassReflection $traitReflection) + public function __construct(private Node\Stmt\Trait_ $originalNode, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) { parent::__construct($originalNode->getAttributes()); } @@ -27,6 +27,11 @@ public function getTraitReflection(): ClassReflection return $this->traitReflection; } + public function getImplementingClassReflection(): ClassReflection + { + return $this->implementingClassReflection; + } + public function getType(): string { return 'PHPStan_Stmt_InTraitNode'; diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index a1409ca9171..1949fce6564 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -26,13 +26,16 @@ use PHPStan\Rules\Classes\LocalTypeAliasesCheck; use PHPStan\Rules\Classes\LocalTypeAliasesRule; use PHPStan\Rules\Classes\LocalTypeTraitAliasesRule; +use PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule; use PHPStan\Rules\Classes\MethodTagCheck; use PHPStan\Rules\Classes\MethodTagRule; use PHPStan\Rules\Classes\MethodTagTraitRule; +use PHPStan\Rules\Classes\MethodTagTraitUseRule; use PHPStan\Rules\Classes\MixinRule; use PHPStan\Rules\Classes\PropertyTagCheck; use PHPStan\Rules\Classes\PropertyTagRule; use PHPStan\Rules\Classes\PropertyTagTraitRule; +use PHPStan\Rules\Classes\PropertyTagTraitUseRule; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; use PHPStan\Rules\FunctionDefinitionCheck; @@ -242,11 +245,14 @@ private function getRuleRegistry(Container $container): RuleRegistry $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); $rules[] = new MethodTagRule($methodTagCheck); $rules[] = new MethodTagTraitRule($methodTagCheck, $reflectionProvider); + $rules[] = new MethodTagTraitUseRule($methodTagCheck); $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); $rules[] = new PropertyTagRule($propertyTagCheck); $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); + $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); $rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); + $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); } return new DirectRuleRegistry($rules); diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index cc687fdfcc8..be4ef76e9ee 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -129,6 +129,8 @@ class ClassReflection private false|ResolvedPhpDocBlock $resolvedPhpDocBlock = false; + private false|ResolvedPhpDocBlock $traitContextResolvedPhpDocBlock = false; + /** @var ClassReflection[]|null */ private ?array $cachedInterfaces = null; @@ -1580,6 +1582,31 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock return $this->resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $this->getName(), null, null, $this->reflectionDocComment); } + public function getTraitContextResolvedPhpDoc(self $implementingClass): ?ResolvedPhpDocBlock + { + if (!$this->isTrait()) { + throw new ShouldNotHappenException(); + } + if (!$implementingClass->isClass()) { + throw new ShouldNotHappenException(); + } + $fileName = $this->getFileName(); + if (is_bool($this->reflectionDocComment)) { + $docComment = $this->reflection->getDocComment(); + $this->reflectionDocComment = $docComment !== false ? $docComment : null; + } + + if ($this->reflectionDocComment === null) { + return null; + } + + if ($this->traitContextResolvedPhpDocBlock !== false) { + return $this->traitContextResolvedPhpDocBlock; + } + + return $this->traitContextResolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $implementingClass->getName(), $this->getName(), null, $this->reflectionDocComment); + } + private function getFirstExtendsTag(): ?ExtendsTag { foreach ($this->getExtendsTags() as $tag) { diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 71e807ecd72..5347681a906 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -53,6 +53,22 @@ public function __construct( * @return list */ public function check(ClassReflection $reflection, ClassLike $node): array + { + $errors = []; + foreach ($this->checkInTraitDefinitionContext($reflection) as $error) { + $errors[] = $error; + } + foreach ($this->checkInTraitUseContext($reflection, $reflection, $node) as $error) { + $errors[] = $error; + } + + return $errors; + } + + /** + * @return list + */ + public function checkInTraitDefinitionContext(ClassReflection $reflection): array { $phpDoc = $reflection->getResolvedPhpDoc(); if ($phpDoc === null) { @@ -69,7 +85,7 @@ public function check(ClassReflection $reflection, ClassLike $node): array }; $errors = []; - $className = $reflection->getName(); + $className = $reflection->getDisplayName(); $importedAliases = []; @@ -162,32 +178,7 @@ public function check(ClassReflection $reflection, ClassLike $node): array } $resolvedType = $typeAliasTag->getTypeAlias()->resolve($this->typeNodeResolver); - $foundError = false; - TypeTraverser::map($resolvedType, static function (Type $type, callable $traverse) use (&$errors, &$foundError, $aliasName): Type { - if ($foundError) { - return $type; - } - - if ($type instanceof CircularTypeAliasErrorType) { - $errors[] = RuleErrorBuilder::message(sprintf('Circular definition detected in type alias %s.', $aliasName)) - ->identifier('typeAlias.circular') - ->build(); - $foundError = true; - return $type; - } - - if ($type instanceof ErrorType) { - $errors[] = RuleErrorBuilder::message(sprintf('Invalid type definition detected in type alias %s.', $aliasName)) - ->identifier('typeAlias.invalidType') - ->build(); - $foundError = true; - return $type; - } - - return $traverse($type); - }); - - if ($foundError) { + if ($this->hasErrorType($resolvedType, $aliasName, $errors)) { continue; } @@ -195,45 +186,78 @@ public function check(ClassReflection $reflection, ClassLike $node): array continue; } - if ($this->checkMissingTypehints) { - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with no value type specified in iterable type %s.', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $iterableTypeDescription, - )) - ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) - ->identifier('missingType.iterableValue') - ->build(); - } + if (!$this->checkMissingTypehints) { + continue; + } - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with generic %s but does not specify its types: %s', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $name, - implode(', ', $genericTypeNames), - )) - ->identifier('missingType.generics') - ->build(); - } + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no value type specified in iterable type %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has type alias %s with no signature specified for %s.', - $reflection->getClassTypeDescription(), - $reflection->getDisplayName(), - $aliasName, - $callableType->describe(VerbosityLevel::typeOnly()), - ))->identifier('missingType.callable')->build(); - } + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with generic %s but does not specify its types: %s', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no signature specified for %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); } + } + return $errors; + } + + /** + * @return list + */ + public function checkInTraitUseContext( + ClassReflection $reflection, + ClassReflection $implementingClassReflection, + ClassLike $node, + ): array + { + if ($reflection->getNativeReflection()->getName() === $implementingClassReflection->getName()) { + $phpDoc = $reflection->getResolvedPhpDoc(); + } else { + $phpDoc = $reflection->getTraitContextResolvedPhpDoc($implementingClassReflection); + } + if ($phpDoc === null) { + return []; + } + + $errors = []; + + foreach ($phpDoc->getTypeAliasTags() as $typeAliasTag) { + $aliasName = $typeAliasTag->getAliasName(); + $resolvedType = $typeAliasTag->getTypeAlias()->resolve($this->typeNodeResolver); + $throwawayErrors = []; + if ($this->hasErrorType($resolvedType, $aliasName, $throwawayErrors)) { + continue; + } foreach ($resolvedType->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) @@ -304,4 +328,38 @@ private function isAliasNameValid(string $aliasName, ?NameScope $nameScope): boo || $aliasNameResolvedType instanceof TemplateType; // aliases take precedence over type parameters, this is reported by other rules using TemplateTypeCheck } + /** + * @param list $errors + * @param-out list $errors + */ + private function hasErrorType(Type $type, string $aliasName, array &$errors): bool + { + $foundError = false; + TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$errors, &$foundError, $aliasName): Type { + if ($foundError) { + return $type; + } + + if ($type instanceof CircularTypeAliasErrorType) { + $errors[] = RuleErrorBuilder::message(sprintf('Circular definition detected in type alias %s.', $aliasName)) + ->identifier('typeAlias.circular') + ->build(); + $foundError = true; + return $type; + } + + if ($type instanceof ErrorType) { + $errors[] = RuleErrorBuilder::message(sprintf('Invalid type definition detected in type alias %s.', $aliasName)) + ->identifier('typeAlias.invalidType') + ->build(); + $foundError = true; + return $type; + } + + return $traverse($type); + }); + + return $foundError; + } + } diff --git a/src/Rules/Classes/LocalTypeTraitAliasesRule.php b/src/Rules/Classes/LocalTypeTraitAliasesRule.php index 58d72696ad3..1f7fe5021dc 100644 --- a/src/Rules/Classes/LocalTypeTraitAliasesRule.php +++ b/src/Rules/Classes/LocalTypeTraitAliasesRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); + return $this->check->checkInTraitDefinitionContext($this->reflectionProvider->getClass($traitName->toString())); } } diff --git a/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php b/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php new file mode 100644 index 00000000000..2523e3a9b44 --- /dev/null +++ b/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php @@ -0,0 +1,34 @@ + + */ +final class LocalTypeTraitUseAliasesRule implements Rule +{ + + public function __construct(private LocalTypeAliasesCheck $check) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkInTraitUseContext( + $node->getTraitReflection(), + $node->getImplementingClassReflection(), + $node->getOriginalNode(), + ); + } + +} diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index 1c61442c9df..e8c44d416ec 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -47,7 +47,10 @@ public function check( foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { $i++; $parameterDescription = sprintf('parameter #%d $%s', $i, $parameterName); - foreach ($this->checkMethodType($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType()) as $error) { + $errors[] = $error; + } + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { $errors[] = $error; } @@ -55,12 +58,20 @@ public function check( continue; } - foreach ($this->checkMethodType($classReflection, $methodName, sprintf('%s default value', $parameterDescription), $parameterTag->getDefaultValue(), $node) as $error) { + $defaultValueDescription = sprintf('%s default value', $parameterDescription); + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue()) as $error) { + $errors[] = $error; + } + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { $errors[] = $error; } } - foreach ($this->checkMethodType($classReflection, $methodName, 'return type', $methodTag->getReturnType(), $node) as $error) { + $returnTypeDescription = 'return type'; + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType()) as $error) { + $errors[] = $error; + } + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { $errors[] = $error; } } @@ -71,34 +82,86 @@ public function check( /** * @return list */ - private function checkMethodType(ClassReflection $classReflection, string $methodName, string $description, Type $type, ClassLike $node): array + public function checkInTraitDefinitionContext(ClassReflection $classReflection): array { - if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { - return [ - RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @method for method %s::%s() %s contains unresolvable type.', - $classReflection->getDisplayName(), - $methodName, - $description, - ))->identifier('methodTag.unresolvableType') - ->build(), - ]; + $errors = []; + foreach ($classReflection->getMethodTags() as $methodName => $methodTag) { + $i = 0; + foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { + $i++; + $parameterDescription = sprintf('parameter #%d $%s', $i, $parameterName); + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType()) as $error) { + $errors[] = $error; + } + + if ($parameterTag->getDefaultValue() === null) { + continue; + } + + $defaultValueDescription = sprintf('%s default value', $parameterDescription); + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue()) as $error) { + $errors[] = $error; + } + } + + $returnTypeDescription = 'return type'; + foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType()) as $error) { + $errors[] = $error; + } } - $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); - $escapedMethodName = SprintfHelper::escapeFormatString($methodName); - $escapedDescription = SprintfHelper::escapeFormatString($description); + return $errors; + } - $errors = $this->genericObjectTypeCheck->check( - $type, - sprintf('PHPDoc tag @method for method %s::%s() %s contains generic type %%s but %%s %%s is not generic.', $escapedClassName, $escapedMethodName, $escapedDescription), - sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s does not specify all template types of %%s %%s: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), - sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), - sprintf('Type %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is not subtype of template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), - sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is in conflict with %%s template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), - sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is redundant, template type %%s of %%s %%s has the same variance.', $escapedClassName, $escapedMethodName, $escapedDescription), - ); + /** + * @return list + */ + public function checkInTraitUseContext( + ClassReflection $classReflection, + ClassReflection $implementingClass, + ClassLike $node, + ): array + { + $phpDoc = $classReflection->getTraitContextResolvedPhpDoc($implementingClass); + if ($phpDoc === null) { + return []; + } + $errors = []; + foreach ($phpDoc->getMethodTags() as $methodName => $methodTag) { + $i = 0; + foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { + $i++; + $parameterDescription = sprintf('parameter #%d $%s', $i, $parameterName); + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { + $errors[] = $error; + } + + if ($parameterTag->getDefaultValue() === null) { + continue; + } + + $defaultValueDescription = sprintf('%s default value', $parameterDescription); + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { + $errors[] = $error; + } + } + + $returnTypeDescription = 'return type'; + foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + /** + * @return list + */ + private function checkMethodTypeInTraitDefinitionContext(ClassReflection $classReflection, string $methodName, string $description, Type $type): array + { + $errors = []; foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { $errors[] = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @method for method %s::%s() %s contains generic %s but does not specify its types: %s', @@ -138,6 +201,15 @@ private function checkMethodType(ClassReflection $classReflection, string $metho ))->identifier('missingType.callable')->build(); } + return $errors; + } + + /** + * @return list + */ + private function checkMethodTypeInTraitUseContext(ClassReflection $classReflection, string $methodName, string $description, Type $type, ClassLike $node): array + { + $errors = []; foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains unknown class %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) @@ -158,7 +230,31 @@ private function checkMethodType(ClassReflection $classReflection, string $metho } } - return $errors; + if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @method for method %s::%s() %s contains unresolvable type.', + $classReflection->getDisplayName(), + $methodName, + $description, + ))->identifier('methodTag.unresolvableType')->build(); + } + + $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); + $escapedMethodName = SprintfHelper::escapeFormatString($methodName); + $escapedDescription = SprintfHelper::escapeFormatString($description); + + return array_merge( + $errors, + $this->genericObjectTypeCheck->check( + $type, + sprintf('PHPDoc tag @method for method %s::%s() %s contains generic type %%s but %%s %%s is not generic.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s does not specify all template types of %%s %%s: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Generic type %%s in PHPDoc tag @method for method %s::%s() %s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Type %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is not subtype of template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is in conflict with %%s template type %%s of %%s %%s.', $escapedClassName, $escapedMethodName, $escapedDescription), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @method for method %s::%s() %s is redundant, template type %%s of %%s %%s has the same variance.', $escapedClassName, $escapedMethodName, $escapedDescription), + ), + ); } } diff --git a/src/Rules/Classes/MethodTagRule.php b/src/Rules/Classes/MethodTagRule.php index cdfc6759e7a..ddb3cf254db 100644 --- a/src/Rules/Classes/MethodTagRule.php +++ b/src/Rules/Classes/MethodTagRule.php @@ -24,7 +24,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + return $this->check->check( + $node->getClassReflection(), + $node->getOriginalNode(), + ); } } diff --git a/src/Rules/Classes/MethodTagTraitRule.php b/src/Rules/Classes/MethodTagTraitRule.php index 57f84a39419..157c46f7f4c 100644 --- a/src/Rules/Classes/MethodTagTraitRule.php +++ b/src/Rules/Classes/MethodTagTraitRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); + return $this->check->checkInTraitDefinitionContext($this->reflectionProvider->getClass($traitName->toString())); } } diff --git a/src/Rules/Classes/MethodTagTraitUseRule.php b/src/Rules/Classes/MethodTagTraitUseRule.php new file mode 100644 index 00000000000..1f6d6f1f7c1 --- /dev/null +++ b/src/Rules/Classes/MethodTagTraitUseRule.php @@ -0,0 +1,34 @@ + + */ +final class MethodTagTraitUseRule implements Rule +{ + + public function __construct(private MethodTagCheck $check) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkInTraitUseContext( + $node->getTraitReflection(), + $node->getImplementingClassReflection(), + $node->getOriginalNode(), + ); + } + +} diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index abbc2746984..788c252d471 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Internal\SprintfHelper; +use PHPStan\PhpDoc\Tag\PropertyTag; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; @@ -44,32 +45,30 @@ public function check( { $errors = []; foreach ($classReflection->getPropertyTags() as $propertyName => $propertyTag) { - $readableType = $propertyTag->getReadableType(); - $writableType = $propertyTag->getWritableType(); - - $types = []; - $tagName = '@property'; - if ($readableType !== null) { - if ($writableType !== null) { - if ($writableType->equals($readableType)) { - $types[] = $readableType; - } else { - $types[] = $readableType; - $types[] = $writableType; - } - } else { - $tagName = '@property-read'; - $types[] = $readableType; + [$types, $tagName] = $this->getTypesAndTagName($propertyTag); + foreach ($types as $type) { + foreach ($this->checkPropertyTypeInTraitDefinitionContext($classReflection, $propertyName, $tagName, $type) as $error) { + $errors[] = $error; + } + foreach ($this->checkPropertyTypeInTraitUseContext($classReflection, $propertyName, $tagName, $type, $node) as $error) { + $errors[] = $error; } - } elseif ($writableType !== null) { - $tagName = '@property-write'; - $types[] = $writableType; - } else { - throw new ShouldNotHappenException(); } + } + return $errors; + } + + /** + * @return list + */ + public function checkInTraitDefinitionContext(ClassReflection $classReflection): array + { + $errors = []; + foreach ($classReflection->getPropertyTags() as $propertyName => $propertyTag) { + [$types, $tagName] = $this->getTypesAndTagName($propertyTag); foreach ($types as $type) { - foreach ($this->checkPropertyType($classReflection, $propertyName, $tagName, $type, $node) as $error) { + foreach ($this->checkPropertyTypeInTraitDefinitionContext($classReflection, $propertyName, $tagName, $type) as $error) { $errors[] = $error; } } @@ -81,33 +80,68 @@ public function check( /** * @return list */ - private function checkPropertyType(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type, ClassLike $node): array + public function checkInTraitUseContext( + ClassReflection $classReflection, + ClassReflection $implementingClass, + ClassLike $node, + ): array { - if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { - return [ - RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for property %s::$%s contains unresolvable type.', - $tagName, - $classReflection->getDisplayName(), - $propertyName, - ))->identifier('propertyTag.unresolvableType') - ->build(), - ]; + $phpDoc = $classReflection->getTraitContextResolvedPhpDoc($implementingClass); + if ($phpDoc === null) { + return []; } - $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); - $escapedPropertyName = SprintfHelper::escapeFormatString($propertyName); - $escapedTagName = SprintfHelper::escapeFormatString($tagName); + $errors = []; + foreach ($phpDoc->getPropertyTags() as $propertyName => $propertyTag) { + [$types, $tagName] = $this->getTypesAndTagName($propertyTag); + foreach ($types as $type) { + foreach ($this->checkPropertyTypeInTraitUseContext($classReflection, $propertyName, $tagName, $type, $node) as $error) { + $errors[] = $error; + } + } + } - $errors = $this->genericObjectTypeCheck->check( - $type, - sprintf('PHPDoc tag %s for property %s::$%s contains generic type %%s but %%s %%s is not generic.', $escapedTagName, $escapedClassName, $escapedPropertyName), - sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s does not specify all template types of %%s %%s: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), - sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), - sprintf('Type %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is not subtype of template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), - sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is in conflict with %%s template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), - sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is redundant, template type %%s of %%s %%s has the same variance.', $escapedTagName, $escapedClassName, $escapedPropertyName), - ); + return $errors; + } + + /** + * @return array{list, string} + */ + private function getTypesAndTagName(PropertyTag $propertyTag): array + { + $readableType = $propertyTag->getReadableType(); + $writableType = $propertyTag->getWritableType(); + + $types = []; + $tagName = '@property'; + if ($readableType !== null) { + if ($writableType !== null) { + if ($writableType->equals($readableType)) { + $types[] = $readableType; + } else { + $types[] = $readableType; + $types[] = $writableType; + } + } else { + $tagName = '@property-read'; + $types[] = $readableType; + } + } elseif ($writableType !== null) { + $tagName = '@property-write'; + $types[] = $writableType; + } else { + throw new ShouldNotHappenException(); + } + + return [$types, $tagName]; + } + + /** + * @return list + */ + private function checkPropertyTypeInTraitDefinitionContext(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type): array + { + $errors = []; foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { $errors[] = RuleErrorBuilder::message(sprintf( @@ -148,6 +182,15 @@ private function checkPropertyType(ClassReflection $classReflection, string $pro ))->identifier('missingType.callable')->build(); } + return $errors; + } + + /** + * @return list + */ + private function checkPropertyTypeInTraitUseContext(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type, ClassLike $node): array + { + $errors = []; foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains unknown class %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) @@ -168,7 +211,31 @@ private function checkPropertyType(ClassReflection $classReflection, string $pro } } - return $errors; + if ($this->unresolvableTypeHelper->containsUnresolvableType($type)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for property %s::$%s contains unresolvable type.', + $tagName, + $classReflection->getDisplayName(), + $propertyName, + ))->identifier('propertyTag.unresolvableType')->build(); + } + + $escapedClassName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); + $escapedPropertyName = SprintfHelper::escapeFormatString($propertyName); + $escapedTagName = SprintfHelper::escapeFormatString($tagName); + + return array_merge( + $errors, + $this->genericObjectTypeCheck->check( + $type, + sprintf('PHPDoc tag %s for property %s::$%s contains generic type %%s but %%s %%s is not generic.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s does not specify all template types of %%s %%s: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Generic type %%s in PHPDoc tag %s for property %s::$%s specifies %%d template types, but %%s %%s supports only %%d: %%s', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Type %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is not subtype of template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is in conflict with %%s template type %%s of %%s %%s.', $escapedTagName, $escapedClassName, $escapedPropertyName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for property %s::$%s is redundant, template type %%s of %%s %%s has the same variance.', $escapedTagName, $escapedClassName, $escapedPropertyName), + ), + ); } } diff --git a/src/Rules/Classes/PropertyTagTraitRule.php b/src/Rules/Classes/PropertyTagTraitRule.php index cd3a54c9fd1..bd5de407baf 100644 --- a/src/Rules/Classes/PropertyTagTraitRule.php +++ b/src/Rules/Classes/PropertyTagTraitRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($this->reflectionProvider->getClass($traitName->toString()), $node); + return $this->check->checkInTraitDefinitionContext($this->reflectionProvider->getClass($traitName->toString())); } } diff --git a/src/Rules/Classes/PropertyTagTraitUseRule.php b/src/Rules/Classes/PropertyTagTraitUseRule.php new file mode 100644 index 00000000000..f381cd0dfd2 --- /dev/null +++ b/src/Rules/Classes/PropertyTagTraitUseRule.php @@ -0,0 +1,34 @@ + + */ +final class PropertyTagTraitUseRule implements Rule +{ + + public function __construct(private PropertyTagCheck $check) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkInTraitUseContext( + $node->getTraitReflection(), + $node->getImplementingClassReflection(), + $node->getOriginalNode(), + ); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 6a8c6b517dd..a5aac09b6e4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -181,6 +181,9 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-9542.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-9803.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'); } /** diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 1501b40f797..fb443854dfb 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -117,4 +117,9 @@ public function testRule(): void ]); } + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php new file mode 100644 index 00000000000..58ddda39a42 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -0,0 +1,78 @@ + + */ +class LocalTypeTraitUseAliasesRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new LocalTypeTraitUseAliasesRule( + new LocalTypeAliasesCheck( + ['GlobalTypeAlias' => 'int|string'], + $this->createReflectionProvider(), + self::getContainer()->getByType(TypeNodeResolver::class), + new MissingTypehintCheck(true, true, true, true, []), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new UnresolvableTypeHelper(), + new GenericObjectTypeCheck(), + true, + true, + true, + ), + ); + } + + public function testRule(): void + { + // everything reported by LocalTypeTraitAliasesRule + $this->analyse([__DIR__ . '/data/local-type-trait-aliases.php'], []); + } + + public function testRuleSpecific(): void + { + $this->analyse([__DIR__ . '/data/local-type-trait-use-aliases.php'], [ + [ + 'Type alias A contains unknown class LocalTypeTraitUseAliases\Nonexistent.', + 16, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Type alias B contains invalid type LocalTypeTraitUseAliases\SomeTrait.', + 16, + ], + [ + 'Type alias C contains unresolvable type.', + 16, + ], + [ + 'Type alias D contains generic type Exception but class Exception is not generic.', + 16, + ], + ]); + } + + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591.php'], []); + } + +} diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index 543e0d9d70b..a2c07386e2c 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -39,27 +39,18 @@ protected function getRule(): TRule public function testRule(): void { - $fooTraitLine = 12; $this->analyse([__DIR__ . '/data/method-tag-trait.php'], [ - [ - 'PHPDoc tag @method for method MethodTagTrait\Foo::doFoo() return type contains unknown class MethodTagTrait\intt.', - $fooTraitLine, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'PHPDoc tag @method for method MethodTagTrait\Foo::doBar() parameter #1 $a contains unresolvable type.', - $fooTraitLine, - ], - [ - 'PHPDoc tag @method for method MethodTagTrait\Foo::doBaz2() parameter #1 $a default value contains unresolvable type.', - $fooTraitLine, - ], [ 'Trait MethodTagTrait\Foo has PHPDoc tag @method for method doMissingIterablueValue() return type with no value type specified in iterable type array.', - $fooTraitLine, + 12, MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, ], ]); } + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591-method-tag.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php new file mode 100644 index 00000000000..4e775a48577 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -0,0 +1,64 @@ + + */ +class MethodTagTraitUseRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MethodTagTraitUseRule( + new MethodTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + ); + } + + public function testRule(): void + { + $fooTraitLine = 12; + $this->analyse([__DIR__ . '/data/method-tag-trait.php'], [ + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doFoo() return type contains unknown class MethodTagTrait\intt.', + $fooTraitLine, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doBar() parameter #1 $a contains unresolvable type.', + $fooTraitLine, + ], + [ + 'PHPDoc tag @method for method MethodTagTrait\Foo::doBaz2() parameter #1 $a default value contains unresolvable type.', + $fooTraitLine, + ], + ]); + } + + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591-method-tag.php'], []); + } + +} diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index c6e140604c6..887cebd583f 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -41,11 +41,16 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/property-tag-trait.php'], [ [ - 'PHPDoc tag @property for property PropertyTagTrait\Foo::$foo contains unknown class PropertyTagTrait\intt.', - 8, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + 'Trait PropertyTagTrait\Foo has PHPDoc tag @property for property $bar with no value type specified in iterable type array.', + 9, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, ], ]); } + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591-property-tag.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php new file mode 100644 index 00000000000..c19a36419af --- /dev/null +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -0,0 +1,55 @@ + + */ +class PropertyTagTraitUseRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new PropertyTagTraitUseRule( + new PropertyTagCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + ), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/property-tag-trait.php'], [ + [ + 'PHPDoc tag @property for property PropertyTagTrait\Foo::$foo contains unknown class PropertyTagTrait\intt.', + 9, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + + public function testBug11591(): void + { + $this->analyse([__DIR__ . '/data/bug-11591-property-tag.php'], []); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/bug-11591-method-tag.php b/tests/PHPStan/Rules/Classes/data/bug-11591-method-tag.php new file mode 100644 index 00000000000..7ef678f8fb7 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11591-method-tag.php @@ -0,0 +1,32 @@ + withTrashed(bool $withTrashed = true) + * @method static Builder onlyTrashed() + * @method static Builder withoutTrashed() + * @method static bool restore() + * @method static static restoreOrCreate(array $attributes = [], array $values = []) + * @method static static createOrRestore(array $attributes = [], array $values = []) + */ +trait SoftDeletes {} + +function test(): void { + assertType('Bug11591MethodTag\\Builder', User::withTrashed()); + assertType('Bug11591MethodTag\\Builder', User::onlyTrashed()); + assertType('Bug11591MethodTag\\Builder', User::withoutTrashed()); + assertType(User::class, User::createOrRestore()); + assertType(User::class, User::restoreOrCreate()); +} diff --git a/tests/PHPStan/Rules/Classes/data/bug-11591-property-tag.php b/tests/PHPStan/Rules/Classes/data/bug-11591-property-tag.php new file mode 100644 index 00000000000..c9bf36e246a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11591-property-tag.php @@ -0,0 +1,26 @@ + $a + * @property static $b + */ +trait SoftDeletes {} + +function test(User $user): void { + assertType('Bug11591PropertyTag\\Builder', $user->a); + assertType(User::class, $user->b); +} diff --git a/tests/PHPStan/Rules/Classes/data/bug-11591.php b/tests/PHPStan/Rules/Classes/data/bug-11591.php new file mode 100644 index 00000000000..e41413653a4 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11591.php @@ -0,0 +1,44 @@ + + */ +trait WithConfig { + /** + * @param SettingsFactory $settings + */ + public function setConfig(callable $settings): void { + $settings($this); + } + + /** + * @param callable(static): array $settings + */ + public function setConfig2(callable $settings): void { + $settings($this); + } + + /** + * @param callable(self): array $settings + */ + public function setConfig3(callable $settings): void { + $settings($this); + } +} + +class A +{ + use WithConfig; +} + +function (A $a): void { + $a->setConfig(function ($who) { + assertType(A::class, $who); + + return []; + }); +}; diff --git a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php index 6aaa554d523..6628e0db7c4 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php @@ -70,3 +70,16 @@ trait MissingType { } + +class Usages +{ + + use Foo; + use Bar; + use Baz; + use Qux; + use Generic; + use Invalid; + use MissingType; + +} diff --git a/tests/PHPStan/Rules/Classes/data/local-type-trait-use-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-trait-use-aliases.php new file mode 100644 index 00000000000..94e019280c8 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/local-type-trait-use-aliases.php @@ -0,0 +1,26 @@ + + */ +trait Foo +{ + +} + +class Usage +{ + + use Foo; + +} diff --git a/tests/PHPStan/Rules/Classes/data/method-tag-trait.php b/tests/PHPStan/Rules/Classes/data/method-tag-trait.php index 149d43a8542..504033696a6 100644 --- a/tests/PHPStan/Rules/Classes/data/method-tag-trait.php +++ b/tests/PHPStan/Rules/Classes/data/method-tag-trait.php @@ -21,3 +21,10 @@ class ClassWithConstant public const FOO = 1; } + +class Usages +{ + + use Foo; + +} diff --git a/tests/PHPStan/Rules/Classes/data/property-tag-trait.php b/tests/PHPStan/Rules/Classes/data/property-tag-trait.php index c5a50f30dd3..9bee89824eb 100644 --- a/tests/PHPStan/Rules/Classes/data/property-tag-trait.php +++ b/tests/PHPStan/Rules/Classes/data/property-tag-trait.php @@ -4,8 +4,16 @@ /** * @property intt $foo + * @property array $bar */ trait Foo { } + +class Usages +{ + + use Foo; + +} From 47a85bf1453a076bade7a30c94c06c0825abca7c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 14:43:59 +0200 Subject: [PATCH 0097/3097] Refactoring: introduce MethodTagTemplateTypeCheck --- conf/config.neon | 3 + .../Generics/MethodTagTemplateTypeCheck.php | 80 +++++++++++++++++++ .../Generics/MethodTagTemplateTypeRule.php | 54 ++----------- .../MethodTagTemplateTypeRuleTest.php | 20 ++--- 4 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 src/Rules/Generics/MethodTagTemplateTypeCheck.php diff --git a/conf/config.neon b/conf/config.neon index dc3e3b85f4b..704b0611719 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1005,6 +1005,9 @@ services: - class: PHPStan\Rules\Generics\GenericObjectTypeCheck + - + class: PHPStan\Rules\Generics\MethodTagTemplateTypeCheck + - class: PHPStan\Rules\Generics\TemplateTypeCheck arguments: diff --git a/src/Rules/Generics/MethodTagTemplateTypeCheck.php b/src/Rules/Generics/MethodTagTemplateTypeCheck.php new file mode 100644 index 00000000000..b0b6441c922 --- /dev/null +++ b/src/Rules/Generics/MethodTagTemplateTypeCheck.php @@ -0,0 +1,80 @@ + + */ + public function check( + ClassReflection $classReflection, + Scope $scope, + ClassLike $node, + string $docComment, + ): array + { + $className = $classReflection->getDisplayName(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + null, + $docComment, + ); + + $messages = []; + $escapedClassName = SprintfHelper::escapeFormatString($className); + $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); + + foreach ($resolvedPhpDoc->getMethodTags() as $methodName => $methodTag) { + $methodTemplateTags = $methodTag->getTemplateTags(); + $escapedMethodName = SprintfHelper::escapeFormatString($methodName); + + $messages = array_merge($messages, $this->templateTypeCheck->check( + $scope, + $node, + TemplateTypeScope::createWithMethod($className, $methodName), + $methodTemplateTags, + sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing class %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), + )); + + foreach (array_keys($methodTemplateTags) as $name) { + if (!isset($classTemplateTypes[$name])) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false))) + ->identifier('methodTag.shadowTemplate') + ->build(); + } + } + + return $messages; + } + +} diff --git a/src/Rules/Generics/MethodTagTemplateTypeRule.php b/src/Rules/Generics/MethodTagTemplateTypeRule.php index eafb0e946d1..b2f3e2a08c7 100644 --- a/src/Rules/Generics/MethodTagTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTagTemplateTypeRule.php @@ -4,16 +4,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateTypeScope; -use PHPStan\Type\VerbosityLevel; -use function array_keys; -use function array_merge; -use function sprintf; /** * @implements Rule @@ -22,8 +14,7 @@ final class MethodTagTemplateTypeRule implements Rule { public function __construct( - private FileTypeMapper $fileTypeMapper, - private TemplateTypeCheck $templateTypeCheck, + private MethodTagTemplateTypeCheck $check, ) { } @@ -40,47 +31,12 @@ public function processNode(Node $node, Scope $scope): array return []; } - $classReflection = $node->getClassReflection(); - $className = $classReflection->getDisplayName(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $classReflection->getName(), - $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - null, + return $this->check->check( + $node->getClassReflection(), + $scope, + $node->getOriginalNode(), $docComment->getText(), ); - - $messages = []; - $escapedClassName = SprintfHelper::escapeFormatString($className); - $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); - - foreach ($resolvedPhpDoc->getMethodTags() as $methodName => $methodTag) { - $methodTemplateTags = $methodTag->getTemplateTags(); - $escapedMethodName = SprintfHelper::escapeFormatString($methodName); - - $messages = array_merge($messages, $this->templateTypeCheck->check( - $scope, - $node, - TemplateTypeScope::createWithMethod($className, $methodName), - $methodTemplateTags, - sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing class %%s as its name.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), - sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), - )); - - foreach (array_keys($methodTemplateTags) as $name) { - if (!isset($classTemplateTypes[$name])) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method template %s for method %s::%s() shadows @template %s for class %s.', $name, $className, $methodName, $classTemplateTypes[$name]->describe(VerbosityLevel::typeOnly()), $classReflection->getDisplayName(false))) - ->identifier('methodTag.shadowTemplate') - ->build(); - } - } - - return $messages; } } diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php index e7547945662..1db002fd944 100644 --- a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php @@ -21,16 +21,18 @@ protected function getRule(): Rule $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $reflectionProvider); return new MethodTagTemplateTypeRule( - self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck( - $reflectionProvider, - new ClassNameCheck( - new ClassCaseSensitivityCheck($reflectionProvider, true), - new ClassForbiddenNameCheck(self::getContainer()), + new MethodTagTemplateTypeCheck( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, ), - new GenericObjectTypeCheck(), - $typeAliasResolver, - true, ), ); } From 085fcf40fefa63fc3672897276e460a6405206fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 14:50:46 +0200 Subject: [PATCH 0098/3097] Add missing rule to StubValidator --- src/PhpDoc/StubValidator.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 1949fce6564..8fee0f5f9b5 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -52,6 +52,8 @@ use PHPStan\Rules\Generics\InterfaceAncestorsRule; use PHPStan\Rules\Generics\InterfaceTemplateTypeRule; use PHPStan\Rules\Generics\MethodSignatureVarianceRule; +use PHPStan\Rules\Generics\MethodTagTemplateTypeCheck; +use PHPStan\Rules\Generics\MethodTagTemplateTypeRule; use PHPStan\Rules\Generics\MethodTemplateTypeRule; use PHPStan\Rules\Generics\TemplateTypeCheck; use PHPStan\Rules\Generics\TraitTemplateTypeRule; @@ -178,6 +180,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $localTypeAliasesCheck = $container->getByType(LocalTypeAliasesCheck::class); $phpClassReflectionExtension = $container->getByType(PhpClassReflectionExtension::class); $genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class); + $methodTagTemplateTypeCheck = $container->getByType(MethodTagTemplateTypeCheck::class); $rules = [ // level 0 @@ -201,6 +204,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new InterfaceAncestorsRule($genericAncestorsCheck, $crossCheckInterfacesHelper), new InterfaceTemplateTypeRule($templateTypeCheck), new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck), + new MethodTagTemplateTypeRule($methodTagTemplateTypeCheck), new MethodSignatureVarianceRule($varianceCheck), new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new IncompatiblePhpDocTypeRule($fileTypeMapper, $genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper), From aadbf62d3ae4517fc7a212b07130bedcef8d13ac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 14:51:15 +0200 Subject: [PATCH 0099/3097] Bleeding edge - MethodTagTemplateTypeTraitRule --- conf/config.level2.neon | 5 ++ src/PhpDoc/StubValidator.php | 2 + .../MethodTagTemplateTypeTraitRule.php | 52 +++++++++++++++ .../MethodTagTemplateTypeTraitRuleTest.php | 63 +++++++++++++++++++ .../data/method-tag-trait-template.php | 13 ++++ 5 files changed, 135 insertions(+) create mode 100644 src/Rules/Generics/MethodTagTemplateTypeTraitRule.php create mode 100644 tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php create mode 100644 tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index c60247afb1d..e43d074210e 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -65,6 +65,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% + PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: @@ -127,6 +129,9 @@ services: reportMaybes: %reportMaybes% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule - class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule - diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 8fee0f5f9b5..fc6a44ee283 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -54,6 +54,7 @@ use PHPStan\Rules\Generics\MethodSignatureVarianceRule; use PHPStan\Rules\Generics\MethodTagTemplateTypeCheck; use PHPStan\Rules\Generics\MethodTagTemplateTypeRule; +use PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule; use PHPStan\Rules\Generics\MethodTemplateTypeRule; use PHPStan\Rules\Generics\TemplateTypeCheck; use PHPStan\Rules\Generics\TraitTemplateTypeRule; @@ -257,6 +258,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); $rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); + $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); } return new DirectRuleRegistry($rules); diff --git a/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php b/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php new file mode 100644 index 00000000000..d0235e975c6 --- /dev/null +++ b/src/Rules/Generics/MethodTagTemplateTypeTraitRule.php @@ -0,0 +1,52 @@ + + */ +final class MethodTagTemplateTypeTraitRule implements Rule +{ + + public function __construct( + private MethodTagTemplateTypeCheck $check, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->check( + $this->reflectionProvider->getClass($traitName->toString()), + $scope, + $node, + $docComment->getText(), + ); + } + +} diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php new file mode 100644 index 00000000000..773f6c30c3a --- /dev/null +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php @@ -0,0 +1,63 @@ + + */ +class MethodTagTemplateTypeTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $reflectionProvider); + + return new MethodTagTemplateTypeTraitRule( + new MethodTagTemplateTypeCheck( + self::getContainer()->getByType(FileTypeMapper::class), + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, + ), + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/method-tag-trait-template.php'], [ + [ + 'PHPDoc tag @method template U for method MethodTagTraitTemplate\HelloWorld::sayHello() has invalid bound type MethodTagTraitTemplate\Nonexisting.', + 11, + ], + [ + 'PHPDoc tag @method template for method MethodTagTraitTemplate\HelloWorld::sayHello() cannot have existing class stdClass as its name.', + 11, + ], + [ + 'PHPDoc tag @method template T for method MethodTagTraitTemplate\HelloWorld::sayHello() shadows @template T for class MethodTagTraitTemplate\HelloWorld.', + 11, + ], + [ + 'PHPDoc tag @method template for method MethodTagTraitTemplate\HelloWorld::typeAlias() cannot have existing type alias TypeAlias as its name.', + 11, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php b/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php new file mode 100644 index 00000000000..57a93beb5a3 --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/method-tag-trait-template.php @@ -0,0 +1,13 @@ +(T $a, U $b, stdClass $c) + * @method void typeAlias(TypeAlias $a) + */ +trait HelloWorld +{ +} From 934d68e52b9d7deb6f262fccdba867085ebf2fe5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 15:35:58 +0200 Subject: [PATCH 0100/3097] Fix build --- build/enum-adapter-errors.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon index eccbd3eb1a0..eaa39f1d5e0 100644 --- a/build/enum-adapter-errors.neon +++ b/build/enum-adapter-errors.neon @@ -32,7 +32,7 @@ parameters: - message: "#^Call to method getDocComment\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 + count: 2 path: ../src/Reflection/ClassReflection.php - From 058e74f2b27e8f58669f7fb115741d6b27c50c80 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 15:45:11 +0200 Subject: [PATCH 0101/3097] Fix internal error --- src/Reflection/ClassReflection.php | 2 +- .../Classes/MethodTagTraitUseRuleTest.php | 16 ++++++++++++++++ .../Classes/data/method-tag-trait-enum.php | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Classes/data/method-tag-trait-enum.php diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index be4ef76e9ee..9527e526b74 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1587,7 +1587,7 @@ public function getTraitContextResolvedPhpDoc(self $implementingClass): ?Resolve if (!$this->isTrait()) { throw new ShouldNotHappenException(); } - if (!$implementingClass->isClass()) { + if ($implementingClass->isTrait()) { throw new ShouldNotHappenException(); } $fileName = $this->getFileName(); diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index 4e775a48577..59c1cb6aab9 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -10,6 +10,7 @@ use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule as TRule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -56,6 +57,21 @@ public function testRule(): void ]); } + public function testEnum(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/method-tag-trait-enum.php'], [ + [ + 'PHPDoc tag @method for method MethodTagTraitEnum\Foo::doFoo() return type contains unknown class MethodTagTraitEnum\intt.', + 8, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + public function testBug11591(): void { $this->analyse([__DIR__ . '/data/bug-11591-method-tag.php'], []); diff --git a/tests/PHPStan/Rules/Classes/data/method-tag-trait-enum.php b/tests/PHPStan/Rules/Classes/data/method-tag-trait-enum.php new file mode 100644 index 00000000000..9855c178449 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/method-tag-trait-enum.php @@ -0,0 +1,18 @@ += 8.1 + +namespace MethodTagTraitEnum; + +/** + * @method intt doFoo() + */ +trait Foo +{ + +} + +enum FooEnum +{ + + use Foo; + +} From c47730f1f97e4dc6ca9f120e2675ca709fc1402c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 17:37:44 +0200 Subject: [PATCH 0102/3097] Simplify extensions --- .../AnnotationsMethodsClassReflectionExtension.php | 9 --------- .../AnnotationsPropertiesClassReflectionExtension.php | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php index 76fac3fa37c..c234e8e2d13 100644 --- a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php @@ -108,15 +108,6 @@ private function findClassReflectionWithMethod( return $methodWithDeclaringClass; } - foreach ($parentClass->getTraits() as $traitClass) { - $parentTraitMethodWithDeclaringClass = $this->findClassReflectionWithMethod($traitClass, $parentClass, $methodName); - if ($parentTraitMethodWithDeclaringClass === null) { - continue; - } - - return $parentTraitMethodWithDeclaringClass; - } - $parentClass = $parentClass->getParentClass(); } diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index 0d5cdf48987..d6d69179d56 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -90,15 +90,6 @@ private function findClassReflectionWithProperty( return $methodWithDeclaringClass; } - foreach ($parentClass->getTraits() as $traitClass) { - $parentTraitMethodWithDeclaringClass = $this->findClassReflectionWithProperty($traitClass, $parentClass, $propertyName); - if ($parentTraitMethodWithDeclaringClass === null) { - continue; - } - - return $parentTraitMethodWithDeclaringClass; - } - $parentClass = $parentClass->getParentClass(); } From 57ccd8c4d4b16c7edec4c2c2de8589956de8284d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 16:01:13 +0200 Subject: [PATCH 0103/3097] Refactoring - extract MixinCheck --- conf/config.level2.neon | 3 - conf/config.neon | 6 + src/PhpDoc/StubValidator.php | 4 +- src/Rules/Classes/MixinCheck.php | 128 ++++++++++++++++++ src/Rules/Classes/MixinRule.php | 109 +-------------- tests/PHPStan/Rules/Classes/MixinRuleTest.php | 20 +-- 6 files changed, 150 insertions(+), 120 deletions(-) create mode 100644 src/Rules/Classes/MixinCheck.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index e43d074210e..efbc7b76530 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -83,9 +83,6 @@ conditionalTags: services: - class: PHPStan\Rules\Classes\MixinRule - arguments: - checkClassCaseSensitivity: %checkClassCaseSensitivity% - absentTypeChecks: %featureToggles.absentTypeChecks% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 704b0611719..9f62638b65b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -923,6 +923,12 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + - + class: PHPStan\Rules\Classes\MixinCheck + arguments: + checkClassCaseSensitivity: %checkClassCaseSensitivity% + absentTypeChecks: %featureToggles.absentTypeChecks% + - class: PHPStan\Rules\Classes\PropertyTagCheck arguments: diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index fc6a44ee283..e639533f643 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -31,6 +31,7 @@ use PHPStan\Rules\Classes\MethodTagRule; use PHPStan\Rules\Classes\MethodTagTraitRule; use PHPStan\Rules\Classes\MethodTagTraitUseRule; +use PHPStan\Rules\Classes\MixinCheck; use PHPStan\Rules\Classes\MixinRule; use PHPStan\Rules\Classes\PropertyTagCheck; use PHPStan\Rules\Classes\PropertyTagRule; @@ -182,6 +183,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $phpClassReflectionExtension = $container->getByType(PhpClassReflectionExtension::class); $genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class); $methodTagTemplateTypeCheck = $container->getByType(MethodTagTemplateTypeCheck::class); + $mixinCheck = $container->getByType(MixinCheck::class); $rules = [ // level 0 @@ -256,7 +258,7 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new PropertyTagRule($propertyTagCheck); $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); - $rules[] = new MixinRule($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); + $rules[] = new MixinRule($mixinCheck); $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); } diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php new file mode 100644 index 00000000000..e732f6d9e65 --- /dev/null +++ b/src/Rules/Classes/MixinCheck.php @@ -0,0 +1,128 @@ + + */ + public function check(ClassReflection $classReflection, ClassLike $node): array + { + $mixinTags = $classReflection->getMixinTags(); + $errors = []; + foreach ($mixinTags as $mixinTag) { + $type = $mixinTag->getType(); + if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) + ->identifier('mixin.nonObject') + ->build(); + continue; + } + + if ( + $this->unresolvableTypeHelper->containsUnresolvableType($type) + ) { + $errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.') + ->identifier('mixin.unresolvableType') + ->build(); + continue; + } + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $type, + 'PHPDoc tag @mixin contains generic type %s but %s %s is not generic.', + 'Generic type %s in PHPDoc tag @mixin does not specify all template types of %s %s: %s', + 'Generic type %s in PHPDoc tag @mixin specifies %d template types, but %s %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of %s %s.', + 'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is in conflict with %s template type %s of %s %s.', + 'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is redundant, template type %s of %s %s has the same variance.', + )); + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + if ($this->absentTypeChecks) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no signature specified for %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + + foreach ($type->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) + ->identifier('class.notFound') + ->discoveringSymbolsTip() + ->build(); + } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class)) + ->identifier('mixin.trait') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + } + } + + return $errors; + } + +} diff --git a/src/Rules/Classes/MixinRule.php b/src/Rules/Classes/MixinRule.php index 56f19a7203f..8fb28e0888f 100644 --- a/src/Rules/Classes/MixinRule.php +++ b/src/Rules/Classes/MixinRule.php @@ -5,18 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassNode; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Rules\ClassNameCheck; -use PHPStan\Rules\ClassNameNodePair; -use PHPStan\Rules\Generics\GenericObjectTypeCheck; -use PHPStan\Rules\MissingTypehintCheck; -use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\VerbosityLevel; -use function array_merge; -use function implode; -use function sprintf; /** * @implements Rule @@ -24,15 +13,7 @@ final class MixinRule implements Rule { - public function __construct( - private ReflectionProvider $reflectionProvider, - private ClassNameCheck $classCheck, - private GenericObjectTypeCheck $genericObjectTypeCheck, - private MissingTypehintCheck $missingTypehintCheck, - private UnresolvableTypeHelper $unresolvableTypeHelper, - private bool $checkClassCaseSensitivity, - private bool $absentTypeChecks, - ) + public function __construct(private MixinCheck $check) { } @@ -43,93 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $classReflection = $node->getClassReflection(); - $mixinTags = $classReflection->getMixinTags(); - $errors = []; - foreach ($mixinTags as $mixinTag) { - $type = $mixinTag->getType(); - if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) - ->identifier('mixin.nonObject') - ->build(); - continue; - } - - if ( - $this->unresolvableTypeHelper->containsUnresolvableType($type) - ) { - $errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.') - ->identifier('mixin.unresolvableType') - ->build(); - continue; - } - - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $type, - 'PHPDoc tag @mixin contains generic type %s but %s %s is not generic.', - 'Generic type %s in PHPDoc tag @mixin does not specify all template types of %s %s: %s', - 'Generic type %s in PHPDoc tag @mixin specifies %d template types, but %s %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @mixin is not subtype of template type %s of %s %s.', - 'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is in conflict with %s template type %s of %s %s.', - 'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is redundant, template type %s of %s %s has the same variance.', - )); - - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', - $innerName, - implode(', ', $genericTypeNames), - )) - ->identifier('missingType.generics') - ->build(); - } - - if ($this->absentTypeChecks) { - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.', - $classReflection->getClassTypeDescription(), - $classReflection->getDisplayName(), - $iterableTypeDescription, - )) - ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) - ->identifier('missingType.iterableValue') - ->build(); - } - - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has PHPDoc tag @mixin with no signature specified for %s.', - $classReflection->getClassTypeDescription(), - $classReflection->getDisplayName(), - $callableType->describe(VerbosityLevel::typeOnly()), - ))->identifier('missingType.callable')->build(); - } - } - - foreach ($type->getReferencedClasses() as $class) { - if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); - } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class)) - ->identifier('mixin.trait') - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames([ - new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), - ); - } - } - } - - return $errors; + return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index 1d93a8a299c..acaf1974b05 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -23,16 +23,18 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new MixinRule( - $reflectionProvider, - new ClassNameCheck( - new ClassCaseSensitivityCheck($reflectionProvider, true), - new ClassForbiddenNameCheck(self::getContainer()), + new MixinCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + true, ), - new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), - new UnresolvableTypeHelper(), - true, - true, ); } From ba591420c26b174ae561e26aeed01ccf34da9dee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 16:15:09 +0200 Subject: [PATCH 0104/3097] MixinCheck - prepare for trait rules --- src/Rules/Classes/MixinCheck.php | 115 +++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index e732f6d9e65..ab6f5ccb00e 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -37,9 +37,25 @@ public function __construct( */ public function check(ClassReflection $classReflection, ClassLike $node): array { - $mixinTags = $classReflection->getMixinTags(); $errors = []; - foreach ($mixinTags as $mixinTag) { + foreach ($this->checkInTraitDefinitionContext($classReflection) as $error) { + $errors[] = $error; + } + + foreach ($this->checkInTraitUseContext($classReflection, $classReflection, $node) as $error) { + $errors[] = $error; + } + + return $errors; + } + + /** + * @return list + */ + public function checkInTraitDefinitionContext(ClassReflection $classReflection): array + { + $errors = []; + foreach ($classReflection->getMixinTags() as $mixinTag) { $type = $mixinTag->getType(); if (!$type->canCallMethods()->yes() || !$type->canAccessProperties()->yes()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) @@ -48,6 +64,67 @@ public function check(ClassReflection $classReflection, ClassLike $node): array continue; } + if (!$this->absentTypeChecks) { + continue; + } + + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has PHPDoc tag @mixin with no signature specified for %s.', + $classReflection->getClassTypeDescription(), + $classReflection->getDisplayName(), + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + + return $errors; + } + + /** + * @return list + */ + public function checkInTraitUseContext( + ClassReflection $reflection, + ClassReflection $implementingClassReflection, + ClassLike $node, + ): array + { + if ($reflection->getNativeReflection()->getName() === $implementingClassReflection->getName()) { + $phpDoc = $reflection->getResolvedPhpDoc(); + } else { + $phpDoc = $reflection->getTraitContextResolvedPhpDoc($implementingClassReflection); + } + if ($phpDoc === null) { + return []; + } + + $errors = []; + foreach ($phpDoc->getMixinTags() as $mixinTag) { + $type = $mixinTag->getType(); if ( $this->unresolvableTypeHelper->containsUnresolvableType($type) ) { @@ -67,40 +144,6 @@ public function check(ClassReflection $classReflection, ClassLike $node): array 'Call-site variance of %s in generic type %s in PHPDoc tag @mixin is redundant, template type %s of %s %s has the same variance.', )); - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', - $innerName, - implode(', ', $genericTypeNames), - )) - ->identifier('missingType.generics') - ->build(); - } - - if ($this->absentTypeChecks) { - foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { - $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has PHPDoc tag @mixin with no value type specified in iterable type %s.', - $classReflection->getClassTypeDescription(), - $classReflection->getDisplayName(), - $iterableTypeDescription, - )) - ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) - ->identifier('missingType.iterableValue') - ->build(); - } - - foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($type) as $callableType) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s %s has PHPDoc tag @mixin with no signature specified for %s.', - $classReflection->getClassTypeDescription(), - $classReflection->getDisplayName(), - $callableType->describe(VerbosityLevel::typeOnly()), - ))->identifier('missingType.callable')->build(); - } - } - foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) From 0d0de946900adf4eb3c799b1b547567536e23147 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 16:18:34 +0200 Subject: [PATCH 0105/3097] Bleeding edge - check `@mixin` PHPDoc tag above traits --- conf/config.level2.neon | 10 ++++ src/PhpDoc/StubValidator.php | 4 ++ src/Rules/Classes/MixinTraitRule.php | 41 +++++++++++++++ src/Rules/Classes/MixinTraitUseRule.php | 34 ++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../Rules/Classes/MixinTraitRuleTest.php | 52 +++++++++++++++++++ .../Rules/Classes/MixinTraitUseRuleTest.php | 50 ++++++++++++++++++ .../Rules/Classes/data/mixin-trait-use.php | 36 +++++++++++++ .../Rules/Classes/data/mixin-trait.php | 17 ++++++ 9 files changed, 245 insertions(+) create mode 100644 src/Rules/Classes/MixinTraitRule.php create mode 100644 src/Rules/Classes/MixinTraitUseRule.php create mode 100644 tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/mixin-trait-use.php create mode 100644 tests/PHPStan/Rules/Classes/data/mixin-trait.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index efbc7b76530..13993940325 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -55,6 +55,10 @@ conditionalTags: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\MethodTagTraitUseRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MixinTraitRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% + PHPStan\Rules\Classes\MixinTraitUseRule: + phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagRule: phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Classes\PropertyTagTraitRule: @@ -86,6 +90,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Classes\MixinTraitRule + + - + class: PHPStan\Rules\Classes\MixinTraitUseRule + - class: PHPStan\Rules\Classes\MethodTagRule diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index e639533f643..43ecfdd0a50 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -33,6 +33,8 @@ use PHPStan\Rules\Classes\MethodTagTraitUseRule; use PHPStan\Rules\Classes\MixinCheck; use PHPStan\Rules\Classes\MixinRule; +use PHPStan\Rules\Classes\MixinTraitRule; +use PHPStan\Rules\Classes\MixinTraitUseRule; use PHPStan\Rules\Classes\PropertyTagCheck; use PHPStan\Rules\Classes\PropertyTagRule; use PHPStan\Rules\Classes\PropertyTagTraitRule; @@ -259,6 +261,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); $rules[] = new MixinRule($mixinCheck); + $rules[] = new MixinTraitRule($mixinCheck, $reflectionProvider); + $rules[] = new MixinTraitUseRule($mixinCheck); $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); } diff --git a/src/Rules/Classes/MixinTraitRule.php b/src/Rules/Classes/MixinTraitRule.php new file mode 100644 index 00000000000..5cb7c1ecd93 --- /dev/null +++ b/src/Rules/Classes/MixinTraitRule.php @@ -0,0 +1,41 @@ + + */ +final class MixinTraitRule implements Rule +{ + + public function __construct(private MixinCheck $check, private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $traitName = $node->namespacedName; + if ($traitName === null) { + return []; + } + + if (!$this->reflectionProvider->hasClass($traitName->toString())) { + return []; + } + + return $this->check->checkInTraitDefinitionContext( + $this->reflectionProvider->getClass($traitName->toString()), + ); + } + +} diff --git a/src/Rules/Classes/MixinTraitUseRule.php b/src/Rules/Classes/MixinTraitUseRule.php new file mode 100644 index 00000000000..33a3e807808 --- /dev/null +++ b/src/Rules/Classes/MixinTraitUseRule.php @@ -0,0 +1,34 @@ + + */ +final class MixinTraitUseRule implements Rule +{ + + public function __construct(private MixinCheck $check) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->check->checkInTraitUseContext( + $node->getTraitReflection(), + $node->getImplementingClassReflection(), + $node->getOriginalNode(), + ); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a5aac09b6e4..f3e8929e16b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -184,6 +184,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'); } /** diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php new file mode 100644 index 00000000000..f23e120458d --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -0,0 +1,52 @@ + + */ +class MixinTraitRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MixinTraitRule( + new MixinCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + true, + ), + $reflectionProvider, + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/mixin-trait.php'], [ + [ + 'Trait MixinTrait\FooTrait has PHPDoc tag @mixin with no value type specified in iterable type array.', + 14, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php new file mode 100644 index 00000000000..dbc7906da5a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -0,0 +1,50 @@ + + */ +class MixinTraitUseRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new MixinTraitUseRule( + new MixinCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + new MissingTypehintCheck(true, true, true, true, []), + new UnresolvableTypeHelper(), + true, + true, + ), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/mixin-trait-use.php'], [ + [ + 'PHPDoc tag @mixin contains unresolvable type.', + 22, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php b/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php new file mode 100644 index 00000000000..0b67bfb4fb1 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/mixin-trait-use.php @@ -0,0 +1,36 @@ + + * @mixin string&int + */ +trait FooTrait +{ + +} + +class Usages +{ + + use FooTrait; + +} + +function (Usages $u): void { + assertType(Usages::class, $u->get()); +}; diff --git a/tests/PHPStan/Rules/Classes/data/mixin-trait.php b/tests/PHPStan/Rules/Classes/data/mixin-trait.php new file mode 100644 index 00000000000..83c0f0b488d --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/mixin-trait.php @@ -0,0 +1,17 @@ + + */ +trait FooTrait +{ + +} From f5e2e32932644d61b3745e3b0f2c0910f722a86d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 17:31:22 +0200 Subject: [PATCH 0106/3097] Support `@mixin` above traits --- .../MixinMethodsClassReflectionExtension.php | 9 ++++ ...ixinPropertiesClassReflectionExtension.php | 9 ++++ .../Analyser/NodeScopeResolverTest.php | 2 + .../Rules/Methods/CallMethodsRuleTest.php | 9 ++++ .../Rules/Methods/data/trait-mixin.php | 44 ++++++++++++++++++ .../Properties/AccessPropertiesRuleTest.php | 8 ++++ .../Rules/Properties/data/trait-mixin.php | 45 +++++++++++++++++++ 7 files changed, 126 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/trait-mixin.php create mode 100644 tests/PHPStan/Rules/Properties/data/trait-mixin.php diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index 68ccf90ed91..58ae58ca2f6 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -74,6 +74,15 @@ private function findMethod(ClassReflection $classReflection, string $methodName return new MixinMethodReflection($method, $static); } + foreach ($classReflection->getTraits() as $traitClass) { + $methodWithDeclaringClass = $this->findMethod($traitClass, $methodName); + if ($methodWithDeclaringClass === null) { + continue; + } + + return $methodWithDeclaringClass; + } + $parentClass = $classReflection->getParentClass(); while ($parentClass !== null) { $method = $this->findMethod($parentClass, $methodName); diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index 8e6d32054fa..4b21f924516 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -65,6 +65,15 @@ private function findProperty(ClassReflection $classReflection, string $property return $property; } + foreach ($classReflection->getTraits() as $traitClass) { + $methodWithDeclaringClass = $this->findProperty($traitClass, $propertyName); + if ($methodWithDeclaringClass === null) { + continue; + } + + return $methodWithDeclaringClass; + } + $parentClass = $classReflection->getParentClass(); while ($parentClass !== null) { $property = $this->findProperty($parentClass, $propertyName); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index f3e8929e16b..ec6abc47e77 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -114,6 +114,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/conditional-complex-templates.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-7511.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/trait-mixin.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/trait-mixin.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-4708.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-7156.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6364.php'); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 255d9f9c029..3f29976a126 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3345,4 +3345,13 @@ public function testNoNamedArguments(): void ]); } + public function testTraitMixin(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/trait-mixin.php b/tests/PHPStan/Rules/Methods/data/trait-mixin.php new file mode 100644 index 00000000000..1aa5c3b4289 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/trait-mixin.php @@ -0,0 +1,44 @@ + + */ +trait FooTrait +{ + +} + +class Usages +{ + + use FooTrait; + +} + +class ChildUsages extends Usages +{ + +} + +function (Usages $u): void { + assertType(Usages::class, $u->get()); +}; + +function (ChildUsages $u): void { + assertType(ChildUsages::class, $u->get()); +}; diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 7697f826e3a..a07611980b5 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -949,4 +949,12 @@ public function testBug9694(): void $this->analyse([__DIR__ . '/data/bug-9694.php'], []); } + public function testTraitMixin(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/trait-mixin.php b/tests/PHPStan/Rules/Properties/data/trait-mixin.php new file mode 100644 index 00000000000..621a02e3f97 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/trait-mixin.php @@ -0,0 +1,45 @@ + + */ +trait FooTrait +{ + +} + +#[\AllowDynamicProperties] +class Usages +{ + + use FooTrait; + +} + +class ChildUsages extends Usages +{ + +} + +function (Usages $u): void { + assertType(Usages::class, $u->a); +}; + +function (ChildUsages $u): void { + assertType(ChildUsages::class, $u->a); +}; From 1cba4baaab142602996f55038e76f9021947426b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 3 Sep 2024 20:36:52 +0200 Subject: [PATCH 0107/3097] ArrayShapeMatcher - Fix alternations containing a $-only case --- src/Type/Regex/RegexGroupParser.php | 2 +- tests/PHPStan/Analyser/nsrt/preg_match_shapes.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 0cfbb26e2e4..03bbcb6cceb 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -460,7 +460,7 @@ private function walkGroupAst( $isNumeric = TrinaryLogic::createYes(); } - if (!$inOptionalQuantification) { + if (!$inOptionalQuantification && $literalValue !== '') { $isNonEmpty = TrinaryLogic::createYes(); } } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index f4ed3b7ffe6..694a7cc886d 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -746,3 +746,10 @@ function bug11490b (string $expression): void { } } +function bug11622 (string $expression): void { + $matches = []; + + if (preg_match('/^abc(def|$)/', $expression, $matches) === 1) { + assertType("array{string, string}", $matches); + } +} From c50b71fd961e9009419b8fddac835b15696f4ff5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 20:28:48 +0200 Subject: [PATCH 0108/3097] Do not report missing implementation abstract method from trait when it's implicitly implemented by enum --- src/Rules/Classes/EnumSanityRule.php | 13 ----- .../AbstractMethodInNonAbstractClassRule.php | 13 +++++ .../Rules/Classes/EnumSanityRuleTest.php | 31 +++++++++++- .../PHPStan/Rules/Classes/data/bug-11592.php | 47 +++++++++++++++++++ ...stractMethodInNonAbstractClassRuleTest.php | 30 ++++++++++++ 5 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11592.php diff --git a/src/Rules/Classes/EnumSanityRule.php b/src/Rules/Classes/EnumSanityRule.php index e9aebc1c264..2d193095d4c 100644 --- a/src/Rules/Classes/EnumSanityRule.php +++ b/src/Rules/Classes/EnumSanityRule.php @@ -47,20 +47,7 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($enumNode->getMethods() as $methodNode) { - if ($methodNode->isAbstract()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Enum %s contains abstract method %s().', - $classReflection->getDisplayName(), - $methodNode->name->name, - )) - ->identifier('enum.abstractMethod') - ->line($methodNode->getStartLine()) - ->nonIgnorable() - ->build(); - } - $lowercasedMethodName = $methodNode->name->toLowerString(); - if ($methodNode->isMagic()) { if ($lowercasedMethodName === '__construct') { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php index 04ca707933b..82c92a4ecdf 100644 --- a/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php +++ b/src/Rules/Methods/AbstractMethodInNonAbstractClassRule.php @@ -7,6 +7,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use function in_array; use function sprintf; /** @@ -29,6 +30,18 @@ public function processNode(Node $node, Scope $scope): array $class = $scope->getClassReflection(); if (!$class->isAbstract() && $node->isAbstract()) { + if ($class->isEnum()) { + $lowercasedMethodName = $node->name->toLowerString(); + if ($lowercasedMethodName === 'cases') { + return []; + } + if ($class->isBackedEnum()) { + if (in_array($lowercasedMethodName, ['from', 'tryfrom'], true)) { + return []; + } + } + } + $description = $class->getClassTypeDescription(); return [ RuleErrorBuilder::message(sprintf( diff --git a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php index c1e85d214f8..af02a796357 100644 --- a/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php +++ b/tests/PHPStan/Rules/Classes/EnumSanityRuleTest.php @@ -24,10 +24,11 @@ public function testRule(): void } $expected = [ - [ + /*[ + // reported by AbstractMethodInNonAbstractClassRule 'Enum EnumSanity\EnumWithAbstractMethod contains abstract method foo().', 7, - ], + ],*/ [ 'Enum EnumSanity\EnumWithConstructorAndDestructor contains constructor.', 12, @@ -123,4 +124,30 @@ public function testBug9402(): void ]); } + public function testBug11592(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1'); + } + + $this->analyse([__DIR__ . '/data/bug-11592.php'], [ + [ + 'Enum Bug11592\Test2 cannot redeclare native method cases().', + 22, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method cases().', + 37, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method from().', + 39, + ], + [ + 'Enum Bug11592\BackedTest2 cannot redeclare native method tryFrom().', + 41, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-11592.php b/tests/PHPStan/Rules/Classes/data/bug-11592.php new file mode 100644 index 00000000000..b94251a4950 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11592.php @@ -0,0 +1,47 @@ += 8.1 + +namespace Bug11592; + +trait HelloWorld +{ + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; +} + +enum Test +{ + use HelloWorld; +} + +enum Test2 +{ + + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; + +} + +enum BackedTest: int +{ + use HelloWorld; +} + +enum BackedTest2: int +{ + abstract public static function cases(): array; + + abstract public static function from(): self; + + abstract public static function tryFrom(): ?self; +} + +enum EnumWithAbstractMethod +{ + abstract function foo(); +} diff --git a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php index 581d5128906..7ec5a8d7f40 100644 --- a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php +++ b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php @@ -89,4 +89,34 @@ public function testEnum(): void ]); } + public function testBug11592(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1'); + } + + $this->analyse([__DIR__ . '/../Classes/data/bug-11592.php'], [ + [ + 'Enum Bug11592\Test contains abstract method from().', + 9, + ], + [ + 'Enum Bug11592\Test contains abstract method tryFrom().', + 11, + ], + [ + 'Enum Bug11592\Test2 contains abstract method from().', + 24, + ], + [ + 'Enum Bug11592\Test2 contains abstract method tryFrom().', + 26, + ], + [ + 'Enum Bug11592\EnumWithAbstractMethod contains abstract method foo().', + 46, + ], + ]); + } + } From 5909fb2dc78cbee46927c2cb23f7491dfef34165 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 20:55:14 +0200 Subject: [PATCH 0109/3097] Debugging function - `PHPStan\debugScope()` --- composer.json | 2 +- conf/config.neon | 1 + src/Analyser/MutatingScope.php | 2 +- src/Rules/Debug/DebugScopeRule.php | 66 +++++++++++++++++++ ...unctionStatementWithoutSideEffectsRule.php | 1 + src/debugScope.php | 14 ++++ .../Rules/Debug/DebugScopeRuleTest.php | 56 ++++++++++++++++ .../PHPStan/Rules/Debug/data/debug-scope.php | 17 +++++ 8 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/Rules/Debug/DebugScopeRule.php create mode 100644 src/debugScope.php create mode 100644 tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php create mode 100644 tests/PHPStan/Rules/Debug/data/debug-scope.php diff --git a/composer.json b/composer.json index 11803e18a36..f6cb9c01dbe 100644 --- a/composer.json +++ b/composer.json @@ -132,7 +132,7 @@ "src/" ] }, - "files": ["src/dumpType.php", "src/autoloadFunctions.php", "src/Testing/functions.php"] + "files": ["src/debugScope.php", "src/dumpType.php", "src/autoloadFunctions.php", "src/Testing/functions.php"] }, "autoload-dev": { "psr-4": { diff --git a/conf/config.neon b/conf/config.neon index 9f62638b65b..255be78dc29 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -278,6 +278,7 @@ extensions: validateExcludePaths: PHPStan\DependencyInjection\ValidateExcludePathsExtension rules: + - PHPStan\Rules\Debug\DebugScopeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f10fc225081..cb4dd6b858c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5400,7 +5400,7 @@ public function debug(): array $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise()); } foreach ($this->nativeExpressionTypes as $exprString => $nativeTypeHolder) { - $key = sprintf('native %s', $exprString); + $key = sprintf('native %s (%s)', $exprString, $nativeTypeHolder->getCertainty()->describe()); $descriptions[$key] = $nativeTypeHolder->getType()->describe(VerbosityLevel::precise()); } diff --git a/src/Rules/Debug/DebugScopeRule.php b/src/Rules/Debug/DebugScopeRule.php new file mode 100644 index 00000000000..7f6a43930a8 --- /dev/null +++ b/src/Rules/Debug/DebugScopeRule.php @@ -0,0 +1,66 @@ + + */ +final class DebugScopeRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ($functionName === null) { + return []; + } + + if (strtolower($functionName) !== 'phpstan\debugscope') { + return []; + } + + if (!$scope instanceof MutatingScope) { + return []; + } + + $parts = []; + foreach ($scope->debug() as $key => $row) { + $parts[] = sprintf('%s: %s', $key, $row); + } + + if (count($parts) === 0) { + $parts[] = 'Scope is empty'; + } + + return [ + RuleErrorBuilder::message( + implode("\n", $parts), + )->nonIgnorable()->identifier('phpstan.debugScope')->build(), + ]; + } + +} diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 2df15b78f02..3ecbecaa7f8 100644 --- a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -29,6 +29,7 @@ final class CallToFunctionStatementWithoutSideEffectsRule implements Rule public const PHPSTAN_TESTING_FUNCTIONS = [ 'PHPStan\\dumpType', + 'PHPStan\\debugScope', 'PHPStan\\Testing\\assertType', 'PHPStan\\Testing\\assertNativeType', 'PHPStan\\Testing\\assertVariableCertainty', diff --git a/src/debugScope.php b/src/debugScope.php new file mode 100644 index 00000000000..6f331c97bab --- /dev/null +++ b/src/debugScope.php @@ -0,0 +1,14 @@ + + */ +class DebugScopeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DebugScopeRule($this->createReflectionProvider()); + } + + public function testRuleInPhpStanNamespace(): void + { + $this->analyse([__DIR__ . '/data/debug-scope.php'], [ + [ + 'Scope is empty', + 7, + ], + [ + implode("\n", [ + '$a (Yes): int', + '$b (Yes): int', + '$debug (Yes): bool', + 'native $a (Yes): int', + 'native $b (Yes): int', + 'native $debug (Yes): bool', + ]), + 10, + ], + [ + implode("\n", [ + '$a (Yes): int', + '$b (Yes): int', + '$debug (Yes): bool', + '$c (Maybe): 1', + 'native $a (Yes): int', + 'native $b (Yes): int', + 'native $debug (Yes): bool', + 'native $c (Maybe): 1', + 'condition about $c #1: if $debug=false then $c is *ERROR* (No)', + 'condition about $c #2: if $debug=true then $c is 1 (Yes)', + ]), + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Debug/data/debug-scope.php b/tests/PHPStan/Rules/Debug/data/debug-scope.php new file mode 100644 index 00000000000..0e7b8663aa1 --- /dev/null +++ b/tests/PHPStan/Rules/Debug/data/debug-scope.php @@ -0,0 +1,17 @@ + Date: Tue, 3 Sep 2024 21:06:58 +0200 Subject: [PATCH 0110/3097] Fix build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6670f1cea21..3282ee2c4e7 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,7 @@ lint: --exclude tests/PHPStan/Rules/Keywords/data/declare-inline-html.php \ --exclude tests/PHPStan/Rules/Classes/data/extends-readonly-class.php \ --exclude tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php \ + --exclude tests/PHPStan/Rules/Classes/data/bug-11592.php \ src tests cs: From 5892e8debfbe2f44306e6707c457665784b7dacd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 21:08:59 +0200 Subject: [PATCH 0111/3097] Fix how well conditional types play with pre-existing `@param-out` variable after assignment --- src/Analyser/NodeScopeResolver.php | 3 +- tests/PHPStan/Analyser/nsrt/bug-11580.php | 35 +++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-7805.php | 4 +-- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11580.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a885d648b00..b22d651f993 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4953,6 +4953,7 @@ private function processAssignVar( } } + $scope = $result->getScope(); $truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); $falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); @@ -4965,7 +4966,7 @@ private function processAssignVar( $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); $nodeCallback(new VariableAssignNode($var, $assignedExpr, $isAssignOp), $result->getScope()); - $scope = $result->getScope()->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr)); + $scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr)); foreach ($conditionalExpressions as $exprString => $holders) { $scope = $scope->addConditionalExpressions($exprString, $holders); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11580.php b/tests/PHPStan/Analyser/nsrt/bug-11580.php new file mode 100644 index 00000000000..ebb4220372c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11580.php @@ -0,0 +1,35 @@ +", $params); $params = $params === [] ? ['list'] : $params; assertType("array{'list'}", $params); - assertNativeType("non-empty-array", $params); + assertNativeType("array{'list'}", $params); array_unshift($params, 'help'); assertType("array{'help', 'list'}", $params); - assertNativeType("non-empty-array", $params); + assertNativeType("array{'help', 'list'}", $params); } assertType("array{}|array{'help', 'list'}", $params); assertNativeType('array', $params); From 327789e17116cf88e9a1a98d60c5dd40b502ba2b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 3 Sep 2024 23:52:19 +0200 Subject: [PATCH 0112/3097] Add non regression test --- ...berComparisonOperatorsConstantConditionRuleTest.php | 6 ++++++ tests/PHPStan/Rules/Comparison/data/bug-6642.php | 10 ++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-6642.php diff --git a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php index ae47818512e..4d89a240be1 100644 --- a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php @@ -231,4 +231,10 @@ public function testBug6467(): void $this->analyse([__DIR__ . '/data/bug-6467.php'], []); } + public function testBug6642(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-6642.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-6642.php b/tests/PHPStan/Rules/Comparison/data/bug-6642.php new file mode 100644 index 00000000000..b8ae395d177 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-6642.php @@ -0,0 +1,10 @@ + Date: Wed, 20 Dec 2023 15:57:07 +0100 Subject: [PATCH 0113/3097] Open 2.0.x-dev --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/build-issue-bot.yml | 2 +- .github/workflows/changelog-generator.yml | 2 +- .github/workflows/checksum-phar.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/phar.yml | 26 +++++++++++++++++-- .../workflows/pr-base-on-previous-branch.yml | 2 +- .github/workflows/reflection-golden-test.yml | 2 +- .github/workflows/spelling.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 12 files changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 0233e1e422a..d7651c92022 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.12.x" + - "2.0.x" paths: - 'src/**' - '.github/workflows/backward-compatibility.yml' diff --git a/.github/workflows/build-issue-bot.yml b/.github/workflows/build-issue-bot.yml index 278470b4660..0e541ca5b1f 100644 --- a/.github/workflows/build-issue-bot.yml +++ b/.github/workflows/build-issue-bot.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/build-issue-bot.yml' push: branches: - - "1.12.x" + - "2.0.x" paths: - 'issue-bot/**' - '.github/workflows/build-issue-bot.yml' diff --git a/.github/workflows/changelog-generator.yml b/.github/workflows/changelog-generator.yml index 21971571f3d..bda67d4725d 100644 --- a/.github/workflows/changelog-generator.yml +++ b/.github/workflows/changelog-generator.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/changelog-generator.yml' push: branches: - - "1.12.x" + - "2.0.x" paths: - 'changelog-generator/**' - '.github/workflows/changelog-generator.yml' diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index 47256373d0c..994f11ba063 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -12,7 +12,7 @@ on: - '.github/workflows/checksum-phar.yml' push: branches: - - "1.12.x" + - "2.0.x" paths: - 'compiler/**' - '.github/workflows/checksum-phar.yml' diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 53fded3322c..239a5e37812 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.12.x" + - "2.0.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 03420615cde..105fff75887 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.12.x" + - "2.0.x" env: COMPOSER_ROOT_VERSION: "1.12.x-dev" diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index c6c4934b44b..723e8a93be8 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -6,9 +6,9 @@ on: pull_request: push: branches: - - "1.12.x" + - "2.0.x" tags: - - '1.12.*' + - '2.0.*' concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests @@ -107,25 +107,43 @@ jobs: integration-tests: if: github.event_name == 'pull_request' needs: compiler-tests +<<<<<<< HEAD uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.12.x with: ref: 1.12.x +======= + uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x + with: + ref: 2.0.x +>>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' needs: compiler-tests +<<<<<<< HEAD uses: phpstan/phpstan/.github/workflows/extension-tests.yml@1.12.x with: ref: 1.12.x +======= + uses: phpstan/phpstan/.github/workflows/extension-tests.yml@2.0.x + with: + ref: 2.0.x +>>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} other-tests: if: github.event_name == 'pull_request' needs: compiler-tests +<<<<<<< HEAD uses: phpstan/phpstan/.github/workflows/other-tests.yml@1.12.x with: ref: 1.12.x +======= + uses: phpstan/phpstan/.github/workflows/other-tests.yml@2.0.x + with: + ref: 2.0.x +>>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} commit: @@ -152,7 +170,11 @@ jobs: repository: phpstan/phpstan path: phpstan-dist token: ${{ secrets.PHPSTAN_BOT_TOKEN }} +<<<<<<< HEAD ref: 1.12.x +======= + ref: 2.0.x +>>>>>>> 264ce7a58b (Open 2.0.x-dev) - name: "Get previous pushed dist commit" id: previous-commit diff --git a/.github/workflows/pr-base-on-previous-branch.yml b/.github/workflows/pr-base-on-previous-branch.yml index 85b8974449b..f522ea446e0 100644 --- a/.github/workflows/pr-base-on-previous-branch.yml +++ b/.github/workflows/pr-base-on-previous-branch.yml @@ -19,6 +19,6 @@ jobs: - name: Comment PR uses: peter-evans/create-or-update-comment@v4 with: - body: "You've opened the pull request against the latest branch 2.0.x. If your code is relevant on 1.12.x and you want it to be released sooner, please rebase your pull request and change its target to 1.12.x." + body: "You've opened the pull request against the latest branch 2.0.x. PHPStan 2.0 is not going to be released for months. If your code is relevant on 1.12.x and you want it to be released sooner, please rebase your pull request and change its target to 1.12.x." token: ${{ secrets.PHPSTAN_BOT_TOKEN }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 6d16f21aaa0..d996728a384 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.12.x" + - "2.0.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index d91ab1ff1f3..4304d27005d 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.12.x" + - "2.0.x" jobs: typos: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b19b6c10f7f..fb498cd9bce 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -9,7 +9,7 @@ on: - 'apigen/**' push: branches: - - "1.12.x" + - "2.0.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 59d834e9c70..0080b3d697a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "1.12.x" + - "2.0.x" paths-ignore: - 'compiler/**' - 'apigen/**' From 4d7f976728e6f4e012d0396a8d090a39b3f69a96 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:01:14 +0100 Subject: [PATCH 0114/3097] Drop support for PHP 7.2 and 7.3 --- .github/workflows/lint.yml | 2 - .github/workflows/reflection-golden-test.yml | 1 - .github/workflows/static-analysis.yml | 20 +++------ .github/workflows/tests.yml | 44 -------------------- 4 files changed, 6 insertions(+), 61 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 105fff75887..bdc15d968a1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,8 +25,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.2" - - "7.3" - "7.4" - "8.0" - "8.1" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index d996728a384..2bf67cb14f0 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -65,7 +65,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.3" - "7.4" - "8.0" - "8.1" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index fb498cd9bce..24760de7563 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -31,8 +31,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.2" - - "7.3" - "7.4" - "8.0" - "8.1" @@ -61,18 +59,12 @@ jobs: shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - - name: "Paratest patch" - if: matrix.php-version == '7.2' - run: composer config extra.patches.brianium/paratest --json --merge '["patches/paratest.patch"]' - shell: bash - - - name: "Downgrade PHPUnit" - if: matrix.php-version == '7.2' - run: "composer require --dev phpunit/phpunit:^8.5.31 brianium/paratest:^4.0 composer/semver:^1.2 --update-with-dependencies --ignore-platform-reqs" - - - name: "Update PHPUnit" - if: matrix.php-version != '7.2' && matrix.php-version != '7.3' - run: "composer update phpunit/phpunit -W" + - name: "Upload transformed sources" + if: matrix.php-version == '7.4' + uses: actions/upload-artifact@v3 + with: + name: transformed-src + path: src - name: "PHPStan" run: "make phpstan" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0080b3d697a..75f5de1627f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,6 @@ jobs: fail-fast: false matrix: php-version: - - "7.3" - "7.4" - "8.0" - "8.1" @@ -162,46 +161,3 @@ jobs: - name: "Tests" run: "${{ matrix.script }}" - - tests-old-phpunit: - name: "Tests with old PHPUnit" - runs-on: ${{ matrix.operating-system }} - timeout-minutes: 60 - - strategy: - fail-fast: false - matrix: - php-version: - - "7.2" - operating-system: [ ubuntu-latest ] - - steps: - - name: "Checkout" - uses: actions/checkout@v4 - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "${{ matrix.php-version }}" - tools: pecl - extensions: ds,mbstring - ini-file: development - ini-values: memory_limit=2G - - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - - name: "Transform source code" - shell: bash - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - - - name: "Paratest patch" - run: composer config extra.patches.brianium/paratest --json --merge '["patches/paratest.patch"]' - shell: bash - - - name: "Downgrade PHPUnit" - run: "composer require --dev phpunit/phpunit:^8.5.31 brianium/paratest:^4.0 composer/semver:^1.2 --update-with-dependencies --ignore-platform-reqs" - - - name: "Tests" - run: "make tests-coverage" From 5c68a148c88ef4df22ec825d76b23a3931f0cabf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 22 Dec 2023 08:42:44 +0100 Subject: [PATCH 0115/3097] Update nikic/php-parser to v5 --- composer.json | 7 ++-- composer.lock | 112 +++++++++++++++++++++++++------------------------- 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/composer.json b/composer.json index f6cb9c01dbe..7e859b19e4f 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,9 @@ "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", - "nikic/php-parser": "^4.17.1", + "nikic/php-parser": "^5.1.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.17", + "ondrejmirtes/better-reflection": "6.42.0.3", "phpstan/php-8-stubs": "0.3.101", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", @@ -55,8 +55,7 @@ "require-dev": { "brianium/paratest": "^6.5", "cweagans/composer-patches": "^1.7.3", - "nette/finder": "^2.5", - "ondrejmirtes/simple-downgrader": "^1.0", + "ondrejmirtes/simple-downgrader": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2.0", "phpstan/phpstan-deprecation-rules": "^1.2", "phpstan/phpstan-nette": "^1.0", diff --git a/composer.lock b/composer.lock index 71beaf624c0..aba98b14756 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f5af1898ab9d95520d1511334b2000c0", + "content-hash": "6ecf16b4614aa87f10e85e795af26169", "packages": [ { "name": "clue/ndjson-react", @@ -1474,7 +1474,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-07-24T19:11:43+00:00" + "time": "2024-09-01T14:35:14+00:00" }, { "name": "nette/bootstrap", @@ -1963,25 +1963,26 @@ }, { "name": "nette/utils", - "version": "v3.2.7", + "version": "v3.2.10", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", - "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", + "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", "shasum": "" }, "require": { - "php": ">=7.2 <8.2" + "php": ">=7.2 <8.4" }, "conflict": { "nette/di": "<3.0.6" }, "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", "nette/tester": "~2.0", "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.3" @@ -2042,31 +2043,33 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.7" + "source": "https://github.com/nette/utils/tree/v3.2.10" }, - "time": "2022-01-24T11:29:14+00:00" + "time": "2023-07-30T15:38:18+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -2074,7 +2077,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2098,9 +2101,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "ondram/ci-detector", @@ -2176,23 +2179,23 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.17", + "version": "6.42.0.3", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2" + "reference": "bdb626a5e2fb52bfe3fec1d367a9c72e48550954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", - "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/bdb626a5e2fb52bfe3fec1d367a9c72e48550954", + "reference": "bdb626a5e2fb52bfe3fec1d367a9c72e48550954", "shasum": "" }, "require": { "ext-json": "*", "jetbrains/phpstorm-stubs": "dev-master#217ed9356d07ef89109d3cd7d8c5df10aab4b0d4", - "nikic/php-parser": "^4.18.0", - "php": "^7.2 || ^8.0" + "nikic/php-parser": "^5.1.0", + "php": "^7.4 || ^8.0" }, "conflict": { "thecodingmachine/safe": "<1.1.3" @@ -2201,9 +2204,8 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^10.5.12", - "rector/rector": "0.14.3", - "vimeo/psalm": "5.23.0" + "phpunit/phpunit": "^11.3.2", + "rector/rector": "0.14.3" }, "suggest": { "composer/composer": "Required to use the ComposerSourceLocator" @@ -2242,9 +2244,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.17" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.3" }, - "time": "2024-08-26T20:47:13+00:00" + "time": "2024-09-04T11:06:34+00:00" }, { "name": "phpstan/php-8-stubs", @@ -3168,16 +3170,16 @@ }, { "name": "symfony/console", - "version": "v5.4.41", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba" + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba", + "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", + "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", "shasum": "" }, "require": { @@ -3247,7 +3249,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.41" + "source": "https://github.com/symfony/console/tree/v5.4.43" }, "funding": [ { @@ -3263,7 +3265,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T07:48:55+00:00" + "time": "2024-08-13T16:31:56+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3334,16 +3336,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.40", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" + "reference": "ae25a9145a900764158d439653d5630191155ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", + "reference": "ae25a9145a900764158d439653d5630191155ca0", "shasum": "" }, "require": { @@ -3377,7 +3379,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.40" + "source": "https://github.com/symfony/finder/tree/v5.4.43" }, "funding": [ { @@ -3393,7 +3395,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-08-13T14:03:51+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4169,16 +4171,16 @@ }, { "name": "symfony/string", - "version": "v5.4.41", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096" + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/065a9611e0b1fd2197a867e1fb7f2238191b7096", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096", + "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", "shasum": "" }, "require": { @@ -4235,7 +4237,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.41" + "source": "https://github.com/symfony/string/tree/v5.4.43" }, "funding": [ { @@ -4251,7 +4253,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:20:55+00:00" + "time": "2024-08-01T10:24:28+00:00" } ], "packages-dev": [ @@ -4586,22 +4588,22 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "1.0.2", + "version": "2.x-dev", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "832aaae53dcfe358f63180494de8734244773d46" + "reference": "dbbf56fab0bc71310ff3766ea204d84f019e99b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/832aaae53dcfe358f63180494de8734244773d46", - "reference": "832aaae53dcfe358f63180494de8734244773d46", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/dbbf56fab0bc71310ff3766ea204d84f019e99b7", + "reference": "dbbf56fab0bc71310ff3766ea204d84f019e99b7", "shasum": "" }, "require": { "nette/utils": "^3.2.5", - "nikic/php-parser": "^4.18", - "php": "^7.2|^8.0", + "nikic/php-parser": "^5.0", + "php": "^7.4|^8.0", "phpstan/phpdoc-parser": "^1.24.5", "symfony/console": "^5.4", "symfony/finder": "^5.4" @@ -4629,9 +4631,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/1.0.2" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.x" }, - "time": "2024-02-12T19:22:32+00:00" + "time": "2024-02-12T19:24:54+00:00" }, { "name": "phar-io/manifest", From 31742d125d2cae20a869429730b878e0bc9dfabf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:10:06 +0100 Subject: [PATCH 0116/3097] Compatibility with PHP-Parser v5 --- conf/config.neon | 4 ++-- src/Parser/LexerFactory.php | 11 +++-------- src/Parser/PhpParserDecorator.php | 5 +++++ src/Parser/RichParser.php | 25 ++++++++++--------------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 255be78dc29..dac1e96e5f0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2045,7 +2045,7 @@ services: autowired: false currentPhpVersionPhpParser: - class: PhpParser\Parser\Php7 + class: PhpParser\Parser\Php8 # todo use factory and create Php7/Php8 arguments: lexer: @currentPhpVersionLexer autowired: false @@ -2161,7 +2161,7 @@ services: autowired: false php8PhpParser: - class: PhpParser\Parser\Php7 + class: PhpParser\Parser\Php8 arguments: lexer: @php8Lexer autowired: false diff --git a/src/Parser/LexerFactory.php b/src/Parser/LexerFactory.php index 5fa801ddec3..e02bc5ed2c4 100644 --- a/src/Parser/LexerFactory.php +++ b/src/Parser/LexerFactory.php @@ -9,27 +9,22 @@ final class LexerFactory { - private const OPTIONS = ['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos']]; - public function __construct(private PhpVersion $phpVersion) { } public function create(): Lexer { - $options = self::OPTIONS; if ($this->phpVersion->getVersionId() === PHP_VERSION_ID) { - return new Lexer($options); + return new Lexer(); } - $options['phpVersion'] = $this->phpVersion->getVersionString(); - - return new Lexer\Emulative($options); + return new Lexer\Emulative(\PhpParser\PhpVersion::fromString($this->phpVersion->getVersionString())); } public function createEmulative(): Lexer\Emulative { - return new Lexer\Emulative(self::OPTIONS); + return new Lexer\Emulative(); } } diff --git a/src/Parser/PhpParserDecorator.php b/src/Parser/PhpParserDecorator.php index 14c462c39fc..d4e00547a0d 100644 --- a/src/Parser/PhpParserDecorator.php +++ b/src/Parser/PhpParserDecorator.php @@ -31,4 +31,9 @@ public function parse(string $code, ?ErrorHandler $errorHandler = null): array } } + public function getTokens(): array + { + return $this->wrappedParser->getTokens(); + } + } diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index b5863a4b800..e3f2a5e3104 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use PhpParser\Token; use PHPStan\Analyser\Ignore\IgnoreLexer; use PHPStan\Analyser\Ignore\IgnoreParseException; use PHPStan\DependencyInjection\Container; @@ -17,7 +18,6 @@ use function count; use function implode; use function in_array; -use function is_string; use function preg_match_all; use function sprintf; use function str_contains; @@ -72,8 +72,7 @@ public function parseString(string $sourceCode): array $errorHandler = new Collecting(); $nodes = $this->parser->parse($sourceCode, $errorHandler); - /** @var list $tokens */ - $tokens = $this->lexer->getTokens(); + $tokens = $this->parser->getTokens(); if ($errorHandler->hasErrors()) { throw new ParserErrorsException($errorHandler->getErrors(), null); } @@ -109,7 +108,7 @@ public function parseString(string $sourceCode): array } /** - * @param list $tokens + * @param Token[] $tokens * @return array{lines: array|null>, errors: array>} */ private function getLinesToIgnore(array $tokens): array @@ -119,12 +118,8 @@ private function getLinesToIgnore(array $tokens): array $pendingToken = null; $errors = []; foreach ($tokens as $token) { - if (is_string($token)) { - continue; - } - - $type = $token[0]; - $line = $token[2]; + $type = $token->id; + $line = $token->line; if ($type !== T_COMMENT && $type !== T_DOC_COMMENT) { if ($type !== T_WHITESPACE) { if ($pendingToken !== null) { @@ -155,7 +150,7 @@ private function getLinesToIgnore(array $tokens): array continue; } - $text = $token[1]; + $text = $token->text; $isNextLine = str_contains($text, '@phpstan-ignore-next-line'); $isCurrentLine = str_contains($text, '@phpstan-ignore-line'); @@ -204,20 +199,20 @@ private function getLinesToIgnore(array $tokens): array $ignoreLine = substr_count(substr($text, 0, $ignorePos), "\n") - 1; - if ($previousToken !== null && $previousToken[2] === $line) { + if ($previousToken !== null && $previousToken->line === $line) { try { foreach ($this->parseIdentifiers($text, $ignorePos) as $identifier) { $lines[$line][] = $identifier; } } catch (IgnoreParseException $e) { - $errors[] = [$token[2] + $e->getPhpDocLine() + $ignoreLine, $e->getMessage()]; + $errors[] = [$token->line + $e->getPhpDocLine() + $ignoreLine, $e->getMessage()]; } continue; } - $line += substr_count($token[1], "\n"); - $pendingToken = [$text, $ignorePos, $token[2] + $ignoreLine, $line]; + $line += substr_count($token->text, "\n"); + $pendingToken = [$text, $ignorePos, $token->line + $ignoreLine, $line]; } if ($pendingToken !== null) { From 038f0ca7d5910e510e4ec2135e7bfd753700d46d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:19:24 +0100 Subject: [PATCH 0117/3097] PHP-Parser 5 - compiler --- compiler/src/Console/PrepareCommand.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/compiler/src/Console/PrepareCommand.php b/compiler/src/Console/PrepareCommand.php index f7f2b6cdf67..7bef8b99db3 100644 --- a/compiler/src/Console/PrepareCommand.php +++ b/compiler/src/Console/PrepareCommand.php @@ -16,6 +16,7 @@ use function file_get_contents; use function file_put_contents; use function implode; +use function in_array; use function is_dir; use function json_decode; use function json_encode; @@ -184,6 +185,20 @@ private function buildPreloadScript(): void if ($realPath === false) { return; } + if (in_array($realPath, [ + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php', + $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php', + ], true)) { + continue; + } $path = substr($realPath, strlen($root)); $output .= 'require_once __DIR__ . ' . var_export($path, true) . ';' . "\n"; } From e7e3dfdb8451918138007dacf61264bcc25fde11 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 22 Dec 2023 13:39:40 +0100 Subject: [PATCH 0118/3097] Scope changes --- src/Analyser/MutatingScope.php | 19 +++++++-------- src/Analyser/NodeScopeResolver.php | 38 +++++++++++++----------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cb4dd6b858c..3a251908c99 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -21,10 +21,10 @@ use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\InterpolatedStringPart; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\DNumber; -use PhpParser\Node\Scalar\EncapsedStringPart; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; use PhpParser\NodeFinder; @@ -1129,16 +1129,16 @@ private function resolveType(string $exprString, Expr $node): Type return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); } elseif ($node instanceof String_) { return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof Node\Scalar\Encapsed) { + } elseif ($node instanceof Node\Scalar\InterpolatedString) { $resultType = null; - foreach ($node->parts as $part) { - $partType = $part instanceof EncapsedStringPart - ? new ConstantStringType($part->value) - : $this->getType($part)->toString(); + if ($part instanceof InterpolatedStringPart) { + $partType = new ConstantStringType($part->value); + } else { + $partType = $this->getType($part); + } if ($resultType === null) { $resultType = $partType; - continue; } @@ -3455,9 +3455,6 @@ private function enterAnonymousFunctionWithoutReflection( continue; } foreach ($variables as $variable) { - if (!$variable instanceof Variable) { - continue 2; - } if (!is_string($variable->name)) { continue 2; } @@ -4785,7 +4782,7 @@ private function processFinallyScopeVariableTypeHolders( } /** - * @param Expr\ClosureUse[] $byRefUses + * @param Node\ClosureUse[] $byRefUses */ public function processClosureScope( self $closureScope, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b22d651f993..e7b3a0ababc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6,13 +6,13 @@ use Closure; use DivisionByZeroError; use PhpParser\Comment\Doc; +use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\AttributeGroup; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; -use PhpParser\Node\Expr\ArrayItem; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignRef; use PhpParser\Node\Expr\BinaryOp; @@ -48,7 +48,6 @@ use PhpParser\Node\Stmt\Return_; use PhpParser\Node\Stmt\Static_; use PhpParser\Node\Stmt\Switch_; -use PhpParser\Node\Stmt\Throw_; use PhpParser\Node\Stmt\TryCatch; use PhpParser\Node\Stmt\Unset_; use PhpParser\Node\Stmt\While_; @@ -474,7 +473,10 @@ private function processStmtNode( } $stmtScope = $scope; - if ($stmt instanceof Throw_ || $stmt instanceof Return_) { + if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Expr\Throw_) { + $stmtScope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr->expr, $nodeCallback); + } + if ($stmt instanceof Return_) { $stmtScope = $this->processStmtVarAnnotation($scope, $stmt, $stmt->expr, $nodeCallback); } @@ -922,14 +924,6 @@ private function processStmtNode( if ($stmt->type !== null) { $nodeCallback($stmt->type, $scope); } - } elseif ($stmt instanceof Throw_) { - $result = $this->processExprNode($stmt, $stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); - $throwPoints = $result->getThrowPoints(); - $throwPoints[] = ThrowPoint::createExplicit($result->getScope(), $scope->getType($stmt->expr), $stmt, false); - $impurePoints = $result->getImpurePoints(); - return new StatementResult($result->getScope(), $result->hasYield(), true, [ - new StatementExitPoint($stmt, $scope), - ], $throwPoints, $impurePoints); } elseif ($stmt instanceof If_) { $conditionType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($stmt->cond) : $scope->getNativeType($stmt->cond))->toBoolean(); $ifAlwaysTrue = $conditionType->isTrue()->yes(); @@ -1506,7 +1500,7 @@ private function processStmtNode( } foreach ($branchScopeResult->getExitPoints() as $exitPoint) { $finallyExitPoints[] = $exitPoint; - if ($exitPoint->getStatement() instanceof Throw_) { + if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { continue; } if ($finallyScope !== null) { @@ -1668,7 +1662,7 @@ private function processStmtNode( } foreach ($catchScopeResult->getExitPoints() as $exitPoint) { $finallyExitPoints[] = $exitPoint; - if ($exitPoint->getStatement() instanceof Throw_) { + if ($exitPoint->getStatement() instanceof Node\Stmt\Expression && $exitPoint->getStatement()->expr instanceof Expr\Throw_) { continue; } if ($finallyScope !== null) { @@ -2037,7 +2031,7 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo $scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); } elseif ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) { $scope = $this->lookForExpressionCallback($scope, $expr->class, $callback); - } elseif ($expr instanceof Array_ || $expr instanceof List_) { + } elseif ($expr instanceof List_) { foreach ($expr->items as $item) { if ($item === null) { continue; @@ -2942,11 +2936,14 @@ static function (): void { $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); $scope = $result->getScope(); } - } elseif ($expr instanceof Node\Scalar\Encapsed) { + } elseif ($expr instanceof Node\Scalar\InterpolatedString) { $hasYield = false; $throwPoints = []; $impurePoints = []; foreach ($expr->parts as $part) { + if (!$part instanceof Expr) { + continue; + } $result = $this->processExprNode($stmt, $part, $scope, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -3630,7 +3627,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { } else { $items = []; foreach ($filteringExprs as $filteringExpr) { - $items[] = new ArrayItem($filteringExpr); + $items[] = new Node\ArrayItem($filteringExpr); } $filteringExpr = new FuncCall( new Name\FullyQualified('in_array'), @@ -4113,7 +4110,7 @@ private function getAssignedVariables(Expr $expr): array return []; } - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + if ($expr instanceof Expr\List_) { $names = []; foreach ($expr->items as $item) { if ($item === null) { @@ -5271,7 +5268,7 @@ static function (): void { $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } - } elseif ($var instanceof List_ || $var instanceof Array_) { + } elseif ($var instanceof List_) { $result = $processExprCallback($scope); $hasYield = $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -5778,7 +5775,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection $methodAst = clone $stmt; $stmts[$i] = $methodAst; if (array_key_exists($methodName, $methodModifiers)) { - $methodAst->flags = ($methodAst->flags & ~ Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK) | $methodModifiers[$methodName]; + $methodAst->flags = ($methodAst->flags & ~ Modifiers::VISIBILITY_MASK) | $methodModifiers[$methodName]; } if (!array_key_exists($methodName, $methodNames)) { @@ -5867,9 +5864,6 @@ private function processCalledMethod(MethodReflection $methodReflection): ?Mutat foreach ($returnStatement->getExecutionEnds() as $executionEnd) { $statementResult = $executionEnd->getStatementResult(); $endNode = $executionEnd->getNode(); - if ($endNode instanceof Node\Stmt\Throw_) { - continue; - } if ($endNode instanceof Node\Stmt\Expression) { $exprType = $statementResult->getScope()->getType($endNode->expr); if ($exprType instanceof NeverType && $exprType->isExplicit()) { From eb0162f72e48b7e914ffc260e88f7a041dea5dfe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:20:31 +0100 Subject: [PATCH 0119/3097] Rest of src/ changes --- phpstan-baseline.neon | 16 ++++++++++++++++ src/Dependency/DependencyResolver.php | 4 ---- src/Node/ClassPropertiesNode.php | 3 --- src/Node/ClassPropertyNode.php | 14 +++++++------- src/Node/ClassStatementsGatherer.php | 3 --- src/Node/LiteralArrayItem.php | 2 +- src/Parser/LastConditionVisitor.php | 6 +++++- src/Reflection/InitializerExprTypeResolver.php | 4 ---- src/Rules/Arrays/ArrayDestructuringRule.php | 7 +++---- src/Rules/Arrays/ArrayUnpackingRule.php | 2 +- src/Rules/Arrays/EmptyArrayItemRule.php | 1 + src/Rules/Arrays/InvalidKeyInArrayItemRule.php | 4 ++-- .../Cast/InvalidPartOfEncapsedStringRule.php | 6 +++--- src/Rules/Functions/UnusedClosureUsesRule.php | 2 +- src/Rules/NullsafeCheck.php | 2 +- src/Rules/Operators/InvalidAssignVarRule.php | 2 +- .../PhpDoc/WrongVariableNameInVarTagRule.php | 2 +- src/Rules/UnusedFunctionParametersCheck.php | 2 +- src/Type/FileTypeMapper.php | 1 - ...IsCallableFunctionTypeSpecifyingExtension.php | 4 ---- 20 files changed, 44 insertions(+), 43 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 77ff81fd1ad..cdbd956fa21 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1835,3 +1835,19 @@ parameters: message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" count: 1 path: tests/PHPStan/Type/IterableTypeTest.php + + - + message: """ + #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRule\\: + Since PHP\\-Parser 5\\.0 this is a parse error\\.$# + """ + count: 1 + path: tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php + + - + message: """ + #^Return type of method PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRule\\: + Since PHP\\-Parser 5\\.0 this is a parse error\\.$# + """ + count: 1 + path: tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 57a80a5a2e9..2c7d3f581a7 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -479,10 +479,6 @@ private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): return false; } - if ($items[0] === null) { - return false; - } - $itemType = $scope->getType($items[0]->value); return $itemType->isClassStringType()->yes(); } diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 7afc4bc874b..0707a5bc6d1 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -271,9 +271,6 @@ private function collectUninitializedProperties(array $constructors, array $unin $statementResult = $executionEnd->getStatementResult(); $endNode = $executionEnd->getNode(); if ($statementResult->isAlwaysTerminating()) { - if ($endNode instanceof Node\Stmt\Throw_) { - continue; - } if ($endNode instanceof Node\Stmt\Expression) { $exprType = $statementResult->getScope()->getType($endNode->expr); if ($exprType instanceof NeverType && $exprType->isExplicit()) { diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index d8aaea62185..f0ad86ff8cd 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -2,11 +2,11 @@ namespace PHPStan\Node; +use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; use PhpParser\Node\Name; -use PhpParser\Node\Stmt\Class_; use PhpParser\NodeAbstract; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Type; @@ -75,28 +75,28 @@ public function getPhpDocType(): ?Type public function isPublic(): bool { - return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 - || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0; + return ($this->flags & Modifiers::PUBLIC) !== 0 + || ($this->flags & Modifiers::VISIBILITY_MASK) === 0; } public function isProtected(): bool { - return (bool) ($this->flags & Class_::MODIFIER_PROTECTED); + return (bool) ($this->flags & Modifiers::PROTECTED); } public function isPrivate(): bool { - return (bool) ($this->flags & Class_::MODIFIER_PRIVATE); + return (bool) ($this->flags & Modifiers::PRIVATE); } public function isStatic(): bool { - return (bool) ($this->flags & Class_::MODIFIER_STATIC); + return (bool) ($this->flags & Modifiers::STATIC); } public function isReadOnly(): bool { - return (bool) ($this->flags & Class_::MODIFIER_READONLY) || $this->isReadonlyClass; + return (bool) ($this->flags & Modifiers::READONLY) || $this->isReadonlyClass; } public function isReadOnlyByPhpDoc(): bool diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 55ef9077995..7fb27f83519 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -221,9 +221,6 @@ private function gatherNodes(Node $node, Scope $scope): void $this->propertyUsages[] = new PropertyWrite($node->expr, $scope, false); return; } - if ($node instanceof Node\Scalar\EncapsedStringPart) { - return; - } if ($node instanceof FunctionCallableNode) { $node = $node->getOriginalNode(); } elseif ($node instanceof InstantiationCallableNode) { diff --git a/src/Node/LiteralArrayItem.php b/src/Node/LiteralArrayItem.php index ea9be27be6c..1ba0c04ef59 100644 --- a/src/Node/LiteralArrayItem.php +++ b/src/Node/LiteralArrayItem.php @@ -2,7 +2,7 @@ namespace PHPStan\Node; -use PhpParser\Node\Expr\ArrayItem; +use PhpParser\Node\ArrayItem; use PHPStan\Analyser\Scope; /** diff --git a/src/Parser/LastConditionVisitor.php b/src/Parser/LastConditionVisitor.php index ce21f571fd4..d4c4b53ac30 100644 --- a/src/Parser/LastConditionVisitor.php +++ b/src/Parser/LastConditionVisitor.php @@ -18,7 +18,11 @@ public function enterNode(Node $node): ?Node $lastElseIf = count($node->elseifs) - 1; $elseIsMissingOrThrowing = $node->else === null - || (count($node->else->stmts) === 1 && $node->else->stmts[0] instanceof Node\Stmt\Throw_); + || ( + count($node->else->stmts) === 1 + && $node->else->stmts[0] instanceof Node\Stmt\Expression + && $node->else->stmts[0]->expr instanceof Node\Expr\Throw_ + ); foreach ($node->elseifs as $i => $elseif) { $isLast = $i === $lastElseIf && $elseIsMissingOrThrowing; diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index dae4e111407..2f1aaa96a01 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -525,10 +525,6 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); $isList = null; foreach ($expr->items as $arrayItem) { - if ($arrayItem === null) { - continue; - } - $valueType = $getTypeCallback($arrayItem->value); if ($arrayItem->unpack) { $constantArrays = $valueType->getConstantArrays(); diff --git a/src/Rules/Arrays/ArrayDestructuringRule.php b/src/Rules/Arrays/ArrayDestructuringRule.php index ab83f1d0197..d8281b1d886 100644 --- a/src/Rules/Arrays/ArrayDestructuringRule.php +++ b/src/Rules/Arrays/ArrayDestructuringRule.php @@ -40,7 +40,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->var instanceof Node\Expr\List_ && !$node->var instanceof Node\Expr\Array_) { + if (!$node->var instanceof Node\Expr\List_) { return []; } @@ -52,10 +52,9 @@ public function processNode(Node $node, Scope $scope): array } /** - * @param Node\Expr\List_|Node\Expr\Array_ $var * @return list */ - private function getErrors(Scope $scope, Expr $var, Expr $expr): array + private function getErrors(Scope $scope, Node\Expr\List_ $var, Expr $expr): array { $exprTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, @@ -100,7 +99,7 @@ private function getErrors(Scope $scope, Expr $var, Expr $expr): array ); $errors = array_merge($errors, $itemErrors); - if (!$item->value instanceof Node\Expr\List_ && !$item->value instanceof Node\Expr\Array_) { + if (!$item->value instanceof Node\Expr\List_) { $i++; continue; } diff --git a/src/Rules/Arrays/ArrayUnpackingRule.php b/src/Rules/Arrays/ArrayUnpackingRule.php index 4be69c0ac05..f1573bec6d4 100644 --- a/src/Rules/Arrays/ArrayUnpackingRule.php +++ b/src/Rules/Arrays/ArrayUnpackingRule.php @@ -3,7 +3,7 @@ namespace PHPStan\Rules\Arrays; use PhpParser\Node; -use PhpParser\Node\Expr\ArrayItem; +use PhpParser\Node\ArrayItem; use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Php\PhpVersion; diff --git a/src/Rules/Arrays/EmptyArrayItemRule.php b/src/Rules/Arrays/EmptyArrayItemRule.php index 0bfcebf9567..dfe2a48d4ba 100644 --- a/src/Rules/Arrays/EmptyArrayItemRule.php +++ b/src/Rules/Arrays/EmptyArrayItemRule.php @@ -9,6 +9,7 @@ use PHPStan\Rules\RuleErrorBuilder; /** + * @deprecated Since PHP-Parser 5.0 this is a parse error. * @implements Rule */ final class EmptyArrayItemRule implements Rule diff --git a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php index 5b5f3545a95..fb4ab23162a 100644 --- a/src/Rules/Arrays/InvalidKeyInArrayItemRule.php +++ b/src/Rules/Arrays/InvalidKeyInArrayItemRule.php @@ -11,7 +11,7 @@ use function sprintf; /** - * @implements Rule + * @implements Rule */ final class InvalidKeyInArrayItemRule implements Rule { @@ -22,7 +22,7 @@ public function __construct(private bool $reportMaybes) public function getNodeType(): string { - return Node\Expr\ArrayItem::class; + return Node\ArrayItem::class; } public function processNode(Node $node, Scope $scope): array diff --git a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php index 5cf96d8ad22..a2c04dc0542 100644 --- a/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php +++ b/src/Rules/Cast/InvalidPartOfEncapsedStringRule.php @@ -14,7 +14,7 @@ use function sprintf; /** - * @implements Rule + * @implements Rule */ final class InvalidPartOfEncapsedStringRule implements Rule { @@ -28,14 +28,14 @@ public function __construct( public function getNodeType(): string { - return Node\Scalar\Encapsed::class; + return Node\Scalar\InterpolatedString::class; } public function processNode(Node $node, Scope $scope): array { $messages = []; foreach ($node->parts as $part) { - if ($part instanceof Node\Scalar\EncapsedStringPart) { + if ($part instanceof Node\InterpolatedStringPart) { continue; } diff --git a/src/Rules/Functions/UnusedClosureUsesRule.php b/src/Rules/Functions/UnusedClosureUsesRule.php index 1494208b5b9..c019d692404 100644 --- a/src/Rules/Functions/UnusedClosureUsesRule.php +++ b/src/Rules/Functions/UnusedClosureUsesRule.php @@ -34,7 +34,7 @@ public function processNode(Node $node, Scope $scope): array return $this->check->getUnusedParameters( $scope, - array_map(static function (Node\Expr\ClosureUse $use): string { + array_map(static function (Node\ClosureUse $use): string { if (!is_string($use->var->name)) { throw new ShouldNotHappenException(); } diff --git a/src/Rules/NullsafeCheck.php b/src/Rules/NullsafeCheck.php index b8ff7713bfa..a4424b69ac5 100644 --- a/src/Rules/NullsafeCheck.php +++ b/src/Rules/NullsafeCheck.php @@ -36,7 +36,7 @@ public function containsNullSafe(Expr $expr): bool return $this->containsNullSafe($expr->class); } - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + if ($expr instanceof Expr\List_) { foreach ($expr->items as $item) { if ($item === null) { continue; diff --git a/src/Rules/Operators/InvalidAssignVarRule.php b/src/Rules/Operators/InvalidAssignVarRule.php index 7b24c91c52d..a5ae2ac291c 100644 --- a/src/Rules/Operators/InvalidAssignVarRule.php +++ b/src/Rules/Operators/InvalidAssignVarRule.php @@ -85,7 +85,7 @@ private function containsNonAssignableExpression(Expr $expr): bool return false; } - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + if ($expr instanceof Expr\List_) { foreach ($expr->items as $item) { if ($item === null) { continue; diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 9e4fc3ebf1b..b7021dfe925 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -195,7 +195,7 @@ private function getAssignedVariables(Expr $expr): array return []; } - if ($expr instanceof Expr\List_ || $expr instanceof Expr\Array_) { + if ($expr instanceof Expr\List_) { $names = []; foreach ($expr->items as $item) { if ($item === null) { diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index b85150e267f..4fbe76d20d4 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -69,7 +69,7 @@ private function getUsedVariables(Scope $scope, $node): array if ($node instanceof Node\Expr\Variable && is_string($node->name) && $node->name !== 'this') { return [$node->name]; } - if ($node instanceof Node\Expr\ClosureUse && is_string($node->var->name)) { + if ($node instanceof Node\ClosureUse && is_string($node->var->name)) { return [$node->var->name]; } if ( diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index edd8d3eef63..db5d8983f54 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -519,7 +519,6 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $node instanceof Node\Stmt && !$node instanceof Node\Stmt\Namespace_ && !$node instanceof Node\Stmt\Declare_ - && !$node instanceof Node\Stmt\DeclareDeclare && !$node instanceof Node\Stmt\Use_ && !$node instanceof Node\Stmt\UseUse && !$node instanceof Node\Stmt\GroupUse diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index 472683ffb1e..a571338e184 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -51,10 +51,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n && $valueType->isConstantArray()->yes() && !$valueType->isCallable()->no() ) { - if ($value->items[0] === null || $value->items[1] === null) { - throw new ShouldNotHappenException(); - } - $functionCall = new FuncCall(new Name('method_exists'), [ new Arg($value->items[0]->value), new Arg($value->items[1]->value), From a0122ff9469572777d8fa5ca62ff1896641362e7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:20:13 +0100 Subject: [PATCH 0120/3097] Tests changes --- tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php index 51ba629e160..df14e334934 100644 --- a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php @@ -20,7 +20,7 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/empty-array-item.php'], [ [ - 'Literal array contains empty item.', + 'Cannot use empty array elements in arrays on line 5', 5, ], ]); From 357905ed33f9efbac067e99d48c554ef2ad9799d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:32:08 +0100 Subject: [PATCH 0121/3097] Fixes --- src/Analyser/MutatingScope.php | 2 +- .../BetterReflection/SourceLocator/AutoloadSourceLocator.php | 1 + src/Type/Constant/OversizedArrayBuilder.php | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3a251908c99..36eedbe816e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1837,7 +1837,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu } else { $items = []; foreach ($arm->conds as $filteringExpr) { - $items[] = new Expr\ArrayItem($filteringExpr); + $items[] = new Node\ArrayItem($filteringExpr); } $filteringExpr = new FuncCall( new Name\FullyQualified('in_array'), diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index 7506682426a..2edde64fbc6 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -114,6 +114,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): 'startFilePos' => 1, 'endFilePos' => 4, ]), + null, new LocatedSource('getIterableValueType()), new TypeExpr($valueType->getIterableKeyType()), )]); From 5909dd1c1a7213bce79232caf9a60296f1bd948a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Dec 2023 16:46:18 +0100 Subject: [PATCH 0122/3097] Fix deprecations --- src/Dependency/ExportedNodeVisitor.php | 4 ++-- .../BetterReflection/SourceLocator/CachingVisitor.php | 10 +++++----- src/Rules/Whitespace/FileWhitespaceRule.php | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Dependency/ExportedNodeVisitor.php b/src/Dependency/ExportedNodeVisitor.php index 90df53887b5..34dfd1efe18 100644 --- a/src/Dependency/ExportedNodeVisitor.php +++ b/src/Dependency/ExportedNodeVisitor.php @@ -3,7 +3,7 @@ namespace PHPStan\Dependency; use PhpParser\Node; -use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; use PHPStan\ShouldNotHappenException; @@ -52,7 +52,7 @@ public function enterNode(Node $node): ?int || $node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\Trait_ ) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } return null; diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 3a6d1943957..6eb1f576042 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -4,7 +4,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Namespace_; -use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; use PHPStan\BetterReflection\Reflection\Exception\InvalidConstantNode; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; @@ -51,7 +51,7 @@ public function enterNode(Node $node): ?int ); } - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Stmt\Function_) { @@ -64,7 +64,7 @@ public function enterNode(Node $node): ?int ); } - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Stmt\Const_) { @@ -80,7 +80,7 @@ public function enterNode(Node $node): ?int ); } - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof Node\Expr\FuncCall) { @@ -101,7 +101,7 @@ public function enterNode(Node $node): ?int ); $this->constantNodes[ConstantNameHelper::normalize($constantName)][] = $constantNode; - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } return null; diff --git a/src/Rules/Whitespace/FileWhitespaceRule.php b/src/Rules/Whitespace/FileWhitespaceRule.php index d234baa6b1d..3fb3cbf239c 100644 --- a/src/Rules/Whitespace/FileWhitespaceRule.php +++ b/src/Rules/Whitespace/FileWhitespaceRule.php @@ -5,6 +5,7 @@ use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; use PHPStan\Analyser\Scope; use PHPStan\Node\FileNode; @@ -61,7 +62,7 @@ public function enterNode(Node $node) } return null; } - return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } /** From c6acbe9c905e47a6ce49c26f7800e01e0b6040e5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 22 Dec 2023 14:46:05 +0100 Subject: [PATCH 0123/3097] Fix phar.yml --- .github/workflows/phar.yml | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 723e8a93be8..f2f75906b3e 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -10,6 +10,9 @@ on: tags: - '2.0.*' +env: + COMPOSER_ROOT_VERSION: "1.12.x-dev" + concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests cancel-in-progress: true @@ -76,15 +79,12 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" - env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" @@ -107,43 +107,25 @@ jobs: integration-tests: if: github.event_name == 'pull_request' needs: compiler-tests -<<<<<<< HEAD - uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.12.x - with: - ref: 1.12.x -======= uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x with: ref: 2.0.x ->>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' needs: compiler-tests -<<<<<<< HEAD - uses: phpstan/phpstan/.github/workflows/extension-tests.yml@1.12.x - with: - ref: 1.12.x -======= uses: phpstan/phpstan/.github/workflows/extension-tests.yml@2.0.x with: ref: 2.0.x ->>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} other-tests: if: github.event_name == 'pull_request' needs: compiler-tests -<<<<<<< HEAD - uses: phpstan/phpstan/.github/workflows/other-tests.yml@1.12.x - with: - ref: 1.12.x -======= uses: phpstan/phpstan/.github/workflows/other-tests.yml@2.0.x with: ref: 2.0.x ->>>>>>> 264ce7a58b (Open 2.0.x-dev) phar-checksum: ${{needs.compiler-tests.outputs.checksum}} commit: @@ -170,11 +152,7 @@ jobs: repository: phpstan/phpstan path: phpstan-dist token: ${{ secrets.PHPSTAN_BOT_TOKEN }} -<<<<<<< HEAD - ref: 1.12.x -======= ref: 2.0.x ->>>>>>> 264ce7a58b (Open 2.0.x-dev) - name: "Get previous pushed dist commit" id: previous-commit From 8bfffef61f1b44761c1d361c01f3bd64c7ab456b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 29 Dec 2023 10:01:35 +0100 Subject: [PATCH 0124/3097] Fix ThrowExprTypeRuleTest --- .../Rules/Exceptions/ThrowExprTypeRuleTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php index 8ef6277fe18..9ece8025a0a 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php @@ -23,16 +23,16 @@ public function testRule(): void $this->analyse( [__DIR__ . '/data/throw-values.php'], [ - /*[ + [ 'Invalid type int to throw.', 29, ], [ - 'Invalid type ThrowValues\InvalidException to throw.', + 'Invalid type ThrowExprValues\InvalidException to throw.', 32, ], [ - 'Invalid type ThrowValues\InvalidInterfaceException to throw.', + 'Invalid type ThrowExprValues\InvalidInterfaceException to throw.', 35, ], [ @@ -40,10 +40,10 @@ public function testRule(): void 38, ], [ - 'Throwing object of an unknown class ThrowValues\NonexistentClass.', + 'Throwing object of an unknown class ThrowExprValues\NonexistentClass.', 44, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ],*/ + ], [ 'Invalid type int to throw.', 65, @@ -64,10 +64,10 @@ public function testRuleWithNullsafeVariant(): void } $this->analyse([__DIR__ . '/data/throw-values-nullsafe.php'], [ - /*[ + [ 'Invalid type Exception|null to throw.', 17, - ],*/ + ], ]); } From 2ac47f1fe19fe3d00709a6d5d22de792886343db Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 29 Dec 2023 10:02:00 +0100 Subject: [PATCH 0125/3097] Remove obsolete ThrowTypeRule because it targeted Stmt\Throw_ --- conf/config.level3.neon | 1 - src/Rules/Variables/ThrowTypeRule.php | 62 ---------------- .../Rules/Variables/ThrowTypeRuleTest.php | 70 ------------------- .../Variables/data/throw-class-exists.php | 19 ----- .../Variables/data/throw-values-nullsafe.php | 18 ----- .../Rules/Variables/data/throw-values.php | 62 ---------------- 6 files changed, 232 deletions(-) delete mode 100644 src/Rules/Variables/ThrowTypeRule.php delete mode 100644 tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php delete mode 100644 tests/PHPStan/Rules/Variables/data/throw-class-exists.php delete mode 100644 tests/PHPStan/Rules/Variables/data/throw-values-nullsafe.php delete mode 100644 tests/PHPStan/Rules/Variables/data/throw-values.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 55405007141..f205db23b6e 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -30,7 +30,6 @@ rules: - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule - - PHPStan\Rules\Variables\ThrowTypeRule - PHPStan\Rules\Variables\VariableCloningRule parameters: diff --git a/src/Rules/Variables/ThrowTypeRule.php b/src/Rules/Variables/ThrowTypeRule.php deleted file mode 100644 index 03a80e73bf8..00000000000 --- a/src/Rules/Variables/ThrowTypeRule.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ -final class ThrowTypeRule implements Rule -{ - - public function __construct( - private RuleLevelHelper $ruleLevelHelper, - ) - { - } - - public function getNodeType(): string - { - return Node\Stmt\Throw_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $throwableType = new ObjectType(Throwable::class); - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - 'Throwing object of an unknown class %s.', - static fn (Type $type): bool => $throwableType->isSuperTypeOf($type)->yes(), - ); - - $foundType = $typeResult->getType(); - if ($foundType instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - - $isSuperType = $throwableType->isSuperTypeOf($foundType); - if ($isSuperType->yes()) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Invalid type %s to throw.', - $foundType->describe(VerbosityLevel::typeOnly()), - ))->identifier('throw.notThrowable')->build(), - ]; - } - -} diff --git a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php deleted file mode 100644 index 020f7137087..00000000000 --- a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php +++ /dev/null @@ -1,70 +0,0 @@ - - */ -class ThrowTypeRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new ThrowTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); - } - - public function testRule(): void - { - $this->analyse( - [__DIR__ . '/data/throw-values.php'], - [ - [ - 'Invalid type int to throw.', - 29, - ], - [ - 'Invalid type ThrowValues\InvalidException to throw.', - 32, - ], - [ - 'Invalid type ThrowValues\InvalidInterfaceException to throw.', - 35, - ], - [ - 'Invalid type Exception|null to throw.', - 38, - ], - [ - 'Throwing object of an unknown class ThrowValues\NonexistentClass.', - 44, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - ], - ); - } - - public function testClassExists(): void - { - $this->analyse([__DIR__ . '/data/throw-class-exists.php'], []); - } - - public function testRuleWithNullsafeVariant(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/throw-values-nullsafe.php'], [ - [ - 'Invalid type Exception|null to throw.', - 17, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Variables/data/throw-class-exists.php b/tests/PHPStan/Rules/Variables/data/throw-class-exists.php deleted file mode 100644 index f819307398f..00000000000 --- a/tests/PHPStan/Rules/Variables/data/throw-class-exists.php +++ /dev/null @@ -1,19 +0,0 @@ -= 8.0 - -namespace ThrowValuesNullsafe; - -class Bar -{ - - function doException(): \Exception - { - return new \Exception(); - } - -} - -function doFoo(?Bar $bar) -{ - throw $bar?->doException(); -} diff --git a/tests/PHPStan/Rules/Variables/data/throw-values.php b/tests/PHPStan/Rules/Variables/data/throw-values.php deleted file mode 100644 index 5582923fa27..00000000000 --- a/tests/PHPStan/Rules/Variables/data/throw-values.php +++ /dev/null @@ -1,62 +0,0 @@ - $genericExceptionClassName - * @param T $genericException - */ -function test($genericExceptionClassName, $genericException) { - /** @var ValidInterfaceException $validInterface */ - $validInterface = new \Exception(); - /** @var InvalidInterfaceException $invalidInterface */ - $invalidInterface = new \Exception(); - /** @var \Exception|null $nullableException */ - $nullableException = new \Exception(); - - if (rand(0, 1)) { - throw new \Exception(); - } - if (rand(0, 1)) { - throw $validInterface; - } - if (rand(0, 1)) { - throw 123; - } - if (rand(0, 1)) { - throw new InvalidException(); - } - if (rand(0, 1)) { - throw $invalidInterface; - } - if (rand(0, 1)) { - throw $nullableException; - } - if (rand(0, 1)) { - throw foo(); - } - if (rand(0, 1)) { - throw new NonexistentClass(); - } - if (rand(0, 1)) { - throw new $genericExceptionClassName; - } - if (rand(0, 1)) { - throw $genericException; - } -} - -function (\stdClass $foo) { - /** @var \Exception $foo */ - throw $foo; -}; - -function (\stdClass $foo) { - /** @var \Exception */ - throw $foo; -}; From 03bef7e630ad3fff8bfb6aca18d746629959e251 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:07:57 +0100 Subject: [PATCH 0126/3097] Fix ThrowExpressionRule --- conf/config.neon | 5 ++++ src/Parser/StandaloneThrowExprVisitor.php | 28 ++++++++++++++++++++ src/Rules/Exceptions/ThrowExpressionRule.php | 5 ++++ 3 files changed, 38 insertions(+) create mode 100644 src/Parser/StandaloneThrowExprVisitor.php diff --git a/conf/config.neon b/conf/config.neon index dac1e96e5f0..45589553960 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -372,6 +372,11 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\StandaloneThrowExprVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Parser\TryCatchTypeVisitor tags: diff --git a/src/Parser/StandaloneThrowExprVisitor.php b/src/Parser/StandaloneThrowExprVisitor.php new file mode 100644 index 00000000000..e1082461287 --- /dev/null +++ b/src/Parser/StandaloneThrowExprVisitor.php @@ -0,0 +1,28 @@ +expr instanceof Node\Expr\Throw_) { + return null; + } + + $node->expr->setAttribute(self::ATTRIBUTE_NAME, true); + + return $node; + } + +} diff --git a/src/Rules/Exceptions/ThrowExpressionRule.php b/src/Rules/Exceptions/ThrowExpressionRule.php index 5d3c7c2576d..9fcc9c9e88e 100644 --- a/src/Rules/Exceptions/ThrowExpressionRule.php +++ b/src/Rules/Exceptions/ThrowExpressionRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Parser\StandaloneThrowExprVisitor; use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -29,6 +30,10 @@ public function processNode(Node $node, Scope $scope): array return []; } + if ($node->getAttribute(StandaloneThrowExprVisitor::ATTRIBUTE_NAME) === true) { + return []; + } + return [ RuleErrorBuilder::message('Throw expression is supported only on PHP 8.0 and later.')->nonIgnorable() ->identifier('throw.notSupported') From 89bc1e6e4d2ff8f23466cbceeec65d5696b97e6c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:14:46 +0100 Subject: [PATCH 0127/3097] Fixes --- src/Analyser/MutatingScope.php | 10 ++++------ src/Analyser/NodeScopeResolver.php | 1 - src/Dependency/ExportedNodeResolver.php | 2 +- src/Reflection/InitializerExprTypeResolver.php | 8 ++++---- src/Rules/Namespaces/ExistingNamesInUseRule.php | 8 ++++---- src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php | 1 - src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php | 1 - src/Type/FileTypeMapper.php | 1 - 8 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 36eedbe816e..c3f58e1c924 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -24,8 +24,6 @@ use PhpParser\Node\InterpolatedStringPart; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\DNumber; -use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; use PhpParser\NodeFinder; use PHPStan\Node\ExecutionEndNode; @@ -1125,7 +1123,7 @@ private function resolveType(string $exprString, Expr $node): Type return $this->getType($node->expr); } - if ($node instanceof LNumber) { + if ($node instanceof Node\Scalar\Int_) { return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); } elseif ($node instanceof String_) { return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); @@ -1149,7 +1147,7 @@ private function resolveType(string $exprString, Expr $node): Type } return $resultType ?? new ConstantStringType(''); - } elseif ($node instanceof DNumber) { + } elseif ($node instanceof Node\Scalar\Float_) { return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); } elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) { if ($node instanceof FuncCall) { @@ -1707,10 +1705,10 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu } if ($node instanceof Expr\PreInc) { - return $this->getType(new BinaryOp\Plus($node->var, new LNumber(1))); + return $this->getType(new BinaryOp\Plus($node->var, new Node\Scalar\Int_(1))); } - return $this->getType(new BinaryOp\Minus($node->var, new LNumber(1))); + return $this->getType(new BinaryOp\Minus($node->var, new Node\Scalar\Int_(1))); } elseif ($node instanceof Expr\Yield_) { $functionReflection = $this->getFunction(); if ($functionReflection === null) { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e7b3a0ababc..e11eeff70a8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -444,7 +444,6 @@ private function processStmtNode( && !$stmt instanceof Foreach_ && !$stmt instanceof Node\Stmt\Global_ && !$stmt instanceof Node\Stmt\Property - && !$stmt instanceof Node\Stmt\PropertyProperty && !$stmt instanceof Node\Stmt\ClassConst && !$stmt instanceof Node\Stmt\Const_ ) { diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index c9a0c521545..36a752735d4 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -336,7 +336,7 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $docComment = $node->getDocComment(); return new ExportedPropertiesNode( - array_map(static fn (Node\Stmt\PropertyProperty $prop): string => $prop->name->toString(), $node->props), + array_map(static fn (Node\PropertyItem $prop): string => $prop->name->toString(), $node->props), $this->exportPhpDocNode( $fileName, $namespacedName, diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 2f1aaa96a01..6013737759b 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -11,8 +11,8 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Identifier; use PhpParser\Node\Name; -use PhpParser\Node\Scalar\DNumber; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\Float_; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\MagicConst; use PhpParser\Node\Scalar\MagicConst\Dir; use PhpParser\Node\Scalar\MagicConst\File; @@ -113,10 +113,10 @@ public function getType(Expr $expr, InitializerExprContext $context): Type if ($expr instanceof TypeExpr) { return $expr->getExprType(); } - if ($expr instanceof LNumber) { + if ($expr instanceof Int_) { return new ConstantIntegerType($expr->value); } - if ($expr instanceof DNumber) { + if ($expr instanceof Float_) { return new ConstantFloatType($expr->value); } if ($expr instanceof String_) { diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index 381a2e1de61..b93db1ea450 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -58,7 +58,7 @@ public function processNode(Node $node, Scope $scope): array } /** - * @param Node\Stmt\UseUse[] $uses + * @param Node\UseItem[] $uses * @return list */ private function checkConstants(array $uses): array @@ -80,7 +80,7 @@ private function checkConstants(array $uses): array } /** - * @param Node\Stmt\UseUse[] $uses + * @param Node\UseItem[] $uses * @return list */ private function checkFunctions(array $uses): array @@ -117,13 +117,13 @@ private function checkFunctions(array $uses): array } /** - * @param Node\Stmt\UseUse[] $uses + * @param Node\UseItem[] $uses * @return list */ private function checkClasses(array $uses): array { return $this->classCheck->checkClassNames( - array_map(static fn (Node\Stmt\UseUse $use): ClassNameNodePair => new ClassNameNodePair((string) $use->name, $use->name), $uses), + array_map(static fn (Node\UseItem $use): ClassNameNodePair => new ClassNameNodePair((string) $use->name, $use->name), $uses), ); } diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 4a227ffd07f..81b4296100d 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -48,7 +48,6 @@ public function processNode(Node $node, Scope $scope): array { if ( $node instanceof Node\Stmt\Property - || $node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Stmt\ClassConst || $node instanceof Node\Stmt\Const_ ) { diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index b7021dfe925..5ff12a42069 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -51,7 +51,6 @@ public function processNode(Node $node, Scope $scope): array { if ( $node instanceof Node\Stmt\Property - || $node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Stmt\ClassConst || $node instanceof Node\Stmt\Const_ || ($node instanceof VirtualNode && !$node instanceof InFunctionNode && !$node instanceof InClassMethodNode && !$node instanceof InClassNode) diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index db5d8983f54..a0af548147c 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -520,7 +520,6 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun && !$node instanceof Node\Stmt\Namespace_ && !$node instanceof Node\Stmt\Declare_ && !$node instanceof Node\Stmt\Use_ - && !$node instanceof Node\Stmt\UseUse && !$node instanceof Node\Stmt\GroupUse && !$node instanceof Node\Stmt\TraitUse && !$node instanceof Node\Stmt\TraitUseAdaptation From 8dafccb4bc6075bfd63b9ab31042c6cd1c6752d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:26:02 +0100 Subject: [PATCH 0128/3097] ParserFactory to correctly create Php7/Php8 --- conf/config.neon | 6 +++++- src/Parser/PhpParserFactory.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/Parser/PhpParserFactory.php diff --git a/conf/config.neon b/conf/config.neon index 45589553960..d1b5182fe08 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2050,7 +2050,11 @@ services: autowired: false currentPhpVersionPhpParser: - class: PhpParser\Parser\Php8 # todo use factory and create Php7/Php8 + factory: @currentPhpVersionPhpParserFactory::create() + autowired: false + + currentPhpVersionPhpParserFactory: + class: PHPStan\Parser\PhpParserFactory arguments: lexer: @currentPhpVersionLexer autowired: false diff --git a/src/Parser/PhpParserFactory.php b/src/Parser/PhpParserFactory.php new file mode 100644 index 00000000000..dee78b35d2c --- /dev/null +++ b/src/Parser/PhpParserFactory.php @@ -0,0 +1,28 @@ +phpVersion->getVersionString()); + if ($this->phpVersion->getVersionId() >= 80000) { + return new Php8($this->lexer, $phpVersion); + } + + return new Php7($this->lexer, $phpVersion); + } + +} From 503fe0ce4f12f2a515974d75513c92b32f733dd1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:31:15 +0100 Subject: [PATCH 0129/3097] This hack is no longer needed because there is now clear separation between Name and Identifier --- src/Type/TypehintHelper.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 8f9f5616f33..dbfb14973a3 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -16,7 +16,6 @@ use function get_class; use function is_string; use function sprintf; -use function str_ends_with; use function strtolower; final class TypehintHelper @@ -132,20 +131,6 @@ public static function decideTypeFromReflection( } $reflectionTypeString = $reflectionType->getName(); - $loweredReflectionTypeString = strtolower($reflectionTypeString); - if (str_ends_with($loweredReflectionTypeString, '\\object')) { - $reflectionTypeString = 'object'; - } elseif (str_ends_with($loweredReflectionTypeString, '\\mixed')) { - $reflectionTypeString = 'mixed'; - } elseif (str_ends_with($loweredReflectionTypeString, '\\true')) { - $reflectionTypeString = 'true'; - } elseif (str_ends_with($loweredReflectionTypeString, '\\false')) { - $reflectionTypeString = 'false'; - } elseif (str_ends_with($loweredReflectionTypeString, '\\null')) { - $reflectionTypeString = 'null'; - } elseif (str_ends_with($loweredReflectionTypeString, '\\never')) { - $reflectionTypeString = 'never'; - } $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass); if ($reflectionType->allowsNull()) { From 201fca1458f7bcea325de003cdc4e099fb64e462 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:39:24 +0100 Subject: [PATCH 0130/3097] Fix NeverRuleHelper --- src/Rules/Playground/NeverRuleHelper.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Rules/Playground/NeverRuleHelper.php b/src/Rules/Playground/NeverRuleHelper.php index 520b4264249..9865d3d1ce1 100644 --- a/src/Rules/Playground/NeverRuleHelper.php +++ b/src/Rules/Playground/NeverRuleHelper.php @@ -26,10 +26,17 @@ public function shouldReturnNever(ReturnStatementsNode $node, Type $returnType): $other = []; foreach ($node->getExecutionEnds() as $executionEnd) { if ($executionEnd->getStatementResult()->isAlwaysTerminating()) { - if (!$executionEnd->getNode() instanceof Node\Stmt\Throw_) { + $executionEndNode = $executionEnd->getNode(); + if (!$executionEndNode instanceof Node\Stmt\Expression) { $other[] = $executionEnd->getNode(); + continue; } + if ($executionEndNode->expr instanceof Node\Expr\Throw_) { + continue; + } + + $other[] = $executionEnd->getNode(); continue; } From 23060238dda22589a5497b2437a568c7d990df96 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:41:45 +0100 Subject: [PATCH 0131/3097] Get rid of more Stmt\Throw_ instances --- src/Parser/LastConditionVisitor.php | 8 +++++++- .../Exceptions/OverwrittenExitPointByFinallyRule.php | 2 +- src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Parser/LastConditionVisitor.php b/src/Parser/LastConditionVisitor.php index d4c4b53ac30..d20a8f4b906 100644 --- a/src/Parser/LastConditionVisitor.php +++ b/src/Parser/LastConditionVisitor.php @@ -68,7 +68,13 @@ public function enterNode(Node $node): ?Node return null; } - if (!$statements[$statementCount - 1] instanceof Node\Stmt\Throw_) { + $lastStatement = $statements[$statementCount - 1]; + + if (!$lastStatement instanceof Node\Stmt\Expression) { + return null; + } + + if (!$lastStatement->expr instanceof Node\Expr\Throw_) { return null; } diff --git a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php index f4a6e17499d..74fa2eb0aed 100644 --- a/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php +++ b/src/Rules/Exceptions/OverwrittenExitPointByFinallyRule.php @@ -51,7 +51,7 @@ private function describeExitPoint(Node\Stmt $stmt): string return 'return'; } - if ($stmt instanceof Node\Stmt\Throw_) { + if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Node\Expr\Throw_) { return 'throw'; } diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 5ff12a42069..87784c71d61 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -89,10 +89,13 @@ public function processNode(Node $node, Scope $scope): array } if ($node instanceof Node\Stmt\Expression) { + if ($node->expr instanceof Expr\Throw_) { + return $this->processStmt($scope, $varTags, $node->expr); + } return $this->processExpression($scope, $node->expr, $varTags); } - if ($node instanceof Node\Stmt\Throw_ || $node instanceof Node\Stmt\Return_) { + if ($node instanceof Node\Stmt\Return_) { return $this->processStmt($scope, $varTags, $node->expr); } From 9c177958fc2eda3615967a24504196a7848627aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:44:34 +0100 Subject: [PATCH 0132/3097] Fixes --- src/Analyser/NodeScopeResolver.php | 4 ++-- src/Analyser/StatementResult.php | 10 +++++----- src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php | 4 ++-- src/Reflection/InitializerExprTypeResolver.php | 2 +- src/Rules/Arrays/ArrayDestructuringRule.php | 2 +- src/Rules/Classes/EnumSanityRule.php | 2 +- .../Comparison/DoWhileLoopConstantConditionRule.php | 4 ++-- .../Comparison/WhileLoopAlwaysTrueConditionRule.php | 4 ++-- src/Rules/Keywords/ContinueBreakInLoopRule.php | 2 +- src/Rules/Keywords/DeclareStrictTypesRule.php | 2 +- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 2 +- .../Php/ArraySumFunctionDynamicReturnTypeExtension.php | 4 ++-- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e11eeff70a8..a15060536b7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -494,7 +494,7 @@ private function processStmtNode( $nodeCallback($declare->value, $scope); if ( $declare->key->name !== 'strict_types' - || !($declare->value instanceof Node\Scalar\LNumber) + || !($declare->value instanceof Node\Scalar\Int_) || $declare->value->value !== 1 ) { continue; @@ -5298,7 +5298,7 @@ static function (): void { $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); if ($arrayItem->key === null) { - $dimExpr = new Node\Scalar\LNumber($i); + $dimExpr = new Node\Scalar\Int_($i); } else { $dimExpr = $arrayItem->key; } diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index bb9ae78f2e6..985777317e9 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt; /** @@ -58,7 +58,7 @@ public function filterOutLoopExitPoints(): self } $num = $statement->num; - if (!$num instanceof LNumber) { + if (!$num instanceof Int_) { return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints, $this->impurePoints); } @@ -99,7 +99,7 @@ public function getExitPointsByType(string $stmtClass): array continue; } - if (!$value instanceof LNumber) { + if (!$value instanceof Int_) { $exitPoints[] = $exitPoint; continue; } @@ -130,7 +130,7 @@ public function getExitPointsForOuterLoop(): array if ($statement->num === null) { continue; } - if (!$statement->num instanceof LNumber) { + if (!$statement->num instanceof Int_) { continue; } $value = $statement->num->value; @@ -140,7 +140,7 @@ public function getExitPointsForOuterLoop(): array $newNode = null; if ($value > 2) { - $newNode = new LNumber($value - 1); + $newNode = new Int_($value - 1); } if ($statement instanceof Stmt\Continue_) { $newStatement = new Stmt\Continue_($newNode); diff --git a/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php b/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php index 48722eeca51..b57afc5fba8 100644 --- a/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php +++ b/src/Parser/RemoveUnusedCodeByPhpVersionIdVisitor.php @@ -77,7 +77,7 @@ public function enterNode(Node $node): ?Node private function getOperands(Node\Expr $left, Node\Expr $right): ?array { if ( - $left instanceof Node\Scalar\LNumber + $left instanceof Node\Scalar\Int_ && $right instanceof Node\Expr\ConstFetch && $right->name->toString() === 'PHP_VERSION_ID' ) { @@ -85,7 +85,7 @@ private function getOperands(Node\Expr $left, Node\Expr $right): ?array } if ( - $right instanceof Node\Scalar\LNumber + $right instanceof Node\Scalar\Int_ && $left instanceof Node\Expr\ConstFetch && $left->name->toString() === 'PHP_VERSION_ID' ) { diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 6013737759b..174adae5c11 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2044,7 +2044,7 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type } if ($type instanceof IntegerRangeType) { - return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new LNumber(-1))); + return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); } return $type; diff --git a/src/Rules/Arrays/ArrayDestructuringRule.php b/src/Rules/Arrays/ArrayDestructuringRule.php index d8281b1d886..1d9537d2747 100644 --- a/src/Rules/Arrays/ArrayDestructuringRule.php +++ b/src/Rules/Arrays/ArrayDestructuringRule.php @@ -85,7 +85,7 @@ private function getErrors(Scope $scope, Node\Expr\List_ $var, Expr $expr): arra $keyExpr = null; if ($item->key === null) { $keyType = new ConstantIntegerType($i); - $keyExpr = new Node\Scalar\LNumber($i); + $keyExpr = new Node\Scalar\Int_($i); } else { $keyType = $scope->getType($item->key); $keyExpr = new TypeExpr($keyType); diff --git a/src/Rules/Classes/EnumSanityRule.php b/src/Rules/Classes/EnumSanityRule.php index 2d193095d4c..51c07fdfd84 100644 --- a/src/Rules/Classes/EnumSanityRule.php +++ b/src/Rules/Classes/EnumSanityRule.php @@ -143,7 +143,7 @@ public function processNode(Node $node, Scope $scope): array } $caseName = $stmt->name->name; - if ($stmt->expr instanceof Node\Scalar\LNumber || $stmt->expr instanceof Node\Scalar\String_) { + if ($stmt->expr instanceof Node\Scalar\Int_ || $stmt->expr instanceof Node\Scalar\String_) { if ($enumNode->scalarType === null) { $errors[] = RuleErrorBuilder::message(sprintf( 'Enum %s is not backed, but case %s has value %s.', diff --git a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php index 3777b5d6e59..4b437459238 100644 --- a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php +++ b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php @@ -3,7 +3,7 @@ namespace PHPStan\Rules\Comparison; use PhpParser\Node; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Continue_; use PHPStan\Analyser\Scope; @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array if ($statement->num === null) { continue; } - if (!$statement->num instanceof LNumber) { + if (!$statement->num instanceof Int_) { continue; } $value = $statement->num->value; diff --git a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php index 68ac27fbf2c..505c57d7c67 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php @@ -3,7 +3,7 @@ namespace PHPStan\Rules\Comparison; use PhpParser\Node; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Continue_; use PHPStan\Analyser\Scope; @@ -46,7 +46,7 @@ public function processNode( if ($statement->num === null) { continue; } - if (!$statement->num instanceof LNumber) { + if (!$statement->num instanceof Int_) { continue; } $value = $statement->num->value; diff --git a/src/Rules/Keywords/ContinueBreakInLoopRule.php b/src/Rules/Keywords/ContinueBreakInLoopRule.php index d8e8b00fcb4..75657f232f1 100644 --- a/src/Rules/Keywords/ContinueBreakInLoopRule.php +++ b/src/Rules/Keywords/ContinueBreakInLoopRule.php @@ -28,7 +28,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (!$node->num instanceof Node\Scalar\LNumber) { + if (!$node->num instanceof Node\Scalar\Int_) { $value = 1; } else { $value = $node->num->value; diff --git a/src/Rules/Keywords/DeclareStrictTypesRule.php b/src/Rules/Keywords/DeclareStrictTypesRule.php index b0b364030a5..66aaa94026b 100644 --- a/src/Rules/Keywords/DeclareStrictTypesRule.php +++ b/src/Rules/Keywords/DeclareStrictTypesRule.php @@ -40,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array } if ( - !$declare->value instanceof Node\Scalar\LNumber + !$declare->value instanceof Node\Scalar\Int_ || !in_array($declare->value->value, [0, 1], true) ) { return [ diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 0070554896c..67bcd8f88ac 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -53,7 +53,7 @@ public function checkVarType(Scope $scope, Node\Expr $var, Node\Expr $expr, arra continue; } if ($arrayItem->key === null) { - $dimExpr = new Node\Scalar\LNumber($i); + $dimExpr = new Node\Scalar\Int_($i); } else { $dimExpr = $arrayItem->key; } diff --git a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php index b60730e828c..5185fbccc1f 100644 --- a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php @@ -5,7 +5,7 @@ use PhpParser\Node\Expr\BinaryOp\Mul; use PhpParser\Node\Expr\BinaryOp\Plus; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\Int_; use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\FunctionReflection; @@ -35,7 +35,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if (count($argType->getConstantArrays()) > 0) { foreach ($argType->getConstantArrays() as $constantArray) { - $node = new LNumber(0); + $node = new Int_(0); foreach ($constantArray->getValueTypes() as $i => $type) { if ($constantArray->isOptionalKey($i)) { From 05d4303e71f48c97da8a892052a5942902ec6760 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 17:47:49 +0100 Subject: [PATCH 0133/3097] Fixes --- conf/config.neon | 1 - src/Parser/RichParser.php | 2 -- tests/PHPStan/Analyser/AnalyserTest.php | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index d1b5182fe08..4d897a45874 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2015,7 +2015,6 @@ services: class: PHPStan\Parser\RichParser arguments: parser: @currentPhpVersionPhpParser - lexer: @currentPhpVersionLexer enableIgnoreErrorsWithinPhpDocs: %featureToggles.enableIgnoreErrorsWithinPhpDocs% autowired: no diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index e3f2a5e3104..ed0f1840a0d 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -3,7 +3,6 @@ namespace PHPStan\Parser; use PhpParser\ErrorHandler\Collecting; -use PhpParser\Lexer; use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; @@ -42,7 +41,6 @@ final class RichParser implements Parser public function __construct( private \PhpParser\Parser $parser, - private Lexer $lexer, private NameResolver $nameResolver, private Container $container, private IgnoreLexer $ignoreLexer, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index f820c64aff0..d59549b9dac 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -745,13 +745,12 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); - $lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); + $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), $nodeScopeResolver, new RichParser( new Php7($lexer), - $lexer, new NameResolver(), self::getContainer(), new IgnoreLexer(), From 7981c5daade0d5b646dc4d1ac8a1cfdb68ee156a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 20:38:56 +0100 Subject: [PATCH 0134/3097] Simplify TypehintHelper and use ParserNodeTypeToPHPStanType --- src/Type/TypehintHelper.php | 96 ++++++++----------------------------- 1 file changed, 19 insertions(+), 77 deletions(-) diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index dbfb14973a3..5538370ccb6 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -2,95 +2,25 @@ namespace PHPStan\Type; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name\FullyQualified; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateTypeHelper; -use ReflectionIntersectionType; -use ReflectionNamedType; use ReflectionType; -use ReflectionUnionType; use function array_map; use function count; use function get_class; use function is_string; use function sprintf; -use function strtolower; final class TypehintHelper { - private static function getTypeObjectFromTypehint(string $typeString, ClassReflection|string|null $selfClass): Type - { - switch (strtolower($typeString)) { - case 'int': - return new IntegerType(); - case 'bool': - return new BooleanType(); - case 'false': - return new ConstantBooleanType(false); - case 'true': - return new ConstantBooleanType(true); - case 'string': - return new StringType(); - case 'float': - return new FloatType(); - case 'array': - return new ArrayType(new MixedType(), new MixedType()); - case 'iterable': - return new IterableType(new MixedType(), new MixedType()); - case 'callable': - return new CallableType(); - case 'void': - return new VoidType(); - case 'object': - return new ObjectWithoutClassType(); - case 'mixed': - return new MixedType(true); - case 'self': - if ($selfClass instanceof ClassReflection) { - $selfClass = $selfClass->getName(); - } - return $selfClass !== null ? new ObjectType($selfClass) : new ErrorType(); - case 'parent': - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if (is_string($selfClass)) { - if ($reflectionProvider->hasClass($selfClass)) { - $selfClass = $reflectionProvider->getClass($selfClass); - } else { - $selfClass = null; - } - } - if ($selfClass !== null) { - if ($selfClass->getParentClass() !== null) { - return new ObjectType($selfClass->getParentClass()->getName()); - } - } - return new NonexistentParentClassType(); - case 'static': - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if (is_string($selfClass)) { - if ($reflectionProvider->hasClass($selfClass)) { - $selfClass = $reflectionProvider->getClass($selfClass); - } else { - $selfClass = null; - } - } - if ($selfClass !== null) { - return new StaticType($selfClass); - } - - return new ErrorType(); - case 'null': - return new NullType(); - case 'never': - return new NonAcceptingNeverType(); - default: - return new ObjectType($typeString); - } - } - /** @api */ public static function decideTypeFromReflection( ?ReflectionType $reflectionType, @@ -130,9 +60,21 @@ public static function decideTypeFromReflection( throw new ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType))); } - $reflectionTypeString = $reflectionType->getName(); + if ($reflectionType->isIdentifier()) { + $typeNode = new Identifier($reflectionType->getName()); + } else { + $typeNode = new FullyQualified($reflectionType->getName()); + } - $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass); + if (is_string($selfClass)) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($reflectionProvider->hasClass($selfClass)) { + $selfClass = $reflectionProvider->getClass($selfClass); + } else { + $selfClass = null; + } + } + $type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass); if ($reflectionType->allowsNull()) { $type = TypeCombinator::addNull($type); } elseif ($phpDocType !== null) { From f0709245a0b4a6edad2e377e054b93a4d767854f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Dec 2023 20:58:53 +0100 Subject: [PATCH 0135/3097] Stub validator - always use latest PHP 8 parser --- conf/config.stubValidator.neon | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index 1645698a92a..ae22e5ccdc3 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -13,7 +13,7 @@ services: arguments: php8Parser: @php8PhpParser - nodeScopeResolverClassReflector: + nodeScopeResolverReflector: factory: @stubReflector stubBetterReflectionProvider: @@ -38,3 +38,11 @@ services: factory: @stubBetterReflectionProvider autowired: - PHPStan\Reflection\ReflectionProvider + + currentPhpVersionLexer: + factory: @php8Lexer + autowired: false + + currentPhpVersionPhpParser: + factory: @php8PhpParser + autowired: false From dbf06b62f1a2ec7ed3ec28cb45e293aee6b42c30 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 1 Jan 2024 14:31:39 +0100 Subject: [PATCH 0136/3097] Fix PHPStan errors --- phpstan-baseline.neon | 52 ++++--------------- src/Analyser/NodeScopeResolver.php | 5 +- src/Parser/PhpParserDecorator.php | 3 +- src/Type/Constant/OversizedArrayBuilder.php | 6 --- ...InArrayFunctionTypeSpecifyingExtension.php | 3 -- 5 files changed, 13 insertions(+), 56 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cdbd956fa21..fbfeafa6089 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -70,7 +70,7 @@ parameters: path: src/Analyser/NodeScopeResolver.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Analyser/NodeScopeResolver.php @@ -276,7 +276,7 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -286,17 +286,17 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php @@ -1799,43 +1799,6 @@ parameters: count: 1 path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php - - - message: """ - #^Instantiation of deprecated class PHPStan\\\\Rules\\\\DeadCode\\\\NoopRule\\: - Replaced by PHPStan\\\\Rules\\\\DeadCode\\\\BetterNoopRule$# - """ - count: 1 - path: tests/PHPStan/Rules/DeadCode/NoopRuleTest.php - - - - message: """ - #^Return type of method PHPStan\\\\Rules\\\\DeadCode\\\\NoopRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\DeadCode\\\\NoopRule\\: - Replaced by PHPStan\\\\Rules\\\\DeadCode\\\\BetterNoopRule$# - """ - count: 1 - path: tests/PHPStan/Rules/DeadCode/NoopRuleTest.php - - - - message: """ - #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRule\\: - Replaced by PHPStan\\\\Rules\\\\Functions\\\\ImplodeParameterCastableToStringRuleTest$# - """ - count: 1 - path: tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php - - - - message: """ - #^Return type of method PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Functions\\\\ImplodeFunctionRule\\: - Replaced by PHPStan\\\\Rules\\\\Functions\\\\ImplodeParameterCastableToStringRuleTest$# - """ - count: 1 - path: tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php - - - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" - count: 1 - path: tests/PHPStan/Type/IterableTypeTest.php - - message: """ #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRule\\: @@ -1851,3 +1814,8 @@ parameters: """ count: 1 path: tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php + + - + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" + count: 1 + path: tests/PHPStan/Type/IterableTypeTest.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a15060536b7..345a323d03e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1689,7 +1689,7 @@ private function processStmtNode( $finallyScope = $finallyScope->mergeWith($throwPoint->getScope()); } - if ($finallyScope !== null && $stmt->finally !== null) { + if ($finallyScope !== null) { $originalFinallyScope = $finallyScope; $finallyResult = $this->processStmtNodes($stmt->finally, $stmt->finally->stmts, $finallyScope, $nodeCallback, $context); $alwaysTerminating = $alwaysTerminating || $finallyResult->isAlwaysTerminating(); @@ -2973,9 +2973,6 @@ static function (): void { $impurePoints = []; foreach ($expr->items as $arrayItem) { $itemNodes[] = new LiteralArrayItem($scope, $arrayItem); - if ($arrayItem === null) { - continue; - } $nodeCallback($arrayItem, $scope); if ($arrayItem->key !== null) { $keyResult = $this->processExprNode($stmt, $arrayItem->key, $scope, $nodeCallback, $context->enterDeep()); diff --git a/src/Parser/PhpParserDecorator.php b/src/Parser/PhpParserDecorator.php index d4e00547a0d..74815744500 100644 --- a/src/Parser/PhpParserDecorator.php +++ b/src/Parser/PhpParserDecorator.php @@ -6,6 +6,7 @@ use PhpParser\ErrorHandler; use PhpParser\Node; use PhpParser\Parser; +use PHPStan\ShouldNotHappenException; use function sprintf; final class PhpParserDecorator implements Parser @@ -33,7 +34,7 @@ public function parse(string $code, ?ErrorHandler $errorHandler = null): array public function getTokens(): array { - return $this->wrappedParser->getTokens(); + throw new ShouldNotHappenException('PhpParserDecorator::getTokens() should not be called'); } } diff --git a/src/Type/Constant/OversizedArrayBuilder.php b/src/Type/Constant/OversizedArrayBuilder.php index c365e0581f3..e6ac71ded58 100644 --- a/src/Type/Constant/OversizedArrayBuilder.php +++ b/src/Type/Constant/OversizedArrayBuilder.php @@ -34,9 +34,6 @@ public function build(Array_ $expr, callable $getTypeCallback): Type $items = $expr->items; for ($i = 0; $i < count($items); $i++) { $item = $items[$i]; - if ($item === null) { - continue; - } if (!$item->unpack) { continue; } @@ -64,9 +61,6 @@ public function build(Array_ $expr, callable $getTypeCallback): Type } } foreach ($items as $item) { - if ($item === null) { - continue; - } if ($item->unpack) { throw new ShouldNotHappenException(); } diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index df1bf3499a2..1c4436ca464 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -65,9 +65,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ($arrayExpr instanceof Array_) { $types = null; foreach ($arrayExpr->items as $item) { - if ($item === null) { - continue; - } if ($item->unpack) { $types = null; break; From a2df219c86d6f52ecd5cc5a0752adecbd26335e2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 1 Jan 2024 14:42:54 +0100 Subject: [PATCH 0137/3097] Fix --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c3f58e1c924..b6a38997a89 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1133,7 +1133,7 @@ private function resolveType(string $exprString, Expr $node): Type if ($part instanceof InterpolatedStringPart) { $partType = new ConstantStringType($part->value); } else { - $partType = $this->getType($part); + $partType = $this->getType($part)->toString(); } if ($resultType === null) { $resultType = $partType; From 92a9288581c38c08a2b00213351173a26610f614 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 09:39:07 +0200 Subject: [PATCH 0138/3097] Fix --- .../BetterReflection/SourceLocator/AutoloadSourceLocator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index 2edde64fbc6..7506682426a 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -114,7 +114,6 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): 'startFilePos' => 1, 'endFilePos' => 4, ]), - null, new LocatedSource(' Date: Wed, 4 Sep 2024 11:16:22 +0200 Subject: [PATCH 0139/3097] Handle Block statement --- src/Analyser/NodeScopeResolver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 345a323d03e..0b7133e631f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1895,6 +1895,8 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $impurePoints = [ new ImpurePoint($scope, $stmt, 'betweenPhpTags', 'output between PHP opening and closing tags', true), ]; + } elseif ($stmt instanceof Node\Stmt\Block) { + return $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); } elseif ($stmt instanceof Node\Stmt\Nop) { $hasYield = false; $throwPoints = $overridingThrowPoints ?? []; From 21402088feb881170161283a5c08b8b0e8ce297a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 11:19:37 +0200 Subject: [PATCH 0140/3097] Fix standalone Throw_ expr handling --- src/Analyser/NodeScopeResolver.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0b7133e631f..f32e3345b74 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -791,6 +791,9 @@ private function processStmtNode( new StatementExitPoint($stmt, $scope), ], $overridingThrowPoints ?? $throwPoints, $impurePoints); } elseif ($stmt instanceof Node\Stmt\Expression) { + if ($stmt->expr instanceof Expr\Throw_) { + $scope = $stmtScope; + } $earlyTerminationExpr = $this->findEarlyTerminatingExpr($stmt->expr, $scope); $hasAssign = false; $currentScope = $scope; From 9b7a8f41612f9716368935196f4fcb42464dbf5a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 11:36:07 +0200 Subject: [PATCH 0141/3097] Remove obsolete NoBleedingEdge tests --- .../CallMethodsRuleNoBleedingEdgeTest.php | 61 -------- .../PhpDoc/InvalidPHPStanDocTagRuleTest.php | 25 +-- ...idPhpDocTagValueRuleNoBleedingEdgeTest.php | 146 ------------------ .../PhpDoc/InvalidPhpDocTagValueRuleTest.php | 28 +--- ...gnedToPropertiesRuleNoBleedingEdgeTest.php | 50 ------ 5 files changed, 8 insertions(+), 302 deletions(-) delete mode 100644 tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php delete mode 100644 tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleNoBleedingEdgeTest.php delete mode 100644 tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php deleted file mode 100644 index 71678d99ff5..00000000000 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleNoBleedingEdgeTest.php +++ /dev/null @@ -1,61 +0,0 @@ - - */ -class CallMethodsRuleNoBleedingEdgeTest extends RuleTestCase -{ - - private bool $checkExplicitMixed; - - protected function getRule(): Rule - { - $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, false, true, false); - return new CallMethodsRule( - new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, false), - ); - } - - public function testGenericsInferCollection(): void - { - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/generics-infer-collection.php'], [ - [ - 'Parameter #1 $c of method GenericsInferCollection\Foo::doBar() expects GenericsInferCollection\ArrayCollection, GenericsInferCollection\ArrayCollection given.', - 43, - ], - ]); - } - - public function testGenericsInferCollectionLevel8(): void - { - $this->checkExplicitMixed = false; - $this->analyse([__DIR__ . '/data/generics-infer-collection.php'], [ - [ - 'Parameter #1 $c of method GenericsInferCollection\Foo::doBar() expects GenericsInferCollection\ArrayCollection, GenericsInferCollection\ArrayCollection given.', - 43, - ], - ]); - } - - public static function getAdditionalConfigFiles(): array - { - // no bleeding edge - return []; - } - -} diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php index 7995c696f47..e3e82da0ff8 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php @@ -6,7 +6,6 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use function array_merge; /** * @extends RuleTestCase @@ -14,20 +13,18 @@ class InvalidPHPStanDocTagRuleTest extends RuleTestCase { - private bool $checkAllInvalidPhpDocs; - protected function getRule(): Rule { return new InvalidPHPStanDocTagRule( self::getContainer()->getByType(Lexer::class), self::getContainer()->getByType(PhpDocParser::class), - $this->checkAllInvalidPhpDocs, + true, ); } - public function dataRule(): iterable + public function testRule(): void { - $errors = [ + $this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], [ [ 'Unknown PHPDoc tag: @phpstan-extens', 6, @@ -44,29 +41,15 @@ public function dataRule(): iterable 'Unknown PHPDoc tag: @phpstan-varr', 46, ], - ]; - yield [false, $errors]; - yield [true, array_merge($errors, [ [ 'Unknown PHPDoc tag: @phpstan-varr', 56, ], - ])]; - } - - /** - * @dataProvider dataRule - * @param list $expectedErrors - */ - public function testRule(bool $checkAllInvalidPhpDocs, array $expectedErrors): void - { - $this->checkAllInvalidPhpDocs = $checkAllInvalidPhpDocs; - $this->analyse([__DIR__ . '/data/invalid-phpstan-doc.php'], $expectedErrors); + ]); } public function testBug8697(): void { - $this->checkAllInvalidPhpDocs = true; $this->analyse([__DIR__ . '/data/bug-8697.php'], []); } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleNoBleedingEdgeTest.php deleted file mode 100644 index b0bb271349a..00000000000 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleNoBleedingEdgeTest.php +++ /dev/null @@ -1,146 +0,0 @@ - - */ -class InvalidPhpDocTagValueRuleNoBleedingEdgeTest extends RuleTestCase -{ - - private bool $checkAllInvalidPhpDocs; - - protected function getRule(): Rule - { - return new InvalidPhpDocTagValueRule( - self::getContainer()->getByType(Lexer::class), - self::getContainer()->getByType(PhpDocParser::class), - $this->checkAllInvalidPhpDocs, - false, - ); - } - - public function dataRule(): iterable - { - $errors = [ - [ - 'PHPDoc tag @param has invalid value (): Unexpected token "\n * ", expected type at offset 13', - 25, - ], - [ - 'PHPDoc tag @param has invalid value (A & B | C $paramNameA): Unexpected token "|", expected variable at offset 72', - 25, - ], - [ - 'PHPDoc tag @param has invalid value ((A & B $paramNameB): Unexpected token "$paramNameB", expected \')\' at offset 105', - 25, - ], - [ - 'PHPDoc tag @param has invalid value (~A & B $paramNameC): Unexpected token "~A", expected type at offset 127', - 25, - ], - [ - 'PHPDoc tag @var has invalid value (): Unexpected token "\n * ", expected type at offset 156', - 25, - ], - [ - 'PHPDoc tag @var has invalid value ($invalid): Unexpected token "$invalid", expected type at offset 165', - 25, - ], - [ - 'PHPDoc tag @var has invalid value ($invalid Foo): Unexpected token "$invalid", expected type at offset 182', - 25, - ], - [ - 'PHPDoc tag @return has invalid value (): Unexpected token "\n * ", expected type at offset 208', - 25, - ], - [ - 'PHPDoc tag @return has invalid value ([int, string]): Unexpected token "[", expected type at offset 220', - 25, - ], - [ - 'PHPDoc tag @return has invalid value (A & B | C): Unexpected token "|", expected TOKEN_OTHER at offset 251', - 25, - ], - [ - 'PHPDoc tag @var has invalid value (\\\Foo|\Bar $test): Unexpected token "\\\\\\\Foo|\\\Bar", expected type at offset 9', - 29, - ], - [ - 'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18', - 62, - ], - [ - 'PHPDoc tag @throws has invalid value ((\Exception): Unexpected token "*/", expected \')\' at offset 24', - 72, - ], - [ - 'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18', - 81, - ], - [ - 'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15', - 89, - ], - [ - 'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15', - 92, - ], - ]; - - yield [false, $errors]; - yield [true, array_merge($errors, [ - [ - 'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15', - 102, - ], - ])]; - } - - /** - * @dataProvider dataRule - * @param list $expectedErrors - */ - public function testRule(bool $checkAllInvalidPhpDocs, array $expectedErrors): void - { - $this->checkAllInvalidPhpDocs = $checkAllInvalidPhpDocs; - $this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], $expectedErrors); - } - - public function testBug4731(): void - { - $this->checkAllInvalidPhpDocs = true; - $this->analyse([__DIR__ . '/data/bug-4731.php'], []); - } - - public function testBug4731WithoutFirstTag(): void - { - $this->checkAllInvalidPhpDocs = true; - $this->analyse([__DIR__ . '/data/bug-4731-no-first-tag.php'], []); - } - - public function testInvalidTypeInTypeAlias(): void - { - $this->checkAllInvalidPhpDocs = true; - $this->analyse([__DIR__ . '/data/invalid-type-type-alias.php'], [ - [ - 'PHPDoc tag @phpstan-type InvalidFoo has invalid value: Unexpected token "{", expected TOKEN_PHPDOC_EOL at offset 65', - 15, - ], - ]); - } - - public static function getAdditionalConfigFiles(): array - { - // reset bleedingEdge - return []; - } - -} diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index c726559432c..a82880302d0 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -6,7 +6,6 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use function array_merge; /** * @extends RuleTestCase @@ -14,21 +13,19 @@ class InvalidPhpDocTagValueRuleTest extends RuleTestCase { - private bool $checkAllInvalidPhpDocs; - protected function getRule(): Rule { return new InvalidPhpDocTagValueRule( self::getContainer()->getByType(Lexer::class), self::getContainer()->getByType(PhpDocParser::class), - $this->checkAllInvalidPhpDocs, + true, true, ); } - public function dataRule(): iterable + public function testRule(): void { - $errors = [ + $this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], [ [ 'PHPDoc tag @param has invalid value (): Unexpected token "\n * ", expected type at offset 13 on line 2', 6, @@ -97,42 +94,25 @@ public function dataRule(): iterable 'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15 on line 1', 91, ], - ]; - - yield [false, $errors]; - yield [true, array_merge($errors, [ [ 'PHPDoc tag @var has invalid value ((Foo&): Unexpected token "*/", expected type at offset 15 on line 1', 101, ], - ])]; - } - - /** - * @dataProvider dataRule - * @param list $expectedErrors - */ - public function testRule(bool $checkAllInvalidPhpDocs, array $expectedErrors): void - { - $this->checkAllInvalidPhpDocs = $checkAllInvalidPhpDocs; - $this->analyse([__DIR__ . '/data/invalid-phpdoc.php'], $expectedErrors); + ]); } public function testBug4731(): void { - $this->checkAllInvalidPhpDocs = true; $this->analyse([__DIR__ . '/data/bug-4731.php'], []); } public function testBug4731WithoutFirstTag(): void { - $this->checkAllInvalidPhpDocs = true; $this->analyse([__DIR__ . '/data/bug-4731-no-first-tag.php'], []); } public function testInvalidTypeInTypeAlias(): void { - $this->checkAllInvalidPhpDocs = true; $this->analyse([__DIR__ . '/data/invalid-type-type-alias.php'], [ [ 'PHPDoc tag @phpstan-type InvalidFoo has invalid value: Unexpected token "{", expected TOKEN_PHPDOC_EOL at offset 65 on line 3', diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php deleted file mode 100644 index 9b0aaf913d7..00000000000 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ -class TypesAssignedToPropertiesRuleNoBleedingEdgeTest extends RuleTestCase -{ - - private bool $checkExplicitMixed = false; - - protected function getRule(): Rule - { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, true, false), new PropertyReflectionFinder()); - } - - public function testGenericObjectWithUnspecifiedTemplateTypes(): void - { - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [ - [ - 'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection.', - 67, - ], - ]); - } - - public function testGenericObjectWithUnspecifiedTemplateTypesLevel8(): void - { - $this->checkExplicitMixed = false; - $this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [ - [ - 'Property GenericObjectUnspecifiedTemplateTypes\Bar::$ints (GenericObjectUnspecifiedTemplateTypes\ArrayCollection) does not accept GenericObjectUnspecifiedTemplateTypes\ArrayCollection.', - 67, - ], - ]); - } - - public static function getAdditionalConfigFiles(): array - { - // no bleeding edge - return []; - } - -} From cc4bff635ebae19b010b81130360155692283ac6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 11:38:31 +0200 Subject: [PATCH 0142/3097] Fix detecting invalid PHPDocs --- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 3 -- conf/config.neon | 2 +- conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 13 +++----- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 32 +++++-------------- .../PhpDoc/InvalidPhpDocTagValueRule.php | 32 +++++-------------- .../PhpDoc/InvalidPHPStanDocTagRuleTest.php | 1 - .../PhpDoc/InvalidPhpDocTagValueRuleTest.php | 1 - 9 files changed, 21 insertions(+), 65 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8fb6e4da6a1..12694539ae9 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -39,7 +39,6 @@ parameters: newRuleLevelHelper: true instanceofType: true paramOutVariance: true - allInvalidPhpDocs: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true genericPrototypeMessage: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 13993940325..d7a7cc943b6 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -146,7 +146,6 @@ services: - class: PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule arguments: - checkAllInvalidPhpDocs: %featureToggles.allInvalidPhpDocs% invalidPhpDocTagLine: %featureToggles.invalidPhpDocTagLine% tags: - phpstan.rules.rule @@ -159,8 +158,6 @@ services: - phpstan.rules.rule - class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule - arguments: - checkAllInvalidPhpDocs: %featureToggles.allInvalidPhpDocs% tags: - phpstan.rules.rule - diff --git a/conf/config.neon b/conf/config.neon index 4d897a45874..ae411edcca9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -74,7 +74,7 @@ parameters: newRuleLevelHelper: false instanceofType: false paramOutVariance: false - allInvalidPhpDocs: false + strictStaticMethodTemplateTypeVariance: false propertyVariance: false genericPrototypeMessage: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 05d6d79f0cf..8b986b04425 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -69,7 +69,6 @@ parametersSchema: newRuleLevelHelper: bool() instanceofType: bool() paramOutVariance: bool() - allInvalidPhpDocs: bool() strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() genericPrototypeMessage: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 43ecfdd0a50..72605424119 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -217,12 +217,15 @@ private function getRuleRegistry(Container $container): RuleRegistry new InvalidPhpDocTagValueRule( $container->getByType(Lexer::class), $container->getByType(PhpDocParser::class), - $container->getParameter('featureToggles')['allInvalidPhpDocs'], $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), new IncompatibleSelfOutTypeRule($unresolvableTypeHelper, $genericObjectTypeCheck), new IncompatibleClassConstantPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper), + new InvalidPHPStanDocTagRule( + $container->getByType(Lexer::class), + $container->getByType(PhpDocParser::class), + ), new InvalidThrowsPhpDocValueRule($fileTypeMapper), // level 6 @@ -240,14 +243,6 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper); } - if ((bool) $container->getParameter('featureToggles')['allInvalidPhpDocs']) { - $rules[] = new InvalidPHPStanDocTagRule( - $container->getByType(Lexer::class), - $container->getByType(PhpDocParser::class), - true, - ); - } - if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index 923d65e143d..51b22dd5642 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -15,7 +15,7 @@ use function str_starts_with; /** - * @implements Rule + * @implements Rule */ final class InvalidPHPStanDocTagRule implements Rule { @@ -63,39 +63,23 @@ final class InvalidPHPStanDocTagRule implements Rule public function __construct( private Lexer $phpDocLexer, private PhpDocParser $phpDocParser, - private bool $checkAllInvalidPhpDocs, ) { } public function getNodeType(): string { - return Node::class; + return Node\Stmt::class; } public function processNode(Node $node, Scope $scope): array { - if (!$this->checkAllInvalidPhpDocs) { - if ( - !$node instanceof Node\Stmt\ClassLike - && !$node instanceof Node\FunctionLike - && !$node instanceof Node\Stmt\Foreach_ - && !$node instanceof Node\Stmt\Property - && !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignRef - && !$node instanceof Node\Stmt\ClassConst - ) { - return []; - } - } else { - // mirrored with InvalidPhpDocTagValueRule - if ($node instanceof VirtualNode) { - return []; - } - if ($node instanceof Node\Stmt\Expression) { - return []; - } - if ($node instanceof Node\Expr && !$node instanceof Node\Expr\Assign && !$node instanceof Node\Expr\AssignRef) { + // mirrored with InvalidPhpDocTagValueRule + if ($node instanceof VirtualNode) { + return []; + } + if ($node instanceof Node\Stmt\Expression) { + if (!$node->expr instanceof Node\Expr\Assign && !$node->expr instanceof Node\Expr\AssignRef) { return []; } } diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index fe40a1bd618..569c776df35 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -18,7 +18,7 @@ use function str_starts_with; /** - * @implements Rule + * @implements Rule */ final class InvalidPhpDocTagValueRule implements Rule { @@ -26,7 +26,6 @@ final class InvalidPhpDocTagValueRule implements Rule public function __construct( private Lexer $phpDocLexer, private PhpDocParser $phpDocParser, - private bool $checkAllInvalidPhpDocs, private bool $invalidPhpDocTagLine, ) { @@ -34,32 +33,17 @@ public function __construct( public function getNodeType(): string { - return Node::class; + return Node\Stmt::class; } public function processNode(Node $node, Scope $scope): array { - if (!$this->checkAllInvalidPhpDocs) { - if ( - !$node instanceof Node\Stmt\ClassLike - && !$node instanceof Node\FunctionLike - && !$node instanceof Node\Stmt\Foreach_ - && !$node instanceof Node\Stmt\Property - && !$node instanceof Node\Expr\Assign - && !$node instanceof Node\Expr\AssignRef - && !$node instanceof Node\Stmt\ClassConst - ) { - return []; - } - } else { - // mirrored with InvalidPHPStanDocTagRule - if ($node instanceof VirtualNode) { - return []; - } - if ($node instanceof Node\Stmt\Expression) { - return []; - } - if ($node instanceof Node\Expr && !$node instanceof Node\Expr\Assign && !$node instanceof Node\Expr\AssignRef) { + // mirrored with InvalidPHPStanDocTagRule + if ($node instanceof VirtualNode) { + return []; + } + if ($node instanceof Node\Stmt\Expression) { + if (!$node->expr instanceof Node\Expr\Assign && !$node->expr instanceof Node\Expr\AssignRef) { return []; } } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php index e3e82da0ff8..c664e1658af 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php @@ -18,7 +18,6 @@ protected function getRule(): Rule return new InvalidPHPStanDocTagRule( self::getContainer()->getByType(Lexer::class), self::getContainer()->getByType(PhpDocParser::class), - true, ); } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index a82880302d0..542b38f13fc 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -19,7 +19,6 @@ protected function getRule(): Rule self::getContainer()->getByType(Lexer::class), self::getContainer()->getByType(PhpDocParser::class), true, - true, ); } From cd5504c091f33071c843f681d9de3414ff4f8a2e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 11:45:44 +0200 Subject: [PATCH 0143/3097] Fix minor change --- tests/PHPStan/Parser/RichParserTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Parser/RichParserTest.php b/tests/PHPStan/Parser/RichParserTest.php index 103eb129b42..69fc99cbaba 100644 --- a/tests/PHPStan/Parser/RichParserTest.php +++ b/tests/PHPStan/Parser/RichParserTest.php @@ -193,7 +193,17 @@ public function dataLinesToIgnore(): iterable PHP_EOL . '/** @phpstan-ignore test */' . PHP_EOL, [ - 3 => ['test'], + 4 => ['test'], + ], + ]; + + yield [ + ' ['test'], ], ]; From 9bf85519da8fae2eaf737e4ed15ae83a2c752158 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 12:48:43 +0200 Subject: [PATCH 0144/3097] Fix baseline --- phpstan-baseline.neon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fbfeafa6089..7bbba41b082 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -70,7 +70,7 @@ parameters: path: src/Analyser/NodeScopeResolver.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Analyser/NodeScopeResolver.php @@ -276,7 +276,7 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -286,17 +286,17 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Expression\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php From d7c7266e877c5371eed0c7d81ae4d91eaef673f2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:11:59 +0200 Subject: [PATCH 0145/3097] Preparing PHAR - fix php constraint --- compiler/src/Console/PrepareCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/Console/PrepareCommand.php b/compiler/src/Console/PrepareCommand.php index 7bef8b99db3..6d23dde7a6f 100644 --- a/compiler/src/Console/PrepareCommand.php +++ b/compiler/src/Console/PrepareCommand.php @@ -65,7 +65,7 @@ private function fixComposerJson(string $buildDir): void unset($json['replace']); $json['name'] = 'phpstan/phpstan'; - $json['require']['php'] = '^7.2|^8.0'; + $json['require']['php'] = '^7.4|^8.0'; // simplify autoload (remove not packed build directory] $json['autoload']['psr-4']['PHPStan\\'] = 'src/'; From 084f3ec0e2535b2853cc588fa64d4f4f8f5291fd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:16:03 +0200 Subject: [PATCH 0146/3097] Fix tests --- .../ExistingClassesInTypehintsRuleTest.php | 17 +++++---- .../ExistingClassesInTypehintsRuleTest.php | 36 ++++++++++++------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 78415d46167..056740ed997 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -421,13 +421,18 @@ public function dataTrueTypes(): array ]; } - /** - * @dataProvider dataTrueTypes - * @param list $errors - */ - public function testTrueTypehint(int $phpVersion, array $errors): void + public function testTrueTypehint(): void { - $this->phpVersionId = $phpVersion; + if (PHP_VERSION_ID >= 80200) { + $errors = []; + } else { + $errors = [ + [ + 'Function NativeTrueType\alwaysTrue() has invalid return type NativeTrueType\true.', + 5, + ], + ]; + } $this->analyse([__DIR__ . '/data/true-typehint.php'], $errors); } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index b86302f4536..df40cbac04b 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -429,20 +429,30 @@ public function testEnums(): void ]); } - public function dataTrueTypes(): array + public function testTrueTypehint(): void { - return [ - [80200, []], - ]; - } - - /** - * @dataProvider dataTrueTypes - * @param list $errors - */ - public function testTrueTypehint(int $phpVersion, array $errors): void - { - $this->phpVersionId = $phpVersion; + if (PHP_VERSION_ID >= 80200) { + $errors = []; + } else { + $errors = [ + [ + 'Parameter $v of method NativeTrueType\Truthy::foo() has invalid type NativeTrueType\true.', + 10, + ], + [ + 'Method NativeTrueType\Truthy::foo() has invalid return type NativeTrueType\true.', + 10, + ], + [ + 'Parameter $trueUnion of method NativeTrueType\Truthy::trueUnion() has invalid type NativeTrueType\true.', + 14, + ], + [ + 'Method NativeTrueType\Truthy::trueUnionReturn() has invalid return type NativeTrueType\true.', + 31, + ], + ]; + } $this->analyse([__DIR__ . '/data/true-typehint.php'], $errors); } From 4aed8e4b77021953ea84ef31f2f170d58fe826c1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:35:21 +0200 Subject: [PATCH 0147/3097] Update PHPUnit --- .github/workflows/static-analysis.yml | 3 - .github/workflows/tests.yml | 4 - composer.json | 2 +- composer.lock | 270 ++++++++++++++------------ 4 files changed, 142 insertions(+), 137 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 24760de7563..a0c08f6171e 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -99,9 +99,6 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" - - name: "Update PHPUnit" - run: "composer update phpunit/phpunit -W" - - name: "Cache Result cache" uses: actions/cache@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 75f5de1627f..5533bd7e6a9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -65,10 +65,6 @@ jobs: shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - - name: "Update PHPUnit" - if: matrix.php-version != '7.3' - run: "composer update phpunit/phpunit -W" - - name: "Tests" run: "make tests" diff --git a/composer.json b/composer.json index 7e859b19e4f..64c143fd287 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ "phpstan/phpstan-nette": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "^9.5.4", + "phpunit/phpunit": "^9.6", "shipmonk/composer-dependency-analyser": "^1.5", "shipmonk/name-collision-detector": "^2.0" }, diff --git a/composer.lock b/composer.lock index aba98b14756..6ef0c6b966a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6ecf16b4614aa87f10e85e795af26169", + "content-hash": "b0a75f027cffe40f37c639ade2ee9361", "packages": [ { "name": "clue/ndjson-react", @@ -4400,30 +4400,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -4450,7 +4450,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -4466,7 +4466,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "jean85/pretty-package-versions", @@ -4529,16 +4529,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -4546,11 +4546,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -4576,7 +4577,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -4584,7 +4585,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "ondrejmirtes/simple-downgrader", @@ -4637,20 +4638,21 @@ }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -4691,9 +4693,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -5018,35 +5026,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -5055,7 +5063,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -5084,7 +5092,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -5092,7 +5100,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5337,50 +5345,50 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.23", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "888556852e7e9bbeeedb9656afe46118765ade34" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34", - "reference": "888556852e7e9bbeeedb9656afe46118765ade34", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -5388,7 +5396,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -5419,7 +5427,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -5429,22 +5438,26 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-08-22T14:01:36+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -5479,7 +5492,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -5487,7 +5500,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -5602,16 +5615,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -5664,7 +5677,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -5672,7 +5685,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -5733,16 +5746,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -5787,7 +5800,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -5795,7 +5808,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -5862,16 +5875,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -5927,7 +5940,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -5935,20 +5948,20 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -5991,7 +6004,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -5999,7 +6012,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -6172,16 +6185,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -6220,10 +6233,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -6231,20 +6244,20 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -6256,7 +6269,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6277,8 +6290,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -6286,20 +6298,20 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -6311,7 +6323,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -6334,7 +6346,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -6342,7 +6354,7 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -6523,16 +6535,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -6561,7 +6573,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -6569,7 +6581,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], From 078eeab53ced7dd6677068f7b13190d4e542ec3c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:43:12 +0200 Subject: [PATCH 0148/3097] Fix anonymous class --- src/Reflection/BetterReflection/BetterReflectionProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 6ae28ec9c62..54b26ec9f83 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -204,6 +204,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $scopeFile, ); $classNode->name = new Node\Identifier($className); + $classNode->namespacedName = null; if (isset(self::$anonymousClasses[$className])) { return self::$anonymousClasses[$className]; From 0e689a325434ab2658d0c23215a27f13b0ad1357 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:46:03 +0200 Subject: [PATCH 0149/3097] Fix RichParser --- src/Parser/RichParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index ed0f1840a0d..14ade510c31 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -183,7 +183,7 @@ private function getLinesToIgnore(array $tokens): array $line++; } if ($isNextLine || $isCurrentLine) { - $line += substr_count($token[1], "\n"); + $line += substr_count($token->text, "\n"); $lines[$line] = null; continue; From 9cdcd737698ba6c561451e938d3243974a5fc2b0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:49:37 +0200 Subject: [PATCH 0150/3097] Fix --- tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index 542b38f13fc..405e3668a87 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -122,7 +122,6 @@ public function testInvalidTypeInTypeAlias(): void public function testIgnoreWithinPhpDoc(): void { - $this->checkAllInvalidPhpDocs = true; $this->analyse([__DIR__ . '/data/ignore-line-within-phpdoc.php'], []); } From e2440242863fc43bf2bffc82f2763ae0ee307081 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:56:24 +0200 Subject: [PATCH 0151/3097] Fixes --- src/Analyser/NodeScopeResolver.php | 6 +----- src/Parser/PhpParserFactory.php | 2 +- src/Parser/StandaloneThrowExprVisitor.php | 2 +- src/Rules/ClassForbiddenNameCheck.php | 2 +- src/Rules/FunctionDefinitionCheck.php | 2 +- src/Rules/Names/UsedNamesRule.php | 9 ++++----- src/Rules/PhpDoc/PhpDocLineHelper.php | 2 +- src/Rules/Variables/ParameterOutExecutionEndTypeRule.php | 3 --- src/Testing/TypeInferenceTestCase.php | 4 ++-- src/Type/FileTypeMapper.php | 4 ---- 10 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f32e3345b74..8a992f5c139 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1556,8 +1556,7 @@ private function processStmtNode( } $throwNode = $throwPoint->getNode(); if ( - !$throwNode instanceof Throw_ - && !$throwNode instanceof Expr\Throw_ + !$throwNode instanceof Expr\Throw_ && !($throwNode instanceof Node\Stmt\Expression && $throwNode->expr instanceof Expr\Throw_) ) { $onlyExplicitIsThrow = false; @@ -1858,9 +1857,6 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { if ($const->namespacedName !== null) { $constantName = new Name\FullyQualified($const->namespacedName->toString()); } else { - if ($const->name->toString() === '') { - throw new ShouldNotHappenException('Constant cannot have a empty name'); - } $constantName = new Name\FullyQualified($const->name->toString()); } $scope = $scope->assignExpression(new ConstFetch($constantName), $scope->getType($const->value), $scope->getNativeType($const->value)); diff --git a/src/Parser/PhpParserFactory.php b/src/Parser/PhpParserFactory.php index dee78b35d2c..3a1f2cb4ead 100644 --- a/src/Parser/PhpParserFactory.php +++ b/src/Parser/PhpParserFactory.php @@ -8,7 +8,7 @@ use PhpParser\ParserAbstract; use PHPStan\Php\PhpVersion; -class PhpParserFactory +final class PhpParserFactory { public function __construct(private Lexer $lexer, private PhpVersion $phpVersion) diff --git a/src/Parser/StandaloneThrowExprVisitor.php b/src/Parser/StandaloneThrowExprVisitor.php index e1082461287..772a3a1c435 100644 --- a/src/Parser/StandaloneThrowExprVisitor.php +++ b/src/Parser/StandaloneThrowExprVisitor.php @@ -5,7 +5,7 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -class StandaloneThrowExprVisitor extends NodeVisitorAbstract +final class StandaloneThrowExprVisitor extends NodeVisitorAbstract { public const ATTRIBUTE_NAME = 'standaloneThrowExpr'; diff --git a/src/Rules/ClassForbiddenNameCheck.php b/src/Rules/ClassForbiddenNameCheck.php index c7658440abc..f1f9f032a31 100644 --- a/src/Rules/ClassForbiddenNameCheck.php +++ b/src/Rules/ClassForbiddenNameCheck.php @@ -73,7 +73,7 @@ public function checkClassNames(array $pairs): array $projectName, $className, )) - ->line($pair->getNode()->getLine()) + ->line($pair->getNode()->getStartLine()) ->identifier('class.prefixed') ->nonIgnorable(); diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 19a76e042c2..a9ea3ff5219 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -204,7 +204,7 @@ public function checkAnonymousFunction( foreach ($returnType->getReferencedClasses() as $returnTypeClass) { if (!$this->reflectionProvider->hasClass($returnTypeClass)) { $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $returnTypeClass)) - ->line($returnTypeNode->getLine()) + ->line($returnTypeNode->getStartLine()) ->identifier('class.notFound') ->build(); continue; diff --git a/src/Rules/Names/UsedNamesRule.php b/src/Rules/Names/UsedNamesRule.php index a1afbb742b5..5462137e5de 100644 --- a/src/Rules/Names/UsedNamesRule.php +++ b/src/Rules/Names/UsedNamesRule.php @@ -10,7 +10,6 @@ use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Trait_; use PhpParser\Node\Stmt\Use_; -use PhpParser\Node\Stmt\UseUse; use PHPStan\Analyser\Scope; use PHPStan\Node\FileNode; use PHPStan\Rules\IdentifierRuleError; @@ -100,7 +99,7 @@ private function findErrorsForNode(Node $node, string $namespace, array &$usedNa $namespace !== '' ? $namespace . '\\' . $node->name->toString() : $node->name->toString(), )) ->identifier(sprintf('%s.nameInUse', $type)) - ->line($node->getLine()) + ->line($node->getStartLine()) ->nonIgnorable() ->build(), ]; @@ -113,7 +112,7 @@ private function findErrorsForNode(Node $node, string $namespace, array &$usedNa } /** - * @param UseUse[] $uses + * @param Node\UseItem[] $uses * @param array $usedNames * @return list */ @@ -132,7 +131,7 @@ private function findErrorsInUses(array $uses, string $useGroupPrefix, string $l $use->getAlias()->toString(), )) ->identifier('use.nameInUse') - ->line($use->getLine()) + ->line($use->getStartLine()) ->nonIgnorable() ->build(); continue; @@ -142,7 +141,7 @@ private function findErrorsInUses(array $uses, string $useGroupPrefix, string $l return $errors; } - private function shouldBeIgnored(Use_|GroupUse|UseUse $use): bool + private function shouldBeIgnored(Use_|GroupUse|Node\UseItem $use): bool { return in_array($use->type, [Use_::TYPE_FUNCTION, Use_::TYPE_CONSTANT], true); } diff --git a/src/Rules/PhpDoc/PhpDocLineHelper.php b/src/Rules/PhpDoc/PhpDocLineHelper.php index b008e634707..a7894f762fa 100644 --- a/src/Rules/PhpDoc/PhpDocLineHelper.php +++ b/src/Rules/PhpDoc/PhpDocLineHelper.php @@ -19,7 +19,7 @@ public static function detectLine(PhpParserNode $node, PhpDocNode $phpDocNode): $phpDoc = $node->getDocComment(); if ($phpDocTagLine === null || $phpDoc === null) { - return $node->getLine(); + return $node->getStartLine(); } return $phpDoc->getStartLine() + $phpDocTagLine - 1; diff --git a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php index 177079ac6cc..9b42e1909ee 100644 --- a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php +++ b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php @@ -57,9 +57,6 @@ public function processNode(Node $node, Scope $scope): array return []; } } - if ($endNode instanceof Node\Stmt\Throw_) { - return []; - } $variant = ParametersAcceptorSelector::selectSingle($inFunction->getVariants()); $parameters = $variant->getParameters(); diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 4194d4f4d3e..874b6011568 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -178,7 +178,7 @@ public static function gatherAssertTypes(string $file): array 'Expected type must be a literal string, %s given in %s on line %d.', $expectedType->describe(VerbosityLevel::precise()), $relativePathHelper->getRelativePath($file), - $node->getLine(), + $node->getStartLine(), )); } $actualType = $scope->getType($node->getArgs()[1]->value); @@ -190,7 +190,7 @@ public static function gatherAssertTypes(string $file): array 'Expected type must be a literal string, %s given in %s on line %d.', $expectedType->describe(VerbosityLevel::precise()), $relativePathHelper->getRelativePath($file), - $node->getLine(), + $node->getStartLine(), )); } diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index a0af548147c..9a14e3aec4b 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -482,10 +482,6 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $functionName = $functionStack[count($functionStack) - 1] ?? null; $nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName); - if ($namespace === '') { - throw new ShouldNotHappenException('Namespace cannot be empty.'); - } - if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { if (array_key_exists($nameScopeKey, $phpDocNodeMap)) { $phpDocNode = $phpDocNodeMap[$nameScopeKey]; From baaf9d9fa2474e5b06bf7066a66b5d8862dd6a65 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 13:56:51 +0200 Subject: [PATCH 0152/3097] Remove deprecated rule NoopRule --- conf/config.level4.neon | 7 -- src/Rules/DeadCode/NoopRule.php | 73 --------------- tests/PHPStan/Rules/DeadCode/NoopRuleTest.php | 93 ------------------- 3 files changed, 173 deletions(-) delete mode 100644 src/Rules/DeadCode/NoopRule.php delete mode 100644 tests/PHPStan/Rules/DeadCode/NoopRuleTest.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 8f3ecc86bcc..421cafcd9ea 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -96,13 +96,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\DeadCode\NoopRule - arguments: - better: %featureToggles.betterNoop% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule diff --git a/src/Rules/DeadCode/NoopRule.php b/src/Rules/DeadCode/NoopRule.php deleted file mode 100644 index ff0d6eb8e7e..00000000000 --- a/src/Rules/DeadCode/NoopRule.php +++ /dev/null @@ -1,73 +0,0 @@ - - */ -final class NoopRule implements Rule -{ - - public function __construct(private ExprPrinter $exprPrinter, private bool $better) - { - } - - public function getNodeType(): string - { - return Node\Stmt\Expression::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->better) { - // disabled in bleeding edge - return []; - } - $originalExpr = $node->expr; - $expr = $originalExpr; - if ( - $expr instanceof Node\Expr\Cast - || $expr instanceof Node\Expr\UnaryMinus - || $expr instanceof Node\Expr\UnaryPlus - || $expr instanceof Node\Expr\ErrorSuppress - ) { - $expr = $expr->expr; - } - - if (!$this->isNoopExpr($expr)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Expression "%s" on a separate line does not do anything.', - $this->exprPrinter->printExpr($originalExpr), - ))->line($expr->getStartLine()) - ->identifier('expr.resultUnused') - ->build(), - ]; - } - - public function isNoopExpr(Node\Expr $expr): bool - { - return $expr instanceof Node\Expr\Variable - || $expr instanceof Node\Expr\PropertyFetch - || $expr instanceof Node\Expr\StaticPropertyFetch - || $expr instanceof Node\Expr\NullsafePropertyFetch - || $expr instanceof Node\Expr\ArrayDimFetch - || $expr instanceof Node\Scalar - || $expr instanceof Node\Expr\Isset_ - || $expr instanceof Node\Expr\Empty_ - || $expr instanceof Node\Expr\ConstFetch - || $expr instanceof Node\Expr\ClassConstFetch; - } - -} diff --git a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php deleted file mode 100644 index edffaa5b9af..00000000000 --- a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php +++ /dev/null @@ -1,93 +0,0 @@ - - */ -class NoopRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new NoopRule(new ExprPrinter(new Printer()), false); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/noop.php'], [ - [ - 'Expression "$arr" on a separate line does not do anything.', - 9, - ], - [ - 'Expression "$arr[\'test\']" on a separate line does not do anything.', - 10, - ], - [ - 'Expression "$foo::$test" on a separate line does not do anything.', - 11, - ], - [ - 'Expression "$foo->test" on a separate line does not do anything.', - 12, - ], - [ - 'Expression "\'foo\'" on a separate line does not do anything.', - 14, - ], - [ - 'Expression "1" on a separate line does not do anything.', - 15, - ], - [ - 'Expression "@\'foo\'" on a separate line does not do anything.', - 17, - ], - [ - 'Expression "+1" on a separate line does not do anything.', - 18, - ], - [ - 'Expression "-1" on a separate line does not do anything.', - 19, - ], - [ - 'Expression "isset($test)" on a separate line does not do anything.', - 25, - ], - [ - 'Expression "empty($test)" on a separate line does not do anything.', - 26, - ], - [ - 'Expression "true" on a separate line does not do anything.', - 27, - ], - [ - 'Expression "\DeadCodeNoop\Foo::TEST" on a separate line does not do anything.', - 28, - ], - [ - 'Expression "(string) 1" on a separate line does not do anything.', - 30, - ], - ]); - } - - public function testNullsafe(): void - { - $this->analyse([__DIR__ . '/data/nullsafe-property-fetch-noop.php'], [ - [ - 'Expression "$ref?->name" on a separate line does not do anything.', - 10, - ], - ]); - } - -} From 7501f2f73be42279fe769bb9908da30f1ffdd8e2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 14:01:09 +0200 Subject: [PATCH 0153/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 64c143fd287..e4e61cd5328 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.1.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.42.0.3", + "ondrejmirtes/better-reflection": "6.42.0.6", "phpstan/php-8-stubs": "0.3.101", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 6ef0c6b966a..0aba247c22c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b0a75f027cffe40f37c639ade2ee9361", + "content-hash": "bba4725ca58df1d370b5aa291335076d", "packages": [ { "name": "clue/ndjson-react", @@ -2179,16 +2179,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.42.0.3", + "version": "6.42.0.6", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "bdb626a5e2fb52bfe3fec1d367a9c72e48550954" + "reference": "955eefa555a862d35c298c69042a176bb39f88e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/bdb626a5e2fb52bfe3fec1d367a9c72e48550954", - "reference": "bdb626a5e2fb52bfe3fec1d367a9c72e48550954", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/955eefa555a862d35c298c69042a176bb39f88e2", + "reference": "955eefa555a862d35c298c69042a176bb39f88e2", "shasum": "" }, "require": { @@ -2244,9 +2244,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.3" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.6" }, - "time": "2024-09-04T11:06:34+00:00" + "time": "2024-09-04T11:59:59+00:00" }, { "name": "phpstan/php-8-stubs", From ba66abe0a16e1c98f6241009acec756781d7e9a1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 14:07:14 +0200 Subject: [PATCH 0154/3097] Fix --- src/Parser/StandaloneThrowExprVisitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/StandaloneThrowExprVisitor.php b/src/Parser/StandaloneThrowExprVisitor.php index 772a3a1c435..386c903281a 100644 --- a/src/Parser/StandaloneThrowExprVisitor.php +++ b/src/Parser/StandaloneThrowExprVisitor.php @@ -10,7 +10,7 @@ final class StandaloneThrowExprVisitor extends NodeVisitorAbstract public const ATTRIBUTE_NAME = 'standaloneThrowExpr'; - public function enterNode(Node $node) + public function enterNode(Node $node): ?Node\Stmt\Expression { if (!$node instanceof Node\Stmt\Expression) { return null; From d0824eb2fd777dd668e93d9db3e3cfa5b43d5f1f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 14:07:39 +0200 Subject: [PATCH 0155/3097] Remove deprecated rule ImplodeFunctionRule --- conf/config.level5.neon | 6 -- src/Rules/Functions/ImplodeFunctionRule.php | 84 ------------------- .../Functions/ImplodeFunctionRuleTest.php | 61 -------------- 3 files changed, 151 deletions(-) delete mode 100644 src/Rules/Functions/ImplodeFunctionRule.php delete mode 100644 tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 184cee83b8b..470689b7c2c 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -42,12 +42,6 @@ services: - class: PHPStan\Rules\Functions\CallUserFuncRule - - - class: PHPStan\Rules\Functions\ImplodeFunctionRule - arguments: - disabled: %featureToggles.checkParameterCastableToStringFunctions% - tags: - - phpstan.rules.rule - class: PHPStan\Rules\Functions\ParameterCastableToStringRule - diff --git a/src/Rules/Functions/ImplodeFunctionRule.php b/src/Rules/Functions/ImplodeFunctionRule.php deleted file mode 100644 index 93ade0dafca..00000000000 --- a/src/Rules/Functions/ImplodeFunctionRule.php +++ /dev/null @@ -1,84 +0,0 @@ - - */ -final class ImplodeFunctionRule implements Rule -{ - - public function __construct( - private ReflectionProvider $reflectionProvider, - private RuleLevelHelper $ruleLevelHelper, - private bool $disabled, - ) - { - } - - public function getNodeType(): string - { - return FuncCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->disabled) { - return []; - } - - if (!($node->name instanceof Node\Name)) { - return []; - } - - $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); - if (!in_array($functionName, ['implode', 'join'], true)) { - return []; - } - - $args = $node->getArgs(); - if (count($args) === 1) { - $arrayArg = $args[0]->value; - $paramNo = 1; - } elseif (count($args) === 2) { - $arrayArg = $args[1]->value; - $paramNo = 2; - } else { - return []; - } - - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $arrayArg, - '', - static fn (Type $type): bool => !$type->getIterableValueType()->toString() instanceof ErrorType, - ); - - if ($typeResult->getType() instanceof ErrorType - || !$typeResult->getType()->getIterableValueType()->toString() instanceof ErrorType) { - return []; - } - - return [ - RuleErrorBuilder::message( - sprintf('Parameter #%d $array of function %s expects array, %s given.', $paramNo, $functionName, $typeResult->getType()->describe(VerbosityLevel::typeOnly())), - )->identifier('argument.type')->build(), - ]; - } - -} diff --git a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php deleted file mode 100644 index 44755df63d4..00000000000 --- a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php +++ /dev/null @@ -1,61 +0,0 @@ - - */ -class ImplodeFunctionRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - $broker = $this->createReflectionProvider(); - return new ImplodeFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false, false, true, false), false); - } - - public function testFile(): void - { - $this->analyse([__DIR__ . '/data/implode.php'], [ - [ - 'Parameter #2 $array of function implode expects array, array|string> given.', - 9, - ], - [ - 'Parameter #1 $array of function implode expects array, array> given.', - 11, - ], - [ - 'Parameter #1 $array of function implode expects array, array> given.', - 12, - ], - [ - 'Parameter #1 $array of function implode expects array, array> given.', - 13, - ], - [ - 'Parameter #2 $array of function implode expects array, array> given.', - 15, - ], - [ - 'Parameter #2 $array of function join expects array, array> given.', - 16, - ], - ]); - } - - public function testBug6000(): void - { - $this->analyse([__DIR__ . '/../Arrays/data/bug-6000.php'], []); - } - - public function testBug8467a(): void - { - $this->analyse([__DIR__ . '/../Arrays/data/bug-8467a.php'], []); - } - -} From 507fdcae01c19aa03f7bd8109c9a08f50553694a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 14:16:04 +0200 Subject: [PATCH 0156/3097] Fix tests --- .../Functions/ArrowFunctionReturnTypeRuleTest.php | 13 ++++++++++++- ...ingClassesInArrowFunctionTypehintsRuleTest.php | 9 ++++++++- .../data/arrow-function-never-return.php | 15 +++++++++++++++ .../data/arrow-functions-return-type.php | 12 ------------ .../Rules/Playground/FunctionNeverRuleTest.php | 5 +++++ .../Rules/Playground/MethodNeverRuleTest.php | 5 +++++ 6 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/arrow-function-never-return.php diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php index bf46cd56a69..8e530c62aaa 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php @@ -39,9 +39,20 @@ public function testRule(): void 'Anonymous function should return int but returns string.', 14, ], + + ]); + } + + public function testRuleNever(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/arrow-function-never-return.php'], [ [ 'Anonymous function should never return but return statement found.', - 44, + 12, ], ]); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index 2417944d1af..e670804dcd6 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -289,7 +289,14 @@ public function testIntersectionTypes(int $phpVersion, array $errors): void public function testNever(): void { $errors = []; - if (PHP_VERSION_ID < 80200) { + if (PHP_VERSION_ID < 80100) { + $errors = [ + [ + 'Anonymous function has invalid return type ArrowFunctionNever\never.', + 6, + ], + ]; + } elseif (PHP_VERSION_ID < 80200) { $errors = [ [ 'Never return type in arrow function is supported only on PHP 8.2 and later.', diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-never-return.php b/tests/PHPStan/Rules/Functions/data/arrow-function-never-return.php new file mode 100644 index 00000000000..5a9641fb065 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-never-return.php @@ -0,0 +1,15 @@ += 8.1 + +namespace ArrowFunctionNeverReturn; + +class Baz +{ + + public function doFoo(): void + { + $f = fn () => throw new \Exception(); + $g = fn (): never => throw new \Exception(); + $g = fn (): never => 1; + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php index 552bf901c69..4a18708fba6 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-functions-return-type.php @@ -33,15 +33,3 @@ public function doBar(): void } static fn (int $value): iterable => yield $value; - -class Baz -{ - - public function doFoo(): void - { - $f = fn () => throw new \Exception(); - $g = fn (): never => throw new \Exception(); - $g = fn (): never => 1; - } - -} diff --git a/tests/PHPStan/Rules/Playground/FunctionNeverRuleTest.php b/tests/PHPStan/Rules/Playground/FunctionNeverRuleTest.php index a75b82f7149..2f580113f58 100644 --- a/tests/PHPStan/Rules/Playground/FunctionNeverRuleTest.php +++ b/tests/PHPStan/Rules/Playground/FunctionNeverRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -18,6 +19,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1 or greater.'); + } + $this->analyse([__DIR__ . '/data/function-never.php'], [ [ 'Function FunctionNever\doBar() always throws an exception, it should have return type "never".', diff --git a/tests/PHPStan/Rules/Playground/MethodNeverRuleTest.php b/tests/PHPStan/Rules/Playground/MethodNeverRuleTest.php index 583c6a5a5f3..83e315479d6 100644 --- a/tests/PHPStan/Rules/Playground/MethodNeverRuleTest.php +++ b/tests/PHPStan/Rules/Playground/MethodNeverRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -18,6 +19,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1 or greater.'); + } + $this->analyse([__DIR__ . '/data/method-never.php'], [ [ 'Method MethodNever\Foo::doBar() always throws an exception, it should have return type "never".', From 7cb1f1bda8ba1ca3f75a1e2c1fb5834a35291092 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 14:40:27 +0200 Subject: [PATCH 0157/3097] Skip `mixed` tests on PHP < 8.0 --- ...namicReturnTypeExtensionTypeInferenceTest.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/nsrt/abs.php | 4 +++- tests/PHPStan/Analyser/nsrt/array-key-exists.php | 2 +- .../PHPStan/Analyser/nsrt/assert-conditional.php | 2 +- tests/PHPStan/Analyser/nsrt/assert-docblock.php | 2 +- tests/PHPStan/Analyser/nsrt/assert-empty.php | 2 +- .../PHPStan/Analyser/nsrt/assert-inheritance.php | 2 +- .../PHPStan/Analyser/nsrt/assert-intersected.php | 2 +- tests/PHPStan/Analyser/nsrt/assert-invariant.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-10037.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-10254.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-10473.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6293.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7141.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-7788.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7944.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8249.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8803.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9062.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9086.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9341.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-9472.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9764.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9867.php | 2 +- tests/PHPStan/Analyser/nsrt/class-implements.php | 2 +- .../nsrt/conditional-types-inference.php | 2 +- tests/PHPStan/Analyser/nsrt/ctype-digit.php | 4 +++- tests/PHPStan/Analyser/nsrt/enum_exists.php | 2 +- tests/PHPStan/Analyser/nsrt/falsy-isset.php | 2 +- .../PHPStan/Analyser/nsrt/filter-input-array.php | 4 +++- tests/PHPStan/Analyser/nsrt/filter-var-array.php | 2 +- .../PHPStan/Analyser/nsrt/generic-callables.php | 2 +- .../Analyser/nsrt/generic-method-tags.php | 2 +- tests/PHPStan/Analyser/nsrt/key-exists.php | 2 +- tests/PHPStan/Analyser/nsrt/mixed-typehint.php | 2 +- tests/PHPStan/Analyser/nsrt/offset-access.php | 2 +- tests/PHPStan/Reflection/MixedTypeTest.php | 5 +++++ .../Rules/Arrays/IterableInForeachRuleTest.php | 4 ++++ .../NonexistentOffsetInArrayDimFetchRuleTest.php | 4 ++++ .../Arrays/UnpackIterableInArrayRuleTest.php | 4 ++++ tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php | 4 ++++ .../ExistingClassInInstanceOfRuleTest.php | 5 +++++ .../ImpossibleCheckTypeFunctionCallRuleTest.php | 16 ++++++++++++++++ .../ImpossibleCheckTypeMethodCallRuleTest.php | 5 +++++ .../CallToFunctionParametersRuleTest.php | 12 ++++++++++++ ...ngClassesInArrowFunctionTypehintsRuleTest.php | 4 ++++ ...ExistingClassesInClosureTypehintsRuleTest.php | 4 ++++ .../Rules/Functions/ReturnTypeRuleTest.php | 4 ++++ .../Rules/Methods/CallMethodsRuleTest.php | 12 ++++++++++++ .../Rules/Methods/CallStaticMethodsRuleTest.php | 4 ++++ .../ExistingClassesInTypehintsRuleTest.php | 12 ++++++++++++ .../IncompatibleDefaultParameterTypeRuleTest.php | 5 +++++ .../Rules/Methods/OverridingMethodRuleTest.php | 4 ++++ .../Rules/Missing/MissingReturnRuleTest.php | 8 ++++++++ .../Operators/InvalidBinaryOperationRuleTest.php | 4 ++++ .../Operators/InvalidIncDecOperationRuleTest.php | 5 +++++ .../Operators/InvalidUnaryOperationRuleTest.php | 5 +++++ 58 files changed, 181 insertions(+), 37 deletions(-) diff --git a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php index 59ebef356ec..c76ca0ebca1 100644 --- a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php +++ b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php @@ -14,10 +14,10 @@ public function dataAsserts(): iterable if (PHP_VERSION_ID >= 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types-named-args.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7344.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7391b.php'); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 1d17f9ee483..f75730744dd 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -6431,7 +6431,7 @@ public function dataMisleadingTypes(): array '$foo->misleadingIntReturnType()', ], [ - 'mixed', + PHP_VERSION_ID >= 80000 ? 'mixed' : 'MisleadingTypes\mixed', '$foo->misleadingMixedReturnType()', ], ]; diff --git a/tests/PHPStan/Analyser/nsrt/abs.php b/tests/PHPStan/Analyser/nsrt/abs.php index eb644eb4bd5..506f436c02e 100644 --- a/tests/PHPStan/Analyser/nsrt/abs.php +++ b/tests/PHPStan/Analyser/nsrt/abs.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Abs; diff --git a/tests/PHPStan/Analyser/nsrt/array-key-exists.php b/tests/PHPStan/Analyser/nsrt/array-key-exists.php index ed6f552d155..17e49019c73 100644 --- a/tests/PHPStan/Analyser/nsrt/array-key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/array-key-exists.php @@ -1,4 +1,4 @@ -= 8.0 namespace ArrayKeyExistsExtension; diff --git a/tests/PHPStan/Analyser/nsrt/assert-conditional.php b/tests/PHPStan/Analyser/nsrt/assert-conditional.php index 4e52490066a..4a8567a2dbd 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-conditional.php +++ b/tests/PHPStan/Analyser/nsrt/assert-conditional.php @@ -1,4 +1,4 @@ -= 8.0 namespace AssertConditional; diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index 1b094a1cd7b..b6391d651ab 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -1,4 +1,4 @@ -= 8.0 namespace AssertDocblock; diff --git a/tests/PHPStan/Analyser/nsrt/assert-empty.php b/tests/PHPStan/Analyser/nsrt/assert-empty.php index 12176791a3a..73f15aade7a 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-empty.php +++ b/tests/PHPStan/Analyser/nsrt/assert-empty.php @@ -1,4 +1,4 @@ -= 8.0 namespace AssertEmpty; diff --git a/tests/PHPStan/Analyser/nsrt/assert-inheritance.php b/tests/PHPStan/Analyser/nsrt/assert-inheritance.php index b9b362172ea..ffc9552321a 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-inheritance.php +++ b/tests/PHPStan/Analyser/nsrt/assert-inheritance.php @@ -1,4 +1,4 @@ -= 8.0 namespace AssertInheritance; diff --git a/tests/PHPStan/Analyser/nsrt/assert-intersected.php b/tests/PHPStan/Analyser/nsrt/assert-intersected.php index a39ffe14360..17aa63957a3 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-intersected.php +++ b/tests/PHPStan/Analyser/nsrt/assert-intersected.php @@ -1,4 +1,4 @@ -= 8.0 namespace AssertIntersected; diff --git a/tests/PHPStan/Analyser/nsrt/assert-invariant.php b/tests/PHPStan/Analyser/nsrt/assert-invariant.php index b7368f06e9d..4efe160b181 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-invariant.php +++ b/tests/PHPStan/Analyser/nsrt/assert-invariant.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace AssertInvariant; diff --git a/tests/PHPStan/Analyser/nsrt/bug-10037.php b/tests/PHPStan/Analyser/nsrt/bug-10037.php index 58adb961c16..56c49c331bd 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10037.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10037.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug10037; diff --git a/tests/PHPStan/Analyser/nsrt/bug-10254.php b/tests/PHPStan/Analyser/nsrt/bug-10254.php index 3299015ca0b..a16ed81f044 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10254.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10254.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug10254; diff --git a/tests/PHPStan/Analyser/nsrt/bug-10473.php b/tests/PHPStan/Analyser/nsrt/bug-10473.php index d07a7f6804c..bad001bea00 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10473.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10473.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug10473; diff --git a/tests/PHPStan/Analyser/nsrt/bug-6293.php b/tests/PHPStan/Analyser/nsrt/bug-6293.php index 0a5c8548be1..993f7b470e5 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6293.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6293.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug6239; diff --git a/tests/PHPStan/Analyser/nsrt/bug-7141.php b/tests/PHPStan/Analyser/nsrt/bug-7141.php index 277b00d9e60..2cf34a5733d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7141.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7141.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug7141; diff --git a/tests/PHPStan/Analyser/nsrt/bug-7788.php b/tests/PHPStan/Analyser/nsrt/bug-7788.php index 944d10e7e41..fa5c6a73aff 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7788.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7788.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug7788; diff --git a/tests/PHPStan/Analyser/nsrt/bug-7944.php b/tests/PHPStan/Analyser/nsrt/bug-7944.php index 737ab3dcb88..0d219b40b34 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7944.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7944.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug7944; diff --git a/tests/PHPStan/Analyser/nsrt/bug-8249.php b/tests/PHPStan/Analyser/nsrt/bug-8249.php index 960126723d1..46964dc0add 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8249.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8249.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug8249; diff --git a/tests/PHPStan/Analyser/nsrt/bug-8803.php b/tests/PHPStan/Analyser/nsrt/bug-8803.php index a1d9ad568bc..88af4df14d0 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8803.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8803.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug8803; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9062.php b/tests/PHPStan/Analyser/nsrt/bug-9062.php index a4c8cc62510..7280c8634cb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9062.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9062.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug9062; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9086.php b/tests/PHPStan/Analyser/nsrt/bug-9086.php index db0110f2f4f..e099f4eec1d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9086.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9086.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug9086; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9341.php b/tests/PHPStan/Analyser/nsrt/bug-9341.php index 2c1a90f5bd5..3265c4a7b0b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9341.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9341.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug9341; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9472.php b/tests/PHPStan/Analyser/nsrt/bug-9472.php index 923c0534e60..e81f67b7ea9 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9472.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9472.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug9472; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9764.php b/tests/PHPStan/Analyser/nsrt/bug-9764.php index 15807d0b1e0..f24b810fe8d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9764.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9764.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug9764; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9867.php b/tests/PHPStan/Analyser/nsrt/bug-9867.php index 7c677aa8d6f..6ab9515b87c 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9867.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9867.php @@ -1,4 +1,4 @@ -= 8.0 declare(strict_types=1); diff --git a/tests/PHPStan/Analyser/nsrt/class-implements.php b/tests/PHPStan/Analyser/nsrt/class-implements.php index acd6a616ae7..316c8e8ed42 100644 --- a/tests/PHPStan/Analyser/nsrt/class-implements.php +++ b/tests/PHPStan/Analyser/nsrt/class-implements.php @@ -1,4 +1,4 @@ -= 8.0 namespace ClassImplements; diff --git a/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php b/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php index 55335c6e2e1..89bfa50a22b 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php @@ -1,4 +1,4 @@ -= 8.0 namespace ConditionalTypesInference; diff --git a/tests/PHPStan/Analyser/nsrt/ctype-digit.php b/tests/PHPStan/Analyser/nsrt/ctype-digit.php index 835ba4fdccd..00a803d52d4 100644 --- a/tests/PHPStan/Analyser/nsrt/ctype-digit.php +++ b/tests/PHPStan/Analyser/nsrt/ctype-digit.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types=1); namespace CtypeDigit; diff --git a/tests/PHPStan/Analyser/nsrt/enum_exists.php b/tests/PHPStan/Analyser/nsrt/enum_exists.php index 33f12009244..37809016ad5 100644 --- a/tests/PHPStan/Analyser/nsrt/enum_exists.php +++ b/tests/PHPStan/Analyser/nsrt/enum_exists.php @@ -1,4 +1,4 @@ -= 8.0 namespace EnumExists; diff --git a/tests/PHPStan/Analyser/nsrt/falsy-isset.php b/tests/PHPStan/Analyser/nsrt/falsy-isset.php index bce229826ae..eb11c5254d4 100644 --- a/tests/PHPStan/Analyser/nsrt/falsy-isset.php +++ b/tests/PHPStan/Analyser/nsrt/falsy-isset.php @@ -1,4 +1,4 @@ -= 8.0 namespace FalsyIsset; diff --git a/tests/PHPStan/Analyser/nsrt/filter-input-array.php b/tests/PHPStan/Analyser/nsrt/filter-input-array.php index 706a300680c..d773c237f86 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-input-array.php +++ b/tests/PHPStan/Analyser/nsrt/filter-input-array.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types=1); namespace FilterVarArray; diff --git a/tests/PHPStan/Analyser/nsrt/filter-var-array.php b/tests/PHPStan/Analyser/nsrt/filter-var-array.php index 52261b0e8a4..38db914722c 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var-array.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var-array.php @@ -1,4 +1,4 @@ -= 8.0 namespace FilterVarArray; diff --git a/tests/PHPStan/Analyser/nsrt/generic-callables.php b/tests/PHPStan/Analyser/nsrt/generic-callables.php index 94bb3238a25..9fde8228941 100644 --- a/tests/PHPStan/Analyser/nsrt/generic-callables.php +++ b/tests/PHPStan/Analyser/nsrt/generic-callables.php @@ -1,4 +1,4 @@ -= 8.0 namespace GenericCallables; diff --git a/tests/PHPStan/Analyser/nsrt/generic-method-tags.php b/tests/PHPStan/Analyser/nsrt/generic-method-tags.php index 92fdfaef5c6..0aab6ea591c 100644 --- a/tests/PHPStan/Analyser/nsrt/generic-method-tags.php +++ b/tests/PHPStan/Analyser/nsrt/generic-method-tags.php @@ -1,4 +1,4 @@ -= 8.0 namespace GenericMethodTags; diff --git a/tests/PHPStan/Analyser/nsrt/key-exists.php b/tests/PHPStan/Analyser/nsrt/key-exists.php index 11c2ed6a2a3..0c98f24b2bd 100644 --- a/tests/PHPStan/Analyser/nsrt/key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/key-exists.php @@ -1,4 +1,4 @@ -= 8.0 namespace KeyExists; diff --git a/tests/PHPStan/Analyser/nsrt/mixed-typehint.php b/tests/PHPStan/Analyser/nsrt/mixed-typehint.php index 8d7ce4ad165..5b3c17cbb1a 100644 --- a/tests/PHPStan/Analyser/nsrt/mixed-typehint.php +++ b/tests/PHPStan/Analyser/nsrt/mixed-typehint.php @@ -1,4 +1,4 @@ -= 8.0 namespace MixedTypehint; diff --git a/tests/PHPStan/Analyser/nsrt/offset-access.php b/tests/PHPStan/Analyser/nsrt/offset-access.php index 505557b4522..593dd799ab1 100644 --- a/tests/PHPStan/Analyser/nsrt/offset-access.php +++ b/tests/PHPStan/Analyser/nsrt/offset-access.php @@ -1,4 +1,4 @@ -= 8.0 namespace OffsetAccess; diff --git a/tests/PHPStan/Reflection/MixedTypeTest.php b/tests/PHPStan/Reflection/MixedTypeTest.php index f6c511df33d..869f3c2bf85 100644 --- a/tests/PHPStan/Reflection/MixedTypeTest.php +++ b/tests/PHPStan/Reflection/MixedTypeTest.php @@ -6,12 +6,17 @@ use PhpParser\Node\Name; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\MixedType; +use const PHP_VERSION_ID; class MixedTypeTest extends PHPStanTestCase { public function testMixedType(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(Foo::class); $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index dbb64b29cab..31407e99be0 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -135,6 +135,10 @@ public function dataMixed(): array */ public function testMixed(bool $checkExplicitMixed, bool $checkImplicitMixed, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = $checkExplicitMixed; $this->checkImplicitMixed = $checkImplicitMixed; $this->analyse([__DIR__ . '/data/foreach-mixed.php'], $errors); diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index ffc6aa26d5d..bde2d1100e4 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -762,6 +762,10 @@ public function testBug10926(): void public function testMixed(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = true; $this->checkImplicitMixed = true; $this->analyse([__DIR__ . '/data/offset-access-mixed.php'], [ diff --git a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php index 32d49006861..c7de4bc7bff 100644 --- a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php @@ -107,6 +107,10 @@ public function dataMixed(): array */ public function testMixed(bool $checkExplicitMixed, bool $checkImplicitMixed, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = $checkExplicitMixed; $this->checkImplicitMixed = $checkImplicitMixed; $this->analyse([__DIR__ . '/data/unpack-mixed.php'], $errors); diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index 5734b479287..bc7cd35acb7 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -163,6 +163,10 @@ public function dataMixed(): array */ public function testMixed(bool $checkExplicitMixed, bool $checkImplicitMixed, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkImplicitMixed = $checkImplicitMixed; $this->checkExplicitMixed = $checkExplicitMixed; $this->analyse([__DIR__ . '/data/mixed-cast.php'], $errors); diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 4018d2ca627..0e6d0b066f0 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -7,6 +7,7 @@ use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -67,6 +68,10 @@ public function testClassExists(): void public function testBug7720(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-7720.php'], [ [ 'Instanceof between mixed and trait Bug7720\FooBar will always evaluate to false.', diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 88d9315d848..38d61a57dd1 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -46,6 +46,10 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testImpossibleCheckTypeFunctionCall(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse( @@ -274,6 +278,10 @@ public function testBug7898(): void public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkAlwaysTrueCheckTypeFunctionCall = false; $this->treatPhpDocTypesAsCertain = true; $this->analyse( @@ -610,6 +618,10 @@ public function testBug7079(): void public function testConditionalTypesInference(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/conditional-types-inference.php'], [ @@ -645,6 +657,10 @@ public function testBug6697(): void public function testBug6443(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6443.php'], []); diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 7a697e6ffa1..6504b21a6a7 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -199,6 +200,10 @@ public function testReportPhpDoc(): void public function testBug8169(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8169.php'], [ [ diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index d49987e3c58..174f8ece4a2 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1417,6 +1417,10 @@ public function testBug2508(): void public function testBug6175(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-6175.php'], []); } @@ -1600,6 +1604,10 @@ public function testBug9580(): void public function testBug7283(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-7283.php'], []); } @@ -1670,6 +1678,10 @@ public function testParamClosureThis(): void public function testBug10297(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-10297.php'], []); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index e670804dcd6..a409df43919 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -243,6 +243,10 @@ public function dataRequiredParameterAfterOptional(): array */ public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = $phpVersionId; $this->analyse([__DIR__ . '/data/required-parameter-after-optional-arrow.php'], $errors); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 86f87255730..9b1c1c329d4 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -287,6 +287,10 @@ public function dataRequiredParameterAfterOptional(): array */ public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = $phpVersionId; $this->analyse([__DIR__ . '/data/required-parameter-after-optional-closures.php'], $errors); } diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index f16d288869e..53476ec81bc 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -257,6 +257,10 @@ public function testBug8683(): void public function testBug7984(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = true; $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-7984.php'], []); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 3f29976a126..b5565f4d405 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1595,6 +1595,10 @@ public function dataExplicitMixed(): array */ public function testExplicitMixed(bool $checkExplicitMixed, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; @@ -2708,6 +2712,10 @@ public function testBug1517(): void public function testBug7593(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; @@ -3088,6 +3096,10 @@ public function testObjectShapes(): void public function testBug9951(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 27718dc9c79..d46969bdf3e 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -758,6 +758,10 @@ public function dataMixed(): array */ public function testMixed(bool $checkExplicitMixed, bool $checkImplicitMixed, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkThisOnly = false; $this->checkExplicitMixed = $checkExplicitMixed; $this->checkImplicitMixed = $checkImplicitMixed; diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index df40cbac04b..07ea2260ee0 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -362,6 +362,10 @@ public function dataRequiredParameterAfterOptional(): array */ public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = $phpVersionId; $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); } @@ -459,6 +463,10 @@ public function testTrueTypehint(): void public function testConditionalReturnType(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/conditional-return-type.php'], [ [ 'Template type T of method MethodConditionalReturnType\Container::notGet() is not referenced in a parameter.', @@ -474,6 +482,10 @@ public function testBug7519(): void public function testTemplateInParamOut(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/param-out.php'], [ [ 'Template type T of method ParamOutTemplate\FooBar::uselessLocalTemplate() is not referenced in a parameter.', diff --git a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php index e55eb6eedef..0f6d5b81f15 100644 --- a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -75,6 +76,10 @@ public function testDefaultValueForPromotedProperty(): void public function testBug10956(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-10956.php'], []); } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 826586689a9..75f9465e740 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -238,6 +238,10 @@ public function testParameterContravariance( array $expectedErrors, ): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = $phpVersion; $this->analyse([$file], $expectedErrors); } diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index ad9fc94be5d..533ad774ebd 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -265,6 +265,10 @@ public function dataCheckPhpDocMissingReturn(): array */ public function testCheckPhpDocMissingReturn(bool $checkPhpDocMissingReturn, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixedMissingReturn = true; $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; $this->analyse([__DIR__ . '/data/check-phpdoc-missing-return.php'], $errors); @@ -287,6 +291,10 @@ public function dataModelMixin(): array */ public function testModelMixin(bool $checkExplicitMixedMissingReturn): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; $this->checkPhpDocMissingReturn = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/model-mixin.php'], [ diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 41c947e9379..190b83798bb 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -306,6 +306,10 @@ public function testBug5309(): void public function testBinaryMixed(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = true; $this->checkImplicitMixed = true; $this->analyse([__DIR__ . '/data/invalid-binary-mixed.php'], [ diff --git a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php index 5042bb336cd..a70d03a6e77 100644 --- a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -69,6 +70,10 @@ public function testRule(): void public function testMixed(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = true; $this->checkImplicitMixed = true; $this->analyse([__DIR__ . '/data/invalid-inc-dec-mixed.php'], [ diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index 2475fa3a802..ddc41ed3371 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -96,6 +97,10 @@ public function testRule(): void public function testMixed(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkImplicitMixed = true; $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/invalid-unary-mixed.php'], [ From e0ee68d5cf0b6d3b3d66eff598a12dd67e93b02d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 15:01:24 +0200 Subject: [PATCH 0158/3097] Fix lint --- build/composer-dependency-analyser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/composer-dependency-analyser.php b/build/composer-dependency-analyser.php index 723a3ece2e9..7502680e13a 100644 --- a/build/composer-dependency-analyser.php +++ b/build/composer-dependency-analyser.php @@ -33,7 +33,7 @@ ) ->ignoreErrorsOnPackage('phpunit/phpunit', [ErrorType::DEV_DEPENDENCY_IN_PROD]) // prepared test tooling ->ignoreErrorsOnPackage('jetbrains/phpstorm-stubs', [ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV]) // there is no direct usage, but we need newer version then required by ondrejmirtes/BetterReflection - ->ignoreErrorsOnPath(__DIR__ . '/../tests', [ErrorType::UNKNOWN_CLASS, ErrorType::UNKNOWN_FUNCTION]) // to be able to test invalid symbols + ->ignoreErrorsOnPath(__DIR__ . '/../tests', [ErrorType::UNKNOWN_CLASS, ErrorType::UNKNOWN_FUNCTION, ErrorType::SHADOW_DEPENDENCY]) // to be able to test invalid symbols ->ignoreUnknownClasses([ 'JetBrains\PhpStorm\Pure', // not present on composer's classmap 'PHPStan\ExtensionInstaller\GeneratedConfig', // generated From 8b43cfaace4371163aca9d21295e68d065fbfaea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 15:08:50 +0200 Subject: [PATCH 0159/3097] Skip more `mixed` tests --- .../Analyser/AnalyserIntegrationTest.php | 12 +++++++ .../Analyser/NodeScopeResolverTest.php | 5 ++- tests/PHPStan/Analyser/nsrt/bug-10131.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7607.php | 4 ++- tests/PHPStan/Analyser/nsrt/bug-7685.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9105.php | 2 +- .../Analyser/nsrt/falsey-isset-certainty.php | 2 +- .../nsrt/falsey-ternary-certainty.php | 2 +- .../PHPStan/Analyser/nsrt/in_array_loose.php | 2 +- .../ImpossibleCheckTypeMethodCallRuleTest.php | 6 ++-- .../Rules/Comparison/data/bug-8169.php | 4 ++- .../ExistingClassesInTypehintsRuleTest.php | 12 +++++++ .../ExistingClassesInTypehintsRuleTest.php | 35 +++++++++++++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 31 ++++++++++++++++ .../data/method-misleading-mixed-return.php | 21 +++++++++++ .../Rules/Methods/data/returnTypes.php | 4 +-- .../NullsafePropertyFetchRuleTest.php | 4 +++ 17 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/method-misleading-mixed-return.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index b84ff5feb04..e3a3997bf39 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -827,6 +827,10 @@ public function testBug7094(): void public function testOffsetAccess(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $errors = $this->runAnalyse(__DIR__ . '/nsrt/offset-access.php'); $this->assertCount(1, $errors); $this->assertSame('PHPDoc tag @return contains unresolvable type.', $errors[0]->getMessage()); @@ -1063,6 +1067,10 @@ public function testBug8376(): void public function testAssertDocblock(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $errors = $this->runAnalyse(__DIR__ . '/nsrt/assert-docblock.php'); $this->assertCount(4, $errors); $this->assertSame('Call to method AssertDocblock\A::testInt() with string will always evaluate to false.', $errors[0]->getMessage()); @@ -1396,6 +1404,10 @@ public function testBug11147(): void public function testBug11283(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11283.php'); $this->assertNoErrors($errors); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ec6abc47e77..56017066d15 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -152,7 +152,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-7839.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-5333.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-8174.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8169.php'); + + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8169.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-8280.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8277.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-8113.php'); diff --git a/tests/PHPStan/Analyser/nsrt/bug-10131.php b/tests/PHPStan/Analyser/nsrt/bug-10131.php index d78066e2328..11088d2e44e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10131.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10131.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug10131; diff --git a/tests/PHPStan/Analyser/nsrt/bug-7607.php b/tests/PHPStan/Analyser/nsrt/bug-7607.php index b88d6e5c02c..e8d6aa5911e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7607.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7607.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug7607; diff --git a/tests/PHPStan/Analyser/nsrt/bug-7685.php b/tests/PHPStan/Analyser/nsrt/bug-7685.php index 580ad7f33f4..e5674250b49 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7685.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7685.php @@ -1,4 +1,4 @@ -= 8.0 namespace bug7685; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9105.php b/tests/PHPStan/Analyser/nsrt/bug-9105.php index 956d53f0552..296baba23d7 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9105.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9105.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug9105; diff --git a/tests/PHPStan/Analyser/nsrt/falsey-isset-certainty.php b/tests/PHPStan/Analyser/nsrt/falsey-isset-certainty.php index 1fbb9547acf..484d0363e3b 100644 --- a/tests/PHPStan/Analyser/nsrt/falsey-isset-certainty.php +++ b/tests/PHPStan/Analyser/nsrt/falsey-isset-certainty.php @@ -1,4 +1,4 @@ -= 8.0 namespace FalseyIssetCertainty; diff --git a/tests/PHPStan/Analyser/nsrt/falsey-ternary-certainty.php b/tests/PHPStan/Analyser/nsrt/falsey-ternary-certainty.php index 01045e25f96..cc831b87a48 100644 --- a/tests/PHPStan/Analyser/nsrt/falsey-ternary-certainty.php +++ b/tests/PHPStan/Analyser/nsrt/falsey-ternary-certainty.php @@ -1,4 +1,4 @@ -= 8.0 namespace FalseyTernaryCertainty; diff --git a/tests/PHPStan/Analyser/nsrt/in_array_loose.php b/tests/PHPStan/Analyser/nsrt/in_array_loose.php index 4600ae0a139..78d2899b8cb 100644 --- a/tests/PHPStan/Analyser/nsrt/in_array_loose.php +++ b/tests/PHPStan/Analyser/nsrt/in_array_loose.php @@ -1,4 +1,4 @@ -= 8.0 namespace InArrayLoose; diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 6504b21a6a7..29f63a6bf29 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -208,15 +208,15 @@ public function testBug8169(): void $this->analyse([__DIR__ . '/data/bug-8169.php'], [ [ 'Call to method Bug8169\HelloWorld::assertString() with string will always evaluate to true.', - 19, + 21, ], [ 'Call to method Bug8169\HelloWorld::assertString() with string will always evaluate to true.', - 26, + 28, ], [ 'Call to method Bug8169\HelloWorld::assertString() with int will always evaluate to false.', - 33, + 35, ], ]); } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8169.php b/tests/PHPStan/Rules/Comparison/data/bug-8169.php index a6c4d330257..e3ee4aa5a5d 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-8169.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-8169.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug8169; diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 056740ed997..06957666f54 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -368,6 +368,10 @@ public function dataRequiredParameterAfterOptional(): array */ public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = $phpVersionId; $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); } @@ -439,6 +443,10 @@ public function testTrueTypehint(): void public function testConditionalReturnType(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/conditional-return-type.php'], [ [ 'Template type T of function FunctionConditionalReturnType\notGet() is not referenced in a parameter.', @@ -449,6 +457,10 @@ public function testConditionalReturnType(): void public function testTemplateInParamOut(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/param-out.php'], [ [ 'Template type S of function ParamOutTemplate\uselessGeneric() is not referenced in a parameter.', diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 07ea2260ee0..8c9fc563e30 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -437,6 +437,25 @@ public function testTrueTypehint(): void { if (PHP_VERSION_ID >= 80200) { $errors = []; + } elseif (PHP_VERSION_ID >= 80000) { + $errors = [ + [ + 'Parameter $v of method NativeTrueType\Truthy::foo() has invalid type NativeTrueType\true.', + 10, + ], + [ + 'Method NativeTrueType\Truthy::foo() has invalid return type NativeTrueType\true.', + 10, + ], + [ + 'Parameter $trueUnion of method NativeTrueType\Truthy::trueUnion() has invalid type NativeTrueType\true.', + 14, + ], + [ + 'Method NativeTrueType\Truthy::trueUnionReturn() has invalid return type NativeTrueType\true.', + 31, + ], + ]; } else { $errors = [ [ @@ -447,14 +466,30 @@ public function testTrueTypehint(): void 'Method NativeTrueType\Truthy::foo() has invalid return type NativeTrueType\true.', 10, ], + [ + "Method NativeTrueType\Truthy::trueUnion() uses native union types but they're supported only on PHP 8.0 and later.", + 14, + ], [ 'Parameter $trueUnion of method NativeTrueType\Truthy::trueUnion() has invalid type NativeTrueType\true.', 14, ], + [ + 'Parameter $trueUnion of method NativeTrueType\Truthy::trueUnion() has invalid type NativeTrueType\null.', + 14, + ], + [ + "Method NativeTrueType\Truthy::trueUnionReturn() uses native union types but they're supported only on PHP 8.0 and later.", + 31, + ], [ 'Method NativeTrueType\Truthy::trueUnionReturn() has invalid return type NativeTrueType\true.', 31, ], + [ + 'Method NativeTrueType\Truthy::trueUnionReturn() has invalid return type NativeTrueType\null.', + 31, + ], ]; } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6303bdcdc47..f080bd4be8f 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -283,8 +283,31 @@ public function testReturnTypeRule(): void ]); } + public function testMisleadingMixedType(): void + { + if (PHP_VERSION_ID >= 80000) { + $errors = []; + } else { + $errors = [ + [ + 'Method MethodMisleadingMixedReturn\Foo::misleadingMixedReturnType() should return MethodMisleadingMixedReturn\mixed but returns int.', + 11, + ], + [ + 'Method MethodMisleadingMixedReturn\Foo::misleadingMixedReturnType() should return MethodMisleadingMixedReturn\mixed but returns true.', + 14, + ], + ]; + } + $this->analyse([__DIR__ . '/data/method-misleading-mixed-return.php'], $errors); + } + public function testMisleadingTypehintsInClassWithoutNamespace(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/misleadingTypehints.php'], [ [ 'Method FooWithoutNamespace::misleadingBoolReturnType() should return boolean but returns true.', @@ -522,6 +545,10 @@ public function testBug2573(): void public function testBug4603(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-4603.php'], []); } @@ -774,6 +801,10 @@ public function testBug6358(): void public function testBug8071(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-8071.php'], [ [ diff --git a/tests/PHPStan/Rules/Methods/data/method-misleading-mixed-return.php b/tests/PHPStan/Rules/Methods/data/method-misleading-mixed-return.php new file mode 100644 index 00000000000..e7c1b2141a7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-misleading-mixed-return.php @@ -0,0 +1,21 @@ +analyse([__DIR__ . '/../../Analyser/nsrt/bug-9105.php'], []); } From f9af7116d067e64697de231eb7aa155323581c78 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 15:21:19 +0200 Subject: [PATCH 0160/3097] Compile and commit PHAR on 2.0.x --- .github/workflows/checksum-phar.yml | 2 +- .github/workflows/phar.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index 994f11ba063..b5dc04f6dcf 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -37,7 +37,7 @@ jobs: with: repository: phpstan/phpstan path: phpstan-dist - ref: 1.12.x + ref: 2.0.x - name: "Get info" id: info diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index f2f75906b3e..c6534ecd7bb 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -130,7 +130,7 @@ jobs: commit: name: "Commit PHAR" - if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/1.12.x' || startsWith(github.ref, 'refs/tags/'))" + if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/2.0.x' || startsWith(github.ref, 'refs/tags/'))" needs: compiler-tests runs-on: "ubuntu-latest" timeout-minutes: 60 From bb95c5a37b2bd6c0c99ee7f3fd13c17714e5d9f1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 15:31:23 +0200 Subject: [PATCH 0161/3097] Execute some workflows only on 2.0.x --- .github/workflows/apiref.yml | 2 +- .github/workflows/issue-bot.yml | 4 ++-- .github/workflows/merge-maintained-branch.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/apiref.yml b/.github/workflows/apiref.yml index 36b97306902..9375c12fc2f 100644 --- a/.github/workflows/apiref.yml +++ b/.github/workflows/apiref.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: push: branches: - - "1.12.x" + - "2.0.x" paths: - 'src/**' - 'composer.lock' diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index df03ddeb72d..5ef4bd62756 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -11,7 +11,7 @@ on: - 'changelog-generator/**' push: branches: - - "1.12.x" + - "2.0.x" paths-ignore: - 'compiler/**' - 'apigen/**' @@ -167,7 +167,7 @@ jobs: - name: "Evaluate results - push" working-directory: "issue-bot" - if: "github.repository_owner == 'phpstan' && github.ref == 'refs/heads/1.12.x'" + if: "github.repository_owner == 'phpstan' && github.ref == 'refs/heads/2.0.x'" env: GITHUB_PAT: ${{ secrets.PHPSTAN_BOT_TOKEN }} PHPSTAN_SRC_COMMIT_BEFORE: ${{ github.event.before }} diff --git a/.github/workflows/merge-maintained-branch.yml b/.github/workflows/merge-maintained-branch.yml index 4b609e26e2b..f00b6ac9223 100644 --- a/.github/workflows/merge-maintained-branch.yml +++ b/.github/workflows/merge-maintained-branch.yml @@ -5,7 +5,7 @@ name: Merge maintained branch on: push: branches: - - "1.11.x" + - "1.12.x" jobs: merge: @@ -20,5 +20,5 @@ jobs: with: github_token: "${{ secrets.PHPSTAN_BOT_TOKEN }}" source_ref: ${{ github.ref }} - target_branch: '1.12.x' + target_branch: '2.0.x' commit_message_template: 'Merge branch {source_ref} into {target_branch}' From 154d6723cc626e74f9736fa0aaca3b34a1ad6cfc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 15:36:01 +0200 Subject: [PATCH 0162/3097] Fix apiref.yml --- .github/workflows/apiref.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/apiref.yml b/.github/workflows/apiref.yml index 9375c12fc2f..32c74178410 100644 --- a/.github/workflows/apiref.yml +++ b/.github/workflows/apiref.yml @@ -13,6 +13,9 @@ on: - 'apigen/**' - '.github/workflows/apiref.yml' +env: + COMPOSER_ROOT_VERSION: "1.12.x-dev" + concurrency: group: apigen-${{ github.ref }} # will be canceled on subsequent pushes in branch cancel-in-progress: true From a6be9826f0e1d6e4b38f4db9d0cad0264d311d69 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:07:13 +0200 Subject: [PATCH 0163/3097] Get rid of JetBrains PhpStorm attributes in nette/utils Because the PHP-Scoper collapses the code to a single line, it breaks the signature on PHP < 8.0 because it "comments" the rest of the line --- compiler/build/scoper.inc.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 0ea6df31ec5..5b4a21c5b21 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -218,6 +218,16 @@ function (string $filePath, string $prefix, string $content): string { return str_replace(sprintf('use %s\\PhpParser;', $prefix), 'use PhpParser;', $content); }, + function (string $filePath, string $prefix, string $content): string { + if ( + $filePath !== 'vendor/nette/utils/src/Utils/Strings.php' + && $filePath !== 'vendor/nette/utils/src/Utils/Arrays.php' + ) { + return $content; + } + + return str_replace('#[\\JetBrains\\PhpStorm\\Language(\'RegExp\')] ', '', $content); + }, ], 'exclude-namespaces' => [ 'PHPStan', From cbdc65275fa8f9372eebe460b9b67c661923cfef Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:08:45 +0200 Subject: [PATCH 0164/3097] Get rid of old E2E test --- .github/workflows/e2e-tests.yml | 30 - tests/e2e/ResultCacheEndToEndTest.php | 208 --- tests/e2e/baseline.neon | 172 -- tests/e2e/phpstan.neon | 7 - tests/e2e/phpstan_resultcachepath.neon | 7 - tests/e2e/resultCache_1.php | 2019 ----------------------- tests/e2e/resultCache_2.php | 2023 ------------------------ tests/e2e/resultCache_3.php | 2016 ----------------------- 8 files changed, 6482 deletions(-) delete mode 100644 tests/e2e/ResultCacheEndToEndTest.php delete mode 100644 tests/e2e/baseline.neon delete mode 100644 tests/e2e/phpstan.neon delete mode 100644 tests/e2e/phpstan_resultcachepath.neon delete mode 100644 tests/e2e/resultCache_1.php delete mode 100644 tests/e2e/resultCache_2.php delete mode 100644 tests/e2e/resultCache_3.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 239a5e37812..dd8a44e406e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -26,36 +26,6 @@ concurrency: cancel-in-progress: true jobs: - result-cache-php-parser-e2e: - name: "Result cache PHP-Parser E2E test" - - runs-on: ${{ matrix.operating-system }} - timeout-minutes: 60 - - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest] - - steps: - - name: "Checkout" - uses: actions/checkout@v4 - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "8.1" - extensions: mbstring - ini-values: memory_limit=256M - - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - - name: "Tests" - run: | - git clone https://github.com/nikic/PHP-Parser.git tests/e2e/PHP-Parser && git -C tests/e2e/PHP-Parser checkout v3.1.5 && composer install --working-dir tests/e2e/PHP-Parser && vendor/bin/phpunit tests/e2e/ResultCacheEndToEndTest.php - result-cache-e2e-tests: name: "Result cache E2E tests" runs-on: ubuntu-latest diff --git a/tests/e2e/ResultCacheEndToEndTest.php b/tests/e2e/ResultCacheEndToEndTest.php deleted file mode 100644 index 1117af27b0c..00000000000 --- a/tests/e2e/ResultCacheEndToEndTest.php +++ /dev/null @@ -1,208 +0,0 @@ -&1', escapeshellarg(__DIR__ . '/PHP-Parser')), $outputLines, $exitCode); - if ($exitCode === 0) { - return; - } - - $this->fail(implode("\n", $outputLines)); - } - - public function testResultCache(): void - { - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $lexerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php'; - $lexerCode = FileReader::read($lexerPath); - $originalLexerCode = $lexerCode; - - $lexerCode = str_replace('@param string $code', '', $lexerCode); - $lexerCode = str_replace('public function startLexing($code', 'public function startLexing(\\PhpParser\\Node\\Expr\\MethodCall $code', $lexerCode); - file_put_contents($lexerPath, $lexerCode); - - $errorHandlerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/ErrorHandler.php'; - $errorHandlerContents = FileReader::read($errorHandlerPath); - $errorHandlerContents .= "\n\n"; - file_put_contents($errorHandlerPath, $errorHandlerContents); - - $bootstrapPath = __DIR__ . '/PHP-Parser/lib/bootstrap.php'; - $originalBootstrapContents = FileReader::read($bootstrapPath); - file_put_contents($bootstrapPath, "\n\n echo ['foo'];", FILE_APPEND); - - $this->runPhpstanWithErrors(); - $this->runPhpstanWithErrors(); - - file_put_contents($lexerPath, $originalLexerCode); - - unlink($bootstrapPath); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_3.php'); - - file_put_contents($bootstrapPath, $originalBootstrapContents); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - } - - private function runPhpstanWithErrors(): void - { - $result = $this->runPhpstan(1); - $this->assertIsArray($result['totals']); - $this->assertSame(3, $result['totals']['file_errors']); - $this->assertSame(0, $result['totals']['errors']); - - $fileHelper = new FileHelper(__DIR__); - - $this->assertSame('Parameter #1 $code of function token_get_all expects string, PhpParser\Node\Expr\MethodCall given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php')]['messages'][0]['message']); - $this->assertSame('Parameter #1 $code of method PhpParser\Lexer::startLexing() expects PhpParser\Node\Expr\MethodCall, string given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/ParserAbstract.php')]['messages'][0]['message']); - $this->assertSame('Parameter #1 (array{\'foo\'}) of echo cannot be converted to string.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/bootstrap.php')]['messages'][0]['message']); - $this->assertResultCache(__DIR__ . '/resultCache_2.php'); - } - - public function testResultCacheDeleteFile(): void - { - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - - $serializerPath = __DIR__ . '/PHP-Parser/lib/PhpParser/Serializer.php'; - $serializerCode = FileReader::read($serializerPath); - $originalSerializerCode = $serializerCode; - unlink($serializerPath); - - $fileHelper = new FileHelper(__DIR__); - - $result = $this->runPhpstan(1); - $this->assertIsArray($result['totals']); - $this->assertSame(1, $result['totals']['file_errors'], Json::encode($result)); - $this->assertSame(0, $result['totals']['errors'], Json::encode($result)); - - $message = $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][0]['message']; - $this->assertSame('Class PhpParser\\Serializer\\XML implements unknown interface PhpParser\\Serializer.', $message); - - file_put_contents($serializerPath, $originalSerializerCode); - $this->runPhpstan(0); - $this->assertResultCache(__DIR__ . '/resultCache_1.php'); - } - - public function testResultCachePath(): void - { - $this->runPhpstan(0, __DIR__ . '/phpstan_resultcachepath.neon'); - - $this->assertFileExists(sys_get_temp_dir() . '/phpstan/myResultCacheFile.php'); - $this->assertResultCache(__DIR__ . '/resultCache_1.php', sys_get_temp_dir() . '/phpstan/myResultCacheFile.php'); - } - - /** - * @return mixed[] - */ - private function runPhpstan(int $expectedExitCode, string $phpstanConfigPath = __DIR__ . '/phpstan.neon'): array - { - exec(sprintf( - '%s %s analyse -c %s -l 5 --no-progress --error-format json lib 2>&1', - escapeshellarg(PHP_BINARY), - escapeshellarg(__DIR__ . '/../../bin/phpstan'), - escapeshellarg($phpstanConfigPath), - ), $outputLines, $exitCode); - $output = implode("\n", $outputLines); - - try { - $json = Json::decode($output, Json::FORCE_ARRAY); - $this->assertIsArray($json); - } catch (JsonException $e) { - $this->fail(sprintf('%s: %s', $e->getMessage(), $output)); - } - - if ($exitCode !== $expectedExitCode) { - $this->fail($output); - } - - return $json; - } - - /** - * @param mixed[] $resultCache - * @return array> - */ - private function transformResultCache(array $resultCache): array - { - $new = []; - $this->assertIsArray($resultCache['dependencies']); - foreach ($resultCache['dependencies'] as $file => $data) { - $this->assertIsString($file); - $this->assertIsArray($data); - $this->assertIsArray($data['dependentFiles']); - - $files = []; - foreach ($data['dependentFiles'] as $filePath) { - $this->assertIsString($filePath); - $files[] = $this->relativizePath($filePath); - } - sort($files); - $new[$this->relativizePath($file)] = $files; - } - - ksort($new); - - return $new; - } - - private function relativizePath(string $path): string - { - $path = str_replace('\\', '/', $path); - $helper = new SimpleRelativePathHelper(str_replace('\\', '/', __DIR__ . '/PHP-Parser')); - return $helper->getRelativePath($path); - } - - private function assertResultCache(string $expectedCachePath, string $actualCachePath = __DIR__ . '/tmp/resultCache.php'): void - { - $resultCache = $this->transformResultCache(require $actualCachePath); - $expectedResultCachePath = require $expectedCachePath; - $this->assertSame($expectedResultCachePath, $resultCache); - } - -} diff --git a/tests/e2e/baseline.neon b/tests/e2e/baseline.neon deleted file mode 100644 index aad958b3b50..00000000000 --- a/tests/e2e/baseline.neon +++ /dev/null @@ -1,172 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$interfaces$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Builder/Class_.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$stmts\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Builder/Interface_.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$interfaces$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Builder/Interface_.php - - - - message: "#^Access to an undefined property PhpParser\\\\BuilderAbstract\\:\\:\\$flags\\.$#" - count: 2 - path: PHP-Parser/lib/PhpParser/BuilderAbstract.php - - - - message: "#^Method PhpParser\\\\BuilderAbstract\\:\\:normalizeValue\\(\\) should return PhpParser\\\\Node\\\\Expr but returns PhpParser\\\\Node\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/BuilderAbstract.php - - - - message: "#^PHPDoc tag @param has invalid value \\(string\\|Node\\\\Name Name to alias\\)\\: Unexpected token \"Name\", expected variable at offset 88$#" - count: 1 - path: PHP-Parser/lib/PhpParser/BuilderFactory.php - - - - message: "#^Expression \"@\\$undefinedVariable\" on a separate line does not do anything\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Lexer.php - - - - message: "#^Undefined variable\\: \\$undefinedVariable$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Lexer.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Lexer.php - - - - message: "#^Empty array passed to foreach\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Lexer/Emulative.php - - - - message: "#^Method PhpParser\\\\Node\\\\Expr\\\\Closure\\:\\:getStmts\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Node/Expr/Closure.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 4 - path: PHP-Parser/lib/PhpParser/Node/Name.php - - - - message: "#^Method PhpParser\\\\Node\\\\Stmt\\\\ClassMethod\\:\\:getStmts\\(\\) should return array\\ but returns array\\\\|null\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Node/Stmt/ClassMethod.php - - - - message: "#^Method PhpParser\\\\Node\\\\Stmt\\\\Function_\\:\\:getStmts\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Node/Stmt/Function_.php - - - - message: "#^PHPDoc tag @param for parameter \\$attributes with type array\\|null is not subtype of native type array\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Node/Stmt/TryCatch.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$name\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitor/NameResolver.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$namespacedName\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitor/NameResolver.php - - - - message: "#^Method PhpParser\\\\NodeVisitor\\\\NameResolver\\:\\:beforeTraverse\\(\\) should return array\\\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitor/NameResolver.php - - - - message: "#^Method PhpParser\\\\NodeVisitor\\\\NameResolver\\:\\:enterNode\\(\\) should return int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitor/NameResolver.php - - - - message: "#^Method PhpParser\\\\NodeVisitorAbstract\\:\\:afterTraverse\\(\\) should return array\\\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitorAbstract.php - - - - message: "#^Method PhpParser\\\\NodeVisitorAbstract\\:\\:beforeTraverse\\(\\) should return array\\\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitorAbstract.php - - - - message: "#^Method PhpParser\\\\NodeVisitorAbstract\\:\\:enterNode\\(\\) should return int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitorAbstract.php - - - - message: "#^Method PhpParser\\\\NodeVisitorAbstract\\:\\:leaveNode\\(\\) should return array\\\\|int\\|PhpParser\\\\Node\\|false\\|null but return statement is missing\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/NodeVisitorAbstract.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\\\Expr\\:\\:\\$class\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Parser/Php5.php - - - - message: "#^Access to an undefined property PhpParser\\\\Node\\\\Expr\\:\\:\\$name\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Parser/Php5.php - - - - message: "#^Variable \\$s might not be defined\\.$#" - count: 3 - path: PHP-Parser/lib/PhpParser/Parser/Php5.php - - - - message: "#^Variable \\$s might not be defined\\.$#" - count: 3 - path: PHP-Parser/lib/PhpParser/Parser/Php7.php - - - - message: "#^Comparison operation \"\\<\" between \\(array\\|float\\|int\\<0, max\\>\\) and int results in an error\\.$#" - count: 3 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Comparison operation \"\\>\\=\" between \\(array\\|float\\|int\\) and 0 results in an error\\.$#" - count: 3 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Property PhpParser\\\\ParserAbstract\\:\\:\\$endAttributeStack \\(array\\\\) does not accept array\\\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Property PhpParser\\\\ParserAbstract\\:\\:\\$endAttributes \\(array\\) does not accept string\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Variable \\$action might not be defined\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Variable \\$tokenValue might not be defined\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/ParserAbstract.php - - - - message: "#^Argument of an invalid type PhpParser\\\\Node supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: PHP-Parser/lib/PhpParser/Serializer/XML.php - diff --git a/tests/e2e/phpstan.neon b/tests/e2e/phpstan.neon deleted file mode 100644 index b9fe1cd9c14..00000000000 --- a/tests/e2e/phpstan.neon +++ /dev/null @@ -1,7 +0,0 @@ -includes: - - baseline.neon - -parameters: - phpVersion: 80000 - tmpDir: tmp - treatPhpDocTypesAsCertain: false diff --git a/tests/e2e/phpstan_resultcachepath.neon b/tests/e2e/phpstan_resultcachepath.neon deleted file mode 100644 index 3ad1d1a3b1a..00000000000 --- a/tests/e2e/phpstan_resultcachepath.neon +++ /dev/null @@ -1,7 +0,0 @@ -includes: - - baseline.neon - -parameters: - phpVersion: 80000 - resultCachePath: %tmpDir%/myResultCacheFile.php - treatPhpDocTypesAsCertain: false diff --git a/tests/e2e/resultCache_1.php b/tests/e2e/resultCache_1.php deleted file mode 100644 index fd17dda8f29..00000000000 --- a/tests/e2e/resultCache_1.php +++ /dev/null @@ -1,2019 +0,0 @@ - - array ( - 0 => 'lib/bootstrap.php', - ), - 'lib/PhpParser/Builder.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Class_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Declaration.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Function_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Interface_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Method.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Property.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Trait_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Use_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderFactory.php' => - array ( - ), - 'lib/PhpParser/Comment.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment/Doc.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/NodeDumper.php', - 8 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 9 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Comment/Doc.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Error.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler.php', - 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 3 => 'lib/PhpParser/Lexer.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/String_.php', - 6 => 'lib/PhpParser/Node/Stmt/Class_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Multiple.php', - 9 => 'lib/PhpParser/Parser/Php5.php', - 10 => 'lib/PhpParser/Parser/Php7.php', - 11 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 2 => 'lib/PhpParser/Lexer.php', - 3 => 'lib/PhpParser/Lexer/Emulative.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser.php', - 6 => 'lib/PhpParser/Parser/Multiple.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( - ), - 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Multiple.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/Lexer.php' => - array ( - 0 => 'lib/PhpParser/Lexer/Emulative.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Lexer/Emulative.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Node.php' => - array ( - 0 => 'lib/PhpParser/Builder.php', - 1 => 'lib/PhpParser/Builder/Class_.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - 13 => 'lib/PhpParser/Node/Arg.php', - 14 => 'lib/PhpParser/Node/Const_.php', - 15 => 'lib/PhpParser/Node/Expr.php', - 16 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 17 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 18 => 'lib/PhpParser/Node/Expr/Array_.php', - 19 => 'lib/PhpParser/Node/Expr/Assign.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 32 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 33 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 61 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 62 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 63 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 64 => 'lib/PhpParser/Node/Expr/Cast.php', - 65 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 71 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 72 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 73 => 'lib/PhpParser/Node/Expr/Clone_.php', - 74 => 'lib/PhpParser/Node/Expr/Closure.php', - 75 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 76 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 77 => 'lib/PhpParser/Node/Expr/Empty_.php', - 78 => 'lib/PhpParser/Node/Expr/Error.php', - 79 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 80 => 'lib/PhpParser/Node/Expr/Eval_.php', - 81 => 'lib/PhpParser/Node/Expr/Exit_.php', - 82 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 83 => 'lib/PhpParser/Node/Expr/Include_.php', - 84 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 85 => 'lib/PhpParser/Node/Expr/Isset_.php', - 86 => 'lib/PhpParser/Node/Expr/List_.php', - 87 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 88 => 'lib/PhpParser/Node/Expr/New_.php', - 89 => 'lib/PhpParser/Node/Expr/PostDec.php', - 90 => 'lib/PhpParser/Node/Expr/PostInc.php', - 91 => 'lib/PhpParser/Node/Expr/PreDec.php', - 92 => 'lib/PhpParser/Node/Expr/PreInc.php', - 93 => 'lib/PhpParser/Node/Expr/Print_.php', - 94 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 95 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 96 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 97 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 98 => 'lib/PhpParser/Node/Expr/Ternary.php', - 99 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 100 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 101 => 'lib/PhpParser/Node/Expr/Variable.php', - 102 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 103 => 'lib/PhpParser/Node/Expr/Yield_.php', - 104 => 'lib/PhpParser/Node/FunctionLike.php', - 105 => 'lib/PhpParser/Node/Name.php', - 106 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 107 => 'lib/PhpParser/Node/Name/Relative.php', - 108 => 'lib/PhpParser/Node/NullableType.php', - 109 => 'lib/PhpParser/Node/Param.php', - 110 => 'lib/PhpParser/Node/Scalar.php', - 111 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 112 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 113 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 114 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 115 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 123 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 124 => 'lib/PhpParser/Node/Scalar/String_.php', - 125 => 'lib/PhpParser/Node/Stmt.php', - 126 => 'lib/PhpParser/Node/Stmt/Break_.php', - 127 => 'lib/PhpParser/Node/Stmt/Case_.php', - 128 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 129 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 131 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 132 => 'lib/PhpParser/Node/Stmt/Class_.php', - 133 => 'lib/PhpParser/Node/Stmt/Const_.php', - 134 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 135 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 136 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 137 => 'lib/PhpParser/Node/Stmt/Do_.php', - 138 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 139 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 140 => 'lib/PhpParser/Node/Stmt/Else_.php', - 141 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 142 => 'lib/PhpParser/Node/Stmt/For_.php', - 143 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 144 => 'lib/PhpParser/Node/Stmt/Function_.php', - 145 => 'lib/PhpParser/Node/Stmt/Global_.php', - 146 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 147 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 148 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 149 => 'lib/PhpParser/Node/Stmt/If_.php', - 150 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 151 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 152 => 'lib/PhpParser/Node/Stmt/Label.php', - 153 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 154 => 'lib/PhpParser/Node/Stmt/Nop.php', - 155 => 'lib/PhpParser/Node/Stmt/Property.php', - 156 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 157 => 'lib/PhpParser/Node/Stmt/Return_.php', - 158 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 159 => 'lib/PhpParser/Node/Stmt/Static_.php', - 160 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 161 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 162 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 165 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 166 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 167 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 168 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 169 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 170 => 'lib/PhpParser/Node/Stmt/Use_.php', - 171 => 'lib/PhpParser/Node/Stmt/While_.php', - 172 => 'lib/PhpParser/NodeAbstract.php', - 173 => 'lib/PhpParser/NodeDumper.php', - 174 => 'lib/PhpParser/NodeTraverser.php', - 175 => 'lib/PhpParser/NodeTraverserInterface.php', - 176 => 'lib/PhpParser/NodeVisitor.php', - 177 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 178 => 'lib/PhpParser/NodeVisitorAbstract.php', - 179 => 'lib/PhpParser/Parser.php', - 180 => 'lib/PhpParser/Parser/Multiple.php', - 181 => 'lib/PhpParser/Parser/Php5.php', - 182 => 'lib/PhpParser/Parser/Php7.php', - 183 => 'lib/PhpParser/ParserAbstract.php', - 184 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 185 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 186 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Node/Arg.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 2 => 'lib/PhpParser/Node/Expr/New_.php', - 3 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Const_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 1 => 'lib/PhpParser/Node/Stmt/Const_.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr.php' => - array ( - 0 => 'lib/PhpParser/Builder/Param.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Node/Arg.php', - 4 => 'lib/PhpParser/Node/Const_.php', - 5 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 6 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 7 => 'lib/PhpParser/Node/Expr/Array_.php', - 8 => 'lib/PhpParser/Node/Expr/Assign.php', - 9 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 12 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 13 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 14 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 15 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 16 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 17 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 18 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 19 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 22 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 23 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 27 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 28 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 29 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 30 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 31 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 32 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 33 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 51 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 52 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 53 => 'lib/PhpParser/Node/Expr/Cast.php', - 54 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 55 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 56 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 57 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 58 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 59 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 60 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 61 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 62 => 'lib/PhpParser/Node/Expr/Clone_.php', - 63 => 'lib/PhpParser/Node/Expr/Closure.php', - 64 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 65 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 66 => 'lib/PhpParser/Node/Expr/Empty_.php', - 67 => 'lib/PhpParser/Node/Expr/Error.php', - 68 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 69 => 'lib/PhpParser/Node/Expr/Eval_.php', - 70 => 'lib/PhpParser/Node/Expr/Exit_.php', - 71 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 72 => 'lib/PhpParser/Node/Expr/Include_.php', - 73 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 74 => 'lib/PhpParser/Node/Expr/Isset_.php', - 75 => 'lib/PhpParser/Node/Expr/List_.php', - 76 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 77 => 'lib/PhpParser/Node/Expr/New_.php', - 78 => 'lib/PhpParser/Node/Expr/PostDec.php', - 79 => 'lib/PhpParser/Node/Expr/PostInc.php', - 80 => 'lib/PhpParser/Node/Expr/PreDec.php', - 81 => 'lib/PhpParser/Node/Expr/PreInc.php', - 82 => 'lib/PhpParser/Node/Expr/Print_.php', - 83 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 84 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 85 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 86 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 87 => 'lib/PhpParser/Node/Expr/Ternary.php', - 88 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 89 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 90 => 'lib/PhpParser/Node/Expr/Variable.php', - 91 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 92 => 'lib/PhpParser/Node/Expr/Yield_.php', - 93 => 'lib/PhpParser/Node/Param.php', - 94 => 'lib/PhpParser/Node/Scalar.php', - 95 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 96 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 97 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 98 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 99 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 100 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 101 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 102 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 103 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 104 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 105 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 106 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 107 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 108 => 'lib/PhpParser/Node/Scalar/String_.php', - 109 => 'lib/PhpParser/Node/Stmt/Break_.php', - 110 => 'lib/PhpParser/Node/Stmt/Case_.php', - 111 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 112 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 113 => 'lib/PhpParser/Node/Stmt/Do_.php', - 114 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 115 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 116 => 'lib/PhpParser/Node/Stmt/For_.php', - 117 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 118 => 'lib/PhpParser/Node/Stmt/Global_.php', - 119 => 'lib/PhpParser/Node/Stmt/If_.php', - 120 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 121 => 'lib/PhpParser/Node/Stmt/Return_.php', - 122 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 123 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 124 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 125 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 126 => 'lib/PhpParser/Node/Stmt/While_.php', - 127 => 'lib/PhpParser/NodeDumper.php', - 128 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 129 => 'lib/PhpParser/Parser/Php5.php', - 130 => 'lib/PhpParser/Parser/Php7.php', - 131 => 'lib/PhpParser/ParserAbstract.php', - 132 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 133 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Expr/Array_.php', - 2 => 'lib/PhpParser/Node/Expr/List_.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Array_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Assign.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 4 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 5 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 6 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 7 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 8 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 9 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 12 => 'lib/PhpParser/Parser/Php5.php', - 13 => 'lib/PhpParser/Parser/Php7.php', - 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 4 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 5 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 6 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 7 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 8 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 9 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 10 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 11 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 12 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 13 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 14 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 15 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 16 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 17 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 18 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 19 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 20 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 21 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 22 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 23 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 27 => 'lib/PhpParser/Parser/Php5.php', - 28 => 'lib/PhpParser/Parser/Php7.php', - 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 3 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 4 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 5 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 6 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Closure.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Closure.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Error.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Include_.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/List_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/New_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Print_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Variable.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Trait_.php', - 3 => 'lib/PhpParser/Node/Expr/Closure.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 6 => 'lib/PhpParser/Node/Stmt/Function_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/ParserAbstract.php', - 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 11 => 'lib/PhpParser/Node/Expr/Closure.php', - 12 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 13 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 14 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 15 => 'lib/PhpParser/Node/Expr/New_.php', - 16 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 17 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 18 => 'lib/PhpParser/Node/FunctionLike.php', - 19 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 20 => 'lib/PhpParser/Node/Name/Relative.php', - 21 => 'lib/PhpParser/Node/NullableType.php', - 22 => 'lib/PhpParser/Node/Param.php', - 23 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 24 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 25 => 'lib/PhpParser/Node/Stmt/Class_.php', - 26 => 'lib/PhpParser/Node/Stmt/Function_.php', - 27 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 28 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 29 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 30 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 31 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 32 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 33 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 34 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 35 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 36 => 'lib/PhpParser/Parser/Php5.php', - 37 => 'lib/PhpParser/Parser/Php7.php', - 38 => 'lib/PhpParser/ParserAbstract.php', - 39 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 40 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name/Relative.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/NullableType.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Param.php', - 4 => 'lib/PhpParser/BuilderAbstract.php', - 5 => 'lib/PhpParser/Node/Expr/Closure.php', - 6 => 'lib/PhpParser/Node/FunctionLike.php', - 7 => 'lib/PhpParser/Node/Param.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Function_.php', - 10 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Param.php', - 2 => 'lib/PhpParser/Node/Expr/Closure.php', - 3 => 'lib/PhpParser/Node/FunctionLike.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 5 => 'lib/PhpParser/Node/Stmt/Function_.php', - 6 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/ParserAbstract.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 3 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 8 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 9 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 10 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 11 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 12 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 13 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 14 => 'lib/PhpParser/Node/Scalar/String_.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/ParserAbstract.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( - 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 3 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 4 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/String_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Interface_.php', - 3 => 'lib/PhpParser/Builder/Method.php', - 4 => 'lib/PhpParser/Builder/Namespace_.php', - 5 => 'lib/PhpParser/Builder/Property.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/Closure.php', - 11 => 'lib/PhpParser/Node/Expr/New_.php', - 12 => 'lib/PhpParser/Node/FunctionLike.php', - 13 => 'lib/PhpParser/Node/Stmt/Break_.php', - 14 => 'lib/PhpParser/Node/Stmt/Case_.php', - 15 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 16 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 17 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 18 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 19 => 'lib/PhpParser/Node/Stmt/Class_.php', - 20 => 'lib/PhpParser/Node/Stmt/Const_.php', - 21 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 22 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 23 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 24 => 'lib/PhpParser/Node/Stmt/Do_.php', - 25 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 26 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 27 => 'lib/PhpParser/Node/Stmt/Else_.php', - 28 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 29 => 'lib/PhpParser/Node/Stmt/For_.php', - 30 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 31 => 'lib/PhpParser/Node/Stmt/Function_.php', - 32 => 'lib/PhpParser/Node/Stmt/Global_.php', - 33 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 34 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 35 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 36 => 'lib/PhpParser/Node/Stmt/If_.php', - 37 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 38 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 39 => 'lib/PhpParser/Node/Stmt/Label.php', - 40 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 41 => 'lib/PhpParser/Node/Stmt/Nop.php', - 42 => 'lib/PhpParser/Node/Stmt/Property.php', - 43 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 44 => 'lib/PhpParser/Node/Stmt/Return_.php', - 45 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 46 => 'lib/PhpParser/Node/Stmt/Static_.php', - 47 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 48 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 49 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 50 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 51 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 52 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 53 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 54 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 55 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 56 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 57 => 'lib/PhpParser/Node/Stmt/Use_.php', - 58 => 'lib/PhpParser/Node/Stmt/While_.php', - 59 => 'lib/PhpParser/NodeDumper.php', - 60 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 61 => 'lib/PhpParser/Parser/Php5.php', - 62 => 'lib/PhpParser/Parser/Php7.php', - 63 => 'lib/PhpParser/ParserAbstract.php', - 64 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Interface_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Property.php', - 4 => 'lib/PhpParser/Builder/Trait_.php', - 5 => 'lib/PhpParser/BuilderAbstract.php', - 6 => 'lib/PhpParser/Node/Expr/New_.php', - 7 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Class_.php', - 10 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 11 => 'lib/PhpParser/Node/Stmt/Property.php', - 12 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 13 => 'lib/PhpParser/NodeDumper.php', - 14 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( - 0 => 'lib/PhpParser/Builder/Method.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/ParserAbstract.php', - 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Property.php', - 3 => 'lib/PhpParser/BuilderAbstract.php', - 4 => 'lib/PhpParser/Node/Expr/New_.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 6 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 7 => 'lib/PhpParser/Node/Stmt/Property.php', - 8 => 'lib/PhpParser/NodeDumper.php', - 9 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 10 => 'lib/PhpParser/Parser/Php5.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/ParserAbstract.php', - 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/For_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/If_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Interface_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Label.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Namespace_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Property.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Node/Stmt/Property.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Static_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 2 => 'lib/PhpParser/Node/Stmt/Use_.php', - 3 => 'lib/PhpParser/NodeDumper.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser/Php5.php', - 6 => 'lib/PhpParser/Parser/Php7.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 3 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 4 => 'lib/PhpParser/NodeDumper.php', - 5 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 6 => 'lib/PhpParser/Parser/Php5.php', - 7 => 'lib/PhpParser/Parser/Php7.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/While_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/NodeAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Property.php', - 8 => 'lib/PhpParser/Builder/Trait_.php', - 9 => 'lib/PhpParser/Builder/Use_.php', - 10 => 'lib/PhpParser/BuilderAbstract.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - 12 => 'lib/PhpParser/Node/Arg.php', - 13 => 'lib/PhpParser/Node/Const_.php', - 14 => 'lib/PhpParser/Node/Expr.php', - 15 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 16 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 17 => 'lib/PhpParser/Node/Expr/Array_.php', - 18 => 'lib/PhpParser/Node/Expr/Assign.php', - 19 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 32 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 33 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 61 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 62 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 63 => 'lib/PhpParser/Node/Expr/Cast.php', - 64 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 65 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 71 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 72 => 'lib/PhpParser/Node/Expr/Clone_.php', - 73 => 'lib/PhpParser/Node/Expr/Closure.php', - 74 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 75 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 76 => 'lib/PhpParser/Node/Expr/Empty_.php', - 77 => 'lib/PhpParser/Node/Expr/Error.php', - 78 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 79 => 'lib/PhpParser/Node/Expr/Eval_.php', - 80 => 'lib/PhpParser/Node/Expr/Exit_.php', - 81 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 82 => 'lib/PhpParser/Node/Expr/Include_.php', - 83 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 84 => 'lib/PhpParser/Node/Expr/Isset_.php', - 85 => 'lib/PhpParser/Node/Expr/List_.php', - 86 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 87 => 'lib/PhpParser/Node/Expr/New_.php', - 88 => 'lib/PhpParser/Node/Expr/PostDec.php', - 89 => 'lib/PhpParser/Node/Expr/PostInc.php', - 90 => 'lib/PhpParser/Node/Expr/PreDec.php', - 91 => 'lib/PhpParser/Node/Expr/PreInc.php', - 92 => 'lib/PhpParser/Node/Expr/Print_.php', - 93 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 94 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 95 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 96 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 97 => 'lib/PhpParser/Node/Expr/Ternary.php', - 98 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 99 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 100 => 'lib/PhpParser/Node/Expr/Variable.php', - 101 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 102 => 'lib/PhpParser/Node/Expr/Yield_.php', - 103 => 'lib/PhpParser/Node/FunctionLike.php', - 104 => 'lib/PhpParser/Node/Name.php', - 105 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 106 => 'lib/PhpParser/Node/Name/Relative.php', - 107 => 'lib/PhpParser/Node/NullableType.php', - 108 => 'lib/PhpParser/Node/Param.php', - 109 => 'lib/PhpParser/Node/Scalar.php', - 110 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 111 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 112 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 113 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 114 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 115 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 123 => 'lib/PhpParser/Node/Scalar/String_.php', - 124 => 'lib/PhpParser/Node/Stmt.php', - 125 => 'lib/PhpParser/Node/Stmt/Break_.php', - 126 => 'lib/PhpParser/Node/Stmt/Case_.php', - 127 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 128 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 129 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 131 => 'lib/PhpParser/Node/Stmt/Class_.php', - 132 => 'lib/PhpParser/Node/Stmt/Const_.php', - 133 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 134 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 135 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 136 => 'lib/PhpParser/Node/Stmt/Do_.php', - 137 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 138 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 139 => 'lib/PhpParser/Node/Stmt/Else_.php', - 140 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 141 => 'lib/PhpParser/Node/Stmt/For_.php', - 142 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 143 => 'lib/PhpParser/Node/Stmt/Function_.php', - 144 => 'lib/PhpParser/Node/Stmt/Global_.php', - 145 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 146 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 147 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 148 => 'lib/PhpParser/Node/Stmt/If_.php', - 149 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 150 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 151 => 'lib/PhpParser/Node/Stmt/Label.php', - 152 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 153 => 'lib/PhpParser/Node/Stmt/Nop.php', - 154 => 'lib/PhpParser/Node/Stmt/Property.php', - 155 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 156 => 'lib/PhpParser/Node/Stmt/Return_.php', - 157 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 158 => 'lib/PhpParser/Node/Stmt/Static_.php', - 159 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 160 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 161 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 162 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 165 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 166 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 167 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 168 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 169 => 'lib/PhpParser/Node/Stmt/Use_.php', - 170 => 'lib/PhpParser/Node/Stmt/While_.php', - 171 => 'lib/PhpParser/NodeDumper.php', - 172 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 173 => 'lib/PhpParser/Parser/Php5.php', - 174 => 'lib/PhpParser/Parser/Php7.php', - 175 => 'lib/PhpParser/ParserAbstract.php', - 176 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 177 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/NodeDumper.php' => - array ( - ), - 'lib/PhpParser/NodeTraverser.php' => - array ( - ), - 'lib/PhpParser/NodeTraverserInterface.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - ), - 'lib/PhpParser/NodeVisitor.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - 1 => 'lib/PhpParser/NodeTraverserInterface.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/NodeVisitorAbstract.php', - ), - 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( - ), - 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - ), - 'lib/PhpParser/Parser.php' => - array ( - 0 => 'lib/PhpParser/Parser/Multiple.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Multiple.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php5.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php7.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Tokens.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/Lexer/Emulative.php', - ), - 'lib/PhpParser/ParserAbstract.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/ParserFactory.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( - 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Serializer.php' => - array ( - 0 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Serializer/XML.php' => - array ( - ), - 'lib/PhpParser/Unserializer.php' => - array ( - 0 => 'lib/PhpParser/Unserializer/XML.php', - ), - 'lib/PhpParser/Unserializer/XML.php' => - array ( - ), - 'lib/bootstrap.php' => - array ( - ), -); diff --git a/tests/e2e/resultCache_2.php b/tests/e2e/resultCache_2.php deleted file mode 100644 index fb4e9bb6d88..00000000000 --- a/tests/e2e/resultCache_2.php +++ /dev/null @@ -1,2023 +0,0 @@ - - array ( - 0 => 'lib/bootstrap.php', - ), - 'lib/PhpParser/Builder.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Class_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Declaration.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Function_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Interface_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Method.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Property.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Trait_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Use_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderFactory.php' => - array ( - ), - 'lib/PhpParser/Comment.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment/Doc.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/NodeDumper.php', - 8 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 9 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Comment/Doc.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Error.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler.php', - 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 3 => 'lib/PhpParser/Lexer.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/String_.php', - 6 => 'lib/PhpParser/Node/Stmt/Class_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Multiple.php', - 9 => 'lib/PhpParser/Parser/Php5.php', - 10 => 'lib/PhpParser/Parser/Php7.php', - 11 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 2 => 'lib/PhpParser/Lexer.php', - 3 => 'lib/PhpParser/Lexer/Emulative.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser.php', - 6 => 'lib/PhpParser/Parser/Multiple.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( - ), - 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Multiple.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/Lexer.php' => - array ( - 0 => 'lib/PhpParser/Lexer/Emulative.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Lexer/Emulative.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Node.php' => - array ( - 0 => 'lib/PhpParser/Builder.php', - 1 => 'lib/PhpParser/Builder/Class_.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - 13 => 'lib/PhpParser/Lexer.php', - 14 => 'lib/PhpParser/Node/Arg.php', - 15 => 'lib/PhpParser/Node/Const_.php', - 16 => 'lib/PhpParser/Node/Expr.php', - 17 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 18 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 19 => 'lib/PhpParser/Node/Expr/Array_.php', - 20 => 'lib/PhpParser/Node/Expr/Assign.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 32 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 33 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 34 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 61 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 62 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 63 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 64 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 65 => 'lib/PhpParser/Node/Expr/Cast.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 71 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 72 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 73 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 74 => 'lib/PhpParser/Node/Expr/Clone_.php', - 75 => 'lib/PhpParser/Node/Expr/Closure.php', - 76 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 77 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 78 => 'lib/PhpParser/Node/Expr/Empty_.php', - 79 => 'lib/PhpParser/Node/Expr/Error.php', - 80 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 81 => 'lib/PhpParser/Node/Expr/Eval_.php', - 82 => 'lib/PhpParser/Node/Expr/Exit_.php', - 83 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 84 => 'lib/PhpParser/Node/Expr/Include_.php', - 85 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 86 => 'lib/PhpParser/Node/Expr/Isset_.php', - 87 => 'lib/PhpParser/Node/Expr/List_.php', - 88 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 89 => 'lib/PhpParser/Node/Expr/New_.php', - 90 => 'lib/PhpParser/Node/Expr/PostDec.php', - 91 => 'lib/PhpParser/Node/Expr/PostInc.php', - 92 => 'lib/PhpParser/Node/Expr/PreDec.php', - 93 => 'lib/PhpParser/Node/Expr/PreInc.php', - 94 => 'lib/PhpParser/Node/Expr/Print_.php', - 95 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 96 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 97 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 98 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 99 => 'lib/PhpParser/Node/Expr/Ternary.php', - 100 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 101 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 102 => 'lib/PhpParser/Node/Expr/Variable.php', - 103 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 104 => 'lib/PhpParser/Node/Expr/Yield_.php', - 105 => 'lib/PhpParser/Node/FunctionLike.php', - 106 => 'lib/PhpParser/Node/Name.php', - 107 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 108 => 'lib/PhpParser/Node/Name/Relative.php', - 109 => 'lib/PhpParser/Node/NullableType.php', - 110 => 'lib/PhpParser/Node/Param.php', - 111 => 'lib/PhpParser/Node/Scalar.php', - 112 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 113 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 114 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 115 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 123 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 124 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 125 => 'lib/PhpParser/Node/Scalar/String_.php', - 126 => 'lib/PhpParser/Node/Stmt.php', - 127 => 'lib/PhpParser/Node/Stmt/Break_.php', - 128 => 'lib/PhpParser/Node/Stmt/Case_.php', - 129 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 131 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 132 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 133 => 'lib/PhpParser/Node/Stmt/Class_.php', - 134 => 'lib/PhpParser/Node/Stmt/Const_.php', - 135 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 136 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 137 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 138 => 'lib/PhpParser/Node/Stmt/Do_.php', - 139 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 140 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 141 => 'lib/PhpParser/Node/Stmt/Else_.php', - 142 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 143 => 'lib/PhpParser/Node/Stmt/For_.php', - 144 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 145 => 'lib/PhpParser/Node/Stmt/Function_.php', - 146 => 'lib/PhpParser/Node/Stmt/Global_.php', - 147 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 148 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 149 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 150 => 'lib/PhpParser/Node/Stmt/If_.php', - 151 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 152 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 153 => 'lib/PhpParser/Node/Stmt/Label.php', - 154 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 155 => 'lib/PhpParser/Node/Stmt/Nop.php', - 156 => 'lib/PhpParser/Node/Stmt/Property.php', - 157 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 158 => 'lib/PhpParser/Node/Stmt/Return_.php', - 159 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 160 => 'lib/PhpParser/Node/Stmt/Static_.php', - 161 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 162 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 165 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 166 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 167 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 168 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 169 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 170 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 171 => 'lib/PhpParser/Node/Stmt/Use_.php', - 172 => 'lib/PhpParser/Node/Stmt/While_.php', - 173 => 'lib/PhpParser/NodeAbstract.php', - 174 => 'lib/PhpParser/NodeDumper.php', - 175 => 'lib/PhpParser/NodeTraverser.php', - 176 => 'lib/PhpParser/NodeTraverserInterface.php', - 177 => 'lib/PhpParser/NodeVisitor.php', - 178 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 179 => 'lib/PhpParser/NodeVisitorAbstract.php', - 180 => 'lib/PhpParser/Parser.php', - 181 => 'lib/PhpParser/Parser/Multiple.php', - 182 => 'lib/PhpParser/Parser/Php5.php', - 183 => 'lib/PhpParser/Parser/Php7.php', - 184 => 'lib/PhpParser/ParserAbstract.php', - 185 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 186 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 187 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Node/Arg.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 2 => 'lib/PhpParser/Node/Expr/New_.php', - 3 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Const_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 1 => 'lib/PhpParser/Node/Stmt/Const_.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr.php' => - array ( - 0 => 'lib/PhpParser/Builder/Param.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Lexer.php', - 4 => 'lib/PhpParser/Node/Arg.php', - 5 => 'lib/PhpParser/Node/Const_.php', - 6 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 7 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 8 => 'lib/PhpParser/Node/Expr/Array_.php', - 9 => 'lib/PhpParser/Node/Expr/Assign.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 12 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 13 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 14 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 15 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 16 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 17 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 18 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 19 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 23 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 27 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 28 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 29 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 30 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 31 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 32 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 33 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 52 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 53 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 54 => 'lib/PhpParser/Node/Expr/Cast.php', - 55 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 56 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 57 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 58 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 59 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 60 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 61 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 62 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 63 => 'lib/PhpParser/Node/Expr/Clone_.php', - 64 => 'lib/PhpParser/Node/Expr/Closure.php', - 65 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 66 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 67 => 'lib/PhpParser/Node/Expr/Empty_.php', - 68 => 'lib/PhpParser/Node/Expr/Error.php', - 69 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 70 => 'lib/PhpParser/Node/Expr/Eval_.php', - 71 => 'lib/PhpParser/Node/Expr/Exit_.php', - 72 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 73 => 'lib/PhpParser/Node/Expr/Include_.php', - 74 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 75 => 'lib/PhpParser/Node/Expr/Isset_.php', - 76 => 'lib/PhpParser/Node/Expr/List_.php', - 77 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 78 => 'lib/PhpParser/Node/Expr/New_.php', - 79 => 'lib/PhpParser/Node/Expr/PostDec.php', - 80 => 'lib/PhpParser/Node/Expr/PostInc.php', - 81 => 'lib/PhpParser/Node/Expr/PreDec.php', - 82 => 'lib/PhpParser/Node/Expr/PreInc.php', - 83 => 'lib/PhpParser/Node/Expr/Print_.php', - 84 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 85 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 86 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 87 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 88 => 'lib/PhpParser/Node/Expr/Ternary.php', - 89 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 90 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 91 => 'lib/PhpParser/Node/Expr/Variable.php', - 92 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 93 => 'lib/PhpParser/Node/Expr/Yield_.php', - 94 => 'lib/PhpParser/Node/Param.php', - 95 => 'lib/PhpParser/Node/Scalar.php', - 96 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 97 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 98 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 99 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 100 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 101 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 102 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 103 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 104 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 105 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 106 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 107 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 108 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 109 => 'lib/PhpParser/Node/Scalar/String_.php', - 110 => 'lib/PhpParser/Node/Stmt/Break_.php', - 111 => 'lib/PhpParser/Node/Stmt/Case_.php', - 112 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 113 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 114 => 'lib/PhpParser/Node/Stmt/Do_.php', - 115 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 116 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 117 => 'lib/PhpParser/Node/Stmt/For_.php', - 118 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 119 => 'lib/PhpParser/Node/Stmt/Global_.php', - 120 => 'lib/PhpParser/Node/Stmt/If_.php', - 121 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 122 => 'lib/PhpParser/Node/Stmt/Return_.php', - 123 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 124 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 125 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 126 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 127 => 'lib/PhpParser/Node/Stmt/While_.php', - 128 => 'lib/PhpParser/NodeDumper.php', - 129 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 130 => 'lib/PhpParser/Parser/Php5.php', - 131 => 'lib/PhpParser/Parser/Php7.php', - 132 => 'lib/PhpParser/ParserAbstract.php', - 133 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 134 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Expr/Array_.php', - 2 => 'lib/PhpParser/Node/Expr/List_.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Array_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Assign.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 4 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 5 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 6 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 7 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 8 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 9 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 12 => 'lib/PhpParser/Parser/Php5.php', - 13 => 'lib/PhpParser/Parser/Php7.php', - 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 4 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 5 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 6 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 7 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 8 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 9 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 10 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 11 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 12 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 13 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 14 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 15 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 16 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 17 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 18 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 19 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 20 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 21 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 22 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 23 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 27 => 'lib/PhpParser/Parser/Php5.php', - 28 => 'lib/PhpParser/Parser/Php7.php', - 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 3 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 4 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 5 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 6 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Closure.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Closure.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Error.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Include_.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/List_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/New_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Print_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Variable.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Trait_.php', - 3 => 'lib/PhpParser/Node/Expr/Closure.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 6 => 'lib/PhpParser/Node/Stmt/Function_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/ParserAbstract.php', - 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 11 => 'lib/PhpParser/Node/Expr/Closure.php', - 12 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 13 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 14 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 15 => 'lib/PhpParser/Node/Expr/New_.php', - 16 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 17 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 18 => 'lib/PhpParser/Node/FunctionLike.php', - 19 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 20 => 'lib/PhpParser/Node/Name/Relative.php', - 21 => 'lib/PhpParser/Node/NullableType.php', - 22 => 'lib/PhpParser/Node/Param.php', - 23 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 24 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 25 => 'lib/PhpParser/Node/Stmt/Class_.php', - 26 => 'lib/PhpParser/Node/Stmt/Function_.php', - 27 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 28 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 29 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 30 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 31 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 32 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 33 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 34 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 35 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 36 => 'lib/PhpParser/Parser/Php5.php', - 37 => 'lib/PhpParser/Parser/Php7.php', - 38 => 'lib/PhpParser/ParserAbstract.php', - 39 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 40 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name/Relative.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/NullableType.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Param.php', - 4 => 'lib/PhpParser/BuilderAbstract.php', - 5 => 'lib/PhpParser/Node/Expr/Closure.php', - 6 => 'lib/PhpParser/Node/FunctionLike.php', - 7 => 'lib/PhpParser/Node/Param.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Function_.php', - 10 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Param.php', - 2 => 'lib/PhpParser/Node/Expr/Closure.php', - 3 => 'lib/PhpParser/Node/FunctionLike.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 5 => 'lib/PhpParser/Node/Stmt/Function_.php', - 6 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/ParserAbstract.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 3 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 8 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 9 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 10 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 11 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 12 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 13 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 14 => 'lib/PhpParser/Node/Scalar/String_.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/ParserAbstract.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( - 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 3 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 4 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/String_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Interface_.php', - 3 => 'lib/PhpParser/Builder/Method.php', - 4 => 'lib/PhpParser/Builder/Namespace_.php', - 5 => 'lib/PhpParser/Builder/Property.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/Closure.php', - 11 => 'lib/PhpParser/Node/Expr/New_.php', - 12 => 'lib/PhpParser/Node/FunctionLike.php', - 13 => 'lib/PhpParser/Node/Stmt/Break_.php', - 14 => 'lib/PhpParser/Node/Stmt/Case_.php', - 15 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 16 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 17 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 18 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 19 => 'lib/PhpParser/Node/Stmt/Class_.php', - 20 => 'lib/PhpParser/Node/Stmt/Const_.php', - 21 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 22 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 23 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 24 => 'lib/PhpParser/Node/Stmt/Do_.php', - 25 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 26 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 27 => 'lib/PhpParser/Node/Stmt/Else_.php', - 28 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 29 => 'lib/PhpParser/Node/Stmt/For_.php', - 30 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 31 => 'lib/PhpParser/Node/Stmt/Function_.php', - 32 => 'lib/PhpParser/Node/Stmt/Global_.php', - 33 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 34 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 35 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 36 => 'lib/PhpParser/Node/Stmt/If_.php', - 37 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 38 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 39 => 'lib/PhpParser/Node/Stmt/Label.php', - 40 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 41 => 'lib/PhpParser/Node/Stmt/Nop.php', - 42 => 'lib/PhpParser/Node/Stmt/Property.php', - 43 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 44 => 'lib/PhpParser/Node/Stmt/Return_.php', - 45 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 46 => 'lib/PhpParser/Node/Stmt/Static_.php', - 47 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 48 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 49 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 50 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 51 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 52 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 53 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 54 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 55 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 56 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 57 => 'lib/PhpParser/Node/Stmt/Use_.php', - 58 => 'lib/PhpParser/Node/Stmt/While_.php', - 59 => 'lib/PhpParser/NodeDumper.php', - 60 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 61 => 'lib/PhpParser/Parser/Php5.php', - 62 => 'lib/PhpParser/Parser/Php7.php', - 63 => 'lib/PhpParser/ParserAbstract.php', - 64 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Interface_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Property.php', - 4 => 'lib/PhpParser/Builder/Trait_.php', - 5 => 'lib/PhpParser/BuilderAbstract.php', - 6 => 'lib/PhpParser/Node/Expr/New_.php', - 7 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Class_.php', - 10 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 11 => 'lib/PhpParser/Node/Stmt/Property.php', - 12 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 13 => 'lib/PhpParser/NodeDumper.php', - 14 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( - 0 => 'lib/PhpParser/Builder/Method.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/ParserAbstract.php', - 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Property.php', - 3 => 'lib/PhpParser/BuilderAbstract.php', - 4 => 'lib/PhpParser/Node/Expr/New_.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 6 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 7 => 'lib/PhpParser/Node/Stmt/Property.php', - 8 => 'lib/PhpParser/NodeDumper.php', - 9 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 10 => 'lib/PhpParser/Parser/Php5.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/ParserAbstract.php', - 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/For_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/If_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Interface_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Label.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Namespace_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Property.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Node/Stmt/Property.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Static_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 2 => 'lib/PhpParser/Node/Stmt/Use_.php', - 3 => 'lib/PhpParser/NodeDumper.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser/Php5.php', - 6 => 'lib/PhpParser/Parser/Php7.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 3 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 4 => 'lib/PhpParser/NodeDumper.php', - 5 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 6 => 'lib/PhpParser/Parser/Php5.php', - 7 => 'lib/PhpParser/Parser/Php7.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/While_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/NodeAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Property.php', - 8 => 'lib/PhpParser/Builder/Trait_.php', - 9 => 'lib/PhpParser/Builder/Use_.php', - 10 => 'lib/PhpParser/BuilderAbstract.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - 12 => 'lib/PhpParser/Lexer.php', - 13 => 'lib/PhpParser/Node/Arg.php', - 14 => 'lib/PhpParser/Node/Const_.php', - 15 => 'lib/PhpParser/Node/Expr.php', - 16 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 17 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 18 => 'lib/PhpParser/Node/Expr/Array_.php', - 19 => 'lib/PhpParser/Node/Expr/Assign.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 32 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 33 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 61 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 62 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 63 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 64 => 'lib/PhpParser/Node/Expr/Cast.php', - 65 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 71 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 72 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 73 => 'lib/PhpParser/Node/Expr/Clone_.php', - 74 => 'lib/PhpParser/Node/Expr/Closure.php', - 75 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 76 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 77 => 'lib/PhpParser/Node/Expr/Empty_.php', - 78 => 'lib/PhpParser/Node/Expr/Error.php', - 79 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 80 => 'lib/PhpParser/Node/Expr/Eval_.php', - 81 => 'lib/PhpParser/Node/Expr/Exit_.php', - 82 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 83 => 'lib/PhpParser/Node/Expr/Include_.php', - 84 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 85 => 'lib/PhpParser/Node/Expr/Isset_.php', - 86 => 'lib/PhpParser/Node/Expr/List_.php', - 87 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 88 => 'lib/PhpParser/Node/Expr/New_.php', - 89 => 'lib/PhpParser/Node/Expr/PostDec.php', - 90 => 'lib/PhpParser/Node/Expr/PostInc.php', - 91 => 'lib/PhpParser/Node/Expr/PreDec.php', - 92 => 'lib/PhpParser/Node/Expr/PreInc.php', - 93 => 'lib/PhpParser/Node/Expr/Print_.php', - 94 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 95 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 96 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 97 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 98 => 'lib/PhpParser/Node/Expr/Ternary.php', - 99 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 100 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 101 => 'lib/PhpParser/Node/Expr/Variable.php', - 102 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 103 => 'lib/PhpParser/Node/Expr/Yield_.php', - 104 => 'lib/PhpParser/Node/FunctionLike.php', - 105 => 'lib/PhpParser/Node/Name.php', - 106 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 107 => 'lib/PhpParser/Node/Name/Relative.php', - 108 => 'lib/PhpParser/Node/NullableType.php', - 109 => 'lib/PhpParser/Node/Param.php', - 110 => 'lib/PhpParser/Node/Scalar.php', - 111 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 112 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 113 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 114 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 115 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 123 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 124 => 'lib/PhpParser/Node/Scalar/String_.php', - 125 => 'lib/PhpParser/Node/Stmt.php', - 126 => 'lib/PhpParser/Node/Stmt/Break_.php', - 127 => 'lib/PhpParser/Node/Stmt/Case_.php', - 128 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 129 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 131 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 132 => 'lib/PhpParser/Node/Stmt/Class_.php', - 133 => 'lib/PhpParser/Node/Stmt/Const_.php', - 134 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 135 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 136 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 137 => 'lib/PhpParser/Node/Stmt/Do_.php', - 138 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 139 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 140 => 'lib/PhpParser/Node/Stmt/Else_.php', - 141 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 142 => 'lib/PhpParser/Node/Stmt/For_.php', - 143 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 144 => 'lib/PhpParser/Node/Stmt/Function_.php', - 145 => 'lib/PhpParser/Node/Stmt/Global_.php', - 146 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 147 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 148 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 149 => 'lib/PhpParser/Node/Stmt/If_.php', - 150 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 151 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 152 => 'lib/PhpParser/Node/Stmt/Label.php', - 153 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 154 => 'lib/PhpParser/Node/Stmt/Nop.php', - 155 => 'lib/PhpParser/Node/Stmt/Property.php', - 156 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 157 => 'lib/PhpParser/Node/Stmt/Return_.php', - 158 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 159 => 'lib/PhpParser/Node/Stmt/Static_.php', - 160 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 161 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 162 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 165 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 166 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 167 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 168 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 169 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 170 => 'lib/PhpParser/Node/Stmt/Use_.php', - 171 => 'lib/PhpParser/Node/Stmt/While_.php', - 172 => 'lib/PhpParser/NodeDumper.php', - 173 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 174 => 'lib/PhpParser/Parser/Php5.php', - 175 => 'lib/PhpParser/Parser/Php7.php', - 176 => 'lib/PhpParser/ParserAbstract.php', - 177 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 178 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/NodeDumper.php' => - array ( - ), - 'lib/PhpParser/NodeTraverser.php' => - array ( - ), - 'lib/PhpParser/NodeTraverserInterface.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - ), - 'lib/PhpParser/NodeVisitor.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - 1 => 'lib/PhpParser/NodeTraverserInterface.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/NodeVisitorAbstract.php', - ), - 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( - ), - 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - ), - 'lib/PhpParser/Parser.php' => - array ( - 0 => 'lib/PhpParser/Parser/Multiple.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Multiple.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php5.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php7.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Tokens.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/Lexer/Emulative.php', - ), - 'lib/PhpParser/ParserAbstract.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/ParserFactory.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( - 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Serializer.php' => - array ( - 0 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Serializer/XML.php' => - array ( - ), - 'lib/PhpParser/Unserializer.php' => - array ( - 0 => 'lib/PhpParser/Unserializer/XML.php', - ), - 'lib/PhpParser/Unserializer/XML.php' => - array ( - ), - 'lib/bootstrap.php' => - array ( - ), -); diff --git a/tests/e2e/resultCache_3.php b/tests/e2e/resultCache_3.php deleted file mode 100644 index 993f2b65ee8..00000000000 --- a/tests/e2e/resultCache_3.php +++ /dev/null @@ -1,2016 +0,0 @@ - - array ( - 0 => 'lib/bootstrap.php', - ), - 'lib/PhpParser/Builder.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Class_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Declaration.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Function_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Interface_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Method.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Property.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Trait_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/Builder/Use_.php' => - array ( - 0 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Declaration.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - ), - 'lib/PhpParser/BuilderFactory.php' => - array ( - ), - 'lib/PhpParser/Comment.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment/Doc.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/NodeDumper.php', - 8 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 9 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Comment/Doc.php' => - array ( - 0 => 'lib/PhpParser/Builder/Declaration.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Comment.php', - 4 => 'lib/PhpParser/Lexer.php', - 5 => 'lib/PhpParser/Node.php', - 6 => 'lib/PhpParser/NodeAbstract.php', - 7 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Error.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler.php', - 1 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 2 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 3 => 'lib/PhpParser/Lexer.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/String_.php', - 6 => 'lib/PhpParser/Node/Stmt/Class_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Multiple.php', - 9 => 'lib/PhpParser/Parser/Php5.php', - 10 => 'lib/PhpParser/Parser/Php7.php', - 11 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler.php' => - array ( - 0 => 'lib/PhpParser/ErrorHandler/Collecting.php', - 1 => 'lib/PhpParser/ErrorHandler/Throwing.php', - 2 => 'lib/PhpParser/Lexer.php', - 3 => 'lib/PhpParser/Lexer/Emulative.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser.php', - 6 => 'lib/PhpParser/Parser/Multiple.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/ErrorHandler/Collecting.php' => - array ( - ), - 'lib/PhpParser/ErrorHandler/Throwing.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Multiple.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - ), - 'lib/PhpParser/Lexer.php' => - array ( - 0 => 'lib/PhpParser/Lexer/Emulative.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Lexer/Emulative.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Node.php' => - array ( - 0 => 'lib/PhpParser/Builder.php', - 1 => 'lib/PhpParser/Builder/Class_.php', - 2 => 'lib/PhpParser/Builder/FunctionLike.php', - 3 => 'lib/PhpParser/Builder/Function_.php', - 4 => 'lib/PhpParser/Builder/Interface_.php', - 5 => 'lib/PhpParser/Builder/Method.php', - 6 => 'lib/PhpParser/Builder/Namespace_.php', - 7 => 'lib/PhpParser/Builder/Param.php', - 8 => 'lib/PhpParser/Builder/Property.php', - 9 => 'lib/PhpParser/Builder/Trait_.php', - 10 => 'lib/PhpParser/Builder/Use_.php', - 11 => 'lib/PhpParser/BuilderAbstract.php', - 12 => 'lib/PhpParser/BuilderFactory.php', - 13 => 'lib/PhpParser/Node/Arg.php', - 14 => 'lib/PhpParser/Node/Const_.php', - 15 => 'lib/PhpParser/Node/Expr.php', - 16 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 17 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 18 => 'lib/PhpParser/Node/Expr/Array_.php', - 19 => 'lib/PhpParser/Node/Expr/Assign.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 32 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 33 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 61 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 62 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 63 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 64 => 'lib/PhpParser/Node/Expr/Cast.php', - 65 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 71 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 72 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 73 => 'lib/PhpParser/Node/Expr/Clone_.php', - 74 => 'lib/PhpParser/Node/Expr/Closure.php', - 75 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 76 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 77 => 'lib/PhpParser/Node/Expr/Empty_.php', - 78 => 'lib/PhpParser/Node/Expr/Error.php', - 79 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 80 => 'lib/PhpParser/Node/Expr/Eval_.php', - 81 => 'lib/PhpParser/Node/Expr/Exit_.php', - 82 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 83 => 'lib/PhpParser/Node/Expr/Include_.php', - 84 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 85 => 'lib/PhpParser/Node/Expr/Isset_.php', - 86 => 'lib/PhpParser/Node/Expr/List_.php', - 87 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 88 => 'lib/PhpParser/Node/Expr/New_.php', - 89 => 'lib/PhpParser/Node/Expr/PostDec.php', - 90 => 'lib/PhpParser/Node/Expr/PostInc.php', - 91 => 'lib/PhpParser/Node/Expr/PreDec.php', - 92 => 'lib/PhpParser/Node/Expr/PreInc.php', - 93 => 'lib/PhpParser/Node/Expr/Print_.php', - 94 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 95 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 96 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 97 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 98 => 'lib/PhpParser/Node/Expr/Ternary.php', - 99 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 100 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 101 => 'lib/PhpParser/Node/Expr/Variable.php', - 102 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 103 => 'lib/PhpParser/Node/Expr/Yield_.php', - 104 => 'lib/PhpParser/Node/FunctionLike.php', - 105 => 'lib/PhpParser/Node/Name.php', - 106 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 107 => 'lib/PhpParser/Node/Name/Relative.php', - 108 => 'lib/PhpParser/Node/NullableType.php', - 109 => 'lib/PhpParser/Node/Param.php', - 110 => 'lib/PhpParser/Node/Scalar.php', - 111 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 112 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 113 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 114 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 115 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 123 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 124 => 'lib/PhpParser/Node/Scalar/String_.php', - 125 => 'lib/PhpParser/Node/Stmt.php', - 126 => 'lib/PhpParser/Node/Stmt/Break_.php', - 127 => 'lib/PhpParser/Node/Stmt/Case_.php', - 128 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 129 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 131 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 132 => 'lib/PhpParser/Node/Stmt/Class_.php', - 133 => 'lib/PhpParser/Node/Stmt/Const_.php', - 134 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 135 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 136 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 137 => 'lib/PhpParser/Node/Stmt/Do_.php', - 138 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 139 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 140 => 'lib/PhpParser/Node/Stmt/Else_.php', - 141 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 142 => 'lib/PhpParser/Node/Stmt/For_.php', - 143 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 144 => 'lib/PhpParser/Node/Stmt/Function_.php', - 145 => 'lib/PhpParser/Node/Stmt/Global_.php', - 146 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 147 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 148 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 149 => 'lib/PhpParser/Node/Stmt/If_.php', - 150 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 151 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 152 => 'lib/PhpParser/Node/Stmt/Label.php', - 153 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 154 => 'lib/PhpParser/Node/Stmt/Nop.php', - 155 => 'lib/PhpParser/Node/Stmt/Property.php', - 156 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 157 => 'lib/PhpParser/Node/Stmt/Return_.php', - 158 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 159 => 'lib/PhpParser/Node/Stmt/Static_.php', - 160 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 161 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 162 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 165 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 166 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 167 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 168 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 169 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 170 => 'lib/PhpParser/Node/Stmt/Use_.php', - 171 => 'lib/PhpParser/Node/Stmt/While_.php', - 172 => 'lib/PhpParser/NodeAbstract.php', - 173 => 'lib/PhpParser/NodeDumper.php', - 174 => 'lib/PhpParser/NodeTraverser.php', - 175 => 'lib/PhpParser/NodeTraverserInterface.php', - 176 => 'lib/PhpParser/NodeVisitor.php', - 177 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 178 => 'lib/PhpParser/NodeVisitorAbstract.php', - 179 => 'lib/PhpParser/Parser.php', - 180 => 'lib/PhpParser/Parser/Multiple.php', - 181 => 'lib/PhpParser/Parser/Php5.php', - 182 => 'lib/PhpParser/Parser/Php7.php', - 183 => 'lib/PhpParser/ParserAbstract.php', - 184 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 185 => 'lib/PhpParser/PrettyPrinterAbstract.php', - 186 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Node/Arg.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 1 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 2 => 'lib/PhpParser/Node/Expr/New_.php', - 3 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Const_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 1 => 'lib/PhpParser/Node/Stmt/Const_.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr.php' => - array ( - 0 => 'lib/PhpParser/Builder/Param.php', - 1 => 'lib/PhpParser/Builder/Property.php', - 2 => 'lib/PhpParser/BuilderAbstract.php', - 3 => 'lib/PhpParser/Node/Arg.php', - 4 => 'lib/PhpParser/Node/Const_.php', - 5 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 6 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 7 => 'lib/PhpParser/Node/Expr/Array_.php', - 8 => 'lib/PhpParser/Node/Expr/Assign.php', - 9 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 12 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 13 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 14 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 15 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 16 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 17 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 18 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 19 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 22 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 23 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 27 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 28 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 29 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 30 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 31 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 32 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 33 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 51 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 52 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 53 => 'lib/PhpParser/Node/Expr/Cast.php', - 54 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 55 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 56 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 57 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 58 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 59 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 60 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 61 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 62 => 'lib/PhpParser/Node/Expr/Clone_.php', - 63 => 'lib/PhpParser/Node/Expr/Closure.php', - 64 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 65 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 66 => 'lib/PhpParser/Node/Expr/Empty_.php', - 67 => 'lib/PhpParser/Node/Expr/Error.php', - 68 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 69 => 'lib/PhpParser/Node/Expr/Eval_.php', - 70 => 'lib/PhpParser/Node/Expr/Exit_.php', - 71 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 72 => 'lib/PhpParser/Node/Expr/Include_.php', - 73 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 74 => 'lib/PhpParser/Node/Expr/Isset_.php', - 75 => 'lib/PhpParser/Node/Expr/List_.php', - 76 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 77 => 'lib/PhpParser/Node/Expr/New_.php', - 78 => 'lib/PhpParser/Node/Expr/PostDec.php', - 79 => 'lib/PhpParser/Node/Expr/PostInc.php', - 80 => 'lib/PhpParser/Node/Expr/PreDec.php', - 81 => 'lib/PhpParser/Node/Expr/PreInc.php', - 82 => 'lib/PhpParser/Node/Expr/Print_.php', - 83 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 84 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 85 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 86 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 87 => 'lib/PhpParser/Node/Expr/Ternary.php', - 88 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 89 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 90 => 'lib/PhpParser/Node/Expr/Variable.php', - 91 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 92 => 'lib/PhpParser/Node/Expr/Yield_.php', - 93 => 'lib/PhpParser/Node/Param.php', - 94 => 'lib/PhpParser/Node/Scalar.php', - 95 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 96 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 97 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 98 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 99 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 100 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 101 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 102 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 103 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 104 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 105 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 106 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 107 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 108 => 'lib/PhpParser/Node/Scalar/String_.php', - 109 => 'lib/PhpParser/Node/Stmt/Break_.php', - 110 => 'lib/PhpParser/Node/Stmt/Case_.php', - 111 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 112 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 113 => 'lib/PhpParser/Node/Stmt/Do_.php', - 114 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 115 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 116 => 'lib/PhpParser/Node/Stmt/For_.php', - 117 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 118 => 'lib/PhpParser/Node/Stmt/Global_.php', - 119 => 'lib/PhpParser/Node/Stmt/If_.php', - 120 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 121 => 'lib/PhpParser/Node/Stmt/Return_.php', - 122 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 123 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 124 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 125 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 126 => 'lib/PhpParser/Node/Stmt/While_.php', - 127 => 'lib/PhpParser/NodeDumper.php', - 128 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 129 => 'lib/PhpParser/Parser/Php5.php', - 130 => 'lib/PhpParser/Parser/Php7.php', - 131 => 'lib/PhpParser/ParserAbstract.php', - 132 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 133 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Expr/ArrayDimFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ArrayItem.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Expr/Array_.php', - 2 => 'lib/PhpParser/Node/Expr/List_.php', - 3 => 'lib/PhpParser/Parser/Php5.php', - 4 => 'lib/PhpParser/Parser/Php7.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Array_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Assign.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 4 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 5 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 6 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 7 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 8 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 9 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 10 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 11 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 12 => 'lib/PhpParser/Parser/Php5.php', - 13 => 'lib/PhpParser/Parser/Php7.php', - 14 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/AssignRef.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 1 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 2 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 3 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 4 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 5 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 6 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 7 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 8 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 9 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 10 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 11 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 12 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 13 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 14 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 15 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 16 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 17 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 18 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 19 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 20 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 21 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 22 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 23 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 24 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 25 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 26 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 27 => 'lib/PhpParser/Parser/Php5.php', - 28 => 'lib/PhpParser/Parser/Php7.php', - 29 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Div.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BitwiseNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/BooleanNot.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 1 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 2 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 3 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 4 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 5 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 6 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Array_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Bool_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Double.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Int_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Object_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/String_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Cast/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClassConstFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Clone_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Closure.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ClosureUse.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/Closure.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ConstFetch.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Empty_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Error.php' => - array ( - 0 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ErrorSuppress.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Eval_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Exit_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/FuncCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Include_.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Instanceof_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Isset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/List_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/MethodCall.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/New_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PostInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreDec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PreInc.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Print_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/PropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/ShellExec.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticCall.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Ternary.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryMinus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/UnaryPlus.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Variable.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/YieldFrom.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Expr/Yield_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/FunctionLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Trait_.php', - 3 => 'lib/PhpParser/Node/Expr/Closure.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 6 => 'lib/PhpParser/Node/Stmt/Function_.php', - 7 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/ParserAbstract.php', - 11 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 11 => 'lib/PhpParser/Node/Expr/Closure.php', - 12 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 13 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 14 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 15 => 'lib/PhpParser/Node/Expr/New_.php', - 16 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 17 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 18 => 'lib/PhpParser/Node/FunctionLike.php', - 19 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 20 => 'lib/PhpParser/Node/Name/Relative.php', - 21 => 'lib/PhpParser/Node/NullableType.php', - 22 => 'lib/PhpParser/Node/Param.php', - 23 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 24 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 25 => 'lib/PhpParser/Node/Stmt/Class_.php', - 26 => 'lib/PhpParser/Node/Stmt/Function_.php', - 27 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 28 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 29 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 30 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 31 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 32 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 33 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 34 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 35 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 36 => 'lib/PhpParser/Parser/Php5.php', - 37 => 'lib/PhpParser/Parser/Php7.php', - 38 => 'lib/PhpParser/ParserAbstract.php', - 39 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 40 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Name/FullyQualified.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Name/Relative.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/NullableType.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Param.php', - 4 => 'lib/PhpParser/BuilderAbstract.php', - 5 => 'lib/PhpParser/Node/Expr/Closure.php', - 6 => 'lib/PhpParser/Node/FunctionLike.php', - 7 => 'lib/PhpParser/Node/Param.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Function_.php', - 10 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Param.php' => - array ( - 0 => 'lib/PhpParser/Builder/FunctionLike.php', - 1 => 'lib/PhpParser/Builder/Param.php', - 2 => 'lib/PhpParser/Node/Expr/Closure.php', - 3 => 'lib/PhpParser/Node/FunctionLike.php', - 4 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 5 => 'lib/PhpParser/Node/Stmt/Function_.php', - 6 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 7 => 'lib/PhpParser/Parser/Php5.php', - 8 => 'lib/PhpParser/Parser/Php7.php', - 9 => 'lib/PhpParser/ParserAbstract.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 2 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 3 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 4 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 8 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 9 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 10 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 11 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 12 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 13 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 14 => 'lib/PhpParser/Node/Scalar/String_.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/DNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/Encapsed.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/LNumber.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/ParserAbstract.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst.php' => - array ( - 0 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 1 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 2 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 3 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 4 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 5 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 6 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 7 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 8 => 'lib/PhpParser/Parser/Php5.php', - 9 => 'lib/PhpParser/Parser/Php7.php', - 10 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/File.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Line.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Method.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Scalar/String_.php' => - array ( - 0 => 'lib/PhpParser/BuilderAbstract.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Function_.php', - 2 => 'lib/PhpParser/Builder/Interface_.php', - 3 => 'lib/PhpParser/Builder/Method.php', - 4 => 'lib/PhpParser/Builder/Namespace_.php', - 5 => 'lib/PhpParser/Builder/Property.php', - 6 => 'lib/PhpParser/Builder/Trait_.php', - 7 => 'lib/PhpParser/Builder/Use_.php', - 8 => 'lib/PhpParser/BuilderAbstract.php', - 9 => 'lib/PhpParser/BuilderFactory.php', - 10 => 'lib/PhpParser/Node/Expr/Closure.php', - 11 => 'lib/PhpParser/Node/Expr/New_.php', - 12 => 'lib/PhpParser/Node/FunctionLike.php', - 13 => 'lib/PhpParser/Node/Stmt/Break_.php', - 14 => 'lib/PhpParser/Node/Stmt/Case_.php', - 15 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 16 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 17 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 18 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 19 => 'lib/PhpParser/Node/Stmt/Class_.php', - 20 => 'lib/PhpParser/Node/Stmt/Const_.php', - 21 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 22 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 23 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 24 => 'lib/PhpParser/Node/Stmt/Do_.php', - 25 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 26 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 27 => 'lib/PhpParser/Node/Stmt/Else_.php', - 28 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 29 => 'lib/PhpParser/Node/Stmt/For_.php', - 30 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 31 => 'lib/PhpParser/Node/Stmt/Function_.php', - 32 => 'lib/PhpParser/Node/Stmt/Global_.php', - 33 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 34 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 35 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 36 => 'lib/PhpParser/Node/Stmt/If_.php', - 37 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 38 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 39 => 'lib/PhpParser/Node/Stmt/Label.php', - 40 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 41 => 'lib/PhpParser/Node/Stmt/Nop.php', - 42 => 'lib/PhpParser/Node/Stmt/Property.php', - 43 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 44 => 'lib/PhpParser/Node/Stmt/Return_.php', - 45 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 46 => 'lib/PhpParser/Node/Stmt/Static_.php', - 47 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 48 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 49 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 50 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 51 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 52 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 53 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 54 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 55 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 56 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 57 => 'lib/PhpParser/Node/Stmt/Use_.php', - 58 => 'lib/PhpParser/Node/Stmt/While_.php', - 59 => 'lib/PhpParser/NodeDumper.php', - 60 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 61 => 'lib/PhpParser/Parser/Php5.php', - 62 => 'lib/PhpParser/Parser/Php7.php', - 63 => 'lib/PhpParser/ParserAbstract.php', - 64 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 65 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Break_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Case_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Catch_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassConst.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassLike.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Interface_.php', - 2 => 'lib/PhpParser/Builder/Method.php', - 3 => 'lib/PhpParser/Builder/Property.php', - 4 => 'lib/PhpParser/Builder/Trait_.php', - 5 => 'lib/PhpParser/BuilderAbstract.php', - 6 => 'lib/PhpParser/Node/Expr/New_.php', - 7 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 8 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 9 => 'lib/PhpParser/Node/Stmt/Class_.php', - 10 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 11 => 'lib/PhpParser/Node/Stmt/Property.php', - 12 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 13 => 'lib/PhpParser/NodeDumper.php', - 14 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 15 => 'lib/PhpParser/Parser/Php5.php', - 16 => 'lib/PhpParser/Parser/Php7.php', - 17 => 'lib/PhpParser/ParserAbstract.php', - 18 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ClassMethod.php' => - array ( - 0 => 'lib/PhpParser/Builder/Method.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/ParserAbstract.php', - 7 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Class_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/Method.php', - 2 => 'lib/PhpParser/Builder/Property.php', - 3 => 'lib/PhpParser/BuilderAbstract.php', - 4 => 'lib/PhpParser/Node/Expr/New_.php', - 5 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 6 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 7 => 'lib/PhpParser/Node/Stmt/Property.php', - 8 => 'lib/PhpParser/NodeDumper.php', - 9 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 10 => 'lib/PhpParser/Parser/Php5.php', - 11 => 'lib/PhpParser/Parser/Php7.php', - 12 => 'lib/PhpParser/ParserAbstract.php', - 13 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Const_.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Continue_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/DeclareDeclare.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Declare_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Do_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Echo_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/ElseIf_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Else_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/If_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Finally_.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/For_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Foreach_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Function_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Function_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Global_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Goto_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/GroupUse.php' => - array ( - 0 => 'lib/PhpParser/NodeDumper.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/HaltCompiler.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/If_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/InlineHTML.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Interface_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Interface_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Label.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Namespace_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Namespace_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 6 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Nop.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 4 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/Node/Stmt/Property.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Builder/Trait_.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/ParserAbstract.php', - 5 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/PropertyProperty.php' => - array ( - 0 => 'lib/PhpParser/Builder/Property.php', - 1 => 'lib/PhpParser/Node/Stmt/Property.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Return_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/StaticVar.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/Static_.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Static_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Switch_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Throw_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php' => - array ( - 0 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 1 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 2 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 3 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 4 => 'lib/PhpParser/Parser/Php5.php', - 5 => 'lib/PhpParser/Parser/Php7.php', - 6 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Trait_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Trait_.php', - 1 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 2 => 'lib/PhpParser/Parser/Php5.php', - 3 => 'lib/PhpParser/Parser/Php7.php', - 4 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/TryCatch.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserAbstract.php', - 3 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Unset_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/UseUse.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 2 => 'lib/PhpParser/Node/Stmt/Use_.php', - 3 => 'lib/PhpParser/NodeDumper.php', - 4 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 5 => 'lib/PhpParser/Parser/Php5.php', - 6 => 'lib/PhpParser/Parser/Php7.php', - 7 => 'lib/PhpParser/ParserAbstract.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/Use_.php' => - array ( - 0 => 'lib/PhpParser/Builder/Use_.php', - 1 => 'lib/PhpParser/BuilderFactory.php', - 2 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 3 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 4 => 'lib/PhpParser/NodeDumper.php', - 5 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 6 => 'lib/PhpParser/Parser/Php5.php', - 7 => 'lib/PhpParser/Parser/Php7.php', - 8 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Node/Stmt/While_.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/NodeAbstract.php' => - array ( - 0 => 'lib/PhpParser/Builder/Class_.php', - 1 => 'lib/PhpParser/Builder/FunctionLike.php', - 2 => 'lib/PhpParser/Builder/Function_.php', - 3 => 'lib/PhpParser/Builder/Interface_.php', - 4 => 'lib/PhpParser/Builder/Method.php', - 5 => 'lib/PhpParser/Builder/Namespace_.php', - 6 => 'lib/PhpParser/Builder/Param.php', - 7 => 'lib/PhpParser/Builder/Property.php', - 8 => 'lib/PhpParser/Builder/Trait_.php', - 9 => 'lib/PhpParser/Builder/Use_.php', - 10 => 'lib/PhpParser/BuilderAbstract.php', - 11 => 'lib/PhpParser/BuilderFactory.php', - 12 => 'lib/PhpParser/Node/Arg.php', - 13 => 'lib/PhpParser/Node/Const_.php', - 14 => 'lib/PhpParser/Node/Expr.php', - 15 => 'lib/PhpParser/Node/Expr/ArrayDimFetch.php', - 16 => 'lib/PhpParser/Node/Expr/ArrayItem.php', - 17 => 'lib/PhpParser/Node/Expr/Array_.php', - 18 => 'lib/PhpParser/Node/Expr/Assign.php', - 19 => 'lib/PhpParser/Node/Expr/AssignOp.php', - 20 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php', - 21 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php', - 22 => 'lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php', - 23 => 'lib/PhpParser/Node/Expr/AssignOp/Concat.php', - 24 => 'lib/PhpParser/Node/Expr/AssignOp/Div.php', - 25 => 'lib/PhpParser/Node/Expr/AssignOp/Minus.php', - 26 => 'lib/PhpParser/Node/Expr/AssignOp/Mod.php', - 27 => 'lib/PhpParser/Node/Expr/AssignOp/Mul.php', - 28 => 'lib/PhpParser/Node/Expr/AssignOp/Plus.php', - 29 => 'lib/PhpParser/Node/Expr/AssignOp/Pow.php', - 30 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php', - 31 => 'lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php', - 32 => 'lib/PhpParser/Node/Expr/AssignRef.php', - 33 => 'lib/PhpParser/Node/Expr/BinaryOp.php', - 34 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php', - 35 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php', - 36 => 'lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php', - 37 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php', - 38 => 'lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php', - 39 => 'lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php', - 40 => 'lib/PhpParser/Node/Expr/BinaryOp/Concat.php', - 41 => 'lib/PhpParser/Node/Expr/BinaryOp/Div.php', - 42 => 'lib/PhpParser/Node/Expr/BinaryOp/Equal.php', - 43 => 'lib/PhpParser/Node/Expr/BinaryOp/Greater.php', - 44 => 'lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php', - 45 => 'lib/PhpParser/Node/Expr/BinaryOp/Identical.php', - 46 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php', - 47 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php', - 48 => 'lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php', - 49 => 'lib/PhpParser/Node/Expr/BinaryOp/Minus.php', - 50 => 'lib/PhpParser/Node/Expr/BinaryOp/Mod.php', - 51 => 'lib/PhpParser/Node/Expr/BinaryOp/Mul.php', - 52 => 'lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php', - 53 => 'lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php', - 54 => 'lib/PhpParser/Node/Expr/BinaryOp/Plus.php', - 55 => 'lib/PhpParser/Node/Expr/BinaryOp/Pow.php', - 56 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php', - 57 => 'lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php', - 58 => 'lib/PhpParser/Node/Expr/BinaryOp/Smaller.php', - 59 => 'lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php', - 60 => 'lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', - 61 => 'lib/PhpParser/Node/Expr/BitwiseNot.php', - 62 => 'lib/PhpParser/Node/Expr/BooleanNot.php', - 63 => 'lib/PhpParser/Node/Expr/Cast.php', - 64 => 'lib/PhpParser/Node/Expr/Cast/Array_.php', - 65 => 'lib/PhpParser/Node/Expr/Cast/Bool_.php', - 66 => 'lib/PhpParser/Node/Expr/Cast/Double.php', - 67 => 'lib/PhpParser/Node/Expr/Cast/Int_.php', - 68 => 'lib/PhpParser/Node/Expr/Cast/Object_.php', - 69 => 'lib/PhpParser/Node/Expr/Cast/String_.php', - 70 => 'lib/PhpParser/Node/Expr/Cast/Unset_.php', - 71 => 'lib/PhpParser/Node/Expr/ClassConstFetch.php', - 72 => 'lib/PhpParser/Node/Expr/Clone_.php', - 73 => 'lib/PhpParser/Node/Expr/Closure.php', - 74 => 'lib/PhpParser/Node/Expr/ClosureUse.php', - 75 => 'lib/PhpParser/Node/Expr/ConstFetch.php', - 76 => 'lib/PhpParser/Node/Expr/Empty_.php', - 77 => 'lib/PhpParser/Node/Expr/Error.php', - 78 => 'lib/PhpParser/Node/Expr/ErrorSuppress.php', - 79 => 'lib/PhpParser/Node/Expr/Eval_.php', - 80 => 'lib/PhpParser/Node/Expr/Exit_.php', - 81 => 'lib/PhpParser/Node/Expr/FuncCall.php', - 82 => 'lib/PhpParser/Node/Expr/Include_.php', - 83 => 'lib/PhpParser/Node/Expr/Instanceof_.php', - 84 => 'lib/PhpParser/Node/Expr/Isset_.php', - 85 => 'lib/PhpParser/Node/Expr/List_.php', - 86 => 'lib/PhpParser/Node/Expr/MethodCall.php', - 87 => 'lib/PhpParser/Node/Expr/New_.php', - 88 => 'lib/PhpParser/Node/Expr/PostDec.php', - 89 => 'lib/PhpParser/Node/Expr/PostInc.php', - 90 => 'lib/PhpParser/Node/Expr/PreDec.php', - 91 => 'lib/PhpParser/Node/Expr/PreInc.php', - 92 => 'lib/PhpParser/Node/Expr/Print_.php', - 93 => 'lib/PhpParser/Node/Expr/PropertyFetch.php', - 94 => 'lib/PhpParser/Node/Expr/ShellExec.php', - 95 => 'lib/PhpParser/Node/Expr/StaticCall.php', - 96 => 'lib/PhpParser/Node/Expr/StaticPropertyFetch.php', - 97 => 'lib/PhpParser/Node/Expr/Ternary.php', - 98 => 'lib/PhpParser/Node/Expr/UnaryMinus.php', - 99 => 'lib/PhpParser/Node/Expr/UnaryPlus.php', - 100 => 'lib/PhpParser/Node/Expr/Variable.php', - 101 => 'lib/PhpParser/Node/Expr/YieldFrom.php', - 102 => 'lib/PhpParser/Node/Expr/Yield_.php', - 103 => 'lib/PhpParser/Node/FunctionLike.php', - 104 => 'lib/PhpParser/Node/Name.php', - 105 => 'lib/PhpParser/Node/Name/FullyQualified.php', - 106 => 'lib/PhpParser/Node/Name/Relative.php', - 107 => 'lib/PhpParser/Node/NullableType.php', - 108 => 'lib/PhpParser/Node/Param.php', - 109 => 'lib/PhpParser/Node/Scalar.php', - 110 => 'lib/PhpParser/Node/Scalar/DNumber.php', - 111 => 'lib/PhpParser/Node/Scalar/Encapsed.php', - 112 => 'lib/PhpParser/Node/Scalar/EncapsedStringPart.php', - 113 => 'lib/PhpParser/Node/Scalar/LNumber.php', - 114 => 'lib/PhpParser/Node/Scalar/MagicConst.php', - 115 => 'lib/PhpParser/Node/Scalar/MagicConst/Class_.php', - 116 => 'lib/PhpParser/Node/Scalar/MagicConst/Dir.php', - 117 => 'lib/PhpParser/Node/Scalar/MagicConst/File.php', - 118 => 'lib/PhpParser/Node/Scalar/MagicConst/Function_.php', - 119 => 'lib/PhpParser/Node/Scalar/MagicConst/Line.php', - 120 => 'lib/PhpParser/Node/Scalar/MagicConst/Method.php', - 121 => 'lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php', - 122 => 'lib/PhpParser/Node/Scalar/MagicConst/Trait_.php', - 123 => 'lib/PhpParser/Node/Scalar/String_.php', - 124 => 'lib/PhpParser/Node/Stmt.php', - 125 => 'lib/PhpParser/Node/Stmt/Break_.php', - 126 => 'lib/PhpParser/Node/Stmt/Case_.php', - 127 => 'lib/PhpParser/Node/Stmt/Catch_.php', - 128 => 'lib/PhpParser/Node/Stmt/ClassConst.php', - 129 => 'lib/PhpParser/Node/Stmt/ClassLike.php', - 130 => 'lib/PhpParser/Node/Stmt/ClassMethod.php', - 131 => 'lib/PhpParser/Node/Stmt/Class_.php', - 132 => 'lib/PhpParser/Node/Stmt/Const_.php', - 133 => 'lib/PhpParser/Node/Stmt/Continue_.php', - 134 => 'lib/PhpParser/Node/Stmt/DeclareDeclare.php', - 135 => 'lib/PhpParser/Node/Stmt/Declare_.php', - 136 => 'lib/PhpParser/Node/Stmt/Do_.php', - 137 => 'lib/PhpParser/Node/Stmt/Echo_.php', - 138 => 'lib/PhpParser/Node/Stmt/ElseIf_.php', - 139 => 'lib/PhpParser/Node/Stmt/Else_.php', - 140 => 'lib/PhpParser/Node/Stmt/Finally_.php', - 141 => 'lib/PhpParser/Node/Stmt/For_.php', - 142 => 'lib/PhpParser/Node/Stmt/Foreach_.php', - 143 => 'lib/PhpParser/Node/Stmt/Function_.php', - 144 => 'lib/PhpParser/Node/Stmt/Global_.php', - 145 => 'lib/PhpParser/Node/Stmt/Goto_.php', - 146 => 'lib/PhpParser/Node/Stmt/GroupUse.php', - 147 => 'lib/PhpParser/Node/Stmt/HaltCompiler.php', - 148 => 'lib/PhpParser/Node/Stmt/If_.php', - 149 => 'lib/PhpParser/Node/Stmt/InlineHTML.php', - 150 => 'lib/PhpParser/Node/Stmt/Interface_.php', - 151 => 'lib/PhpParser/Node/Stmt/Label.php', - 152 => 'lib/PhpParser/Node/Stmt/Namespace_.php', - 153 => 'lib/PhpParser/Node/Stmt/Nop.php', - 154 => 'lib/PhpParser/Node/Stmt/Property.php', - 155 => 'lib/PhpParser/Node/Stmt/PropertyProperty.php', - 156 => 'lib/PhpParser/Node/Stmt/Return_.php', - 157 => 'lib/PhpParser/Node/Stmt/StaticVar.php', - 158 => 'lib/PhpParser/Node/Stmt/Static_.php', - 159 => 'lib/PhpParser/Node/Stmt/Switch_.php', - 160 => 'lib/PhpParser/Node/Stmt/Throw_.php', - 161 => 'lib/PhpParser/Node/Stmt/TraitUse.php', - 162 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation.php', - 163 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php', - 164 => 'lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php', - 165 => 'lib/PhpParser/Node/Stmt/Trait_.php', - 166 => 'lib/PhpParser/Node/Stmt/TryCatch.php', - 167 => 'lib/PhpParser/Node/Stmt/Unset_.php', - 168 => 'lib/PhpParser/Node/Stmt/UseUse.php', - 169 => 'lib/PhpParser/Node/Stmt/Use_.php', - 170 => 'lib/PhpParser/Node/Stmt/While_.php', - 171 => 'lib/PhpParser/NodeDumper.php', - 172 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 173 => 'lib/PhpParser/Parser/Php5.php', - 174 => 'lib/PhpParser/Parser/Php7.php', - 175 => 'lib/PhpParser/ParserAbstract.php', - 176 => 'lib/PhpParser/PrettyPrinter/Standard.php', - 177 => 'lib/PhpParser/PrettyPrinterAbstract.php', - ), - 'lib/PhpParser/NodeDumper.php' => - array ( - ), - 'lib/PhpParser/NodeTraverser.php' => - array ( - ), - 'lib/PhpParser/NodeTraverserInterface.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - ), - 'lib/PhpParser/NodeVisitor.php' => - array ( - 0 => 'lib/PhpParser/NodeTraverser.php', - 1 => 'lib/PhpParser/NodeTraverserInterface.php', - 2 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - 3 => 'lib/PhpParser/NodeVisitorAbstract.php', - ), - 'lib/PhpParser/NodeVisitor/NameResolver.php' => - array ( - ), - 'lib/PhpParser/NodeVisitorAbstract.php' => - array ( - 0 => 'lib/PhpParser/NodeVisitor/NameResolver.php', - ), - 'lib/PhpParser/Parser.php' => - array ( - 0 => 'lib/PhpParser/Parser/Multiple.php', - 1 => 'lib/PhpParser/Parser/Php5.php', - 2 => 'lib/PhpParser/Parser/Php7.php', - 3 => 'lib/PhpParser/ParserAbstract.php', - 4 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Multiple.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php5.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Php7.php' => - array ( - 0 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/Parser/Tokens.php' => - array ( - 0 => 'lib/PhpParser/Lexer.php', - 1 => 'lib/PhpParser/Lexer/Emulative.php', - ), - 'lib/PhpParser/ParserAbstract.php' => - array ( - 0 => 'lib/PhpParser/Parser/Php5.php', - 1 => 'lib/PhpParser/Parser/Php7.php', - 2 => 'lib/PhpParser/ParserFactory.php', - ), - 'lib/PhpParser/ParserFactory.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinter/Standard.php' => - array ( - ), - 'lib/PhpParser/PrettyPrinterAbstract.php' => - array ( - 0 => 'lib/PhpParser/PrettyPrinter/Standard.php', - ), - 'lib/PhpParser/Serializer.php' => - array ( - 0 => 'lib/PhpParser/Serializer/XML.php', - ), - 'lib/PhpParser/Serializer/XML.php' => - array ( - ), - 'lib/PhpParser/Unserializer.php' => - array ( - 0 => 'lib/PhpParser/Unserializer/XML.php', - ), - 'lib/PhpParser/Unserializer/XML.php' => - array ( - ), -); From e812e1e501ee09cf0e3f48bb06062f946cedf2a0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0165/3097] Issue bot - let all comments about 2.0.x and PHP-Parser 5 through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 9369822dd518e8e6df30da6f1edc2c9a31598aaf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 21:19:53 +0200 Subject: [PATCH 0166/3097] Revert "Issue bot - let all comments about 2.0.x and PHP-Parser 5 through" This reverts commit e812e1e501ee09cf0e3f48bb06062f946cedf2a0. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 1dc48d6c858d22839e6a77aadde9590f07194110 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 21:54:22 +0200 Subject: [PATCH 0167/3097] Update PHPStan Pro branch --- src/Command/FixerApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 6236c3c63f6..e3dc297d4c5 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -300,7 +300,7 @@ private function downloadPhar( ): void { $currentVersion = null; - $branch = '1.1.x'; + $branch = '2.0.x'; if (is_file($pharPath) && is_file($infoPath)) { /** @var array{version: string, date: string, branch?: string} $currentInfo */ $currentInfo = Json::decode(FileReader::read($infoPath), Json::FORCE_ARRAY); From f3ccf44a5149d3355223f4ba625224b099f32a40 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 22:07:11 +0200 Subject: [PATCH 0168/3097] Remove obsolete tests and skipping conditions --- .../Analyser/AnalyserIntegrationTest.php | 6 - .../Analyser/LegacyNodeScopeResolverTest.php | 37 ----- .../Analyser/NodeScopeResolverTest.php | 16 +-- ...rClosureTypeExtensionArrowFunctionTest.php | 9 -- tests/PHPStan/Analyser/data/array-spread.php | 2 +- .../Analyser/data/arrow-functions-inside.php | 2 +- .../PHPStan/Analyser/data/arrow-functions.php | 2 +- tests/PHPStan/Analyser/data/bug-4902.php | 2 +- .../PHPStan/Analyser/data/coalesce-assign.php | 2 +- tests/PHPStan/Analyser/data/die-73.php | 3 - .../PHPStan/Analyser/data/mb-strlen-php72.php | 56 -------- .../Analyser/data/property-native-types.php | 2 +- .../nsrt/array-filter-arrow-functions.php | 2 +- .../Analyser/nsrt/arrow-function-types.php | 2 +- .../PHPStan/Analyser/nsrt/bug-11311-php72.php | 30 ---- tests/PHPStan/Analyser/nsrt/bug-11311.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-3276.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4188.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4339.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6859.php | 2 +- .../Analyser/nsrt/callable-in-union.php | 2 +- .../Analyser/nsrt/filesystem-functions.php | 2 +- .../nsrt/isset-coalesce-empty-type.php | 2 +- tests/PHPStan/Analyser/nsrt/native-types.php | 2 +- .../nsrt/nullable-closure-parameter.php | 2 +- .../Analyser/nsrt/param-closure-this.php | 2 +- .../nsrt/predefined-constants-php74.php | 2 +- .../nsrt/preg_replace_callback_shapes.php | 2 +- .../PHPStan/Analyser/nsrt/reflection-type.php | 2 +- tests/PHPStan/Composer/AutoloadFilesTest.php | 10 +- .../PHPStan/Generics/data/typeProjections.php | 2 +- .../OptimizedDirectorySourceLocatorTest.php | 4 - ...nexistentOffsetInArrayDimFetchRuleTest.php | 4 - .../Rules/Arrays/data/array-unpacking.php | 2 +- tests/PHPStan/Rules/Arrays/data/bug-6243.php | 2 +- tests/PHPStan/Rules/Arrays/data/bug-8292.php | 2 +- .../nonexistent-offset-coalesce-assign.php | 2 +- .../Rules/Arrays/data/unpack-iterable.php | 2 +- .../Rules/Classes/InstantiationRuleTest.php | 4 - .../PHPStan/Rules/Classes/data/bug-3311a.php | 2 +- tests/PHPStan/Rules/Classes/data/bug-9946.php | 2 +- .../Classes/data/phpstan-internal-class.php | 2 +- .../BooleanNotConstantConditionRuleTest.php | 5 - ...rictComparisonOfDifferentTypesRuleTest.php | 4 - .../Rules/Comparison/data/bug-5969.php | 2 +- .../Rules/Comparison/data/bug-6473.php | 2 +- .../Rules/Comparison/data/bug-8474.php | 2 +- .../Rules/Comparison/data/bug-8516.php | 2 +- .../Rules/Comparison/data/bug-8727.php | 2 +- ...-comparison-last-condition-always-true.php | 2 +- ...trict-comparison-property-native-types.php | 2 +- .../Rules/DeadCode/BetterNoopRuleTest.php | 5 - .../UnusedPrivatePropertyRuleTest.php | 8 -- .../PHPStan/Rules/DeadCode/data/bug-11001.php | 2 +- .../PHPStan/Rules/DeadCode/data/bug-3636.php | 2 +- .../PHPStan/Rules/DeadCode/data/bug-5337.php | 2 +- .../PHPStan/Rules/DeadCode/data/bug-5971.php | 2 +- .../PHPStan/Rules/DeadCode/data/bug-6107.php | 2 +- .../DeadCode/data/unused-private-property.php | 2 +- .../CatchWithUnthrownExceptionRuleTest.php | 16 --- .../TooWideMethodThrowTypeRuleTest.php | 4 - .../Rules/Exceptions/data/bug-6256.php | 2 +- .../Rules/Exceptions/data/bug-6786.php | 2 +- .../Rules/Exceptions/data/bug-6791.php | 2 +- .../ArrowFunctionReturnTypeRuleTest.php | 4 - .../CallToFunctionParametersRuleTest.php | 18 +-- .../Functions/ClosureReturnTypeRuleTest.php | 5 - ...owFunctionDefaultParameterTypeRuleTest.php | 4 - ...reFunctionDefaultParameterTypeRuleTest.php | 4 - .../Functions/data/array_reduce_arrow.php | 2 +- .../Rules/Functions/data/array_walk_arrow.php | 2 +- .../data/arrow-function-attributes.php | 2 +- .../Functions/data/arrow-function-never.php | 2 +- .../data/arrow-functions-return-type.php | 2 +- .../PHPStan/Rules/Functions/data/bug-3261.php | 2 +- .../PHPStan/Rules/Functions/data/bug-3660.php | 2 +- .../PHPStan/Rules/Functions/data/bug-5356.php | 2 +- .../PHPStan/Rules/Functions/data/bug-9697.php | 2 +- ...bug-anonymous-function-method-constant.php | 2 +- .../data/function-call-param-closure-this.php | 2 +- ...default-parameter-type-arrow-functions.php | 2 +- ...patible-default-parameter-type-closure.php | 2 +- .../Rules/Functions/data/uasort_arrow.php | 2 +- .../Rules/Functions/data/uksort_arrow.php | 2 +- .../Rules/Functions/data/usort_arrow.php | 2 +- .../Methods/ConsistentConstructorRuleTest.php | 7 +- .../Rules/Methods/MethodSignatureRuleTest.php | 19 --- .../Methods/OverridingMethodRuleTest.php | 22 --- .../Rules/Methods/ReturnTypeRuleTest.php | 4 - .../Methods/data/arrow-function-bind.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-4083.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-4188.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-5372.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-6023.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-6104.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-6249.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-6423.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-7717.php | 2 +- .../Methods/data/final-private-method.php | 2 +- tests/PHPStan/Rules/PhpDoc/data/bug-4227.php | 2 +- tests/PHPStan/Rules/PhpDoc/data/bug-7240.php | 2 +- .../incompatible-property-native-types.php | 2 +- .../data/template-type-native-type-object.php | 2 +- .../AccessPropertiesInAssignRuleTest.php | 5 - .../Properties/AccessPropertiesRuleTest.php | 3 - ...AccessStaticPropertiesInAssignRuleTest.php | 4 - .../AccessStaticPropertiesRuleTest.php | 3 - ...ValueTypesAssignedToPropertiesRuleTest.php | 4 - ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 4 - .../TypesAssignedToPropertiesRuleTest.php | 22 --- .../data/access-properties-assign-op.php | 2 +- .../access-static-properties-assign-op.php | 2 +- .../Rules/Properties/data/bug-11275.php | 2 +- .../Rules/Properties/data/bug-3311b.php | 2 +- .../Rules/Properties/data/bug-3572.php | 2 +- .../Rules/Properties/data/bug-5382.php | 2 +- .../Rules/Properties/data/bug-6286.php | 2 +- .../Rules/Properties/data/bug-6333.php | 2 +- .../Rules/Properties/data/bug-6356b.php | 2 +- .../Rules/Properties/data/bug-6757.php | 2 +- .../Rules/Properties/data/bug-7190.php | 2 +- .../Rules/Properties/data/bug-7219.php | 2 +- .../Rules/Properties/data/bug-7933.php | 2 +- .../Rules/Properties/data/bug-8190.php | 2 +- .../Rules/Properties/data/bug-8222.php | 2 +- .../Rules/Properties/data/bug-9131.php | 2 +- .../Rules/Properties/data/bug-9619.php | 2 +- .../Properties/data/efabrica-latte-bug.php | 2 +- ...issing-readonly-property-assign-phpdoc.php | 2 +- .../data/php-82-dynamic-properties-allow.php | 2 +- .../data/php-82-dynamic-properties.php | 2 +- .../data/properties-native-types.php | 2 +- .../Rules/Properties/data/require-extends.php | 2 +- .../Properties/data/require-implements.php | 2 +- ...lized-property-additional-constructors.php | 2 +- ...uninitialized-property-readonly-phpdoc.php | 2 +- .../data/uninitialized-property.php | 2 +- .../Rules/Pure/data/impure-assign-ref.php | 2 +- .../RegularExpressionPatternRuleTest.php | 131 +----------------- .../Rules/TooWideTypehints/data/bug-10684.php | 2 +- .../Rules/TooWideTypehints/data/bug-6158.php | 2 +- .../Rules/TooWideTypehints/data/bug-6175.php | 2 +- .../data/tooWideArrowFunctionReturnType.php | 2 +- .../Rules/Variables/NullCoalesceRuleTest.php | 4 - .../Rules/Variables/data/bug-10151.php | 2 +- .../PHPStan/Rules/Variables/data/bug-7292.php | 2 +- .../PHPStan/Rules/Variables/data/bug-7724.php | 2 +- .../defined-variables-arrow-functions.php | 2 +- .../defined-variables-coalesce-assign.php | 2 +- .../data/isset-native-property-types.php | 2 +- .../Variables/data/null-coalesce-assign.php | 2 +- .../data/variable-certainty-null-assign.php | 2 +- .../PHPStan/Rules/WarningEmittingRuleTest.php | 5 - 153 files changed, 126 insertions(+), 601 deletions(-) delete mode 100644 tests/PHPStan/Analyser/data/die-73.php delete mode 100644 tests/PHPStan/Analyser/data/mb-strlen-php72.php delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-11311-php72.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e3a3997bf39..70781bf86cf 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -411,9 +411,6 @@ public function testFunctionThatExistsOn72AndLater(): void public function testBug4715(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $errors = $this->runAnalyse(__DIR__ . '/data/bug-4715.php'); $this->assertNoErrors($errors); } @@ -1368,9 +1365,6 @@ public function testBug10979(): void public function testBug11026(): void { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('Test requires PHP 7.3.'); - } $errors = $this->runAnalyse(__DIR__ . '/data/bug-11026.php'); $this->assertNoErrors($errors); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index f75730744dd..98080c2b67c 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8914,9 +8914,6 @@ public function testPhp73Functions( string $expression, ): void { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('Test requires PHP 7.3'); - } $this->assertTypes( __DIR__ . '/data/php73_functions.php', $description, @@ -9046,9 +9043,6 @@ public function testPhp74Functions( string $expression, ): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4'); - } $this->assertTypes( __DIR__ . '/data/php74_functions.php', $description, @@ -9480,34 +9474,6 @@ public function testArraySpread( ); } - public function dataPhp74FunctionsIn73(): array - { - return [ - [ - 'mixed', - 'password_algos()', - ], - ]; - } - - /** - * @dataProvider dataPhp74FunctionsIn73 - */ - public function testPhp74FunctionsIn73( - string $description, - string $expression, - ): void - { - if (PHP_VERSION_ID >= 70400) { - $this->markTestSkipped('Test does not run on PHP >= 7.4.'); - } - $this->assertTypes( - __DIR__ . '/data/die-73.php', - $description, - $expression, - ); - } - public function dataPhp74FunctionsIn74(): array { return [ @@ -9526,9 +9492,6 @@ public function testPhp74FunctionsIn74( string $expression, ): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->assertTypes( __DIR__ . '/data/die-74.php', $description, diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 56017066d15..dce99191c00 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -20,7 +20,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/enum-reflection-php81.php'); } - if (PHP_VERSION_ID < 80000 && PHP_VERSION_ID >= 70400) { + if (PHP_VERSION_ID < 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php'); } @@ -29,8 +29,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php82.php'); } elseif (PHP_VERSION_ID >= 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php8.php'); - } elseif (PHP_VERSION_ID < 70300) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php72.php'); } else { yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php73.php'); } @@ -69,9 +67,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/varying-acceptor.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); - if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); - } + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5372_2.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); @@ -85,9 +81,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/new-in-initializers-runtime.php'); } - if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6473.php'); - } + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6473.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php'); @@ -125,9 +119,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-7469.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-3391.php'); - if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-anonymous-function-method-constant.php'); - } + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-anonymous-function-method-constant.php'); if (PHP_VERSION_ID >= 80200) { yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/true-typehint.php'); diff --git a/tests/PHPStan/Analyser/ParameterClosureTypeExtensionArrowFunctionTest.php b/tests/PHPStan/Analyser/ParameterClosureTypeExtensionArrowFunctionTest.php index 025513c4bfa..c3476c32fac 100644 --- a/tests/PHPStan/Analyser/ParameterClosureTypeExtensionArrowFunctionTest.php +++ b/tests/PHPStan/Analyser/ParameterClosureTypeExtensionArrowFunctionTest.php @@ -3,17 +3,12 @@ namespace PHPStan\Analyser; use PHPStan\Testing\TypeInferenceTestCase; -use const PHP_VERSION_ID; class ParameterClosureTypeExtensionArrowFunctionTest extends TypeInferenceTestCase { public function dataFileAsserts(): iterable { - if (PHP_VERSION_ID < 70400) { - return []; - } - yield from $this->gatherAssertTypes(__DIR__ . '/data/parameter-closure-type-extension-arrow-function.php'); } @@ -27,10 +22,6 @@ public function testFileAsserts( ...$args, ): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->assertFileAsserts($assertType, $file, ...$args); } diff --git a/tests/PHPStan/Analyser/data/array-spread.php b/tests/PHPStan/Analyser/data/array-spread.php index 5bd12fdc383..f752a032703 100644 --- a/tests/PHPStan/Analyser/data/array-spread.php +++ b/tests/PHPStan/Analyser/data/array-spread.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.2 - -namespace MbStrlenPhp72; - -use function PHPStan\Testing\assertType; - -class MbStrlenPhp72 -{ - - /** - * @param non-empty-string $nonEmpty - * @param 'utf-8'|'8bit' $utf8And8bit - * @param 'utf-8'|'foo' $utf8AndInvalidEncoding - * @param '1'|'2'|'5'|'10' $constUnion - * @param 1|2|5|10|123|'1234'|false $constUnionMixed - * @param int|float $intFloat - * @param non-empty-string|int|float $nonEmptyStringIntFloat - * @param ""|false|null $emptyStringFalseNull - * @param ""|bool|null $emptyStringBoolNull - * @param "pass"|"none" $encodingsValidOnlyUntilPhp72 - */ - public function doFoo(int $i, string $s, bool $bool, float $float, $intFloat, $nonEmpty, $nonEmptyStringIntFloat, $emptyStringFalseNull, $emptyStringBoolNull, $constUnion, $constUnionMixed, $utf8And8bit, $utf8AndInvalidEncoding, string $unknownEncoding, $encodingsValidOnlyUntilPhp72) - { - assertType('0', mb_strlen('')); - assertType('5', mb_strlen('hallo')); - assertType('int<0, 1>', mb_strlen($bool)); - assertType('int<1, max>', mb_strlen($i)); - assertType('int<0, max>', mb_strlen($s)); - assertType('int<1, max>', mb_strlen($nonEmpty)); - assertType('int<1, 2>', mb_strlen($constUnion)); - assertType('int<0, 4>', mb_strlen($constUnionMixed)); - assertType('3', mb_strlen(123)); - assertType('1', mb_strlen(true)); - assertType('0', mb_strlen(false)); - assertType('0', mb_strlen(null)); - assertType('1', mb_strlen(1.0)); - assertType('4', mb_strlen(1.23)); - assertType('int<1, max>', mb_strlen($float)); - assertType('int<1, max>', mb_strlen($intFloat)); - assertType('int<1, max>', mb_strlen($nonEmptyStringIntFloat)); - assertType('0', mb_strlen($emptyStringFalseNull)); - assertType('int<0, 1>', mb_strlen($emptyStringBoolNull)); - assertType('8', mb_strlen('паляниця', 'utf-8')); - assertType('11', mb_strlen('alias test🤔', 'utf8')); - assertType('false', mb_strlen('', 'invalid encoding')); - assertType('int<5, 6>', mb_strlen('école', $utf8And8bit)); - assertType('5|false', mb_strlen('école', $utf8AndInvalidEncoding)); - assertType('1|3|5|6|false', mb_strlen('école', $unknownEncoding)); - assertType('2|4|5|6|8|false', mb_strlen('מזגן', $unknownEncoding)); - assertType('6|8|12|13|15|18|24|false', mb_strlen('いい天気ですね〜', $unknownEncoding)); - assertType('3|false', mb_strlen(123, $utf8AndInvalidEncoding)); - assertType('3', mb_strlen('foo', $encodingsValidOnlyUntilPhp72)); - } - -} - diff --git a/tests/PHPStan/Analyser/data/property-native-types.php b/tests/PHPStan/Analyser/data/property-native-types.php index 2892179613f..87d990b96f3 100644 --- a/tests/PHPStan/Analyser/data/property-native-types.php +++ b/tests/PHPStan/Analyser/data/property-native-types.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 +\d+)\.(?\d+)(?:\.(?\d+))?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, major: numeric-string, 1: numeric-string, minor: numeric-string, 2: numeric-string, patch?: numeric-string, 3?: numeric-string}', $matches); - } -} - -function doUnmatchedAsNull(string $s): void { - if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{0: string, 1?: ''|'foo', 2?: ''|'bar', 3?: 'baz'}", $matches); - } - assertType("array{}|array{0: string, 1?: ''|'foo', 2?: ''|'bar', 3?: 'baz'}", $matches); -} - -// see https://3v4l.org/VeDob#veol -function unmatchedAsNullWithOptionalGroup(string $s): void { - if (preg_match('/Price: (£|€)?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{0: string, 1?: '£'|'€'}", $matches); - } else { - assertType('array{}', $matches); - } - assertType("array{}|array{0: string, 1?: '£'|'€'}", $matches); -} diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index 0d2eaec66dd..a30f261fae2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 70300) { - array_splice($expectedFiles, 6, 0, [ - $phpunitFunctions, - ]); - } - $expectedFiles = array_map(static fn (string $path): string => $fileHelper->normalizePath($path), $expectedFiles); sort($expectedFiles); diff --git a/tests/PHPStan/Generics/data/typeProjections.php b/tests/PHPStan/Generics/data/typeProjections.php index 8cfe1f92db4..555c1a50ae9 100644 --- a/tests/PHPStan/Generics/data/typeProjections.php +++ b/tests/PHPStan/Generics/data/typeProjections.php @@ -1,4 +1,4 @@ -= 7.4 +getByType(OptimizedDirectorySourceLocatorFactory::class); $locator = $factory->createByFiles([__DIR__ . '/data/bug-5525.php']); $reflector = new DefaultReflector($locator); diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index bde2d1100e4..a31fe488e2d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -687,10 +687,6 @@ public function testBug8068(): void public function testBug6243(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-6243.php'], []); } diff --git a/tests/PHPStan/Rules/Arrays/data/array-unpacking.php b/tests/PHPStan/Rules/Arrays/data/array-unpacking.php index d7d1e64de53..ff2f652cac1 100644 --- a/tests/PHPStan/Rules/Arrays/data/array-unpacking.php +++ b/tests/PHPStan/Rules/Arrays/data/array-unpacking.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 +markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-3311a.php'], [ [ 'Parameter #1 $bar of class Bug3311a\Foo constructor expects list, array{1: \'baz\'} given.', diff --git a/tests/PHPStan/Rules/Classes/data/bug-3311a.php b/tests/PHPStan/Rules/Classes/data/bug-3311a.php index d4f646deb5f..bb28ed1eb11 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-3311a.php +++ b/tests/PHPStan/Rules/Classes/data/bug-3311a.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 + @@ -126,10 +125,6 @@ public function testTreatPhpDocTypesAsCertainRegression(bool $treatPhpDocTypesAs public function testBug6473(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6473.php'], []); } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index e1c99901dee..ff6e8c9aca8 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -678,10 +678,6 @@ public function testBug8485(): void public function testBug8516(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8516.php'], []); } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-5969.php b/tests/PHPStan/Rules/Comparison/data/bug-5969.php index 87693235ad9..b01b9b2412e 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-5969.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-5969.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 + @@ -146,10 +145,6 @@ public function testRuleImpurePoints(): void public function testBug11001(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-11001.php'], []); } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index e17c06a69d3..3f60827491e 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -241,10 +241,6 @@ public function testBug5337(): void public function testBug5971(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; $this->analyse([__DIR__ . '/data/bug-5971.php'], []); @@ -252,10 +248,6 @@ public function testBug5971(): void public function testBug6107(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; $this->analyse([__DIR__ . '/data/bug-6107.php'], []); diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11001.php b/tests/PHPStan/Rules/DeadCode/data/bug-11001.php index 5351fc8302e..4b39b689c13 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-11001.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11001.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 +markTestSkipped('Test requires PHP 7.3.'); - } - $this->analyse([__DIR__ . '/data/bug-4814.php'], [ [ 'Dead catch - JsonException is never thrown in the try block.', @@ -415,10 +411,6 @@ public function testBug6262(): void public function testBug6256(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-6256.php'], [ [ 'Dead catch - TypeError is never thrown in the try block.', @@ -449,10 +441,6 @@ public function testBug6256(): void public function testBug6791(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-6791.php'], [ [ 'Dead catch - TypeError is never thrown in the try block.', @@ -471,10 +459,6 @@ public function testBug6791(): void public function testBug6786(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-6786.php'], []); } diff --git a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php index 98c3737887d..5a323519860 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php @@ -55,10 +55,6 @@ public function testBug6233(): void public function testImmediatelyCalledArrowFunction(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/immediately-called-arrow-function.php'], [ [ 'Method ImmediatelyCalledArrowFunction\ImmediatelyCalledCallback::doFoo2() has InvalidArgumentException in PHPDoc @throws tag but it\'s not thrown.', diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-6256.php b/tests/PHPStan/Rules/Exceptions/data/bug-6256.php index c7cb4766ad2..ed14c557557 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-6256.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-6256.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 +markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-anonymous-function-method-constant.php'], []); } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 174f8ece4a2..7dc89a5f589 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -316,9 +316,6 @@ public function testImplodeOnPhp74(): void 8, ], ]; - if (PHP_VERSION_ID < 70400) { - $errors = []; - } if (PHP_VERSION_ID >= 80000) { $errors = [ [ @@ -333,7 +330,6 @@ public function testImplodeOnPhp74(): void public function testImplodeOnLessThanPhp74(): void { - $errors = []; if (PHP_VERSION_ID >= 80000) { $errors = [ [ @@ -341,7 +337,7 @@ public function testImplodeOnLessThanPhp74(): void 8, ], ]; - } elseif (PHP_VERSION_ID >= 70400) { + } else { $errors = [ [ 'Parameter #1 $glue of function implode expects string, array given.', @@ -815,10 +811,6 @@ public function testExplode(): void public function testProcOpen(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/proc_open.php'], [ [ "Parameter #1 \$command of function proc_open expects list|string, array{something: 'bogus', in: 'here'} given.", @@ -1632,10 +1624,6 @@ public function testDiscussion10454(): void public function testBug10527(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4'); - } - $this->analyse([__DIR__ . '/data/bug-10527.php'], []); } @@ -1660,10 +1648,6 @@ public function testArgon2PasswordHash(): void public function testParamClosureThis(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/function-call-param-closure-this.php'], [ [ 'Parameter #1 $cb of function FunctionCallParamClosureThis\acceptClosure expects bindable closure, static closure given.', diff --git a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php index 020000b38a9..2a7a309e1af 100644 --- a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php @@ -6,7 +6,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -131,10 +130,6 @@ public function testBug7220(): void public function testBugFunctionMethodConstants(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/bug-anonymous-function-method-constant.php'], []); } diff --git a/tests/PHPStan/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRuleTest.php index 9ec40a74a97..b6a79b68171 100644 --- a/tests/PHPStan/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRuleTest.php @@ -4,7 +4,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -19,9 +18,6 @@ protected function getRule(): Rule public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-arrow-functions.php'], [ [ 'Default value of the parameter #1 $i (string) of anonymous function is incompatible with type int.', diff --git a/tests/PHPStan/Rules/Functions/IncompatibleClosureFunctionDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Functions/IncompatibleClosureFunctionDefaultParameterTypeRuleTest.php index 8d0506f9fc9..88ce97861e0 100644 --- a/tests/PHPStan/Rules/Functions/IncompatibleClosureFunctionDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/IncompatibleClosureFunctionDefaultParameterTypeRuleTest.php @@ -4,7 +4,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -19,9 +18,6 @@ protected function getRule(): Rule public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/incompatible-default-parameter-type-closure.php'], [ [ 'Default value of the parameter #1 $i (string) of anonymous function is incompatible with type int.', diff --git a/tests/PHPStan/Rules/Functions/data/array_reduce_arrow.php b/tests/PHPStan/Rules/Functions/data/array_reduce_arrow.php index 0606d7a2754..d93d7b6ffc3 100644 --- a/tests/PHPStan/Rules/Functions/data/array_reduce_arrow.php +++ b/tests/PHPStan/Rules/Functions/data/array_reduce_arrow.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 + 1, 'bar' => 2]; array_walk( diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php b/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php index fa1ec0f17aa..30f1a938844 100644 --- a/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-attributes.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 +analyse([__DIR__ . '/data/consistent-constructor.php'], [ [ - sprintf('Parameter #1 $b (int) of method ConsistentConstructor\Bar2::__construct() is not %s with parameter #1 $b (string) of method ConsistentConstructor\Bar::__construct().', PHP_VERSION_ID >= 70400 ? 'contravariant' : 'compatible'), + sprintf('Parameter #1 $b (int) of method ConsistentConstructor\Bar2::__construct() is not %s with parameter #1 $b (string) of method ConsistentConstructor\Bar::__construct().', 'contravariant'), 13, ], [ @@ -40,10 +40,7 @@ public function testRule(): void public function testRuleNoErrors(): void { - $this->analyse( - [__DIR__ . '/data/consistent-constructor-no-errors.php'], - PHP_VERSION_ID < 70400 ? [['Parameter #1 $b (ConsistentConstructorNoErrors\A) of method ConsistentConstructorNoErrors\Baz::__construct() is not compatible with parameter #1 $b (ConsistentConstructorNoErrors\B) of method ConsistentConstructorNoErrors\Foo::__construct().', 49]] : [], - ); + $this->analyse([__DIR__ . '/data/consistent-constructor-no-errors.php'], []); } } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 93c6a67b9d2..cbdac548bcc 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -252,9 +252,6 @@ public function testBug4003(): void public function testBug4017(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->reportMaybes = true; $this->reportStatic = true; $this->analyse([__DIR__ . '/data/bug-4017.php'], []); @@ -504,10 +501,6 @@ public function testBug10184(): void public function testBug10208(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->reportMaybes = true; $this->reportStatic = true; @@ -516,10 +509,6 @@ public function testBug10208(): void public function testBug6462(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->reportMaybes = true; $this->reportStatic = true; @@ -528,10 +517,6 @@ public function testBug6462(): void public function testBug4396(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->reportMaybes = true; $this->reportStatic = true; @@ -540,10 +525,6 @@ public function testBug4396(): void public function testBug3580(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->reportMaybes = true; $this->reportStatic = true; diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 75f9465e740..916532a7b9a 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -559,18 +559,12 @@ public function testBug6264(): void public function testBug7717(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-7717.php'], []); } public function testBug6104(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-6104.php'], []); } @@ -624,10 +618,6 @@ public function testBug7859(): void public function testBug8081(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-8081.php'], [ [ @@ -639,10 +629,6 @@ public function testBug8081(): void public function testBug8500(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-8500.php'], [ [ @@ -654,10 +640,6 @@ public function testBug8500(): void public function testBug9014(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-9014.php'], [ [ @@ -684,10 +666,6 @@ public function testBug9135(): void public function testBug10101(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-10101.php'], [ [ diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index f080bd4be8f..d3b5a5a2379 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1033,10 +1033,6 @@ public function testWrongListTip(): void public function testArrowFunctionReturningVoidClosure(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/arrow-function-returning-void-closure.php'], []); } diff --git a/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php b/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php index 449d409b2c2..a71fa7d8097 100644 --- a/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php +++ b/tests/PHPStan/Rules/Methods/data/arrow-function-bind.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 + @@ -40,10 +39,6 @@ public function testRule(): void public function testRuleAssignOp(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } - $tipText = 'Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property'; $this->analyse([__DIR__ . '/data/access-properties-assign-op.php'], [ [ diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index a07611980b5..2809d62a9a5 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -301,9 +301,6 @@ public function testAccessPropertiesWithoutUnionTypes(): void public function testRuleAssignOp(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index e8cd8306ded..593aa13f974 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -47,9 +46,6 @@ public function testRule(): void public function testRuleAssignOp(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/access-static-properties-assign-op.php'], [ [ 'Access to an undefined static property AccessStaticProperties\AssignOpNonexistentProperty::$flags.', diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 14e4779f22c..0ae3edbc3c2 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -416,9 +416,6 @@ public function testAccessStaticPropertiesPhp82(): void public function testRuleAssignOp(): void { - if (PHP_VERSION_ID < 70400) { - self::markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/access-static-properties-assign-op.php'], [ [ 'Access to an undefined static property AccessStaticProperties\AssignOpNonexistentProperty::$flags.', diff --git a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php index 5c921624108..7f99a5ae721 100644 --- a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php @@ -5,7 +5,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -58,9 +57,6 @@ public function testBug5607(): void public function testBug7933(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/bug-7933.php'], []); } diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php index c7a90b03ab2..a72e7d23f25 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -59,10 +59,6 @@ private function isEntityId(PropertyReflection $property, string $propertyName): public function testRule(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->analyse([__DIR__ . '/data/missing-readonly-property-assign-phpdoc.php'], [ [ 'Class MissingReadOnlyPropertyAssignPhpDoc\Foo has an uninitialized @readonly property $unassigned. Assign it in the constructor.', diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 09f34d1d40d..fea9ffb9e1f 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -315,9 +315,6 @@ public function testBug5804(): void public function testBug6286(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->analyse([__DIR__ . '/data/bug-6286.php'], [ [ 'Property Bug6286\HelloWorld::$details (array{name: string, age: int}) does not accept array{name: string, age: \'Forty-two\'}.', @@ -408,19 +405,12 @@ public function testGenericObjectWithUnspecifiedTemplateTypesLevel8(): void public function testBug5382(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } $this->checkExplicitMixed = false; $this->analyse([__DIR__ . '/data/bug-5382.php'], []); } public function testBug6757(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-6757.php'], []); } @@ -506,10 +496,6 @@ public function testIntegerRangesAndConstants(): void public function testBug3311b(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-3311b.php'], [ [ @@ -528,20 +514,12 @@ public function testBug7789(): void public function testBug9131(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-9131.php'], []); } public function testBug8222(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-8222.php'], []); } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties-assign-op.php b/tests/PHPStan/Rules/Properties/data/access-properties-assign-op.php index 7af71ae1b87..ddac393d08c 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties-assign-op.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties-assign-op.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 70300) { - $this->markTestSkipped('This test requires PHP < 7.3.0'); - } - - $this->analyse( - [__DIR__ . '/data/valid-regex-pattern.php'], - [ - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 6, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 7, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 11, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 12, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 16, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 17, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 21, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 22, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 26, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 27, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 29, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 29, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 32, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 33, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 35, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 35, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 38, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 39, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 41, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 41, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok', - 43, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 1 in pattern: ~(~', - 43, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok(?:.*)', - 57, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok(?:.*)', - 58, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 7 in pattern: ~((?:.*)~', - 59, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok(?:.*)nono', - 61, - ], - [ - 'Regex pattern is invalid: Delimiter must not be alphanumeric or backslash in pattern: nok(?:.*)nope', - 62, - ], - [ - 'Regex pattern is invalid: Compilation failed: missing ) at offset 7 in pattern: ~((?:.*)~', - 63, - ], - ], - ); - } - - public function testValidRegexPatternAfter73(): void - { - if (PHP_VERSION_ID < 70300) { - $this->markTestSkipped('This test requires PHP >= 7.3.0'); - } - $messagePart = 'alphanumeric or backslash'; if (PHP_VERSION_ID >= 80200) { $messagePart = 'alphanumeric, backslash, or NUL'; diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-10684.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-10684.php index 1949d4d8b64..4335dabd720 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/bug-10684.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-10684.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 +markTestSkipped('Test requires PHP 7.4.'); - } - $this->treatPhpDocTypesAsCertain = true; $this->strictUnnecessaryNullsafePropertyFetch = false; diff --git a/tests/PHPStan/Rules/Variables/data/bug-10151.php b/tests/PHPStan/Rules/Variables/data/bug-10151.php index f93e860eb8b..78d5c4219d9 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-10151.php +++ b/tests/PHPStan/Rules/Variables/data/bug-10151.php @@ -1,4 +1,4 @@ -= 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 += 7.4 + @@ -37,10 +36,6 @@ public function processNode(Node $node, Scope $scope): array public function testRule(): void { - if (PHP_VERSION_ID < 70300) { - self::markTestSkipped('For some reason this test does not work on PHP 7.2 with old PHPUnit'); - } - try { $this->analyse([__DIR__ . '/data/empty-file.php'], []); self::fail('Should throw an exception'); From 7fdf5b5d106a996f30ae36f6e27f9e79e892ac2a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 23:10:33 +0200 Subject: [PATCH 0169/3097] Update PHPStan dependencies --- composer.json | 8 ++--- composer.lock | 91 +++++++++++++++++++++++++-------------------------- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/composer.json b/composer.json index e4e61cd5328..84ed1845a02 100644 --- a/composer.json +++ b/composer.json @@ -57,10 +57,10 @@ "cweagans/composer-patches": "^1.7.3", "ondrejmirtes/simple-downgrader": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-deprecation-rules": "^1.2", - "phpstan/phpstan-nette": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.6", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-nette": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "shipmonk/composer-dependency-analyser": "^1.5", "shipmonk/name-collision-detector": "^2.0" diff --git a/composer.lock b/composer.lock index 0aba247c22c..33f2c2e5d03 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bba4725ca58df1d370b5aa291335076d", + "content-hash": "3413a4b61ab62bc50a603a5313fda2e9", "packages": [ { "name": "clue/ndjson-react", @@ -4817,26 +4817,26 @@ }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + "reference": "4590cf64974274acb3cf683bddfbe59031272949" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/4590cf64974274acb3cf683bddfbe59031272949", + "reference": "4590cf64974274acb3cf683bddfbe59031272949", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -4858,27 +4858,27 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" }, - "time": "2024-04-20T06:39:48+00:00" + "time": "2024-09-04T20:43:23+00:00" }, { "name": "phpstan/phpstan-nette", - "version": "1.3.8", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "bc74b8b208b47f163fe55708fcf1a0333247fa79" + "reference": "93a4f025a4d11ffcf9523617cb3c620c5373fe56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/bc74b8b208b47f163fe55708fcf1a0333247fa79", - "reference": "bc74b8b208b47f163fe55708fcf1a0333247fa79", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/93a4f025a4d11ffcf9523617cb3c620c5373fe56", + "reference": "93a4f025a4d11ffcf9523617cb3c620c5373fe56", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "conflict": { "nette/application": "<2.3.0", @@ -4892,12 +4892,12 @@ "nette/application": "^3.0", "nette/forms": "^3.0", "nette/utils": "^2.3.0 || ^3.0.0", - "nikic/php-parser": "^4.13.2", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "~9.5.28" + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4919,36 +4919,35 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/1.3.8" + "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.x" }, - "time": "2024-08-25T12:11:12+00:00" + "time": "2024-09-04T21:08:28+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11" + "reference": "3faa60573a32522772e7cda004003b15466e2b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11", - "reference": "f3ea021866f4263f07ca3636bf22c64be9610c11", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/3faa60573a32522772e7cda004003b15466e2b5b", + "reference": "3faa60573a32522772e7cda004003b15466e2b5b", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -4971,35 +4970,35 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.x" }, - "time": "2024-04-20T06:39:00+00:00" + "time": "2024-09-04T20:57:24+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "8e2c8b0abb83ec35ba2fca475898880f7e700783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8e2c8b0abb83ec35ba2fca475898880f7e700783", + "reference": "8e2c8b0abb83ec35ba2fca475898880f7e700783", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -5020,9 +5019,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2024-09-04T21:09:40+00:00" }, { "name": "phpunit/php-code-coverage", From b701c07ec88365dc34440a0c3891f355cf2c67a9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 23:11:06 +0200 Subject: [PATCH 0170/3097] Update COMPOSER_ROOT_VERSION in workflows --- .github/workflows/apiref.yml | 2 +- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/build-issue-bot.yml | 2 +- .github/workflows/changelog-generator.yml | 2 +- .github/workflows/checksum-phar.yml | 6 +++--- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/issue-bot.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/phar.yml | 2 +- .github/workflows/reflection-golden-test.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/apiref.yml b/.github/workflows/apiref.yml index 32c74178410..7700ceb9115 100644 --- a/.github/workflows/apiref.yml +++ b/.github/workflows/apiref.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/apiref.yml' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: apigen-${{ github.ref }} # will be canceled on subsequent pushes in branch diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index d7651c92022..90782432326 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -12,7 +12,7 @@ on: - '.github/workflows/backward-compatibility.yml' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: bc-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/build-issue-bot.yml b/.github/workflows/build-issue-bot.yml index 0e541ca5b1f..882a0eb6aeb 100644 --- a/.github/workflows/build-issue-bot.yml +++ b/.github/workflows/build-issue-bot.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/build-issue-bot.yml' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: build-issue-bot-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/changelog-generator.yml b/.github/workflows/changelog-generator.yml index bda67d4725d..5989cfb021d 100644 --- a/.github/workflows/changelog-generator.yml +++ b/.github/workflows/changelog-generator.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/changelog-generator.yml' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: changelog-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index b5dc04f6dcf..3f2e1b80f9c 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -18,7 +18,7 @@ on: - '.github/workflows/checksum-phar.yml' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: checksum-phar-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches @@ -101,14 +101,14 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index dd8a44e406e..1b32f42e3d4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: e2e-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 5ef4bd62756..fa9cc1f8452 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -18,7 +18,7 @@ on: - 'changelog-generator/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: run-issue-bot-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bdc15d968a1..a262c8e2a2f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ on: - "2.0.x" env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: lint-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index c6534ecd7bb..03926a683d5 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -11,7 +11,7 @@ on: - '2.0.*' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 2bf67cb14f0..39fc0583d5d 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" REFLECTION_GOLDEN_TEST_FILE: "/tmp/reflection-golden.test" REFLECTION_GOLDEN_SYMBOLS_FILE: "/tmp/reflection-golden-symbols.txt" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index a0c08f6171e..6dc9e9e0da6 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -15,7 +15,7 @@ on: - 'apigen/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: sa-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5533bd7e6a9..25a7bebfd2a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" + COMPOSER_ROOT_VERSION: "2.0.x-dev" concurrency: group: tests-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches From fa25a041d4997eb1fcc857328233793326474b9d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 23:25:17 +0200 Subject: [PATCH 0171/3097] Fix build --- tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php b/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php index bcec59c6288..28b0091af95 100644 --- a/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php @@ -5,7 +5,6 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function sprintf; -use const PHP_VERSION_ID; /** @extends RuleTestCase */ class ConsistentConstructorRuleTest extends RuleTestCase From 9a88a777beb54f9b3e4e48c297e1b2c1da7b75d5 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:18:38 +0000 Subject: [PATCH 0172/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f6cb9c01dbe..feb31fdf19b 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.17", - "phpstan/php-8-stubs": "0.3.101", + "phpstan/php-8-stubs": "0.3.102", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 71beaf624c0..51285a4922e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f5af1898ab9d95520d1511334b2000c0", + "content-hash": "04ff25ded8cb4f666f7974b5a6ea36ac", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.101", + "version": "0.3.102", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "07a716723f30182d2f77c49426cc08327e5e9df1" + "reference": "607eedcd3bf7bc7baa2bc187741d772c776cc7ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/07a716723f30182d2f77c49426cc08327e5e9df1", - "reference": "07a716723f30182d2f77c49426cc08327e5e9df1", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/607eedcd3bf7bc7baa2bc187741d772c776cc7ee", + "reference": "607eedcd3bf7bc7baa2bc187741d772c776cc7ee", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.101" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.102" }, - "time": "2024-09-02T17:25:11+00:00" + "time": "2024-09-05T00:17:54+00:00" }, { "name": "phpstan/phpdoc-parser", From bfa057c19566b82a425ba4fd52de3bfa07d7e21a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 10:16:34 +0200 Subject: [PATCH 0173/3097] Remove polyfills for unsupported PHP versions --- bin/phpstan | 8 - compiler/build/scoper.inc.php | 4 - composer.json | 5 +- composer.lock | 155 +------------------ tests/PHPStan/Composer/AutoloadFilesTest.php | 2 - 5 files changed, 3 insertions(+), 171 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index bb97758ff6b..d4c45e59342 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -45,8 +45,6 @@ use Symfony\Component\Console\Helper\ProgressBar; || !array_key_exists('a4a119a56e50fbb293281d9a48007e0e', $composerAutoloadFiles) || !array_key_exists('0e6d7bf4a5811bfa5cf40c5ccd6fae6a', $composerAutoloadFiles) || !array_key_exists('e69f7f6ee287b969198c3c9d6777bd38', $composerAutoloadFiles) - || !array_key_exists('0d59ee240a4cd96ddbb4ff164fccea4d', $composerAutoloadFiles) - || !array_key_exists('b686b8e46447868025a15ce5d0cb2634', $composerAutoloadFiles) || !array_key_exists('8825ede83f2f289127722d4e842cf7e8', $composerAutoloadFiles) || !array_key_exists('23c18046f52bef3eea034657bafda50f', $composerAutoloadFiles) ) { @@ -69,12 +67,6 @@ use Symfony\Component\Console\Helper\ProgressBar; // vendor/symfony/polyfill-intl-normalizer/bootstrap.php 'e69f7f6ee287b969198c3c9d6777bd38' => true, - // vendor/symfony/polyfill-php73/bootstrap.php - '0d59ee240a4cd96ddbb4ff164fccea4d' => true, - - // vendor/symfony/polyfill-php74/bootstrap.php - 'b686b8e46447868025a15ce5d0cb2634' => true, - // vendor/symfony/polyfill-intl-grapheme/bootstrap.php '8825ede83f2f289127722d4e842cf7e8' => true, diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 5b4a21c5b21..fd867cbb483 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -20,8 +20,6 @@ '../../vendor/symfony/polyfill-php81', '../../vendor/symfony/polyfill-mbstring', '../../vendor/symfony/polyfill-intl-normalizer', - '../../vendor/symfony/polyfill-php73', - '../../vendor/symfony/polyfill-php74', '../../vendor/symfony/polyfill-intl-grapheme', ]) as $file) { if ($file->getPathName() === '../../vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { @@ -238,8 +236,6 @@ function (string $filePath, string $prefix, string $content): string { 'Symfony\Polyfill\Php81', 'Symfony\Polyfill\Mbstring', 'Symfony\Polyfill\Intl\Normalizer', - 'Symfony\Polyfill\Php73', - 'Symfony\Polyfill\Php74', 'Symfony\Polyfill\Intl\Grapheme', ], 'expose-global-functions' => false, diff --git a/composer.json b/composer.json index b2feaaa21e8..52c510ff2d5 100644 --- a/composer.json +++ b/composer.json @@ -41,8 +41,6 @@ "symfony/polyfill-intl-grapheme": "^1.23", "symfony/polyfill-intl-normalizer": "^1.23", "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php73": "^1.23", - "symfony/polyfill-php74": "^1.23", "symfony/polyfill-php80": "^1.23", "symfony/polyfill-php81": "^1.27", "symfony/process": "^5.4.3", @@ -50,7 +48,8 @@ "symfony/string": "^5.4.3" }, "replace": { - "phpstan/phpstan": "self.version" + "phpstan/phpstan": "self.version", + "symfony/polyfill-php73": "*" }, "require-dev": { "brianium/paratest": "^6.5", diff --git a/composer.lock b/composer.lock index eae3007e679..9318843ada7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a7d5c10ae8b0587d4147084f8a0455e8", + "content-hash": "4dde3e8ef5a5e7291597a6d5f2ca13fb", "packages": [ { "name": "clue/ndjson-react", @@ -3715,159 +3715,6 @@ ], "time": "2024-06-19T12:30:46+00:00" }, - { - "name": "symfony/polyfill-php73", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T15:07:36+00:00" - }, - { - "name": "symfony/polyfill-php74", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php74.git", - "reference": "37f1d1a2fb3ebc494f9f9b0f7e92064b43332321" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/37f1d1a2fb3ebc494f9f9b0f7e92064b43332321", - "reference": "37f1d1a2fb3ebc494f9f9b0f7e92064b43332321", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php74\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T15:07:36+00:00" - }, { "name": "symfony/polyfill-php80", "version": "v1.30.0", diff --git a/tests/PHPStan/Composer/AutoloadFilesTest.php b/tests/PHPStan/Composer/AutoloadFilesTest.php index e86af176174..3d735839e3f 100644 --- a/tests/PHPStan/Composer/AutoloadFilesTest.php +++ b/tests/PHPStan/Composer/AutoloadFilesTest.php @@ -66,8 +66,6 @@ public function testExpectedFiles(): void 'symfony/polyfill-intl-grapheme/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-intl-normalizer/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-mbstring/bootstrap.php', // afaik polyfills aren't necessary - 'symfony/polyfill-php73/bootstrap.php', // afaik polyfills aren't necessary - 'symfony/polyfill-php74/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-php80/bootstrap.php', // afaik polyfills aren't necessary 'symfony/polyfill-php81/bootstrap.php', // afaik polyfills aren't necessary 'symfony/string/Resources/functions.php', // afaik polyfills aren't necessary From 8acca821f1acbcfbd9499b378eb92a3de5d57f80 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 10:23:10 +0200 Subject: [PATCH 0174/3097] Fix PHAR compilation --- compiler/src/Console/PrepareCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/Console/PrepareCommand.php b/compiler/src/Console/PrepareCommand.php index 6d23dde7a6f..9dbc3e31923 100644 --- a/compiler/src/Console/PrepareCommand.php +++ b/compiler/src/Console/PrepareCommand.php @@ -63,7 +63,7 @@ private function fixComposerJson(string $buildDir): void { $json = json_decode($this->filesystem->read($buildDir . '/composer.json'), true); - unset($json['replace']); + unset($json['replace']['phpstan/phpstan']); $json['name'] = 'phpstan/phpstan'; $json['require']['php'] = '^7.4|^8.0'; From 2edfdb221acec87bf2afc506ff8678509c3daa13 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 10:23:54 +0200 Subject: [PATCH 0175/3097] Fix build --- build/composer-dependency-analyser.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/build/composer-dependency-analyser.php b/build/composer-dependency-analyser.php index 7502680e13a..79dc6942532 100644 --- a/build/composer-dependency-analyser.php +++ b/build/composer-dependency-analyser.php @@ -9,8 +9,6 @@ 'symfony/polyfill-intl-grapheme', 'symfony/polyfill-intl-normalizer', 'symfony/polyfill-mbstring', - 'symfony/polyfill-php73', - 'symfony/polyfill-php74', 'symfony/polyfill-php80', 'symfony/polyfill-php81', ]; From 9bd027c56330c0f5cc2abab2159549373539583d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 10:42:07 +0200 Subject: [PATCH 0176/3097] PHP 8.4 - report deprecated implicitly nullable parameter types --- src/Dependency/ExportedNodeResolver.php | 52 ++---------- src/Node/Printer/NodeTypePrinter.php | 52 ++++++++++++ src/Php/PhpVersion.php | 5 ++ src/Rules/FunctionDefinitionCheck.php | 85 ++++++++++++++++++- ...stingClassesInClosureTypehintsRuleTest.php | 22 +++++ .../data/closure-implicitly-nullable.php | 24 ++++++ .../ExistingClassesInTypehintsRuleTest.php | 22 +++++ .../data/method-implicitly-nullable.php | 23 +++++ 8 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 src/Node/Printer/NodeTypePrinter.php create mode 100644 tests/PHPStan/Rules/Functions/data/closure-implicitly-nullable.php create mode 100644 tests/PHPStan/Rules/Methods/data/method-implicitly-nullable.php diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index c9a0c521545..8e6eb17f61e 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -22,10 +22,10 @@ use PHPStan\Dependency\ExportedNode\ExportedTraitNode; use PHPStan\Dependency\ExportedNode\ExportedTraitUseAdaptation; use PHPStan\Node\Printer\ExprPrinter; +use PHPStan\Node\Printer\NodeTypePrinter; use PHPStan\ShouldNotHappenException; use PHPStan\Type\FileTypeMapper; use function array_map; -use function implode; use function is_string; final class ExportedNodeResolver @@ -165,7 +165,7 @@ public function resolve(string $fileName, Node $node): ?RootExportedNode $docComment !== null ? $docComment->getText() : null, ), $node->byRef, - $this->printType($node->returnType), + NodeTypePrinter::printType($node->returnType), $this->exportParameterNodes($node->params), $this->exportAttributeNodes($node->attrGroups), ); @@ -174,48 +174,6 @@ public function resolve(string $fileName, Node $node): ?RootExportedNode return null; } - /** - * @param Node\Identifier|Node\Name|Node\ComplexType|null $type - */ - private function printType($type): ?string - { - if ($type === null) { - return null; - } - - if ($type instanceof Node\NullableType) { - return '?' . $this->printType($type->type); - } - - if ($type instanceof Node\UnionType) { - return implode('|', array_map(function ($innerType): string { - $printedType = $this->printType($innerType); - if ($printedType === null) { - throw new ShouldNotHappenException(); - } - - return $printedType; - }, $type->types)); - } - - if ($type instanceof Node\IntersectionType) { - return implode('&', array_map(function ($innerType): string { - $printedType = $this->printType($innerType); - if ($printedType === null) { - throw new ShouldNotHappenException(); - } - - return $printedType; - }, $type->types)); - } - - if ($type instanceof Node\Identifier || $type instanceof Name) { - return $type->toString(); - } - - throw new ShouldNotHappenException(); - } - /** * @param Node\Param[] $params * @return ExportedParameterNode[] @@ -243,7 +201,7 @@ private function exportParameterNodes(array $params): array } $nodes[] = new ExportedParameterNode( $param->var->name, - $this->printType($type), + NodeTypePrinter::printType($type), $param->byRef, $param->variadic, $param->default !== null, @@ -321,7 +279,7 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $node->isAbstract(), $node->isFinal(), $node->isStatic(), - $this->printType($node->returnType), + NodeTypePrinter::printType($node->returnType), $this->exportParameterNodes($node->params), $this->exportAttributeNodes($node->attrGroups), ); @@ -343,7 +301,7 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string null, $docComment !== null ? $docComment->getText() : null, ), - $this->printType($node->type), + NodeTypePrinter::printType($node->type), $node->isPublic(), $node->isPrivate(), $node->isStatic(), diff --git a/src/Node/Printer/NodeTypePrinter.php b/src/Node/Printer/NodeTypePrinter.php new file mode 100644 index 00000000000..f2a110f048c --- /dev/null +++ b/src/Node/Printer/NodeTypePrinter.php @@ -0,0 +1,52 @@ +type); + } + + if ($type instanceof Node\UnionType) { + return implode('|', array_map(static function ($innerType): string { + $printedType = self::printType($innerType); + if ($printedType === null) { + throw new ShouldNotHappenException(); + } + + return $printedType; + }, $type->types)); + } + + if ($type instanceof Node\IntersectionType) { + return implode('&', array_map(static function ($innerType): string { + $printedType = self::printType($innerType); + if ($printedType === null) { + throw new ShouldNotHappenException(); + } + + return $printedType; + }, $type->types)); + } + + if ($type instanceof Node\Identifier || $type instanceof Node\Name) { + return $type->toString(); + } + + throw new ShouldNotHappenException(); + } + +} diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 817316d16f9..4eae1e59f91 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -343,4 +343,9 @@ public function highlightStringDoesNotReturnFalse(): bool return $this->versionId >= 80400; } + public function deprecatesImplicitlyNullableParameterTypes(): bool + { + return $this->versionId >= 80400; + } + } diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 19a76e042c2..e50021d7ed3 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules; use PhpParser\Node; +use PhpParser\Node\ComplexType; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; @@ -15,6 +16,7 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\UnionType; use PHPStan\Analyser\Scope; +use PHPStan\Node\Printer\NodeTypePrinter; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; @@ -41,6 +43,7 @@ use function in_array; use function is_string; use function sprintf; +use function strtolower; final class FunctionDefinitionCheck { @@ -103,7 +106,7 @@ public function checkAnonymousFunction( { $errors = []; $unionTypeReported = false; - foreach ($parameters as $param) { + foreach ($parameters as $i => $param) { if ($param->type === null) { continue; } @@ -123,6 +126,18 @@ public function checkAnonymousFunction( if (!$param->var instanceof Variable || !is_string($param->var->name)) { throw new ShouldNotHappenException(); } + + $implicitlyNullableTypeError = $this->checkImplicitlyNullableType( + $param->type, + $param->default, + $i + 1, + $param->getStartLine(), + $param->var->name, + ); + if ($implicitlyNullableTypeError !== null) { + $errors[] = $implicitlyNullableTypeError; + } + $type = $scope->getFunctionType($param->type, false, false); if ($type->isVoid()->yes()) { $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, 'void')) @@ -333,6 +348,18 @@ private function checkParametersAcceptor( } } + foreach ($parameterNodes as $i => $parameterNode) { + if (!$parameterNode->var instanceof Variable || !is_string($parameterNode->var->name)) { + throw new ShouldNotHappenException(); + } + $implicitlyNullableTypeError = $this->checkImplicitlyNullableType($parameterNode->type, $parameterNode->default, $i + 1, $parameterNode->getStartLine(), $parameterNode->var->name); + if ($implicitlyNullableTypeError === null) { + continue; + } + + $errors[] = $implicitlyNullableTypeError; + } + if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) { $errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameterNodes)); } @@ -654,4 +681,60 @@ private function getReturnTypeReferencedClasses(ParametersAcceptor $parametersAc ); } + private function checkImplicitlyNullableType( + Identifier|Name|ComplexType|null $type, + ?Node\Expr $default, + int $order, + int $line, + string $name, + ): ?IdentifierRuleError + { + if (!$default instanceof ConstFetch) { + return null; + } + + if ($default->name->toLowerString() !== 'null') { + return null; + } + + if ($type === null) { + return null; + } + + if ($type instanceof NullableType || $type instanceof IntersectionType) { + return null; + } + + if (!$this->phpVersion->deprecatesImplicitlyNullableParameterTypes()) { + return null; + } + + if ($type instanceof Identifier && strtolower($type->name) === 'mixed') { + return null; + } + if ($type instanceof Name && $type->toLowerString() === 'mixed') { + return null; + } + + if ($type instanceof UnionType) { + foreach ($type->types as $innerType) { + if ($innerType instanceof Identifier && strtolower($innerType->name) === 'null') { + return null; + } + if ($innerType instanceof Name && $innerType->toLowerString() === 'null') { + return null; + } + } + } + + return RuleErrorBuilder::message(sprintf( + 'Deprecated in PHP 8.4: Parameter #%d $%s (%s) is implicitly nullable via default value null.', + $order, + $name, + NodeTypePrinter::printType($type), + ))->line($line) + ->identifier('parameter.implicitlyNullable') + ->build(); + } + } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 86f87255730..aa026a0f07b 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -330,4 +330,26 @@ public function testIntersectionTypes(int $phpVersion, array $errors): void $this->analyse([__DIR__ . '/data/closure-intersection-types.php'], $errors); } + public function testDeprecatedImplicitlyNullableParameterType(): void + { + if (PHP_VERSION_ID < 80400) { + self::markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/closure-implicitly-nullable.php'], [ + [ + 'Deprecated in PHP 8.4: Parameter #3 $c (int) is implicitly nullable via default value null.', + 13, + ], + [ + 'Deprecated in PHP 8.4: Parameter #5 $e (int|string) is implicitly nullable via default value null.', + 15, + ], + [ + 'Deprecated in PHP 8.4: Parameter #7 $g (stdClass) is implicitly nullable via default value null.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/closure-implicitly-nullable.php b/tests/PHPStan/Rules/Functions/data/closure-implicitly-nullable.php new file mode 100644 index 00000000000..f513b60f61f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/closure-implicitly-nullable.php @@ -0,0 +1,24 @@ += 8.0 + +namespace ClosureImplicitNullable; + +class Foo +{ + + public function doFoo(): void + { + $c = function ( + $a = null, + int $b = 1, + int $c = null, + mixed $d = null, + int|string $e = null, + int|string|null $f = null, + \stdClass $g = null, + ?\stdClass $h = null, + ): void { + + }; + } + +} diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index b86302f4536..f95708eda30 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -526,4 +526,26 @@ public function testSelfOut(): void ]); } + public function testDeprecatedImplicitlyNullableParameterType(): void + { + if (PHP_VERSION_ID < 80400) { + self::markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/method-implicitly-nullable.php'], [ + [ + 'Deprecated in PHP 8.4: Parameter #3 $c (int) is implicitly nullable via default value null.', + 13, + ], + [ + 'Deprecated in PHP 8.4: Parameter #5 $e (int|string) is implicitly nullable via default value null.', + 15, + ], + [ + 'Deprecated in PHP 8.4: Parameter #7 $g (stdClass) is implicitly nullable via default value null.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/method-implicitly-nullable.php b/tests/PHPStan/Rules/Methods/data/method-implicitly-nullable.php new file mode 100644 index 00000000000..f5690b2c727 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-implicitly-nullable.php @@ -0,0 +1,23 @@ + Date: Thu, 5 Sep 2024 11:24:38 +0200 Subject: [PATCH 0177/3097] Simplify code --- src/Rules/FunctionDefinitionCheck.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index c8e839e478d..3942cc1b405 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -712,18 +712,12 @@ private function checkImplicitlyNullableType( if ($type instanceof Identifier && strtolower($type->name) === 'mixed') { return null; } - if ($type instanceof Name && $type->toLowerString() === 'mixed') { - return null; - } if ($type instanceof UnionType) { foreach ($type->types as $innerType) { if ($innerType instanceof Identifier && strtolower($innerType->name) === 'null') { return null; } - if ($innerType instanceof Name && $innerType->toLowerString() === 'null') { - return null; - } } } From 0ea1d4b36ed5701a6f74dcd97781633a2145bf37 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 11:31:15 +0200 Subject: [PATCH 0178/3097] Fix tests --- tests/PHPStan/Analyser/data/bug-8072.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/data/bug-8072.php b/tests/PHPStan/Analyser/data/bug-8072.php index 2b81792b99e..2f9b8810572 100644 --- a/tests/PHPStan/Analyser/data/bug-8072.php +++ b/tests/PHPStan/Analyser/data/bug-8072.php @@ -8,13 +8,13 @@ function say(\Closure $bar): string } function (): void { - echo say(fn (string $name = null) => 'Hi'); - echo say((fn (string $name = null) => 'Hi')(...)); + echo say(fn (?string $name = null) => 'Hi'); + echo say((fn (?string $name = null) => 'Hi')(...)); - echo say(function (string $name = null) { + echo say(function (?string $name = null) { return 'Hi'; }); - echo say((function (string $name = null) { + echo say((function (?string $name = null) { return 'Hi'; })(...)); }; From 42709fcc73a407cef468a9500c55e1fcd9968b93 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 11:34:33 +0200 Subject: [PATCH 0179/3097] Fix stubs --- stubs/core.stub | 12 ++++++------ stubs/ext-ds.stub | 30 +++++++++++++++--------------- stubs/ibm_db2.stub | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/stubs/core.stub b/stubs/core.stub index e3a0b751ee9..2dc82fe7aa5 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -80,7 +80,7 @@ function parse_str(string $string, array &$result): void {} function mb_parse_str(string $string, array &$result): bool {} /** @param-out float $percent */ -function similar_text(string $string1, string $string2, float &$percent = null) : int {} +function similar_text(string $string1, string $string2, ?float &$percent = null) : int {} /** * @param mixed $output @@ -258,7 +258,7 @@ function preg_filter($pattern, $replacement, $subject, int $limit = -1, &$count * @param-out int $count * @return list|string */ -function str_replace($search, $replace, $subject, int &$count = null) {} +function str_replace($search, $replace, $subject, ?int &$count = null) {} /** * @param array|string $search @@ -267,7 +267,7 @@ function str_replace($search, $replace, $subject, int &$count = null) {} * @param-out int $count * @return list|string */ -function str_ireplace($search, $replace, $subject, int &$count = null) {} +function str_ireplace($search, $replace, $subject, ?int &$count = null) {} /** * @template TRead of null|array @@ -294,16 +294,16 @@ function flock($stream, int $operation, mixed &$would_block = null): bool {} * @param-out string $error_message * @return resource|false */ -function fsockopen(string $hostname, int $port = -1, int &$error_code = null, string &$error_message = null, ?float $timeout = null) {} +function fsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null) {} /** * @param-out string $filename * @param-out int $line */ -function headers_sent(string &$filename = null, int &$line = null): bool {} +function headers_sent(?string &$filename = null, ?int &$line = null): bool {} /** * @param-out callable-string $callable_name * @return ($value is callable ? true : false) */ -function is_callable(mixed $value, bool $syntax_only = false, string &$callable_name = null): bool {} +function is_callable(mixed $value, bool $syntax_only = false, ?string &$callable_name = null): bool {} diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index 05fdf38f0a4..ba72a0d5840 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -61,7 +61,7 @@ final class Deque implements Sequence * @param (callable(TValue): bool)|null $callback * @return Deque */ - public function filter(callable $callback = null): Deque + public function filter(?callable $callback = null): Deque { } @@ -190,7 +190,7 @@ final class Map implements Collection, ArrayAccess * @param (callable(TKey, TValue): bool)|null $callback * @return Map */ - public function filter(callable $callback = null): Map + public function filter(?callable $callback = null): Map { } @@ -284,7 +284,7 @@ final class Map implements Collection, ArrayAccess * @param (callable(TValue, TValue): int)|null $comparator * @return void */ - public function sort(callable $comparator = null) + public function sort(?callable $comparator = null) { } @@ -292,7 +292,7 @@ final class Map implements Collection, ArrayAccess * @param (callable(TValue, TValue): int)|null $comparator * @return Map */ - public function sorted(callable $comparator = null): Map + public function sorted(?callable $comparator = null): Map { } @@ -300,7 +300,7 @@ final class Map implements Collection, ArrayAccess * @param (callable(TKey, TKey): int)|null $comparator * @return void */ - public function ksort(callable $comparator = null) + public function ksort(?callable $comparator = null) { } @@ -308,7 +308,7 @@ final class Map implements Collection, ArrayAccess * @param (callable(TKey, TKey): int)|null $comparator * @return Map */ - public function ksorted(callable $comparator = null): Map + public function ksorted(?callable $comparator = null): Map { } @@ -401,7 +401,7 @@ interface Sequence extends Collection, ArrayAccess * @param (callable(TValue): bool)|null $callback * @return Sequence */ - public function filter(callable $callback = null); + public function filter(?callable $callback = null); /** * @param TValue $value @@ -432,7 +432,7 @@ interface Sequence extends Collection, ArrayAccess * @param string $glue * @return string */ - public function join(string $glue = null): string; + public function join(?string $glue = null): string; /** * @return TValue @@ -509,13 +509,13 @@ interface Sequence extends Collection, ArrayAccess * @param (callable(TValue, TValue): int)|null $comparator * @return void */ - public function sort(callable $comparator = null); + public function sort(?callable $comparator = null); /** * @param (callable(TValue, TValue): int)|null $comparator * @return Sequence */ - public function sorted(callable $comparator = null); + public function sorted(?callable $comparator = null); /** * @param TValue ...$values @@ -563,7 +563,7 @@ final class Vector implements Sequence * @param (callable(TValue, TValue): int)|null $comparator * @return Vector */ - public function sorted(callable $comparator = null): Vector + public function sorted(?callable $comparator = null): Vector { } @@ -571,7 +571,7 @@ final class Vector implements Sequence * @param (callable(TValue): bool)|null $callback * @return Vector */ - public function filter(callable $callback = null): Vector + public function filter(?callable $callback = null): Vector { } @@ -642,7 +642,7 @@ final class Set implements Collection, ArrayAccess * @param (callable(TValue): bool)|null $callback * @return Set */ - public function filter(callable $callback = null): Set + public function filter(?callable $callback = null): Set { } @@ -731,7 +731,7 @@ final class Set implements Collection, ArrayAccess /** * @param (callable(TValue, TValue): int)|null $comparator */ - public function sort(callable $comparator = null): void + public function sort(?callable $comparator = null): void { } @@ -739,7 +739,7 @@ final class Set implements Collection, ArrayAccess * @param (callable(TValue, TValue): int)|null $comparator * @return Set */ - public function sorted(callable $comparator = null): Set + public function sorted(?callable $comparator = null): Set { } diff --git a/stubs/ibm_db2.stub b/stubs/ibm_db2.stub index 1b0e578bfcd..544473f615d 100644 --- a/stubs/ibm_db2.stub +++ b/stubs/ibm_db2.stub @@ -6,4 +6,4 @@ * * @return ($value is null ? \DB2_AUTOCOMMIT_OFF|\DB2_AUTOCOMMIT_ON : bool) */ -function db2_autocommit($connection, int $value = null) {} +function db2_autocommit($connection, ?int $value = null) {} From c889baa9ec60394e9201b2a7054486c66b40fa9b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 14:20:49 +0200 Subject: [PATCH 0180/3097] Run `@mixin` class reflection extensions after all other class reflection extensions --- conf/config.neon | 4 ---- .../LazyClassReflectionExtensionRegistryProvider.php | 9 +++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 255be78dc29..236aed720d8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -797,15 +797,11 @@ services: - class: PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension - tags: - - phpstan.broker.methodsClassReflectionExtension arguments: mixinExcludeClasses: %mixinExcludeClasses% - class: PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension - tags: - - phpstan.broker.propertiesClassReflectionExtension arguments: mixinExcludeClasses: %mixinExcludeClasses% diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index 34b91d99a53..cb8c2d6543b 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -8,6 +8,8 @@ use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension; use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; use PHPStan\Reflection\ClassReflectionExtensionRegistry; +use PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension; +use PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension; use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension; @@ -29,10 +31,13 @@ public function getRegistry(): ClassReflectionExtensionRegistry $annotationsMethodsClassReflectionExtension = $this->container->getByType(AnnotationsMethodsClassReflectionExtension::class); $annotationsPropertiesClassReflectionExtension = $this->container->getByType(AnnotationsPropertiesClassReflectionExtension::class); + $mixinMethodsClassReflectionExtension = $this->container->getByType(MixinMethodsClassReflectionExtension::class); + $mixinPropertiesClassReflectionExtension = $this->container->getByType(MixinPropertiesClassReflectionExtension::class); + $this->registry = new ClassReflectionExtensionRegistry( $this->container->getByType(Broker::class), - array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension]), - array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension]), + array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension, $mixinPropertiesClassReflectionExtension]), + array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension, $mixinMethodsClassReflectionExtension]), $this->container->getServicesByTag(BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG), $this->container->getByType(RequireExtendsPropertiesClassReflectionExtension::class), $this->container->getByType(RequireExtendsMethodsClassReflectionExtension::class), From b83a1eba8b183a43ee0dccf15894f2b4d581d1b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 17:29:38 +0200 Subject: [PATCH 0181/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/10159 --- .../Rules/Methods/CallMethodsRuleTest.php | 9 ++++++ .../PHPStan/Rules/Methods/data/bug-10159.php | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10159.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 3f29976a126..8bb53330d43 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3354,4 +3354,13 @@ public function testTraitMixin(): void $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); } + public function testBug10159(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-10159.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10159.php b/tests/PHPStan/Rules/Methods/data/bug-10159.php new file mode 100644 index 00000000000..6d1c7fb276e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10159.php @@ -0,0 +1,30 @@ +someMethod()->methodFromChild(); +}; From 94ac43a704e86a54ba29b9b460b4aa10fa203c23 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 17:40:04 +0200 Subject: [PATCH 0182/3097] Fix phar.yml workflow --- .github/workflows/phar.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index c6c4934b44b..47b7b6e300d 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -10,6 +10,9 @@ on: tags: - '1.12.*' +env: + COMPOSER_ROOT_VERSION: "1.12.x-dev" + concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests cancel-in-progress: true @@ -76,15 +79,12 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" - env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" From 263d5e42e2e734733a718eac427623ad607b5656 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 17:42:04 +0200 Subject: [PATCH 0183/3097] Fix phar.yml --- .github/workflows/phar.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 131592080ab..03926a683d5 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -13,9 +13,6 @@ on: env: COMPOSER_ROOT_VERSION: "2.0.x-dev" -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests cancel-in-progress: true From ead586b08c1b3cc76feb95325bc2b30ba4385a28 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Thu, 5 Sep 2024 15:36:24 +0000 Subject: [PATCH 0184/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index feb31fdf19b..670a2ffb39f 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.17", + "ondrejmirtes/better-reflection": "6.25.0.18", "phpstan/php-8-stubs": "0.3.102", "phpstan/phpdoc-parser": "1.30.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 51285a4922e..dcea32fd8ea 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "04ff25ded8cb4f666f7974b5a6ea36ac", + "content-hash": "8688fdadbf2effbbc87fb35192f48d8e", "packages": [ { "name": "clue/ndjson-react", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.17", + "version": "6.25.0.18", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2" + "reference": "04ce3daaa7bcbf96be471b56ee95d336201a75eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", - "reference": "2c9cf932ab3306e0fcb90471f3b63fa3190bf7b2", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/04ce3daaa7bcbf96be471b56ee95d336201a75eb", + "reference": "04ce3daaa7bcbf96be471b56ee95d336201a75eb", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.17" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.18" }, - "time": "2024-08-26T20:47:13+00:00" + "time": "2024-09-05T15:34:08+00:00" }, { "name": "phpstan/php-8-stubs", From f7b6fe39bc5a35e980c888ecc76c24f0e8559c5c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Sep 2024 09:45:48 +0200 Subject: [PATCH 0185/3097] Simplify isFloat checks --- src/Reflection/InitializerExprTypeResolver.php | 5 ++--- src/Type/ExponentiateHelper.php | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index dae4e111407..1eec9736d1e 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1192,17 +1192,16 @@ public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): return TypeCombinator::union(...$resultTypes); } - $floatType = new FloatType(); $leftNumberType = $leftType->toNumber(); if ($leftNumberType instanceof ConstantIntegerType && $leftNumberType->getValue() === 0) { - if ($floatType->isSuperTypeOf($rightType)->yes()) { + if ($rightType->isFloat()->yes()) { return new ConstantFloatType(0.0); } return new ConstantIntegerType(0); } $rightNumberType = $rightType->toNumber(); if ($rightNumberType instanceof ConstantIntegerType && $rightNumberType->getValue() === 0) { - if ($floatType->isSuperTypeOf($leftType)->yes()) { + if ($leftType->isFloat()->yes()) { return new ConstantFloatType(0.0); } return new ConstantIntegerType(0); diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 10da4dd4849..fd65dc9e51f 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -46,8 +46,7 @@ public static function exponentiate(Type $base, Type $exponent): Type } // exponentiation of a float, stays a float - $float = new FloatType(); - $isFloatBase = $float->isSuperTypeOf($base)->yes(); + $isFloatBase = $base->isFloat()->yes(); $isLooseZero = (new ConstantIntegerType(0))->isSuperTypeOf($exponent->toNumber()); if ($isLooseZero->yes()) { From 008f65e87320c91249001686485b95e04d813123 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 5 Sep 2024 17:45:53 +0200 Subject: [PATCH 0186/3097] RegexArrayShapeMatcher - Don't optimize alternations with optional groups for tagged unions --- src/Type/Php/RegexArrayShapeMatcher.php | 4 ++++ src/Type/Regex/RegexCapturingGroup.php | 5 +++++ tests/PHPStan/Analyser/nsrt/preg_match_shapes.php | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 628bcb0d3cc..fb94b88ab0c 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -280,6 +280,10 @@ private function getOnlyTopLevelAlternation(array $captureGroups): ?RegexAlterna return null; } + if ($captureGroup->inOptionalQuantification()) { + return null; + } + if ($alternation === null) { $alternation = $captureGroup->getAlternation(); } elseif ($alternation->getId() !== $captureGroup->getAlternation()->getId()) { diff --git a/src/Type/Regex/RegexCapturingGroup.php b/src/Type/Regex/RegexCapturingGroup.php index 62708a2de30..51a1fc9d85e 100644 --- a/src/Type/Regex/RegexCapturingGroup.php +++ b/src/Type/Regex/RegexCapturingGroup.php @@ -82,6 +82,11 @@ public function isOptional(): bool || $this->parent !== null && $this->parent->isOptional(); } + public function inOptionalQuantification(): bool + { + return $this->inOptionalQuantification; + } + public function inOptionalAlternation(): bool { if (!$this->inAlternation()) { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 694a7cc886d..4b17f15ed40 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -753,3 +753,18 @@ function bug11622 (string $expression): void { assertType("array{string, string}", $matches); } } + +function bug11604 (string $string): void { + if (! preg_match('/(XX)|(YY)?ZZ/', $string, $matches)) { + return; + } + + assertType("array{0: string, 1?: ''|'XX', 2?: 'YY'}", $matches); + // could be array{string, '', 'YY'}|array{string, 'XX'}|array{string} +} + +function bug11604b (string $string): void { + if (preg_match('/(XX)|(YY)?(ZZ)/', $string, $matches)) { + assertType("array{0: string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); + } +} From 5379e31a7f1f1d71b3b0c55012875c035e2e2754 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 2 Sep 2024 20:30:04 +0200 Subject: [PATCH 0187/3097] Narrow array on count() with positive-int --- src/Analyser/TypeSpecifier.php | 121 ++++++++++-------- tests/PHPStan/Analyser/nsrt/bug-3993.php | 2 +- tests/PHPStan/Analyser/nsrt/count-type.php | 25 +++- .../Analyser/nsrt/strlen-int-range.php | 14 ++ 4 files changed, 109 insertions(+), 53 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 356d0b30bef..fa2a2f2ef9f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -39,6 +39,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\ConditionalTypeForParameter; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -1049,7 +1050,7 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, $offsetType = new ConstantIntegerType($i); $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), true); } - } else { + } elseif ($type->isConstantArray()->yes()) { for ($i = $sizeType->getMin();; $i++) { $offsetType = new ConstantIntegerType($i); $hasOffset = $type->hasOffsetValueType($offsetType); @@ -1060,7 +1061,11 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, } } - return $valueTypesBuilder->getArray(); + + $arrayType = $valueTypesBuilder->getArray(); + if ($arrayType->isIterableAtLeastOnce()->yes()) { + return $arrayType; + } } return null; @@ -1102,54 +1107,6 @@ private function specifyTypesForConstantBinaryExpression( )); } - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->getArgs()) >= 1 - && $exprNode->name instanceof Name - && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) - && $constantType instanceof ConstantIntegerType - ) { - if ($constantType->getValue() < 0) { - return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); - } - - $argType = $scope->getType($exprNode->getArgs()[0]->value); - - if ($argType instanceof UnionType) { - $narrowed = $this->narrowUnionByArraySize($exprNode, $argType, $constantType, $context, $scope, $rootExpr); - if ($narrowed !== null) { - return $narrowed; - } - } - - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - - if ($argType->isArray()->yes()) { - if ( - $context->truthy() - && $argType->isConstantArray()->yes() - && $constantType->isSuperTypeOf($argType->getArraySize())->no() - ) { - return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); - } - - $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - $constArray = $this->turnListIntoConstantArray($exprNode, $argType, $constantType, $scope); - if ($context->truthy() && $constArray !== null) { - $valueTypes = $this->create($exprNode->getArgs()[0]->value, $constArray, $context, false, $scope, $rootExpr); - } else { - $valueTypes = $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope, $rootExpr); - } - return $funcTypes->unionWith($valueTypes); - } - } - } - if ( !$context->null() && $exprNode instanceof FuncCall @@ -2137,6 +2094,70 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } $rightType = $scope->getType($rightExpr); + if ( + !$context->null() + && $unwrappedLeftExpr instanceof FuncCall + && count($unwrappedLeftExpr->getArgs()) >= 1 + && $unwrappedLeftExpr->name instanceof Name + && in_array(strtolower((string) $unwrappedLeftExpr->name), ['count', 'sizeof'], true) + && $rightType->isInteger()->yes() + ) { + if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($rightType)->yes()) { + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + } + + $argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value); + $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($rightType); + if ($isZero->yes()) { + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + + if ($context->truthy() && !$argType->isArray()->yes()) { + $newArgType = new UnionType([ + new ObjectType(Countable::class), + new ConstantArrayType([], []), + ]); + } else { + $newArgType = new ConstantArrayType([], []); + } + + return $funcTypes->unionWith( + $this->create($unwrappedLeftExpr->getArgs()[0]->value, $newArgType, $context, false, $scope, $rootExpr), + ); + } + + if ($argType instanceof UnionType) { + $narrowed = $this->narrowUnionByArraySize($unwrappedLeftExpr, $argType, $rightType, $context, $scope, $rootExpr); + if ($narrowed !== null) { + return $narrowed; + } + } + + if ($context->truthy()) { + if ($argType->isArray()->yes()) { + if ( + $argType->isConstantArray()->yes() + && $rightType->isSuperTypeOf($argType->getArraySize())->no() + ) { + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + } + + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + $constArray = $this->turnListIntoConstantArray($unwrappedLeftExpr, $argType, $rightType, $scope); + if ($constArray !== null) { + return $funcTypes->unionWith( + $this->create($unwrappedLeftExpr->getArgs()[0]->value, $constArray, $context, false, $scope, $rootExpr), + ); + } elseif (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) { + return $funcTypes->unionWith( + $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, false, $scope, $rootExpr), + ); + } + + return $funcTypes; + } + } + } + if ( $context->true() && $unwrappedLeftExpr instanceof FuncCall diff --git a/tests/PHPStan/Analyser/nsrt/bug-3993.php b/tests/PHPStan/Analyser/nsrt/bug-3993.php index e472a0d68ca..38b1884bf5d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3993.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3993.php @@ -13,7 +13,7 @@ public function doFoo($arguments) return; } - assertType('mixed~null', $arguments); + assertType('mixed~array{}|null', $arguments); array_shift($arguments); diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 09114d90f89..54fb89c2c79 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -44,12 +44,12 @@ public function doFooBar( if (count($arr) == $maybeZero) { assertType('array', $arr); } else { - assertType('non-empty-array', $arr); + assertType('array', $arr); } if (count($arr) === $maybeZero) { assertType('array', $arr); } else { - assertType('non-empty-array', $arr); + assertType('array', $arr); } if (count($arr) == $negative) { @@ -65,3 +65,24 @@ public function doFooBar( } } + +/** + * @param \ArrayObject $obj + */ +function(\ArrayObject $obj): void { + if (count($obj) === 0) { + assertType('ArrayObject', $obj); + return; + } + + assertType('ArrayObject', $obj); +}; + +function($mixed): void { + if (count($mixed) === 0) { + assertType('array{}|Countable', $mixed); + return; + } + + assertType('mixed~array{}', $mixed); +}; diff --git a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php index 7a4e2172875..f66d50c140d 100644 --- a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php +++ b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php @@ -113,3 +113,17 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, assertType('string', $s); } } + +/** + * @param int<1, max> $oneOrMore + * @param int<2, max> $twoOrMore + */ +function doFooBar(array $arr, int $oneOrMore, int $twoOrMore): void +{ + if (count($arr) == $oneOrMore) { + assertType('non-empty-array', $arr); + } + if (count($arr) === $twoOrMore) { + assertType('non-empty-array', $arr); + } +} From 08dc679cf44edf579f81918fd0f1cd6804101317 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 2 Sep 2024 12:48:34 +0200 Subject: [PATCH 0188/3097] Improve narrowing after string functions --- src/Analyser/TypeSpecifier.php | 70 +++++++++---------- .../non-empty-string-substr-specifying.php | 18 ++++- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fa2a2f2ef9f..c7413dec9b5 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1157,41 +1157,6 @@ private function specifyTypesForConstantStringBinaryExpression( } $constantStringValue = $scalarValues[0]; - if ( - $context->truthy() - && $exprNode instanceof FuncCall - && $exprNode->name instanceof Name - && in_array(strtolower($exprNode->name->toString()), [ - 'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst', - 'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst', - 'ucwords', 'mb_convert_case', 'mb_convert_kana', - ], true) - && isset($exprNode->getArgs()[0]) - && $constantStringValue !== '' - ) { - $argType = $scope->getType($exprNode->getArgs()[0]->value); - - if ($argType->isString()->yes()) { - if ($constantStringValue !== '0') { - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), - $context, - false, - $scope, - ); - } - - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), - $context, - false, - $scope, - ); - } - } - if ( $exprNode instanceof FuncCall && $exprNode->name instanceof Name @@ -2192,6 +2157,41 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + if ( + $context->truthy() + && $unwrappedLeftExpr instanceof FuncCall + && $unwrappedLeftExpr->name instanceof Name + && in_array(strtolower($unwrappedLeftExpr->name->toString()), [ + 'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst', + 'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst', + 'ucwords', 'mb_convert_case', 'mb_convert_kana', + ], true) + && isset($unwrappedLeftExpr->getArgs()[0]) + && $rightType->isNonEmptyString()->yes() + ) { + $argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value); + + if ($argType->isString()->yes()) { + if ($rightType->isNonFalsyString()->yes()) { + return $this->create( + $unwrappedLeftExpr->getArgs()[0]->value, + TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), + $context, + false, + $scope, + ); + } + + return $this->create( + $unwrappedLeftExpr->getArgs()[0]->value, + TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), + $context, + false, + $scope, + ); + } + } + if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) { $types = null; foreach ($rightType->getFiniteTypes() as $finiteType) { diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php index 6307af45e9d..8ad670a7f87 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php @@ -4,7 +4,8 @@ use function PHPStan\Testing\assertType; -class Foo { +class Foo +{ public function nonEmptySubstr(string $s, int $offset, int $length): void { if (substr($s, 10) === 'hallo') { @@ -81,4 +82,19 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void assertType('\'hallo\'', $x); } } + + /** + * @param non-empty-string $nonES + * @param non-falsy-string $falsyString + */ + public function stringTypes(string $s, $nonES, $falsyString): void + { + if (substr($s, 10) === $nonES) { + assertType('non-empty-string', $s); + } + + if (substr($s, 10) === $falsyString) { + assertType('non-falsy-string', $s); + } + } } From 3116a1be707b55f119a51589a2914ab370853625 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 23 Aug 2024 11:40:14 +0200 Subject: [PATCH 0189/3097] Add `Type::reverseArray()` --- src/Type/Accessory/AccessoryArrayListType.php | 9 ++++++ src/Type/Accessory/HasOffsetType.php | 9 ++++++ src/Type/Accessory/HasOffsetValueType.php | 9 ++++++ src/Type/Accessory/NonEmptyArrayType.php | 5 +++ src/Type/Accessory/OversizedArrayType.php | 5 +++ src/Type/ArrayType.php | 5 +++ src/Type/Constant/ConstantArrayType.php | 31 +++++++++++++------ src/Type/IntersectionType.php | 5 +++ src/Type/MixedType.php | 9 ++++++ src/Type/NeverType.php | 5 +++ ...rrayReverseFunctionReturnTypeExtension.php | 29 +++++++---------- src/Type/StaticType.php | 5 +++ src/Type/Traits/LateResolvableTypeTrait.php | 5 +++ src/Type/Traits/MaybeArrayTypeTrait.php | 5 +++ src/Type/Traits/NonArrayTypeTrait.php | 5 +++ src/Type/Type.php | 2 ++ src/Type/UnionType.php | 5 +++ .../Analyser/nsrt/array-reverse-php7.php | 16 ++++++++++ .../Analyser/nsrt/array-reverse-php8.php | 16 ++++++++++ tests/PHPStan/Analyser/nsrt/array-reverse.php | 24 ++++++++++++++ 20 files changed, 177 insertions(+), 27 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/array-reverse-php7.php create mode 100644 tests/PHPStan/Analyser/nsrt/array-reverse-php8.php diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 874b177716e..f80ada4ad90 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -218,6 +218,15 @@ public function popArray(): Type return $this; } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + if ($preserveKeys->no()) { + return $this; + } + + return new MixedType(); + } + public function searchArray(Type $needleType): Type { return new MixedType(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index cdb258d6138..ac4b6e402b5 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -189,6 +189,15 @@ public function intersectKeyArray(Type $otherArraysType): Type return new MixedType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + if ($preserveKeys->yes()) { + return $this; + } + + return new NonEmptyArrayType(); + } + public function shuffleArray(): Type { return new NonEmptyArrayType(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index a87f7879abc..59a2a26b217 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -233,6 +233,15 @@ public function intersectKeyArray(Type $otherArraysType): Type return new MixedType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + if ($preserveKeys->yes()) { + return $this; + } + + return new NonEmptyArrayType(); + } + public function searchArray(Type $needleType): Type { if ( diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 8ef7ecbb88a..8f2ccac6894 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -199,6 +199,11 @@ public function popArray(): Type return new MixedType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function searchArray(Type $needleType): Type { return new MixedType(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index c6c9c5b6d7f..ad8a1c2a139 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -195,6 +195,11 @@ public function popArray(): Type return $this; } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function searchArray(Type $needleType): Type { return new MixedType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 6040f0ee068..061e002a46d 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -552,6 +552,11 @@ public function popArray(): Type return $this; } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function searchArray(Type $needleType): Type { return TypeCombinator::union($this->getIterableKeyType(), new ConstantBooleanType(false)); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 3853794348c..5ab51870a8e 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -837,6 +837,23 @@ public function popArray(): Type return $this->removeLastElements(1); } + private function reverseConstantArray(TrinaryLogic $preserveKeys): self + { + $keyTypesReversed = array_reverse($this->keyTypes, true); + $keyTypes = array_values($keyTypesReversed); + $keyTypesReversedKeys = array_keys($keyTypesReversed); + $optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); + + $reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo()); + + return $preserveKeys->yes() ? $reversed : $reversed->reindex(); + } + + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this->reverseConstantArray($preserveKeys); + } + public function searchArray(Type $needleType): Type { $matches = []; @@ -1121,9 +1138,9 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel $offset *= -1; $reversedLimit = min($limit, $offset); $reversedOffset = $offset - $reversedLimit; - return $this->reverse(true) + return $this->reverseConstantArray(TrinaryLogic::createYes()) ->slice($reversedOffset, $reversedLimit, $preserveKeys) - ->reverse(true); + ->reverseConstantArray(TrinaryLogic::createYes()); } if ($offset > 0) { @@ -1162,16 +1179,10 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel return $preserveKeys ? $slice : $slice->reindex(); } + /** @deprecated Use reverseArray() instead */ public function reverse(bool $preserveKeys = false): self { - $keyTypesReversed = array_reverse($this->keyTypes, true); - $keyTypes = array_values($keyTypesReversed); - $keyTypesReversedKeys = array_keys($keyTypesReversed); - $optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); - - $reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo()); - - return $preserveKeys ? $reversed : $reversed->reindex(); + return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys)); } /** @param positive-int $length */ diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 9b4fbf22bae..08ff4070334 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -744,6 +744,11 @@ public function popArray(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->popArray()); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys)); + } + public function searchArray(Type $needleType): Type { return $this->intersectTypes(static fn (Type $type): Type => $type->searchArray($needleType)); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 402d192a7d9..c45642f0d2b 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -225,6 +225,15 @@ public function popArray(): Type return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + if ($this->isArray()->no()) { + return new ErrorType(); + } + + return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); + } + public function searchArray(Type $needleType): Type { if ($this->isArray()->no()) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index b619768b6dd..a21dcb7d236 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -316,6 +316,11 @@ public function popArray(): Type return new NeverType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return new NeverType(); + } + public function searchArray(Type $needleType): Type { return new NeverType(); diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 1a693eff8d6..1696d39d48f 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -4,16 +4,21 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; -use function count; final class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'array_reverse'; @@ -26,24 +31,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $type = $scope->getType($functionCall->getArgs()[0]->value); - $preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new NeverType(); - $preserveKeys = $preserveKeysType->isTrue()->yes(); - - if (!$type->isArray()->yes()) { - return null; + if ($type->isArray()->no()) { + return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - $constantArrays = $type->getConstantArrays(); - if (count($constantArrays) > 0) { - $results = []; - foreach ($constantArrays as $constantArray) { - $results[] = $constantArray->reverse($preserveKeys); - } - - return TypeCombinator::union(...$results); - } + $preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new ConstantBooleanType(false); + $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType); - return $type; + return $type->reverseArray($preserveKeys); } } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 766cede665f..9db6c41cd0e 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -435,6 +435,11 @@ public function popArray(): Type return $this->getStaticObjectType()->popArray(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this->getStaticObjectType()->reverseArray($preserveKeys); + } + public function searchArray(Type $needleType): Type { return $this->getStaticObjectType()->searchArray($needleType); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index a749a6fae04..bea1d953a01 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -282,6 +282,11 @@ public function popArray(): Type return $this->resolve()->popArray(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this->resolve()->reverseArray($preserveKeys); + } + public function searchArray(Type $needleType): Type { return $this->resolve()->searchArray($needleType); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index da064c82b7d..7d258975537 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -69,6 +69,11 @@ public function popArray(): Type return new ErrorType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + public function searchArray(Type $needleType): Type { return new ErrorType(); diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index def99b0e94d..8deb186895f 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -69,6 +69,11 @@ public function popArray(): Type return new ErrorType(); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + public function searchArray(Type $needleType): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index ec682c3e034..60e1046d1bf 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -159,6 +159,8 @@ public function intersectKeyArray(Type $otherArraysType): Type; public function popArray(): Type; + public function reverseArray(TrinaryLogic $preserveKeys): Type; + public function searchArray(Type $needleType): Type; public function shiftArray(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 1a8ac35969c..f79dbe88a41 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -721,6 +721,11 @@ public function popArray(): Type return $this->unionTypes(static fn (Type $type): Type => $type->popArray()); } + public function reverseArray(TrinaryLogic $preserveKeys): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys)); + } + public function searchArray(Type $needleType): Type { return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType)); diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse-php7.php b/tests/PHPStan/Analyser/nsrt/array-reverse-php7.php new file mode 100644 index 00000000000..4c9d1ab563e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-reverse-php7.php @@ -0,0 +1,16 @@ += 8.0 + +declare(strict_types = 1); + +namespace ArrayReversePhp8; + +use function PHPStan\Testing\assertType; + +class Foo +{ + public function notArray(bool $bool): void + { + assertType('*NEVER*', array_reverse($bool)); + assertType('*NEVER*', array_reverse($bool, true)); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse.php b/tests/PHPStan/Analyser/nsrt/array-reverse.php index e5a205ac0a8..413e1d5f2a2 100644 --- a/tests/PHPStan/Analyser/nsrt/array-reverse.php +++ b/tests/PHPStan/Analyser/nsrt/array-reverse.php @@ -46,4 +46,28 @@ public function constantArrays(array $a, array $b): void assertType('array{\'bar\', \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b)); assertType('array{19: \'bar\', 17: \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b, true)); } + + /** + * @param list $a + * @param non-empty-list $b + */ + public function list(array $a, array $b): void + { + assertType('list', array_reverse($a)); + assertType('array, string>', array_reverse($a, true)); + + assertType('non-empty-list', array_reverse($b)); + assertType('non-empty-array, string>', array_reverse($b, true)); + } + + public function mixed(mixed $mixed): void + { + assertType('array', array_reverse($mixed)); + assertType('array', array_reverse($mixed, true)); + + if (array_key_exists('foo', $mixed)) { + assertType('non-empty-array', array_reverse($mixed)); + assertType("array&hasOffset('foo')", array_reverse($mixed, true)); + } + } } From eeb46c01650e0864449c683cf47a9020de70234e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 4 Sep 2024 20:31:47 +0200 Subject: [PATCH 0190/3097] version_compare() operator-arg can be null --- resources/functionMap.php | 2 +- resources/functionMap_php80delta.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 558096b86b7..375c3d38d3c 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -13075,7 +13075,7 @@ 'VarnishStat::__construct' => ['void', 'args='=>'array'], 'VarnishStat::getSnapshot' => ['array'], 'version_compare' => ['int', 'version1'=>'string', 'version2'=>'string'], -'version_compare\'1' => ['bool', 'version1'=>'string', 'version2'=>'string', 'operator'=>'string'], +'version_compare\'1' => ['bool', 'version1'=>'string', 'version2'=>'string', 'operator'=>'string|null'], 'vfprintf' => ['int', 'stream'=>'resource', 'format'=>'string', 'args'=>'array<__stringAndStringable|int|float|null|bool>'], 'virtual' => ['bool', 'uri'=>'string'], 'Volatile::__construct' => ['void'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 6ec7d665f7f..529d433dc1f 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -111,7 +111,7 @@ 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], 'substr' => ['string', 'string'=>'string', 'start'=>'int', 'length='=>'int'], 'round' => ['float', 'number'=>'float', 'precision='=>'int', 'mode='=>'1|2|3|4'], - 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], + 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string|null'], 'xml_parser_create' => ['XMLParser', 'encoding='=>'string'], 'xml_parser_create_ns' => ['XMLParser', 'encoding='=>'string', 'sep='=>'string'], 'xml_parser_free' => ['bool', 'parser'=>'XMLParser'], @@ -249,7 +249,7 @@ 'strrpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], 'substr' => ['__benevolent', 'string'=>'string', 'start'=>'int', 'length='=>'int'], - 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string'], + 'version_compare' => ['int|bool', 'version1'=>'string', 'version2'=>'string', 'operator='=>'string|null'], 'xml_parser_create' => ['resource', 'encoding='=>'string'], 'xml_parser_create_ns' => ['resource', 'encoding='=>'string', 'sep='=>'string'], 'xml_parser_free' => ['bool', 'parser'=>'resource'], From 6973519742ab804f57885c099b74971f465c7b24 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 5 Sep 2024 21:19:32 +0200 Subject: [PATCH 0191/3097] Revert "Fix phar.yml workflow" This reverts commit 94ac43a704e86a54ba29b9b460b4aa10fa203c23. --- .github/workflows/phar.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 47b7b6e300d..c6c4934b44b 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -10,9 +10,6 @@ on: tags: - '1.12.*' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests cancel-in-progress: true @@ -79,12 +76,15 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" + env: + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" + COMPOSER_ROOT_VERSION: "1.12.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" From 2e345b8e790b679dc5ae0e0a01472295b66f9a79 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 3 Sep 2024 10:50:57 +0200 Subject: [PATCH 0192/3097] Update stubs and patch --- composer.json | 2 +- composer.lock | 10 +++++----- patches/PDO.patch | 16 ++++++++-------- .../Rules/Generics/ClassAncestorsRuleTest.php | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 670a2ffb39f..02ef955af21 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#5686f9ceebde3d9338bea53b78d70ebde5fb5710", + "jetbrains/phpstorm-stubs": "dev-master#56f6b9e55f5885e651553843a1aaf9ec9c586c04", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.3", diff --git a/composer.lock b/composer.lock index dcea32fd8ea..a72a0e69ef0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8688fdadbf2effbbc87fb35192f48d8e", + "content-hash": "9cb259d4d2ef11aad375ee55188ab4d3", "packages": [ { "name": "clue/ndjson-react", @@ -1434,12 +1434,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "5686f9ceebde3d9338bea53b78d70ebde5fb5710" + "reference": "56f6b9e55f5885e651553843a1aaf9ec9c586c04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/5686f9ceebde3d9338bea53b78d70ebde5fb5710", - "reference": "5686f9ceebde3d9338bea53b78d70ebde5fb5710", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/56f6b9e55f5885e651553843a1aaf9ec9c586c04", + "reference": "56f6b9e55f5885e651553843a1aaf9ec9c586c04", "shasum": "" }, "require-dev": { @@ -1474,7 +1474,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-07-24T19:11:43+00:00" + "time": "2024-09-01T14:35:14+00:00" }, { "name": "nette/bootstrap", diff --git a/patches/PDO.patch b/patches/PDO.patch index 17aff2c148a..607a23fda2e 100644 --- a/patches/PDO.patch +++ b/patches/PDO.patch @@ -1,11 +1,11 @@ --- PDO/PDO.php 2021-12-26 15:44:39.000000000 +0100 +++ PDO/PDO.php 2022-01-03 22:54:21.000000000 +0100 -@@ -1415,7 +1415,7 @@ - * @return array|false if one or more notifications is pending, returns a single row, - * with fields message and pid, otherwise FALSE. - */ -- public function pgsqlGetNotify(int $fetchMode = PDO::FETCH_DEFAULT, int $timeoutMilliseconds = 0): array|false {} -+ public function pgsqlGetNotify(int $fetchMode = 1, int $timeoutMilliseconds = 0): array|false {} +@@ -1476,7 +1476,7 @@ namespace { + * @return array|false if one or more notifications is pending, returns a single row, + * with fields message and pid, otherwise FALSE. + */ +- public function pgsqlGetNotify($fetchMode = PDO::FETCH_DEFAULT, $timeoutMilliseconds = 0) {} ++ public function pgsqlGetNotify($fetchMode = 1, $timeoutMilliseconds = 0) {} - /** - * (PHP 5 >= 5.6.0, PHP 7, PHP 8)
+ /** + * (PHP 5 >= 5.6.0, PHP 7, PHP 8)
diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 05963f5576b..f8d27e26120 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -108,7 +108,7 @@ public function testRuleExtends(): void 215, ], [ - 'Class ClassAncestorsExtends\FooObjectStorage @extends tag contains incompatible type ClassAncestorsExtends\FooObjectStorage.', + 'Class ClassAncestorsExtends\FooObjectStorage @extends tag contains incompatible type ClassAncestorsExtends\FooObjectStorage&iterable.', 226, ], [ From 3e52bb081ddb530bbbd651b489b809b0d6c480e0 Mon Sep 17 00:00:00 2001 From: Takuya Aramaki Date: Fri, 16 Aug 2024 20:13:19 +0900 Subject: [PATCH 0193/3097] Refactor `ArrayFilterFunctionReturnTypeReturnTypeExtension` and support first-class callable --- conf/config.neon | 2 +- phpstan-baseline.neon | 2 +- ...rrayFilterFunctionReturnTypeExtension.php} | 182 ++++++++++++------ .../nsrt/array-filter-string-callables.php | 11 ++ .../Rules/Methods/ReturnTypeRuleTest.php | 5 + .../PHPStan/Rules/Methods/data/bug-11337.php | 72 +++++++ 6 files changed, 215 insertions(+), 59 deletions(-) rename src/Type/Php/{ArrayFilterFunctionReturnTypeReturnTypeExtension.php => ArrayFilterFunctionReturnTypeExtension.php} (60%) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11337.php diff --git a/conf/config.neon b/conf/config.neon index 236aed720d8..e946b925429 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1186,7 +1186,7 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeReturnTypeExtension + class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 77ff81fd1ad..d06f9c49232 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1301,7 +1301,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 - path: src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php + path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php similarity index 60% rename from src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php rename to src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php index c44d17a250a..4672fd1a96d 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php @@ -6,22 +6,24 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Closure; -use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Error; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; -use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ErrorType; use PHPStan\Type\MixedType; @@ -35,12 +37,20 @@ use function count; use function in_array; use function is_string; -use function strtolower; +use function sprintf; use function substr; -final class ArrayFilterFunctionReturnTypeReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + private const USE_BOTH = 1; + private const USE_KEY = 2; + private const USE_ITEM = 3; + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'array_filter'; @@ -72,70 +82,69 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ]); } - if ($callbackArg === null || ($callbackArg instanceof ConstFetch && strtolower($callbackArg->name->getParts()[0]) === 'null')) { + if ($callbackArg === null || $scope->getType($callbackArg)->isNull()->yes()) { return TypeCombinator::union( ...array_map([$this, 'removeFalsey'], $arrayArgType->getArrays()), ); } - if ($flagArg === null) { - if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { - $statement = $callbackArg->stmts[0]; - if ($statement instanceof Return_ && $statement->expr !== null) { - return $this->filterByTruthyValue($scope, $callbackArg->params[0]->var, $arrayArgType, null, $statement->expr); - } - } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { - return $this->filterByTruthyValue($scope, $callbackArg->params[0]->var, $arrayArgType, null, $callbackArg->expr); - } elseif ($callbackArg instanceof String_) { - $funcName = self::createFunctionName($callbackArg->value); - if ($funcName === null) { - return new ErrorType(); - } - - $itemVar = new Variable('item'); - $expr = new FuncCall($funcName, [new Arg($itemVar)]); - return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, null, $expr); - } + $mode = $this->determineMode($flagArg, $scope); + if ($mode === null) { + return new ArrayType($keyType, $itemType); } - if ($flagArg instanceof ConstFetch && $flagArg->name->getParts()[0] === 'ARRAY_FILTER_USE_KEY') { - if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { - $statement = $callbackArg->stmts[0]; - if ($statement instanceof Return_ && $statement->expr !== null) { - return $this->filterByTruthyValue($scope, null, $arrayArgType, $callbackArg->params[0]->var, $statement->expr); + if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { + $statement = $callbackArg->stmts[0]; + if ($statement instanceof Return_ && $statement->expr !== null) { + if ($mode === self::USE_ITEM) { + $keyVar = null; + $itemVar = $callbackArg->params[0]->var; + } elseif ($mode === self::USE_KEY) { + $keyVar = $callbackArg->params[0]->var; + $itemVar = null; + } elseif ($mode === self::USE_BOTH) { + $keyVar = $callbackArg->params[1]->var ?? null; + $itemVar = $callbackArg->params[0]->var; } - } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { - return $this->filterByTruthyValue($scope, null, $arrayArgType, $callbackArg->params[0]->var, $callbackArg->expr); - } elseif ($callbackArg instanceof String_) { - $funcName = self::createFunctionName($callbackArg->value); - if ($funcName === null) { - return new ErrorType(); - } - - $keyVar = new Variable('key'); - $expr = new FuncCall($funcName, [new Arg($keyVar)]); - return $this->filterByTruthyValue($scope, null, $arrayArgType, $keyVar, $expr); + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $statement->expr); } - } + } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { + if ($mode === self::USE_ITEM) { + $keyVar = null; + $itemVar = $callbackArg->params[0]->var; + } elseif ($mode === self::USE_KEY) { + $keyVar = $callbackArg->params[0]->var; + $itemVar = null; + } elseif ($mode === self::USE_BOTH) { + $keyVar = $callbackArg->params[1]->var ?? null; + $itemVar = $callbackArg->params[0]->var; + } + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $callbackArg->expr); + } elseif ( + ($callbackArg instanceof FuncCall || $callbackArg instanceof MethodCall || $callbackArg instanceof StaticCall) + && $callbackArg->isFirstClassCallable() + ) { + [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); + $expr = clone $callbackArg; + $expr->args = $args; + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); + } else { + $constantStrings = $scope->getType($callbackArg)->getConstantStrings(); + if (count($constantStrings) > 0) { + $results = []; + [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); + + foreach ($constantStrings as $constantString) { + $funcName = self::createFunctionName($constantString->getValue()); + if ($funcName === null) { + $results[] = new ErrorType(); + continue; + } - if ($flagArg instanceof ConstFetch && $flagArg->name->getParts()[0] === 'ARRAY_FILTER_USE_BOTH') { - if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { - $statement = $callbackArg->stmts[0]; - if ($statement instanceof Return_ && $statement->expr !== null) { - return $this->filterByTruthyValue($scope, $callbackArg->params[0]->var, $arrayArgType, $callbackArg->params[1]->var ?? null, $statement->expr); - } - } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { - return $this->filterByTruthyValue($scope, $callbackArg->params[0]->var, $arrayArgType, $callbackArg->params[1]->var ?? null, $callbackArg->expr); - } elseif ($callbackArg instanceof String_) { - $funcName = self::createFunctionName($callbackArg->value); - if ($funcName === null) { - return new ErrorType(); + $expr = new FuncCall($funcName, $args); + $results[] = $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); } - - $itemVar = new Variable('item'); - $keyVar = new Variable('key'); - $expr = new FuncCall($funcName, [new Arg($itemVar), new Arg($keyVar)]); - return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); + return TypeCombinator::union(...$results); } } @@ -280,4 +289,63 @@ private static function createFunctionName(string $funcName): ?Name return new Name($funcName); } + /** + * @param self::USE_* $mode + * @return array{list, ?Variable, ?Variable} + */ + private function createDummyArgs(int $mode): array + { + if ($mode === self::USE_ITEM) { + $itemVar = new Variable('item'); + $keyVar = null; + $args = [new Arg($itemVar)]; + } elseif ($mode === self::USE_KEY) { + $itemVar = null; + $keyVar = new Variable('key'); + $args = [new Arg($keyVar)]; + } elseif ($mode === self::USE_BOTH) { + $itemVar = new Variable('item'); + $keyVar = new Variable('key'); + $args = [new Arg($itemVar), new Arg($keyVar)]; + } + return [$args, $itemVar, $keyVar]; + } + + /** + * @param non-empty-string $constantName + */ + private function getConstant(string $constantName): int + { + $constant = $this->reflectionProvider->getConstant(new Name($constantName), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + throw new ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); + } + + return $valueType->getValue(); + } + + /** + * @return self::USE_*|null + */ + private function determineMode(?Expr $flagArg, Scope $scope): ?int + { + if ($flagArg === null) { + return self::USE_ITEM; + } + + $flagValues = $scope->getType($flagArg)->getConstantScalarValues(); + if (count($flagValues) !== 1) { + return null; + } + + if ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_KEY')) { + return self::USE_KEY; + } elseif ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_BOTH')) { + return self::USE_BOTH; + } + + return null; + } + } diff --git a/tests/PHPStan/Analyser/nsrt/array-filter-string-callables.php b/tests/PHPStan/Analyser/nsrt/array-filter-string-callables.php index ef684865fde..f6e0b6c65ee 100644 --- a/tests/PHPStan/Analyser/nsrt/array-filter-string-callables.php +++ b/tests/PHPStan/Analyser/nsrt/array-filter-string-callables.php @@ -77,3 +77,14 @@ public static function isString($value): bool return is_string($value); } } + +function unionOfCallableStrings(): void +{ + $func = rand(0, 1) === 1 ? 'is_string' : 'is_int'; + $list = [ + 1, + 2, + 'foo', + ]; + assertType("array{1, 2}|array{2: 'foo'}", array_filter($list, $func)); +} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6303bdcdc47..2af1147c65d 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1044,4 +1044,9 @@ public function testBug3759(): void $this->analyse([__DIR__ . '/data/bug-3759.php'], []); } + public function testBug11337(): void + { + $this->analyse([__DIR__ . '/data/bug-11337.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11337.php b/tests/PHPStan/Rules/Methods/data/bug-11337.php new file mode 100644 index 00000000000..49dce6d7a81 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11337.php @@ -0,0 +1,72 @@ += 8.1 +declare(strict_types = 1); + +namespace Bug11337; + +use function array_filter; + +class Foo +{ + + /** + * @return array<\stdClass> + */ + public function testFunction(): array + { + $objects = [ + new \stdClass(), + null, + new \stdClass(), + null, + ]; + + return array_filter($objects, is_object(...)); + } + + /** + * @return array<1|2> + */ + public function testMethod(): array + { + $objects = [ + 1, + 2, + -4, + 0, + -1, + ]; + + return array_filter($objects, $this->isPositive(...)); + } + + /** + * @return array<'foo'|'bar'> + */ + public function testStaticMethod(): array + { + $objects = [ + '', + 'foo', + '', + 'bar', + ]; + + return array_filter($objects, self::isNonEmptyString(...)); + } + + /** + * @phpstan-assert-if-true int<1, max> $n + */ + private function isPositive(int $n): bool + { + return $n > 0; + } + + /** + * @phpstan-assert-if-true non-empty-string $str + */ + private static function isNonEmptyString(string $str): bool + { + return \strlen($str) > 0; + } +} From c26886d5caecf2c8a51abe8642ce98dcb694c182 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 5 Sep 2024 21:54:01 +0200 Subject: [PATCH 0194/3097] Prevent resolving conditional types in callable param/return types --- src/Type/TypeUtils.php | 15 +++++++++++++-- tests/PHPStan/Analyser/nsrt/bug-11472.php | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11472.php diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 92cc4343df1..8ae601b8323 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -411,11 +411,22 @@ public static function containsTemplateType(Type $type): bool public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes): Type { - while ($type instanceof LateResolvableType && ($resolveUnresolvableTypes || $type->isResolvable())) { + /** @var int $ignoreResolveUnresolvableTypesLevel */ + $ignoreResolveUnresolvableTypesLevel = 0; + + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes, &$ignoreResolveUnresolvableTypesLevel): Type { + while ($type instanceof LateResolvableType && (($resolveUnresolvableTypes && $ignoreResolveUnresolvableTypesLevel === 0) || $type->isResolvable())) { $type = $type->resolve(); } + if ($type instanceof CallableType || $type instanceof ClosureType) { + $ignoreResolveUnresolvableTypesLevel++; + $result = $traverse($type); + $ignoreResolveUnresolvableTypesLevel--; + + return $result; + } + return $traverse($type); }); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11472.php b/tests/PHPStan/Analyser/nsrt/bug-11472.php new file mode 100644 index 00000000000..a6ee71f0489 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11472.php @@ -0,0 +1,18 @@ += 8.1 + +namespace Bug11472; + +use function PHPStan\Testing\assertType; + +/** + * @phpstan-return ($maybeFoo is 'foo' ? true : false) + */ +function isFoo(mixed $maybeFoo): bool +{ + return $maybeFoo === 'foo'; +} + +function (): void { + assertType('true', isFoo('foo')); + assertType('true', isFoo(...)('foo')); +}; From 092aee82fce74f8f0f62b49dd37a870ae59d9ffa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 09:27:58 +0200 Subject: [PATCH 0195/3097] Regression test --- tests/PHPStan/Analyser/nsrt/bug-11188.php | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11188.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11188.php b/tests/PHPStan/Analyser/nsrt/bug-11188.php new file mode 100644 index 00000000000..4c96faacb62 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11188.php @@ -0,0 +1,28 @@ + $parameters + * @param TExplicit|null $type + * @return ( + * $type is class-string ? new : + * $abstract is class-string ? new : mixed + * ) + */ +function instance(string $abstract, array $parameters = [], ?string $type = null): mixed +{ + return 'something'; +} + +function (): void { + assertType(DateTime::class, instance('cache', [], DateTime::class)); + assertType(DateTime::class, instance(DateTime::class)); +}; From 8c4cb2f583142b7e95e9539ee629759e1a238e05 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 13:30:55 +0200 Subject: [PATCH 0196/3097] Test about BetterReflection Adapter ReflectionEnum return types These will likely need fixes on 2.0.x because PHP-Parser now represents `null` and `false` in union types differently and considers them namespaced names on PHP 7.x --- .../adapter-reflection-enum-return-types.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php diff --git a/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php b/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php new file mode 100644 index 00000000000..4002e1ce169 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php @@ -0,0 +1,29 @@ +getFileName()); + assertType('int|false', $r->getStartLine()); + assertType('int|false', $r->getEndLine()); + assertType('string|false', $r->getDocComment()); + assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant|false', $r->getReflectionConstant($s)); + assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass|false', $r->getParentClass()); + assertType('non-empty-string|false', $r->getExtensionName()); + assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|null', $r->getBackingType()); +}; + +function (ReflectionEnumBackedCase $r): void { + assertType('string|false', $r->getDocComment()); + assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|null', $r->getType()); +}; + +function (ReflectionEnumUnitCase $r): void { + assertType('string|false', $r->getDocComment()); + assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|null', $r->getType()); +}; From ff910cec6a377c25bc99a79c63dabd761ec26dc4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 13:55:11 +0200 Subject: [PATCH 0197/3097] Fix Adapter ReflectionEnum tests on PHP 7.4 --- conf/config.neon | 19 ++++ ...tionEnumCaseDynamicReturnTypeExtension.php | 66 +++++++++++ ...flectionEnumDynamicReturnTypeExtension.php | 105 ++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php create mode 100644 src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 4efef75454f..02d1fd848e8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -794,6 +794,25 @@ services: - class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository + - + class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension + arguments: + class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension + arguments: + class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..8c2f4ca5264 --- /dev/null +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php @@ -0,0 +1,66 @@ +class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), [ + 'getDocComment', + 'getType', + ], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if ($this->phpVersion->getVersionId() >= 80000) { + return null; + } + + if ($methodReflection->getName() === 'getDocComment') { + return new UnionType([ + new StringType(), + new ConstantBooleanType(false), + ]); + } + + if ($methodReflection->getName() === 'getType') { + return new UnionType([ + new ObjectType(ReflectionIntersectionType::class), + new ObjectType(ReflectionNamedType::class), + new ObjectType(ReflectionUnionType::class), + new NullType(), + ]); + } + + return null; + } + +} diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..40c8f05b929 --- /dev/null +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php @@ -0,0 +1,105 @@ +getName(), [ + 'getFileName', + 'getStartLine', + 'getEndLine', + 'getDocComment', + 'getReflectionConstant', + 'getParentClass', + 'getExtensionName', + 'getBackingType', + ], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if ($this->phpVersion->getVersionId() >= 80000) { + return null; + } + + if (in_array($methodReflection->getName(), ['getFileName', 'getExtensionName'], true)) { + return new UnionType([ + new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]), + new ConstantBooleanType(false), + ]); + } + + if ($methodReflection->getName() === 'getDocComment') { + return new UnionType([ + new StringType(), + new ConstantBooleanType(false), + ]); + } + + if (in_array($methodReflection->getName(), ['getStartLine', 'getEndLine'], true)) { + return new UnionType([ + new IntegerType(), + new ConstantBooleanType(false), + ]); + } + + if ($methodReflection->getName() === 'getReflectionConstant') { + return new UnionType([ + new ObjectType(ReflectionClassConstant::class), + new ConstantBooleanType(false), + ]); + } + + if ($methodReflection->getName() === 'getParentClass') { + return new UnionType([ + new ObjectType(ReflectionClass::class), + new ConstantBooleanType(false), + ]); + } + + if ($methodReflection->getName() === 'getBackingType') { + return new UnionType([ + new ObjectType(ReflectionNamedType::class), + new NullType(), + ]); + } + + return null; + } + +} From 3c0c82508701d7711b5975feb28f3bb72c0993ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:07:42 +0200 Subject: [PATCH 0198/3097] Final --- .../AdapterReflectionEnumCaseDynamicReturnTypeExtension.php | 2 +- .../Type/AdapterReflectionEnumDynamicReturnTypeExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php index 8c2f4ca5264..5ab5e725b97 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php @@ -18,7 +18,7 @@ use PHPStan\Type\UnionType; use function in_array; -class AdapterReflectionEnumCaseDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class AdapterReflectionEnumCaseDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function __construct(private PhpVersion $phpVersion, private string $class) diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php index 40c8f05b929..f0e25e92967 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php @@ -22,7 +22,7 @@ use PHPStan\Type\UnionType; use function in_array; -class AdapterReflectionEnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +final class AdapterReflectionEnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { public function __construct(private PhpVersion $phpVersion) From 111e47469011551bd39a815724a4c8254df3bb5c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 14:11:27 +0200 Subject: [PATCH 0199/3097] Fix tests that use `mixed` type --- tests/PHPStan/Analyser/nsrt/array-reverse.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-11188.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse.php b/tests/PHPStan/Analyser/nsrt/array-reverse.php index 413e1d5f2a2..f4165395317 100644 --- a/tests/PHPStan/Analyser/nsrt/array-reverse.php +++ b/tests/PHPStan/Analyser/nsrt/array-reverse.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace ArrayReverse; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11188.php b/tests/PHPStan/Analyser/nsrt/bug-11188.php index 4c96faacb62..1280620263f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11188.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11188.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug11188; From a65f7f806208204f68a5b6b96f6f0a3253cdb2dd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 15:20:57 +0200 Subject: [PATCH 0200/3097] The COMPOSER_ROOT_VERSION hack should no longer be necessary --- .github/workflows/backward-compatibility.yml | 3 --- .github/workflows/build-issue-bot.yml | 3 --- .github/workflows/changelog-generator.yml | 3 --- .github/workflows/checksum-phar.yml | 3 --- .github/workflows/e2e-tests.yml | 3 --- .github/workflows/issue-bot.yml | 3 --- .github/workflows/lint.yml | 3 --- .github/workflows/reflection-golden-test.yml | 1 - .github/workflows/static-analysis.yml | 3 --- .github/workflows/tests.yml | 3 --- README.md | 2 -- composer.json | 2 +- composer.lock | 2 +- 13 files changed, 2 insertions(+), 32 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 0233e1e422a..51e52a365e2 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -11,9 +11,6 @@ on: - 'src/**' - '.github/workflows/backward-compatibility.yml' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: bc-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/build-issue-bot.yml b/.github/workflows/build-issue-bot.yml index 278470b4660..06213ad0526 100644 --- a/.github/workflows/build-issue-bot.yml +++ b/.github/workflows/build-issue-bot.yml @@ -14,9 +14,6 @@ on: - 'issue-bot/**' - '.github/workflows/build-issue-bot.yml' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: build-issue-bot-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/changelog-generator.yml b/.github/workflows/changelog-generator.yml index 21971571f3d..80845232807 100644 --- a/.github/workflows/changelog-generator.yml +++ b/.github/workflows/changelog-generator.yml @@ -14,9 +14,6 @@ on: - 'changelog-generator/**' - '.github/workflows/changelog-generator.yml' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: changelog-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index 47256373d0c..fd975528a36 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -17,9 +17,6 @@ on: - 'compiler/**' - '.github/workflows/checksum-phar.yml' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: checksum-phar-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 53fded3322c..c9357fa047c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -18,9 +18,6 @@ on: - 'changelog-generator/**' - 'issue-bot/**' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: e2e-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 5ef4bd62756..6d6af362f17 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -17,9 +17,6 @@ on: - 'apigen/**' - 'changelog-generator/**' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: run-issue-bot-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 03420615cde..afc5c0db7ce 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,9 +8,6 @@ on: branches: - "1.12.x" -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: lint-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 6d16f21aaa0..8b84920a73e 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -19,7 +19,6 @@ on: - 'issue-bot/**' env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" REFLECTION_GOLDEN_TEST_FILE: "/tmp/reflection-golden.test" REFLECTION_GOLDEN_SYMBOLS_FILE: "/tmp/reflection-golden-symbols.txt" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b19b6c10f7f..e8b9e1ec39d 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -14,9 +14,6 @@ on: - 'compiler/**' - 'apigen/**' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: sa-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 59d834e9c70..292f8ccf273 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,9 +18,6 @@ on: - 'changelog-generator/**' - 'issue-bot/**' -env: - COMPOSER_ROOT_VERSION: "1.12.x-dev" - concurrency: group: tests-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches cancel-in-progress: true diff --git a/README.md b/README.md index 6d2e16963c5..e8176045423 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Any contributions are welcome. composer install ``` -If you encounter dependency problem, try using `export COMPOSER_ROOT_VERSION=1.11.x-dev` - If you are using macOS and are using an older version of `patch`, you may have problems with patch application failure during `composer install`. Try using `brew install gpatch` to install a newer and supported `patch` version. ### Building diff --git a/composer.json b/composer.json index 02ef955af21..eb0fc3b709d 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "symfony/string": "^5.4.3" }, "replace": { - "phpstan/phpstan": "self.version" + "phpstan/phpstan": "1.12.x" }, "require-dev": { "brianium/paratest": "^6.5", diff --git a/composer.lock b/composer.lock index a72a0e69ef0..0b88dc696a7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9cb259d4d2ef11aad375ee55188ab4d3", + "content-hash": "b4ef5f2e88180f1e71abe684e35b9759", "packages": [ { "name": "clue/ndjson-react", From 1c2e2090f92395623179bd46313393d9a12cc247 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 20:28:14 +0200 Subject: [PATCH 0201/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/6861 Closes https://github.com/phpstan/phpstan/issues/5629 --- .../UnknownMixedTypeOnOlderPhpTest.php | 42 +++++++++++++++++++ .../Analyser/data/unknown-mixed-type.php | 13 ++++++ .../PHPStan/Analyser/unknown-mixed-type.neon | 2 + 3 files changed, 57 insertions(+) create mode 100644 tests/PHPStan/Analyser/UnknownMixedTypeOnOlderPhpTest.php create mode 100644 tests/PHPStan/Analyser/data/unknown-mixed-type.php create mode 100644 tests/PHPStan/Analyser/unknown-mixed-type.neon diff --git a/tests/PHPStan/Analyser/UnknownMixedTypeOnOlderPhpTest.php b/tests/PHPStan/Analyser/UnknownMixedTypeOnOlderPhpTest.php new file mode 100644 index 00000000000..3c83a57e579 --- /dev/null +++ b/tests/PHPStan/Analyser/UnknownMixedTypeOnOlderPhpTest.php @@ -0,0 +1,42 @@ + + */ +class UnknownMixedTypeOnOlderPhpTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return self::getContainer()->getByType(ExistingClassesInTypehintsRule::class); + } + + public function testMixedUnknownType(): void + { + $this->analyse([__DIR__ . '/data/unknown-mixed-type.php'], [ + [ + 'Parameter $m of method UnknownMixedType\Foo::doFoo() has invalid type UnknownMixedType\mixed.', + 8, + ], + [ + 'Method UnknownMixedType\Foo::doFoo() has invalid return type UnknownMixedType\mixed.', + 8, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge(parent::getAdditionalConfigFiles(), [ + __DIR__ . '/unknown-mixed-type.neon', + ]); + } + +} diff --git a/tests/PHPStan/Analyser/data/unknown-mixed-type.php b/tests/PHPStan/Analyser/data/unknown-mixed-type.php new file mode 100644 index 00000000000..d603b6d3efe --- /dev/null +++ b/tests/PHPStan/Analyser/data/unknown-mixed-type.php @@ -0,0 +1,13 @@ + Date: Fri, 6 Sep 2024 20:34:13 +0200 Subject: [PATCH 0202/3097] Test block statement --- .../PHPStan/Analyser/nsrt/block-statement.php | 22 +++++++++++++++++++ tests/PHPStan/Rules/Cast/EchoRuleTest.php | 4 ++++ tests/PHPStan/Rules/Cast/data/echo.php | 6 +++++ 3 files changed, 32 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/block-statement.php diff --git a/tests/PHPStan/Analyser/nsrt/block-statement.php b/tests/PHPStan/Analyser/nsrt/block-statement.php new file mode 100644 index 00000000000..fc752007d8d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/block-statement.php @@ -0,0 +1,22 @@ + Date: Fri, 6 Sep 2024 20:39:08 +0200 Subject: [PATCH 0203/3097] New option: `polluteScopeWithBlock` --- conf/config.neon | 2 ++ conf/parametersSchema.neon | 1 + src/Analyser/NodeScopeResolver.php | 16 ++++++++- src/Testing/RuleTestCase.php | 1 + src/Testing/TypeInferenceTestCase.php | 1 + tests/PHPStan/Analyser/AnalyserTest.php | 1 + .../DoNotPolluteScopeWithBlockTest.php | 36 +++++++++++++++++++ .../data/do-not-pollute-scope-with-block.php | 26 ++++++++++++++ .../do-not-pollute-scope-with-block.neon | 2 ++ 9 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/DoNotPolluteScopeWithBlockTest.php create mode 100644 tests/PHPStan/Analyser/data/do-not-pollute-scope-with-block.php create mode 100644 tests/PHPStan/Analyser/do-not-pollute-scope-with-block.neon diff --git a/conf/config.neon b/conf/config.neon index 02d1fd848e8..eda169fc201 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -154,6 +154,7 @@ parameters: phpVersion: null polluteScopeWithLoopInitialAssignments: true polluteScopeWithAlwaysIterableForeach: true + polluteScopeWithBlock: true propertyAlwaysWrittenTags: [] propertyAlwaysReadTags: [] fixerTmpDir: %pro.tmpDir% #unused @@ -544,6 +545,7 @@ services: reflector: @nodeScopeResolverReflector polluteScopeWithLoopInitialAssignments: %polluteScopeWithLoopInitialAssignments% polluteScopeWithAlwaysIterableForeach: %polluteScopeWithAlwaysIterableForeach% + polluteScopeWithBlock: %polluteScopeWithBlock% earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls% earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% implicitThrows: %exceptions.implicitThrows% diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 8b986b04425..d26f5d20921 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -148,6 +148,7 @@ parametersSchema: phpVersion: schema(anyOf(schema(int(), min(70100), max(80499))), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() + polluteScopeWithBlock: bool() propertyAlwaysWrittenTags: listOf(string()) propertyAlwaysReadTags: listOf(string()) additionalConstructors: listOf(string()) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8a992f5c139..360f5ef6ebc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -253,6 +253,7 @@ public function __construct( private readonly ScopeFactory $scopeFactory, private readonly bool $polluteScopeWithLoopInitialAssignments, private readonly bool $polluteScopeWithAlwaysIterableForeach, + private readonly bool $polluteScopeWithBlock, private readonly array $earlyTerminatingMethodCalls, private readonly array $earlyTerminatingFunctionCalls, private readonly array $universalObjectCratesClasses, @@ -1895,7 +1896,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { new ImpurePoint($scope, $stmt, 'betweenPhpTags', 'output between PHP opening and closing tags', true), ]; } elseif ($stmt instanceof Node\Stmt\Block) { - return $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); + $result = $this->processStmtNodes($stmt, $stmt->stmts, $scope, $nodeCallback, $context); + if ($this->polluteScopeWithBlock) { + return $result; + } + + return new StatementResult( + $scope->mergeWith($result->getScope()), + $result->hasYield(), + $result->isAlwaysTerminating(), + $result->getExitPoints(), + $result->getThrowPoints(), + $result->getImpurePoints(), + $result->getEndStatements(), + ); } elseif ($stmt instanceof Node\Stmt\Nop) { $hasYield = false; $throwPoints = $overridingThrowPoints ?? []; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b507b05ae60..6e88b1ab684 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -99,6 +99,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::createScopeFactory($reflectionProvider, $typeSpecifier), $this->shouldPolluteScopeWithLoopInitialAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), + self::getContainer()->getParameter('polluteScopeWithBlock'), [], [], self::getContainer()->getParameter('universalObjectCratesClasses'), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 874b6011568..df477585696 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -79,6 +79,7 @@ public static function processFile( self::createScopeFactory($reflectionProvider, $typeSpecifier), self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'), self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'), + self::getContainer()->getParameter('polluteScopeWithBlock'), static::getEarlyTerminatingMethodCalls(), static::getEarlyTerminatingFunctionCalls(), self::getContainer()->getParameter('universalObjectCratesClasses'), diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index d59549b9dac..a27d75468f1 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -735,6 +735,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser self::createScopeFactory($reflectionProvider, $typeSpecifier), false, true, + true, [], [], [stdClass::class], diff --git a/tests/PHPStan/Analyser/DoNotPolluteScopeWithBlockTest.php b/tests/PHPStan/Analyser/DoNotPolluteScopeWithBlockTest.php new file mode 100644 index 00000000000..cd4ceb5d853 --- /dev/null +++ b/tests/PHPStan/Analyser/DoNotPolluteScopeWithBlockTest.php @@ -0,0 +1,36 @@ +gatherAssertTypes(__DIR__ . '/data/do-not-pollute-scope-with-block.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../conf/bleedingEdge.neon', + __DIR__ . '/do-not-pollute-scope-with-block.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/do-not-pollute-scope-with-block.php b/tests/PHPStan/Analyser/data/do-not-pollute-scope-with-block.php new file mode 100644 index 00000000000..d1569b1d5b4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/do-not-pollute-scope-with-block.php @@ -0,0 +1,26 @@ + Date: Fri, 6 Sep 2024 20:52:41 +0200 Subject: [PATCH 0204/3097] Update phpstan-strict-rules in issue-bot --- issue-bot/composer.json | 2 +- issue-bot/composer.lock | 41 +++++++++++++++++++---------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/issue-bot/composer.json b/issue-bot/composer.json index 92ec4ec6e8b..1d35a96375e 100644 --- a/issue-bot/composer.json +++ b/issue-bot/composer.json @@ -7,7 +7,7 @@ "league/commonmark": "^2.3", "nette/neon": "^3.3", "nette/utils": "^3.2", - "phpstan/phpstan-strict-rules": "1.6.x-dev", + "phpstan/phpstan-strict-rules": "^2.0", "symfony/console": "^6.1", "symfony/finder": "^6.1" }, diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index af4a00fca88..9653a2cef46 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa15712157441e401f9e8107ea219d0e", + "content-hash": "6a526cc8f4e310c4a649c65001b30782", "packages": [ { "name": "clue/stream-filter", @@ -1403,20 +1403,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.x-dev", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7a2e524c7bdc18295d62b0ed598cec1166da80ab" + "reference": "946cf18b3e9f64c41d2f62903f8148b3f0b3be41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7a2e524c7bdc18295d62b0ed598cec1166da80ab", - "reference": "7a2e524c7bdc18295d62b0ed598cec1166da80ab", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/946cf18b3e9f64c41d2f62903f8148b3f0b3be41", + "reference": "946cf18b3e9f64c41d2f62903f8148b3f0b3be41", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1458,32 +1458,31 @@ "type": "github" } ], - "time": "2024-04-19T14:55:18+00:00" + "time": "2024-09-06T18:47:21+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.x-dev", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "c7b4d283fbffd23b9405c01d1f68124739d698f6" + "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/c7b4d283fbffd23b9405c01d1f68124739d698f6", - "reference": "c7b4d283fbffd23b9405c01d1f68124739d698f6", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/5eec39fc6ef36015e6de08949c8e9ae9d64560a3", + "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" }, "default-branch": true, "type": "phpstan-extension", @@ -1506,9 +1505,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.x" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-04-19T14:52:46+00:00" + "time": "2024-09-06T18:44:39+00:00" }, { "name": "psr/cache", @@ -4472,9 +4471,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "phpstan/phpstan-strict-rules": 20 - }, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { From e738d636e1ac8f0de7f5b7dd945854c8af20b057 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 20:53:15 +0200 Subject: [PATCH 0205/3097] Update phpstan-strict-rules --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 4af6cdedafb..383b1bf73d4 100644 --- a/composer.lock +++ b/composer.lock @@ -4827,12 +4827,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "8e2c8b0abb83ec35ba2fca475898880f7e700783" + "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8e2c8b0abb83ec35ba2fca475898880f7e700783", - "reference": "8e2c8b0abb83ec35ba2fca475898880f7e700783", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/5eec39fc6ef36015e6de08949c8e9ae9d64560a3", + "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3", "shasum": "" }, "require": { @@ -4868,7 +4868,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-04T21:09:40+00:00" + "time": "2024-09-06T18:44:39+00:00" }, { "name": "phpunit/php-code-coverage", From e4f5b2be3148b7dea77082c3a56a1980f0c8236c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 6 Sep 2024 20:53:26 +0200 Subject: [PATCH 0206/3097] Update phpstan-deprecation-rules --- composer.lock | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 383b1bf73d4..87b31322469 100644 --- a/composer.lock +++ b/composer.lock @@ -4668,12 +4668,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "4590cf64974274acb3cf683bddfbe59031272949" + "reference": "398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/4590cf64974274acb3cf683bddfbe59031272949", - "reference": "4590cf64974274acb3cf683bddfbe59031272949", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd", + "reference": "398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd", "shasum": "" }, "require": { @@ -4685,6 +4685,7 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4707,7 +4708,7 @@ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" }, - "time": "2024-09-04T20:43:23+00:00" + "time": "2024-09-06T13:40:51+00:00" }, { "name": "phpstan/phpstan-nette", @@ -4796,6 +4797,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { From 6111c2180b312fdb69feb0a9303f055606b2eb9a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 6 Sep 2024 16:40:31 +0200 Subject: [PATCH 0207/3097] Add non regression test --- .../Functions/CallToFunctionParametersRuleTest.php | 5 +++++ tests/PHPStan/Rules/Functions/data/bug-4960.php | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-4960.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index d49987e3c58..45b461725c9 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1650,6 +1650,11 @@ public function testArgon2PasswordHash(): void $this->analyse([__DIR__ . '/data/argon2id-password-hash.php'], []); } + public function testBug4960(): void + { + $this->analyse([__DIR__ . '/data/bug-4960.php'], []); + } + public function testParamClosureThis(): void { if (PHP_VERSION_ID < 70400) { diff --git a/tests/PHPStan/Rules/Functions/data/bug-4960.php b/tests/PHPStan/Rules/Functions/data/bug-4960.php new file mode 100644 index 00000000000..703fb32b873 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-4960.php @@ -0,0 +1,14 @@ + 11); + + password_hash($password, PASSWORD_DEFAULT, $options); + } +} From d9b42676f68748cb18f550f1420150404ac2629c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 6 Sep 2024 16:36:46 +0200 Subject: [PATCH 0208/3097] Add non regression test --- .../Functions/CallToFunctionParametersRuleTest.php | 5 +++++ tests/PHPStan/Rules/Functions/data/bug-10499.php | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-10499.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 45b461725c9..4beb131a6b4 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1752,6 +1752,11 @@ public function testBug11559(): void $this->analyse([__DIR__ . '/data/bug-11559.php'], []); } + public function testBug10499(): void + { + $this->analyse([__DIR__ . '/data/bug-10499.php'], []); + } + public function testBug11559b(): void { $this->analyse([__DIR__ . '/data/bug-11559b.php'], [ diff --git a/tests/PHPStan/Rules/Functions/data/bug-10499.php b/tests/PHPStan/Rules/Functions/data/bug-10499.php new file mode 100644 index 00000000000..d7cdb6c163d --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10499.php @@ -0,0 +1,11 @@ + Date: Fri, 6 Sep 2024 21:08:59 +0200 Subject: [PATCH 0209/3097] Don't prevent checking for `curl_init()` false returns --- conf/config.neon | 5 - resources/functionMap.php | 2 +- resources/functionMap_php80delta.php | 2 + src/Type/Php/CurlInitReturnTypeExtension.php | 102 ------------------ .../Analyser/AnalyserIntegrationTest.php | 6 ++ tests/PHPStan/Analyser/data/bug-11640.php | 9 ++ .../Php8SignatureMapProviderTest.php | 3 +- .../Php/CurlInitReturnTypeExtensionTest.php | 32 ------ .../PHPStan/Type/Php/data/curl-init-php-7.php | 65 ----------- .../PHPStan/Type/Php/data/curl-init-php-8.php | 69 ------------ 10 files changed, 20 insertions(+), 275 deletions(-) delete mode 100644 src/Type/Php/CurlInitReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/bug-11640.php delete mode 100644 tests/PHPStan/Type/Php/CurlInitReturnTypeExtensionTest.php delete mode 100644 tests/PHPStan/Type/Php/data/curl-init-php-7.php delete mode 100644 tests/PHPStan/Type/Php/data/curl-init-php-8.php diff --git a/conf/config.neon b/conf/config.neon index e946b925429..6f189522548 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1360,11 +1360,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\CurlInitReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\DateFunctionReturnTypeHelper diff --git a/resources/functionMap.php b/resources/functionMap.php index 375c3d38d3c..d7f2c1f8ec9 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1494,7 +1494,7 @@ 'curl_exec' => ['bool|string', 'ch'=>'resource'], 'curl_file_create' => ['CURLFile', 'filename'=>'string', 'mimetype='=>'string', 'postfilename='=>'string'], 'curl_getinfo' => ['mixed', 'ch'=>'resource', 'option='=>'int'], -'curl_init' => ['resource|false', 'url='=>'string'], +'curl_init' => ['__benevolent', 'url='=>'string'], 'curl_multi_add_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], 'curl_multi_close' => ['void', 'mh'=>'resource'], 'curl_multi_errno' => ['int', 'mh'=>'resource'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 529d433dc1f..1b234d2280d 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -31,6 +31,7 @@ 'ceil' => ['float', 'number'=>'float'], 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], 'count_chars' => ['array|string', 'input'=>'string', 'mode='=>'int'], + 'curl_init' => ['__benevolent', 'url='=>'string'], 'date_add' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], 'date_date_set' => ['DateTime', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'date_diff' => ['DateInterval', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], @@ -169,6 +170,7 @@ 'convert_cyr_string' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'], 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'bool'], 'count_chars' => ['array|false|string', 'input'=>'string', 'mode='=>'int'], + 'curl_init' => ['__benevolent', 'url='=>'string'], 'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], 'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], diff --git a/src/Type/Php/CurlInitReturnTypeExtension.php b/src/Type/Php/CurlInitReturnTypeExtension.php deleted file mode 100644 index a53cbbc685c..00000000000 --- a/src/Type/Php/CurlInitReturnTypeExtension.php +++ /dev/null @@ -1,102 +0,0 @@ -getName() === 'curl_init'; - } - - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - Node\Expr\FuncCall $functionCall, - Scope $scope, - ): Type - { - $args = $functionCall->getArgs(); - $argsCount = count($args); - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - $notFalseReturnType = TypeCombinator::remove($returnType, new ConstantBooleanType(false)); - if ($argsCount === 0) { - return $notFalseReturnType; - } - - $urlArgType = $scope->getType($args[0]->value); - if ($urlArgType->isConstantScalarValue()->yes() && (new UnionType([new NullType(), new StringType()]))->isSuperTypeOf($urlArgType)->yes()) { - $urlArgReturnTypes = array_map( - fn ($value) => $this->getUrlArgValueReturnType($value, $returnType, $notFalseReturnType), - $urlArgType->getConstantScalarValues(), - ); - return TypeCombinator::union(...$urlArgReturnTypes); - } - - return $returnType; - } - - private function getUrlArgValueReturnType(mixed $urlArgValue, Type $returnType, Type $notFalseReturnType): Type - { - if ($urlArgValue === null) { - return $notFalseReturnType; - } - if (!is_string($urlArgValue)) { - throw new ShouldNotHappenException(); - } - if (str_contains($urlArgValue, "\0")) { - if (!$this->phpVersion->throwsValueErrorForInternalFunctions()) { - // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L112-L115 - return new ConstantBooleanType(false); - } - // https://github.com/php/php-src/blob/php-8.0.0/ext/curl/interface.c#L104-L107 - return new NeverType(); - } - if ($this->phpVersion->isCurloptUrlCheckingFileSchemeWithOpenBasedir()) { - // Before PHP 8.0 an unparsable URL or a file:// scheme would fail if open_basedir is used - // Since we can't detect open_basedir properly, we'll always consider a failure possible if these - // conditions are given - // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L139-L158 - $parsedUrlArgValue = parse_url($urlArgValue); - if ($parsedUrlArgValue === false || (isset($parsedUrlArgValue['scheme']) && strcasecmp($parsedUrlArgValue['scheme'], 'file') === 0)) { - return $returnType; - } - } - if (strlen($urlArgValue) > self::CURL_MAX_INPUT_LENGTH) { - // Since libcurl 7.65.0 this would always fail, but no current PHP version requires it at the moment - // https://github.com/curl/curl/commit/5fc28510a4664f46459d9a40187d81cc08571e60 - return $returnType; - } - return $notFalseReturnType; - } - -} diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index b84ff5feb04..236c066b720 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1437,6 +1437,12 @@ public function testBug11511(): void $this->assertSame('Access to an undefined property object::$bar.', $errors[0]->getMessage()); } + public function testBug11640(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11640.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-11640.php b/tests/PHPStan/Analyser/data/bug-11640.php new file mode 100644 index 00000000000..62b1748d2e5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11640.php @@ -0,0 +1,9 @@ + false, ], ], - new UnionType([ + new BenevolentUnionType([ new ObjectType('CurlHandle'), new ConstantBooleanType(false), ]), diff --git a/tests/PHPStan/Type/Php/CurlInitReturnTypeExtensionTest.php b/tests/PHPStan/Type/Php/CurlInitReturnTypeExtensionTest.php deleted file mode 100644 index 7749b327f11..00000000000 --- a/tests/PHPStan/Type/Php/CurlInitReturnTypeExtensionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -assertFileAsserts($assertType, $file, ...$args); - } - -} diff --git a/tests/PHPStan/Type/Php/data/curl-init-php-7.php b/tests/PHPStan/Type/Php/data/curl-init-php-7.php deleted file mode 100644 index 9778b786e02..00000000000 --- a/tests/PHPStan/Type/Php/data/curl-init-php-7.php +++ /dev/null @@ -1,65 +0,0 @@ - Date: Fri, 6 Sep 2024 13:50:13 +0200 Subject: [PATCH 0210/3097] Fix wrongly convertion of list to array{T} --- src/Analyser/TypeSpecifier.php | 3 +- tests/PHPStan/Analyser/nsrt/bug-11642.php | 44 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11642.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c7413dec9b5..fd243efb16f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1059,7 +1059,8 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, } $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), !$hasOffset->yes()); } - + } else { + return null; } $arrayType = $valueTypesBuilder->getArray(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11642.php b/tests/PHPStan/Analyser/nsrt/bug-11642.php new file mode 100644 index 00000000000..520cf772bfa --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11642.php @@ -0,0 +1,44 @@ + $criteria + * + * @return object[] The objects. + * @psalm-return list<\stdClass> + */ + function findBy(array $criteria): array + { + return [new \stdClass, new \stdCLass, new \stdClass, new \stdClass]; + } +} + +class Payload { + /** @var non-empty-list */ + public array $ids = ['one', 'two']; +} + +function doFoo() { + $payload = new Payload(); + + $fetcher = new Repository(); + $entries = $fetcher->findBy($payload->ids); + assertType('list', $entries); + assertType('int<0, max>', count($entries)); + assertType('int<1, max>', count($payload->ids)); + if (count($entries) !== count($payload->ids)) { + exit(); + } + + assertType('non-empty-list', $entries); + if (count($entries) > 3) { + throw new \RuntimeException(); + } + + assertType('non-empty-list', $entries); +} From 5814c7e3d71b31f74002912fe5b443efaaf2cd2f Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sat, 7 Sep 2024 20:18:26 +0000 Subject: [PATCH 0211/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index eb0fc3b709d..d0c4b970db0 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.18", "phpstan/php-8-stubs": "0.3.102", - "phpstan/phpdoc-parser": "1.30.0", + "phpstan/phpdoc-parser": "1.30.1", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 0b88dc696a7..7bcc05980e5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b4ef5f2e88180f1e71abe684e35b9759", + "content-hash": "339a3f47c630b657cae8a667296f3c1a", "packages": [ { "name": "clue/ndjson-react", @@ -2280,16 +2280,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.0", + "version": "1.30.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", "shasum": "" }, "require": { @@ -2321,9 +2321,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" }, - "time": "2024-08-29T09:54:52+00:00" + "time": "2024-09-07T20:13:05+00:00" }, { "name": "psr/container", From 7928a2a1ea77c017e379ec1bdc90b90cd130a734 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 7 Sep 2024 23:34:08 +0200 Subject: [PATCH 0212/3097] Update phpdoc-parser to 2.0.x --- composer.json | 2 +- composer.lock | 37 ++++++++++--------- conf/bleedingEdge.neon | 2 - conf/config.neon | 21 +++-------- conf/parametersSchema.neon | 2 - phpstan-baseline.neon | 20 ---------- .../ValidateIgnoredErrorsExtension.php | 7 +++- src/PhpDoc/ConstExprParserFactory.php | 19 ---------- src/PhpDoc/PhpDocNodeResolver.php | 6 +-- src/PhpDoc/TypeNodeResolver.php | 2 +- src/Type/Constant/ConstantStringType.php | 4 +- src/Type/Helper/GetTemplateTypeType.php | 4 +- 12 files changed, 39 insertions(+), 87 deletions(-) delete mode 100644 src/PhpDoc/ConstExprParserFactory.php diff --git a/composer.json b/composer.json index 2cd14e4298f..46ba2c3cd6a 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.7", "phpstan/php-8-stubs": "0.3.102", - "phpstan/phpdoc-parser": "1.30.0", + "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 87b31322469..eb3567da211 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6ddd2ca1209453c124e93e7c5aaae4c0", + "content-hash": "35946ee5494c73903dd91c4abfa4c290", "packages": [ { "name": "clue/ndjson-react", @@ -2282,32 +2282,33 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f" + "reference": "9d57f3db5bba9b0d8d11726389eb8af3126780b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/5ceb0e384997db59f38774bf79c2a6134252c08f", - "reference": "5ceb0e384997db59f38774bf79c2a6134252c08f", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9d57f3db5bba9b0d8d11726389eb8af3126780b4", + "reference": "9d57f3db5bba9b0d8d11726389eb8af3126780b4", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.1", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, + "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -2323,9 +2324,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.x" }, - "time": "2024-08-29T09:54:52+00:00" + "time": "2024-09-07T21:23:59+00:00" }, { "name": "psr/container", @@ -4440,19 +4441,19 @@ "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "dbbf56fab0bc71310ff3766ea204d84f019e99b7" + "reference": "1630e1672948a2b59955d7fa634992dc4331ac00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/dbbf56fab0bc71310ff3766ea204d84f019e99b7", - "reference": "dbbf56fab0bc71310ff3766ea204d84f019e99b7", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/1630e1672948a2b59955d7fa634992dc4331ac00", + "reference": "1630e1672948a2b59955d7fa634992dc4331ac00", "shasum": "" }, "require": { "nette/utils": "^3.2.5", "nikic/php-parser": "^5.0", "php": "^7.4|^8.0", - "phpstan/phpdoc-parser": "^1.24.5", + "phpstan/phpdoc-parser": "^2.0", "symfony/console": "^5.4", "symfony/finder": "^5.4" }, @@ -4481,7 +4482,7 @@ "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.x" }, - "time": "2024-02-12T19:24:54+00:00" + "time": "2024-09-07T21:32:41+00:00" }, { "name": "phar-io/manifest", diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 12694539ae9..b07884cc2a3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -16,8 +16,6 @@ parameters: consistentConstructor: true checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true - phpDocParserRequireWhitespaceBeforeDescription: true - phpDocParserIncludeLines: true enableIgnoreErrorsWithinPhpDocs: true runtimeReflectionRules: true notAnalysedTrait: true diff --git a/conf/config.neon b/conf/config.neon index ebb430c3122..3c6e1d40726 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -51,8 +51,6 @@ parameters: consistentConstructor: false checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false - phpDocParserRequireWhitespaceBeforeDescription: false - phpDocParserIncludeLines: false enableIgnoreErrorsWithinPhpDocs: false runtimeReflectionRules: false notAnalysedTrait: false @@ -416,30 +414,23 @@ services: versionId: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% + - + class: PHPStan\PhpDocParser\ParserConfig + arguments: + usedAttributes: + lines: true + - class: PHPStan\PhpDocParser\Lexer\Lexer - class: PHPStan\PhpDocParser\Parser\TypeParser - arguments: - quoteAwareConstExprString: %featureToggles.unescapeStrings% - class: PHPStan\PhpDocParser\Parser\ConstExprParser - factory: @PHPStan\PhpDoc\ConstExprParserFactory::create() - class: PHPStan\PhpDocParser\Parser\PhpDocParser - arguments: - requireWhitespaceBeforeDescription: %featureToggles.phpDocParserRequireWhitespaceBeforeDescription% - preserveTypeAliasesWithInvalidTypes: true - usedAttributes: - lines: %featureToggles.phpDocParserIncludeLines% - - - - class: PHPStan\PhpDoc\ConstExprParserFactory - arguments: - unescapeStrings: %featureToggles.unescapeStrings% - class: PHPStan\PhpDoc\PhpDocInheritanceResolver diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d26f5d20921..528e437a21f 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -46,8 +46,6 @@ parametersSchema: consistentConstructor: bool() checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() - phpDocParserRequireWhitespaceBeforeDescription: bool() - phpDocParserIncludeLines: bool() enableIgnoreErrorsWithinPhpDocs: bool() runtimeReflectionRules: bool() notAnalysedTrait: bool() diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cbc07aa2bec..b4879021923 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -194,21 +194,6 @@ parameters: count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - - message: "#^Property PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\AssertTagMethodValueNode\\:\\:\\$isEquality \\(bool\\) on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpDoc/PhpDocNodeResolver.php - - - - message: "#^Property PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\AssertTagPropertyValueNode\\:\\:\\$isEquality \\(bool\\) on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpDoc/PhpDocNodeResolver.php - - - - message: "#^Property PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\AssertTagValueNode\\:\\:\\$isEquality \\(bool\\) on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpDoc/PhpDocNodeResolver.php - - message: "#^Method PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock\\:\\:getNameScope\\(\\) should return PHPStan\\\\Analyser\\\\NameScope but returns PHPStan\\\\Analyser\\\\NameScope\\|null\\.$#" count: 1 @@ -242,11 +227,6 @@ parameters: count: 2 path: src/PhpDoc/TypeNodeResolver.php - - - message: "#^Property PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\CallableTypeNode\\:\\:\\$templateTypes \\(array\\\\) on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\Identifier\\\\Exception\\\\InvalidIdentifierName is never thrown in the try block\\.$#" count: 3 diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 71a1e04c07d..1973f7e3033 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -20,6 +20,7 @@ use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; use PHPStan\PhpDocParser\Parser\TypeParser; +use PHPStan\PhpDocParser\ParserConfig; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\PhpVersionStaticAccessor; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; @@ -67,11 +68,13 @@ public function loadConfiguration(): void ReflectionProviderStaticAccessor::registerInstance($reflectionProvider); PhpVersionStaticAccessor::registerInstance(new PhpVersion(PHP_VERSION_ID)); $constantResolver = new ConstantResolver($reflectionProviderProvider, []); + + $phpDocParserConfig = new ParserConfig([]); $ignoredRegexValidator = new IgnoredRegexValidator( $parser, new TypeStringResolver( - new Lexer(), - new TypeParser(new ConstExprParser($builder->parameters['featureToggles']['unescapeStrings'])), + new Lexer($phpDocParserConfig), + new TypeParser($phpDocParserConfig, new ConstExprParser($phpDocParserConfig)), new TypeNodeResolver( new DirectTypeNodeResolverExtensionRegistryProvider( new class implements TypeNodeResolverExtensionRegistry { diff --git a/src/PhpDoc/ConstExprParserFactory.php b/src/PhpDoc/ConstExprParserFactory.php deleted file mode 100644 index 4af1282a786..00000000000 --- a/src/PhpDoc/ConstExprParserFactory.php +++ /dev/null @@ -1,19 +0,0 @@ -unescapeStrings, $this->unescapeStrings); - } - -} diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 402f8c7dd1f..75857e5eedb 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -587,19 +587,19 @@ private function resolveAssertTagsFor(PhpDocNode $phpDocNode, NameScope $nameSco foreach ($phpDocNode->getAssertTagValues($tagName) as $assertTagValue) { $type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope); $parameter = new AssertTagParameter($assertTagValue->parameter, null, null); - $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true); + $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality, true); } foreach ($phpDocNode->getAssertPropertyTagValues($tagName) as $assertTagValue) { $type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope); $parameter = new AssertTagParameter($assertTagValue->parameter, $assertTagValue->property, null); - $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true); + $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality, true); } foreach ($phpDocNode->getAssertMethodTagValues($tagName) as $assertTagValue) { $type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope); $parameter = new AssertTagParameter($assertTagValue->parameter, null, $assertTagValue->method); - $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true); + $resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality, true); } return $resolved; diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index c0fa6c55857..ccfd16f32ac 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -894,7 +894,7 @@ private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $ { $templateTags = []; - if (count($typeNode->templateTypes ?? []) > 0) { + if (count($typeNode->templateTypes) > 0) { foreach ($typeNode->templateTypes as $templateType) { $templateTags[$templateType->name] = new TemplateTag( $templateType->name, diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index fbdbaf2f97b..3aa75ee5fb6 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -7,7 +7,7 @@ use PhpParser\Node\Name; use PHPStan\Analyser\OutOfClassScope; use PHPStan\DependencyInjection\BleedingEdgeToggle; -use PHPStan\PhpDocParser\Ast\ConstExpr\QuoteAwareConstExprStringNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\Callables\FunctionCallableVariant; @@ -528,7 +528,7 @@ public function toPhpDocNode(): TypeNode return $this->generalize(GeneralizePrecision::moreSpecific())->toPhpDocNode(); } - return new ConstTypeNode(new QuoteAwareConstExprStringNode($this->value, QuoteAwareConstExprStringNode::SINGLE_QUOTED)); + return new ConstTypeNode(new ConstExprStringNode($this->value, ConstExprStringNode::SINGLE_QUOTED)); } /** diff --git a/src/Type/Helper/GetTemplateTypeType.php b/src/Type/Helper/GetTemplateTypeType.php index 12e03fe1b0d..e8af52e322b 100644 --- a/src/Type/Helper/GetTemplateTypeType.php +++ b/src/Type/Helper/GetTemplateTypeType.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Helper; -use PHPStan\PhpDocParser\Ast\ConstExpr\QuoteAwareConstExprStringNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -98,7 +98,7 @@ public function toPhpDocNode(): TypeNode [ $this->type->toPhpDocNode(), new IdentifierTypeNode($this->ancestorClassName), - new ConstTypeNode(new QuoteAwareConstExprStringNode($this->templateTypeName, QuoteAwareConstExprStringNode::SINGLE_QUOTED)), + new ConstTypeNode(new ConstExprStringNode($this->templateTypeName, ConstExprStringNode::SINGLE_QUOTED)), ], ); } From 052f6b130f53ad50f571b81d4d468b0b0026c2fd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 8 Sep 2024 08:52:42 +0200 Subject: [PATCH 0213/3097] Fix internal error --- src/Type/Php/RegexArrayShapeMatcher.php | 4 +- .../Analyser/AnalyserIntegrationTest.php | 6 +++ tests/PHPStan/Analyser/data/bug-11649.php | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-11649.php diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index fb94b88ab0c..63e44b3ea26 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -157,7 +157,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched if (!$this->containsUnmatchedAsNull($flags, $matchesAll)) { // positive match has a subject but not any capturing group $combiType = TypeCombinator::union( - new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [0], [], true), + new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], true), $combiType, ); } @@ -222,7 +222,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ) ) { // positive match has a subject but not any capturing group - $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [0], [], true); + $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], true); } return TypeCombinator::union(...$combiTypes); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 236c066b720..c98129fc998 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -312,6 +312,12 @@ public function testBug3309(): void $this->assertNoErrors($errors); } + public function testBug11649(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11649.php'); + $this->assertNoErrors($errors); + } + public function testBug6872(): void { if (PHP_VERSION_ID < 80000) { diff --git a/tests/PHPStan/Analyser/data/bug-11649.php b/tests/PHPStan/Analyser/data/bug-11649.php new file mode 100644 index 00000000000..8f31554f435 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11649.php @@ -0,0 +1,38 @@ +(?:\\\[A-Za-z])+)|[' . self::DATE_FORMAT_CHARACTERS . ']|(?P[A-Za-z])/'; + + /** + * Formats a DateTime object using the current translation for weekdays and months + * @param mixed $translation + */ + public static function formatDateTime(DateTime $dateTime, string $format, ?string $language , $translation): ?string + { + return preg_replace_callback( + self::DATE_FORMAT_REGEX, + fn(array $matches): string => match ($matches[0]) { + 'M' => $translation->getStrings('date.months.short')[$dateTime->format('n') - 1], + 'F' => $translation->getStrings('date.months.long')[$dateTime->format('n') - 1], + 'D' => $translation->getStrings('date.weekdays.short')[(int) $dateTime->format('w')], + 'l' => $translation->getStrings('date.weekdays.long')[(int) $dateTime->format('w')], + 'r' => static::formatDateTime($dateTime, DateTime::RFC2822, null, $translation), + default => $dateTime->format($matches[1] ?? $matches[0]) + }, + $format + ); + } +} From b8299b7d0284aa196fcf2b078147afe68bf30609 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 8 Sep 2024 12:33:58 +0200 Subject: [PATCH 0214/3097] Work-in-progress 2.0 changelog --- changelog-2.0.md | 150 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 changelog-2.0.md diff --git a/changelog-2.0.md b/changelog-2.0.md new file mode 100644 index 00000000000..82382370fd5 --- /dev/null +++ b/changelog-2.0.md @@ -0,0 +1,150 @@ +This document is a work in progress. + +When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases) + a separate UPGRADING document. + +Major new features 🚀 +===================== + +Bleeding edge (TODO move to other sections) +===================== + +* Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 +* Countable stub with `0|positive-int` ([#1027](https://github.com/phpstan/phpstan-src/pull/1027)), thanks @staabm! +* Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! +* Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! +* Lower memory consumption thanks to breaking up of reference cycles + * This is a BC break for rules that use `'parent'`, `'next'`, and `'previous'` node attributes. [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) + * In testing the memory consumption was reduced by 50–70 %. +* ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! +* Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! +* `checkMissingIterableValueType: false` no longer does anything (https://github.com/phpstan/phpstan-src/commit/50d0c8e23ea85da508ab8481f1ff2c89148cc80b) +* ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) +* Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! +* Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! +* Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) +* Check that each trait is used and analysed at least once - level 4 (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) +* Check that PHPStan class in class constant fetch is covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) +* Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) +* Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) +* ApiInstanceofRule + * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) + * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) +* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! +* PHPDoc parser: Require whitespace before description with limited start tokens (https://github.com/phpstan/phpdoc-parser/pull/128), https://github.com/phpstan/phpdoc-parser/issues/125, thanks @rvanvelzen! +* Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! +* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! + * Lists are arrays with sequential integer keys starting at 0 +* Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! +* MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! +* Unescape strings in phpdoc-parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) +* Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) +* Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! +* Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) +* Fix invariance composition ([#2054](https://github.com/phpstan/phpstan-src/pull/2054)), thanks @jiripudil! +* Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) + * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones +* Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! +* Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule + * Because "always true" is always reported, these are no longer needed +* IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) +* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) +* Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 +* Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) +* Stricter function signature map (https://github.com/phpstan/phpstan-src/commit/06b746d8e72cc0843707896ec161559bb6a81137, [#2163](https://github.com/phpstan/phpstan-src/pull/2163)), #7239, thanks @staabm! +* Specify `Imagick` parameter types ([#2334](https://github.com/phpstan/phpstan-src/pull/2334)), thanks @zonuexe! +* Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! +* Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! +* OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) +* Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 +* Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) +* InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) +* More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! +* Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! +* More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! +* Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 +* Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! +* `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! +* InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) +* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! +* More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! +* More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! +* More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! +* Detect overriding `@final` method in OverridingMethodRule, #9135 +* MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) +* MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! +* More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! +* PhpDocParser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! +* Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) + * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 +* TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) +* LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 +* NoopRule - report top-level `xor` because that's probably not what the user intended to do (https://github.com/phpstan/phpstan-src/commit/a1fffb3346e09f1e8e8d987d4282263295a55142), #10267 +* Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) +* Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) +* Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) +* **Enhancements in Handling Parameters Passed by Reference** + * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) + * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! +* Add option `reportAnyTypeWideningInVarTag` ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! +* `array_values` rule (report when a `list` type is always passed in) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! +* Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! +* Checking truthiness of `@phpstan-pure` above functions and methods +* Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side + * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 + * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! + * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! + * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! +* BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 +* Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) +* CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) +* Deprecated: returning plain strings as errors, use RuleErrorBuilder + * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) +* Deprecated: returning RuleError without identifier (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) +* Fail build when project config uses custom extensions outside of analysed paths + * This will only occur after a run that uses already present and valid result cache +* Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! +* Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) +* Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) +* More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! +* Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 +* Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) +* Consider implicit throw points when the only explicit one is Throw_ (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) +* Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 +* Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 +* Check invalid `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/95c0a5806c65c975201b9d3a464873f75a04c8b8), #10932 +* Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 +* Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) +* Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) +* Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) +* Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) +* Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) +* Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) +* Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) +* Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 +* Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) +* Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 +* Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) +* RegularExpressionPatternRule: validate preg_quote'd patterns ([#3270](https://github.com/phpstan/phpstan-src/pull/3270)), thanks @staabm! +* No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 +* Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! +* Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! +* Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! +* Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! +* Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) +* Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! +* Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! +* Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3! +* Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! + +Improvements 🔧 +===================== + +Bugfixes 🐛 +===================== + +Function signature fixes 🤖 +======================= + +Internals 🔍 +===================== From 694467a829c21b6b49b49bc805e31e53f81e68fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 8 Sep 2024 12:42:19 +0200 Subject: [PATCH 0215/3097] phodoc-parser has already been updated --- changelog-2.0.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 82382370fd5..5525195f4bf 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -30,13 +30,11 @@ Bleeding edge (TODO move to other sections) * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! -* PHPDoc parser: Require whitespace before description with limited start tokens (https://github.com/phpstan/phpdoc-parser/pull/128), https://github.com/phpstan/phpdoc-parser/issues/125, thanks @rvanvelzen! * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 * Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Unescape strings in phpdoc-parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) @@ -65,7 +63,6 @@ Bleeding edge (TODO move to other sections) * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 * Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! * `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! -* InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) * Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! * More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! * More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! @@ -74,7 +71,6 @@ Bleeding edge (TODO move to other sections) * MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! -* PhpDocParser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! * Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) @@ -140,6 +136,11 @@ Bleeding edge (TODO move to other sections) Improvements 🔧 ===================== +* PHPDoc parser: Require whitespace before description with limited start tokens (https://github.com/phpstan/phpdoc-parser/pull/128), https://github.com/phpstan/phpdoc-parser/issues/125, thanks @rvanvelzen! +* Unescape strings in PHPDoc parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) +* PHPDoc parser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! +* InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) + Bugfixes 🐛 ===================== From 2c8ab048d91378718116bd42a5e84bf68ed20dac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 8 Sep 2024 12:43:50 +0200 Subject: [PATCH 0216/3097] Remove more bleeding edge toggles about phpdoc-parser --- conf/bleedingEdge.neon | 2 - conf/config.level2.neon | 2 - conf/config.neon | 3 -- conf/parametersSchema.neon | 2 - src/Parser/RichParser.php | 3 +- src/PhpDoc/StubValidator.php | 1 - .../PhpDoc/InvalidPhpDocTagValueRule.php | 15 +------ tests/PHPStan/Analyser/AnalyserTest.php | 18 +-------- .../Analyser/data/ignore-next-line-legacy.php | 40 ------------------- .../PhpDoc/InvalidPhpDocTagValueRuleTest.php | 1 - 10 files changed, 5 insertions(+), 82 deletions(-) delete mode 100644 tests/PHPStan/Analyser/data/ignore-next-line-legacy.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index b07884cc2a3..dae0bbb2abf 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -16,7 +16,6 @@ parameters: consistentConstructor: true checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true - enableIgnoreErrorsWithinPhpDocs: true runtimeReflectionRules: true notAnalysedTrait: true curlSetOptTypes: true @@ -41,7 +40,6 @@ parameters: propertyVariance: true genericPrototypeMessage: true stricterFunctionMap: true - invalidPhpDocTagLine: true detectDeadTypeInMultiCatch: true zeroFiles: true projectServicesNotInAnalysedPaths: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index d7a7cc943b6..29234ebe71a 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -145,8 +145,6 @@ services: class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule - class: PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule - arguments: - invalidPhpDocTagLine: %featureToggles.invalidPhpDocTagLine% tags: - phpstan.rules.rule - diff --git a/conf/config.neon b/conf/config.neon index 3c6e1d40726..94074a853ab 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -51,7 +51,6 @@ parameters: consistentConstructor: false checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false - enableIgnoreErrorsWithinPhpDocs: false runtimeReflectionRules: false notAnalysedTrait: false curlSetOptTypes: false @@ -77,7 +76,6 @@ parameters: propertyVariance: false genericPrototypeMessage: false stricterFunctionMap: false - invalidPhpDocTagLine: false detectDeadTypeInMultiCatch: false zeroFiles: false projectServicesNotInAnalysedPaths: false @@ -2018,7 +2016,6 @@ services: class: PHPStan\Parser\RichParser arguments: parser: @currentPhpVersionPhpParser - enableIgnoreErrorsWithinPhpDocs: %featureToggles.enableIgnoreErrorsWithinPhpDocs% autowired: no currentPhpVersionSimpleParser: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 528e437a21f..0d8cfd70a7f 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -46,7 +46,6 @@ parametersSchema: consistentConstructor: bool() checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() - enableIgnoreErrorsWithinPhpDocs: bool() runtimeReflectionRules: bool() notAnalysedTrait: bool() curlSetOptTypes: bool() @@ -71,7 +70,6 @@ parametersSchema: propertyVariance: bool() genericPrototypeMessage: bool() stricterFunctionMap: bool() - invalidPhpDocTagLine: bool() detectDeadTypeInMultiCatch: bool() zeroFiles: bool() projectServicesNotInAnalysedPaths: bool() diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index 14ade510c31..0bf45b07a32 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -44,7 +44,6 @@ public function __construct( private NameResolver $nameResolver, private Container $container, private IgnoreLexer $ignoreLexer, - private bool $enableIgnoreErrorsWithinPhpDocs = false, ) { } @@ -152,7 +151,7 @@ private function getLinesToIgnore(array $tokens): array $isNextLine = str_contains($text, '@phpstan-ignore-next-line'); $isCurrentLine = str_contains($text, '@phpstan-ignore-line'); - if ($this->enableIgnoreErrorsWithinPhpDocs && $type === T_DOC_COMMENT) { + if ($type === T_DOC_COMMENT) { $lines += $this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-line'); if ($isNextLine) { $pattern = sprintf('~%s~si', implode('|', [self::PHPDOC_TAG_REGEX, self::PHPDOC_DOCTRINE_TAG_REGEX])); diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 72605424119..5bc89349593 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -217,7 +217,6 @@ private function getRuleRegistry(Container $container): RuleRegistry new InvalidPhpDocTagValueRule( $container->getByType(Lexer::class), $container->getByType(PhpDocParser::class), - $container->getParameter('featureToggles')['invalidPhpDocTagLine'], ), new IncompatibleParamImmediatelyInvokedCallableRule($fileTypeMapper), new IncompatibleSelfOutTypeRule($unresolvableTypeHelper, $genericObjectTypeCheck), diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index 569c776df35..2caa53394ee 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\PhpDoc; -use Nette\Utils\Strings; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\VirtualNode; @@ -26,7 +25,6 @@ final class InvalidPhpDocTagValueRule implements Rule public function __construct( private Lexer $phpDocLexer, private PhpDocParser $phpDocParser, - private bool $invalidPhpDocTagLine, ) { } @@ -72,7 +70,7 @@ public function processNode(Node $node, Scope $scope): array 'PHPDoc tag %s %s has invalid value: %s', $phpDocTag->name, $phpDocTag->value->alias, - $this->trimExceptionMessage($phpDocTag->value->type->getException()->getMessage()), + $phpDocTag->value->type->getException()->getMessage(), )) ->line(PhpDocLineHelper::detectLine($node, $phpDocTag)) ->identifier('phpDoc.parseError')->build(); @@ -86,7 +84,7 @@ public function processNode(Node $node, Scope $scope): array 'PHPDoc tag %s has invalid value (%s): %s', $phpDocTag->name, $phpDocTag->value->value, - $this->trimExceptionMessage($phpDocTag->value->exception->getMessage()), + $phpDocTag->value->exception->getMessage(), )) ->line(PhpDocLineHelper::detectLine($node, $phpDocTag)) ->identifier('phpDoc.parseError')->build(); @@ -95,13 +93,4 @@ public function processNode(Node $node, Scope $scope): array return $errors; } - private function trimExceptionMessage(string $message): string - { - if ($this->invalidPhpDocTagLine) { - return $message; - } - - return Strings::replace($message, '~( on line \d+)$~', ''); - } - } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index a27d75468f1..39155bdb6fc 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -555,18 +555,6 @@ public function testIgnoreNextLineUnmatched(): void } } - public function testIgnoreNextLineLegacyBehaviour(): void - { - $result = $this->runAnalyser([], false, [__DIR__ . '/data/ignore-next-line-legacy.php'], true, false); - - foreach ([10, 32, 36] as $i => $line) { - $this->assertArrayHasKey($i, $result); - $this->assertInstanceOf(Error::class, $result[$i]); - $this->assertSame('Fail.', $result[$i]->getMessage()); - $this->assertSame($line, $result[$i]->getLine()); - } - } - /** * @dataProvider dataTrueAndFalse */ @@ -653,10 +641,9 @@ private function runAnalyser( bool $reportUnmatchedIgnoredErrors, $filePaths, bool $onlyFiles, - bool $enableIgnoreErrorsWithinPhpDocs = true, ): array { - $analyser = $this->createAnalyser($enableIgnoreErrorsWithinPhpDocs); + $analyser = $this->createAnalyser(); if (is_string($filePaths)) { $filePaths = [$filePaths]; @@ -701,7 +688,7 @@ private function runAnalyser( ); } - private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser + private function createAnalyser(): Analyser { $ruleRegistry = new DirectRuleRegistry([ new AlwaysFailRule(), @@ -755,7 +742,6 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser new NameResolver(), self::getContainer(), new IgnoreLexer(), - $enableIgnoreErrorsWithinPhpDocs, ), new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), new RuleErrorTransformer(), diff --git a/tests/PHPStan/Analyser/data/ignore-next-line-legacy.php b/tests/PHPStan/Analyser/data/ignore-next-line-legacy.php deleted file mode 100644 index fa8f2a50fd3..00000000000 --- a/tests/PHPStan/Analyser/data/ignore-next-line-legacy.php +++ /dev/null @@ -1,40 +0,0 @@ -getByType(Lexer::class), self::getContainer()->getByType(PhpDocParser::class), - true, ); } From e3e80f6c9c246d13e41188d508f0f4afb631b9af Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Sep 2024 09:44:49 +0200 Subject: [PATCH 0217/3097] Missing typehints should be consistently checked on level 6 --- conf/config.neon | 3 +++ src/PhpDoc/StubValidator.php | 4 ++-- src/Rules/Classes/MethodTagCheck.php | 5 +++++ src/Rules/Classes/MixinCheck.php | 5 +++++ src/Rules/Classes/PropertyTagCheck.php | 5 +++++ tests/PHPStan/Rules/Classes/MethodTagRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MixinRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php | 1 + tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php | 1 + tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php | 1 + tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php | 1 + 14 files changed, 29 insertions(+), 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 6f189522548..05a71dbe97d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -919,17 +919,20 @@ services: class: PHPStan\Rules\Classes\MethodTagCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + checkMissingTypehints: %checkMissingTypehints% - class: PHPStan\Rules\Classes\MixinCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% absentTypeChecks: %featureToggles.absentTypeChecks% + checkMissingTypehints: %checkMissingTypehints% - class: PHPStan\Rules\Classes\PropertyTagCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + checkMissingTypehints: %checkMissingTypehints% - class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 43ecfdd0a50..ee4fcaa2a2a 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -251,12 +251,12 @@ private function getRuleRegistry(Container $container): RuleRegistry if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); - $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); + $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $rules[] = new MethodTagRule($methodTagCheck); $rules[] = new MethodTagTraitRule($methodTagCheck, $reflectionProvider); $rules[] = new MethodTagTraitUseRule($methodTagCheck); - $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true); + $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $rules[] = new PropertyTagRule($propertyTagCheck); $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index e8c44d416ec..b37d373772a 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -29,6 +29,7 @@ public function __construct( private MissingTypehintCheck $missingTypehintCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, + private bool $checkMissingTypehints, ) { } @@ -161,6 +162,10 @@ public function checkInTraitUseContext( */ private function checkMethodTypeInTraitDefinitionContext(ClassReflection $classReflection, string $methodName, string $description, Type $type): array { + if (!$this->checkMissingTypehints) { + return []; + } + $errors = []; foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index ab6f5ccb00e..a17ef3d2001 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -28,6 +28,7 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, private bool $absentTypeChecks, + private bool $checkMissingTypehints, ) { } @@ -68,6 +69,10 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): continue; } + if (!$this->checkMissingTypehints) { + continue; + } + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($type) as $iterableType) { $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index 788c252d471..e05c4c676bb 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -31,6 +31,7 @@ public function __construct( private MissingTypehintCheck $missingTypehintCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, + private bool $checkMissingTypehints, ) { } @@ -141,6 +142,10 @@ private function getTypesAndTagName(PropertyTag $propertyTag): array */ private function checkPropertyTypeInTraitDefinitionContext(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type): array { + if (!$this->checkMissingTypehints) { + return []; + } + $errors = []; foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($type) as [$innerName, $genericTypeNames]) { diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php index 9ff4c1337ad..7766e03bd84 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index a2c07386e2c..74f70ca72d1 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index 59c1cb6aab9..7839c99123a 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index acaf1974b05..b1a1bb39ca5 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index f23e120458d..a16f6ac23ff 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index dbc7906da5a..08d3bb1c027 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php index b5718fb844a..7b36d89e903 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index 887cebd583f..3e6ff6c9535 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php index c19a36419af..76b4342abec 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): TRule new MissingTypehintCheck(true, true, true, true, []), new UnresolvableTypeHelper(), true, + true, ), ); } From 77405e80bbcf349ea65b89ddeb959c0ef1648c7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 01:54:56 +0000 Subject: [PATCH 0218/3097] Update crate-ci/typos action to v1.24.5 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index d91ab1ff1f3..e87b7b38c85 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.24.3" + uses: "crate-ci/typos@v1.24.5" with: files: "README.md src/" From 9815bbba4535b0605aef5b5cb9dd64a63bc44b1f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Sep 2024 09:56:48 +0200 Subject: [PATCH 0219/3097] PHPStan Pro: debug corrupted PHAR signature message --- src/Command/FixerApplication.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 6236c3c63f6..40668741d9b 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -49,6 +49,7 @@ use function fclose; use function fopen; use function fwrite; +use function get_class; use function getenv; use function http_build_query; use function ini_get; @@ -240,10 +241,11 @@ private function getFixerProcess(OutputInterface $output, int $serverPort): Proc try { $phar = new Phar($pharPath); - } catch (Throwable) { + } catch (Throwable $e) { @unlink($pharPath); @unlink($infoPath); $output->writeln('PHPStan Pro PHAR signature is corrupted.'); + $output->writeln(sprintf('%s: %s', get_class($e), $e->getMessage())); throw new FixerProcessException(); } @@ -252,6 +254,7 @@ private function getFixerProcess(OutputInterface $output, int $serverPort): Proc @unlink($pharPath); @unlink($infoPath); $output->writeln('PHPStan Pro PHAR signature is corrupted.'); + $output->writeln(sprintf('Wrong hash type: %s', $phar->getSignature()['hash_type'])); throw new FixerProcessException(); } From 44b8b73382bd9de5c99ee50ea430420acd15178a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Sep 2024 13:04:49 +0200 Subject: [PATCH 0220/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/6299 --- .../PhpDoc/InvalidPhpDocTagValueRuleTest.php | 20 ++++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-6299.php | 21 +++++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-6692.php | 27 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-6299.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-6692.php diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index a96c45d7959..0047c107edd 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -124,4 +124,24 @@ public function testIgnoreWithinPhpDoc(): void $this->analyse([__DIR__ . '/data/ignore-line-within-phpdoc.php'], []); } + public function testBug6299(): void + { + $this->analyse([__DIR__ . '/data/bug-6299.php'], [ + [ + "PHPDoc tag @phpstan-return has invalid value (array{'numeric': stdClass[], 'branches': array{'names': string[], 'exclude': bool}}}|int): Unexpected token \"}\", expected TOKEN_HORIZONTAL_WS at offset 107 on line 2", + 10, + ], + ]); + } + + public function testBug6692(): void + { + $this->analyse([__DIR__ . '/data/bug-6692.php'], [ + [ + 'PHPDoc tag @return has invalid value ($this): Unexpected token "<", expected TOKEN_HORIZONTAL_WS at offset 21 on line 2', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-6299.php b/tests/PHPStan/Rules/PhpDoc/data/bug-6299.php new file mode 100644 index 00000000000..025eaefe2e4 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-6299.php @@ -0,0 +1,21 @@ + [], 'branches' => ['names' => [], 'exclude' => false]]; + } + else { + return 0; + } + } +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php b/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php new file mode 100644 index 00000000000..c2312fc0ed8 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php @@ -0,0 +1,27 @@ + + */ + public function change(): static + { + return $this; + } +} + +/** + * @template T + * @extends Wrapper + * + * @method self change() + */ +class SubWrapper extends Wrapper +{ +} From 64d4a3b61c81aced05fa690bb7a418d34bfb4213 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Sep 2024 13:12:46 +0200 Subject: [PATCH 0221/3097] Fix build --- tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon | 5 ++++- .../PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon | 5 ++++- tests/PHPStan/Rules/PhpDoc/data/bug-6692.php | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon b/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon index ca7c8f9c2ca..ff39bfc9d70 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon +++ b/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon @@ -11,7 +11,10 @@ parameters: path: WindowsNewlines.php - - message: "#^PHPDoc tag @param has invalid value \\(\\)\\: Unexpected token \"\\\\n\\\\t \\* \", expected type at offset 113$#" + message: """ + #^PHPDoc tag @param has invalid value \\(\r + \\$object\\)\\: Unexpected token "\\\\r\\\\n\\\\t \\* ", expected type at offset 113 on line 4$# + """ count: 1 path: WindowsNewlines.php diff --git a/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon b/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon index 3bfe998b6ec..398e241bd7a 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon +++ b/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon @@ -11,7 +11,10 @@ parameters: path: UnixNewlines.php - - message: "#^PHPDoc tag @param has invalid value \\(\\)\\: Unexpected token \"\\\\r\\\\n\\\\t \\* \", expected type at offset 110$#" + message: """ + #^PHPDoc tag @param has invalid value \\( + \\$object\\)\\: Unexpected token "\\\\n\\\\t \\* ", expected type at offset 110 on line 4$# + """ count: 1 path: UnixNewlines.php diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php b/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php index c2312fc0ed8..41bd197ed9a 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-6692.php @@ -1,4 +1,4 @@ -= 8.0 namespace Bug6692; From c447d32aceaa85c98f03ce235d19c4c74f784785 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Sep 2024 16:25:59 +0200 Subject: [PATCH 0222/3097] UPGRADING guide WIP --- .github/workflows/phar.yml | 3 +++ UPGRADING.md | 29 +++++++++++++++++++++++++++++ changelog-2.0.md | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 UPGRADING.md diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index adea89e2323..04cda78057d 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -211,6 +211,9 @@ jobs: env: GPG_ID: ${{ steps.import-gpg.outputs.fingerprint }} + - name: "cp UPGRADING.md" + run: cp phpstan-src/UPGRADING.md phpstan-dist/UPGRADING.md + - name: "Verify PHAR" working-directory: phpstan-dist run: "gpg --verify phpstan.phar.asc" diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000000..c209e8ffb06 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,29 @@ +This document is a work in progress. + +Upgrading from PHPStan 1.x to 2.0 +================================= + +## PHP version requirements + +PHPStan now requires PHP 7.4 or newer to run. + +## Upgrading guide for end users + +TODO + +## Upgrading guide for extension developers + +### PHPStan now uses nikic/php-parser v5 + +See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. + +The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class. + +Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The +`Stmt\Throw_` class has been removed. + +### PHPStan now uses phpstan/phpdoc-parser v2 + +See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. + +TODO diff --git a/changelog-2.0.md b/changelog-2.0.md index 5525195f4bf..4f9a5843f13 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -1,6 +1,6 @@ This document is a work in progress. -When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases) + a separate UPGRADING document. +When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases) + a separate [UPGRADING](./UPGRADING.md) document. Major new features 🚀 ===================== From a50b75a81e41de985ffd5b5334319c471c9b9a33 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 9 Sep 2024 21:10:12 +0200 Subject: [PATCH 0223/3097] Fix conditional types in array_map() return value --- .../ArrayMapFunctionReturnTypeExtension.php | 29 +++++++++++++----- .../Rules/Methods/ReturnTypeRuleTest.php | 5 ++++ .../PHPStan/Rules/Methods/data/bug-10715.php | 30 +++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10715.php diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 24561b55982..e8e9e3a4575 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -2,9 +2,11 @@ namespace PHPStan\Type\Php; +use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; @@ -13,10 +15,10 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; -use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; +use function array_map; use function array_slice; use function count; @@ -38,12 +40,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $callableType = $scope->getType($functionCall->getArgs()[0]->value); $callableIsNull = $callableType->isNull()->yes(); + $callableParametersAcceptors = null; + if ($callableType->isCallable()->yes()) { - $valueTypes = [new NeverType()]; - foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) { - $valueTypes[] = $parametersAcceptor->getReturnType(); - } - $valueType = TypeCombinator::union(...$valueTypes); + $callableParametersAcceptors = $callableType->getCallableParametersAcceptors($scope); + $valueType = ParametersAcceptorSelector::selectFromTypes( + array_map( + static fn (Node\Arg $arg) => $scope->getType($arg->value)->getIterableValueType(), + array_slice($functionCall->getArgs(), 1), + ), + $callableParametersAcceptors, + false, + )->getReturnType(); } elseif ($callableIsNull) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { @@ -70,10 +78,17 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if ($totalCount < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { foreach ($constantArrays as $constantArray) { $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $valueTypes = $constantArray->getValueTypes(); foreach ($constantArray->getKeyTypes() as $i => $keyType) { $returnedArrayBuilder->setOffsetValueType( $keyType, - $valueType, + $callableParametersAcceptors !== null + ? ParametersAcceptorSelector::selectFromTypes( + [$valueTypes[$i]], + $callableParametersAcceptors, + false, + )->getReturnType() + : $valueType, $constantArray->isOptionalKey($i), ); } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 2af1147c65d..6d374a6f1c9 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1049,4 +1049,9 @@ public function testBug11337(): void $this->analyse([__DIR__ . '/data/bug-11337.php'], []); } + public function testBug10715(): void + { + $this->analyse([__DIR__ . '/data/bug-10715.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10715.php b/tests/PHPStan/Rules/Methods/data/bug-10715.php new file mode 100644 index 00000000000..82cd7f66ae2 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10715.php @@ -0,0 +1,30 @@ + $word + * + * @return ($word is array ? array : string) + */ + public static function wgtrim(string|array $word): string|array + { + if (\is_array($word)) { + return array_map(static::wgtrim(...), $word); + } + + return 'word'; + } + + /** + * @param array{foo: array, bar: string} $array + * + * @return array{foo: array, bar: string} + */ + public static function example(array $array): array + { + return array_map(static::wgtrim(...), $array); + } +} From a4774be3cf2aa967e17d8afcca1e8247d91cbaaa Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Tue, 10 Sep 2024 09:57:39 +0000 Subject: [PATCH 0224/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index d0c4b970db0..e121521ccee 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.18", + "ondrejmirtes/better-reflection": "6.25.0.19", "phpstan/php-8-stubs": "0.3.102", "phpstan/phpdoc-parser": "1.30.1", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 7bcc05980e5..9fd242603f0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "339a3f47c630b657cae8a667296f3c1a", + "content-hash": "acf4480811082060c5c0e548ad4bf9bf", "packages": [ { "name": "clue/ndjson-react", @@ -2176,16 +2176,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.18", + "version": "6.25.0.19", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "04ce3daaa7bcbf96be471b56ee95d336201a75eb" + "reference": "3c20bd3dea6f5a04a729891f9dd4326feb2e288c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/04ce3daaa7bcbf96be471b56ee95d336201a75eb", - "reference": "04ce3daaa7bcbf96be471b56ee95d336201a75eb", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/3c20bd3dea6f5a04a729891f9dd4326feb2e288c", + "reference": "3c20bd3dea6f5a04a729891f9dd4326feb2e288c", "shasum": "" }, "require": { @@ -2242,9 +2242,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.18" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.19" }, - "time": "2024-09-05T15:34:08+00:00" + "time": "2024-09-10T09:53:53+00:00" }, { "name": "phpstan/php-8-stubs", From fd25c2779ca7fd4decee7038282ab9743b16d167 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 11:48:05 +0200 Subject: [PATCH 0225/3097] Fix false positive when extending SplObjectStorage on PHP < 8.4 --- .../Rules/Methods/MissingMethodImplementationRuleTest.php | 5 +++++ tests/PHPStan/Rules/Methods/data/bug-11665.php | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11665.php diff --git a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php index 10615f2928b..82240f467e2 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php @@ -59,4 +59,9 @@ public function testEnums(): void ]); } + public function testBug11665(): void + { + $this->analyse([__DIR__ . '/data/bug-11665.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11665.php b/tests/PHPStan/Rules/Methods/data/bug-11665.php new file mode 100644 index 00000000000..1926a10ac53 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11665.php @@ -0,0 +1,7 @@ + Date: Tue, 10 Sep 2024 09:55:17 +0200 Subject: [PATCH 0226/3097] Add non regression test for 11056 --- .../CallToFunctionParametersRuleTest.php | 6 ++ .../Rules/Functions/data/bug-11056.php | 59 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11056.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4beb131a6b4..c35966b09f3 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1742,6 +1742,12 @@ public function testNoNamedArguments(): void ]); } + public function testBug11056(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-11056.php'], []); + } + public function testBug11506(): void { $this->analyse([__DIR__ . '/data/bug-11506.php'], []); diff --git a/tests/PHPStan/Rules/Functions/data/bug-11056.php b/tests/PHPStan/Rules/Functions/data/bug-11056.php new file mode 100644 index 00000000000..6c4b8abea1e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11056.php @@ -0,0 +1,59 @@ +|string $class + * @return ($class is class-string ? T : mixed) + */ +function createA(string $class) { + return new $class(); +} + +/** + * @template T + * @param class-string $class + * @return T + */ +function createB(string $class) { + return new $class(); +} + +/** + * @param Item[] $values + */ +function receive(array $values): void { } + +receive( + array_map( + createA(...), + [ A::class, B::class, C::class ] + ) +); + +receive( + array_map( + createB(...), + [ A::class, B::class, C::class ] + ) +); + +receive( + array_map( + static fn($val) => createA($val), + [ A::class, B::class, C::class ] + ) +); + +receive( + array_map( + static fn($val) => createB($val), + [ A::class, B::class, C::class ] + ) +); From 8ffa0f267955b03dc3148e8b261d596d2ef0f485 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 12:33:20 +0200 Subject: [PATCH 0227/3097] Regression test --- tests/PHPStan/Analyser/nsrt/bug-9224b.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9224b.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-9224b.php b/tests/PHPStan/Analyser/nsrt/bug-9224b.php new file mode 100644 index 00000000000..863d17cb85e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9224b.php @@ -0,0 +1,17 @@ += 8.1 + +namespace Bug9224b; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** @param array $arr */ + public function sayHello(array $arr): void + { + assertType('array>', array_map('abs', $arr)); + assertType('array>', array_map(abs(...), $arr)); + } + +} From e3cebb9ff44c76d65a6aedeb45ffe0f24531337d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 12:36:56 +0200 Subject: [PATCH 0228/3097] Regression test --- tests/PHPStan/Analyser/nsrt/bug-5168-php7.php | 12 ++++++++++++ tests/PHPStan/Analyser/nsrt/bug-5168-php8.php | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-5168-php7.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-5168-php8.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php b/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php new file mode 100644 index 00000000000..a0049b55fc8 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php @@ -0,0 +1,12 @@ += 8.0 + +namespace Bug5168Php8; + +use function PHPStan\Testing\assertType; + +function (float $f): void { + define('LARAVEL_START', microtime(true)); + + $comment = 'Calculated in ' . microtime(true) - $f; + assertType('non-falsy-string', $comment); +}; From f22f483702214b194e8dc582a0cca876b4aee78e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 12:42:30 +0200 Subject: [PATCH 0229/3097] Regression test --- tests/PHPStan/Analyser/nsrt/bug-10685.php | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10685.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10685.php b/tests/PHPStan/Analyser/nsrt/bug-10685.php new file mode 100644 index 00000000000..17f51f2b266 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10685.php @@ -0,0 +1,26 @@ += 8.1 + +namespace Bug10685; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @template A + * @param A $value + * @return A + */ + function identity(mixed $value): mixed + { + return $value; + } + + public function doFoo(): void + { + assertType('array{1|2|3, 1|2|3, 1|2|3}', array_map(fn($i) => $i, [1, 2, 3])); + assertType('array{1, 2, 3}', array_map($this->identity(...), [1, 2, 3])); + } + +} From 9d9fb560907fcd4f6dd16789af4278ac7f683736 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 13:42:03 +0200 Subject: [PATCH 0230/3097] Fix SplObjectStorage generic stub for PHP 8.4 --- stubs/SplObjectStorage.stub | 3 ++- tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/stubs/SplObjectStorage.stub b/stubs/SplObjectStorage.stub index 3f7c44f120a..146785e5221 100644 --- a/stubs/SplObjectStorage.stub +++ b/stubs/SplObjectStorage.stub @@ -5,9 +5,10 @@ * @template TData * * @template-implements Iterator + * @template-implements SeekableIterator * @template-implements ArrayAccess */ -class SplObjectStorage implements Countable, Iterator, Serializable, ArrayAccess +class SplObjectStorage implements Countable, Iterator, SeekableIterator, Serializable, ArrayAccess { /** diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index f8d27e26120..05963f5576b 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -108,7 +108,7 @@ public function testRuleExtends(): void 215, ], [ - 'Class ClassAncestorsExtends\FooObjectStorage @extends tag contains incompatible type ClassAncestorsExtends\FooObjectStorage&iterable.', + 'Class ClassAncestorsExtends\FooObjectStorage @extends tag contains incompatible type ClassAncestorsExtends\FooObjectStorage.', 226, ], [ From 68f6e25e2a179d947ba58a25217791ee2f01034a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Sep 2024 15:24:38 +0200 Subject: [PATCH 0231/3097] Prevent warning in range() on php 7.x --- src/Type/Php/RangeFunctionReturnTypeExtension.php | 2 +- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 16c7a37788d..ed43f64f203 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -68,7 +68,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } try { - $rangeValues = range($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue()); + $rangeValues = @range($startConstant->getValue(), $endConstant->getValue(), $stepConstant->getValue()); } catch (ValueError) { continue; } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 1d17f9ee483..0a9beb6fb81 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5685,6 +5685,10 @@ public function dataRangeFunction(): array 'array{2, 4}', 'range(2, 5, 2)', ], + [ + 'array{2, 0}', + "range(2, '', 2)", + ], [ PHP_VERSION_ID < 80300 ? 'array{2.0, 3.0, 4.0, 5.0}' : 'array{2, 3, 4, 5}', 'range(2, 5, 1.0)', From d3a2a92fcd612bf42bbfd19cd3a5625481ff7522 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 13:44:44 +0200 Subject: [PATCH 0232/3097] Process expression assignments other than Variable in by-ref parameters --- src/Analyser/NodeScopeResolver.php | 33 +++++++++----- .../TypesAssignedToPropertiesRuleTest.php | 15 +++++++ .../data/properties-assigned-types.php | 45 +++++++++++++++++++ .../TooWidePropertyTypeRuleTest.php | 5 +++ .../Rules/TooWideTypehints/data/bug-11667.php | 40 +++++++++++++++++ 5 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11667.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b22d651f993..f09e4a9bb67 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4767,18 +4767,29 @@ private function processArgs( } $argValue = $arg->value; - if ($argValue instanceof Variable && is_string($argValue->name)) { - if ($argValue->name !== 'this') { - $paramOutType = $this->getParameterOutExtensionsType($callLike, $calleeReflection, $currentParameter, $scope); - if ($paramOutType !== null) { - $byRefType = $paramOutType; - } - - $nodeCallback(new VariableAssignNode($argValue, new TypeExpr($byRefType), false), $scope); - $scope = $scope->assignVariable($argValue->name, $byRefType, new MixedType()); + if (!$argValue instanceof Variable || $argValue->name !== 'this') { + $paramOutType = $this->getParameterOutExtensionsType($callLike, $calleeReflection, $currentParameter, $scope); + if ($paramOutType !== null) { + $byRefType = $paramOutType; } - } else { - $scope = $scope->invalidateExpression($argValue); + + $result = $this->processAssignVar( + $scope, + $stmt, + $argValue, + new TypeExpr($byRefType), + static function (Node $node, Scope $scope) use ($nodeCallback): void { + if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { + return; + } + + $nodeCallback($node, $scope); + }, + $context, + static fn (MutatingScope $scope): ExpressionResult => new ExpressionResult($scope, false, [], []), + true, + ); + $scope = $result->getScope(); } } elseif ($calleeReflection !== null && $calleeReflection->hasSideEffects()->yes()) { $argType = $scope->getType($arg->value); diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 09f34d1d40d..33551884d84 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -107,6 +107,21 @@ public function testTypesAssignedToProperties(): void 'Property PropertiesAssignedTypes\AppendToArrayAccess::$collection2 (ArrayAccess&Countable) does not accept Countable.', 376, ], + [ + 'Property PropertiesAssignedTypes\ParamOutAssign::$foo (list) does not accept string.', + 400, + 'string is not a list.', + ], + [ + 'Property PropertiesAssignedTypes\ParamOutAssign::$foo2 (list>) does not accept string.', + 410, + 'string is not a list.', + ], + [ + 'Property PropertiesAssignedTypes\ParamOutAssign::$foo2 (list>) does not accept non-empty-list|string>.', + 415, + 'list|string might not be a list.', + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php index 3686d4d2440..14b3ad66e46 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php @@ -376,3 +376,48 @@ public function foo(): void $this->collection2[] = 2; } } + +class ParamOutAssign +{ + + /** @var list */ + private $foo; + + /** @var list> */ + private $foo2; + + /** + * @param mixed $a + * @param-out string $a + */ + public function paramOut(&$a): void + { + + } + + public function doFoo(): void + { + $this->paramOut($this->foo); + } + + public function doFoo2(): void + { + $this->paramOut($this->foo[0]); + } + + public function doBar(): void + { + $this->paramOut($this->foo2); + } + + public function doBar2(): void + { + $this->paramOut($this->foo2[0]); + } + + public function doBar3(): void + { + $this->paramOut($this->foo2[0][0]); + } + +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php index f512d22fc73..1171abd564f 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php @@ -56,4 +56,9 @@ public function testRule(): void ]); } + public function testBug11667(): void + { + $this->analyse([__DIR__ . '/data/bug-11667.php'], []); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11667.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11667.php new file mode 100644 index 00000000000..09bd5a89190 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11667.php @@ -0,0 +1,40 @@ +|null */ + private $matches = null; + + public function match(string $string): void { + preg_match('/Hello (\w+)/', $string, $this->matches); + } + + /** @return list|null */ + public function get(): ?array { + return $this->matches; + } +} + +final class HelloWorld2 { + /** @var list|null */ + private $matches = null; + + public function match(string $string): void { + $this->paramOut($this->matches); + } + + /** + * @param mixed $a + * @param-out list $a + */ + public function paramOut(&$a): void + { + + } + + /** @return list|null */ + public function get(): ?array { + return $this->matches; + } +} From 00d2caf39514380610899b0a305413f60c1c5830 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 14:20:39 +0200 Subject: [PATCH 0233/3097] Allow nonexistent other-than-Variable expressions in by-ref parameters --- src/Analyser/NodeScopeResolver.php | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f09e4a9bb67..6871211346e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4570,20 +4570,18 @@ private function processArgs( $lookForUnset = false; if ($assignByReference) { - if ($arg->value instanceof Variable) { - $isBuiltin = false; - if ($calleeReflection instanceof FunctionReflection && $calleeReflection->isBuiltin()) { - $isBuiltin = true; - } elseif ($calleeReflection instanceof ExtendedMethodReflection && $calleeReflection->getDeclaringClass()->isBuiltin()) { - $isBuiltin = true; - } - if ( - $isBuiltin - || ($parameterNativeType === null || !$parameterNativeType->isNull()->no()) - ) { - $scope = $this->lookForSetAllowedUndefinedExpressions($scope, $arg->value); - $lookForUnset = true; - } + $isBuiltin = false; + if ($calleeReflection instanceof FunctionReflection && $calleeReflection->isBuiltin()) { + $isBuiltin = true; + } elseif ($calleeReflection instanceof ExtendedMethodReflection && $calleeReflection->getDeclaringClass()->isBuiltin()) { + $isBuiltin = true; + } + if ( + $isBuiltin + || ($parameterNativeType === null || !$parameterNativeType->isNull()->no()) + ) { + $scope = $this->lookForSetAllowedUndefinedExpressions($scope, $arg->value); + $lookForUnset = true; } } From c11e98aca2682d26ebb8c5b7bfb5ed803ac37e96 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 15:34:49 +0200 Subject: [PATCH 0234/3097] Fix test --- tests/PHPStan/Analyser/nsrt/bug-5168-php7.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php b/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php index a0049b55fc8..9e981de789d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5168-php7.php @@ -8,5 +8,5 @@ function (float $f): void { define('LARAVEL_START', microtime(true)); $comment = 'Calculated in ' . microtime(true) - $f; - assertType('float', $comment); + assertType('*ERROR*', $comment); }; From 9693201be0f991a1b1d9c3563294a7d82ebee708 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Sep 2024 14:28:45 +0200 Subject: [PATCH 0235/3097] Added regression test --- .../UnusedPrivatePropertyRuleTest.php | 7 ++++ .../PHPStan/Rules/DeadCode/data/bug-8781.php | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-8781.php diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index e17c06a69d3..0dc5ddabe08 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -315,4 +315,11 @@ public function testBug10628(): void $this->analyse([__DIR__ . '/data/bug-10628.php'], []); } + public function testBug8781(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-8781.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-8781.php b/tests/PHPStan/Rules/DeadCode/data/bug-8781.php new file mode 100644 index 00000000000..3e893610286 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-8781.php @@ -0,0 +1,37 @@ + + */ + private $stdOut; + + /** + * @var string + */ + private $command; + + /** + * @param string $command + */ + public function __construct($command) + { + $this->command = $command; + } + + public function run(): void + { + exec($this->command, $this->stdOut); + } + + /** + * @return array + */ + public function wait(): array + { + return $this->stdOut; + } +} From 76973988540e59acc403822a3e5d0b3f108a5543 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 15:41:22 +0200 Subject: [PATCH 0236/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/11617 Closes https://github.com/phpstan/phpstan/issues/5077 Closes https://github.com/phpstan/phpstan/issues/9361 Closes https://github.com/phpstan/phpstan/issues/7251 --- tests/PHPStan/Analyser/nsrt/bug-5077.php | 33 +++++++++++ .../UnusedPrivatePropertyRuleTest.php | 14 +++++ .../PHPStan/Rules/DeadCode/data/bug-7251.php | 18 ++++++ .../PHPStan/Rules/DeadCode/data/bug-9361.php | 58 +++++++++++++++++++ .../TypesAssignedToPropertiesRuleTest.php | 19 ++++++ .../Rules/Properties/data/bug-11617.php | 23 ++++++++ 6 files changed, 165 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-5077.php create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-7251.php create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-9361.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-11617.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-5077.php b/tests/PHPStan/Analyser/nsrt/bug-5077.php new file mode 100644 index 00000000000..f20bf085b98 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-5077.php @@ -0,0 +1,33 @@ +> $array + */ +function test(array &$array): void +{ + $array[] = ['test' => rand(), 'p' => 'test']; +} + +function (): void { + $array = []; + $array['key'] = []; + + assertType('array{key: array{}}', $array); + assertType('array{}', $array['key']); + + test($array['key']); + assertType('array{key: array>}', $array); + assertType('array>', $array['key']); + + test($array['key']); + assertType('array{key: array>}', $array); + assertType('array>', $array['key']); + + test($array['key']); + assertType('array{key: array>}', $array); + assertType('array>', $array['key']); +}; diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 0dc5ddabe08..9e97e8bc4a1 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -322,4 +322,18 @@ public function testBug8781(): void $this->analyse([__DIR__ . '/data/bug-8781.php'], []); } + public function testBug9361(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-9361.php'], []); + } + + public function testBug7251(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-7251.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-7251.php b/tests/PHPStan/Rules/DeadCode/data/bug-7251.php new file mode 100644 index 00000000000..bd7e430d66c --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-7251.php @@ -0,0 +1,18 @@ +setToOne($this->bar); + } + + private function setToOne(&$var) + { + $var = 1; + } +} diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-9361.php b/tests/PHPStan/Rules/DeadCode/data/bug-9361.php new file mode 100644 index 00000000000..c7a5e262479 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-9361.php @@ -0,0 +1,58 @@ +Bound = &$var; + + return $this; + } + + /** + * @param mixed $value + * @return $this + */ + public function setValue($value) + { + if ($this->Bound !== $value) { + $this->Bound = $value; + } + + return $this; + } +} + +class Command +{ + /** + * @var mixed + */ + private $Value; + + /** + * @return Option[] + */ + public function getOptions() + { + return [ + (new Option())->bind($this->Value), + ]; + } + + public function run(): void + { + $value = $this->Value; + } +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 33551884d84..ee0bd80321e 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -645,4 +645,23 @@ public function testBug11275(): void ]); } + public function testBug11617(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-11617.php'], [ + [ + 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 14, + ], + [ + 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 16, + ], + [ + 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 21, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-11617.php b/tests/PHPStan/Rules/Properties/data/bug-11617.php new file mode 100644 index 00000000000..e0854ad0d77 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-11617.php @@ -0,0 +1,23 @@ + + */ + private $params; + + public function sayHello(string $query): void + { + \parse_str($query, $this->params); + \parse_str($query, $tmp); + $this->params = $tmp; + + /** @var array $foo */ + $foo = []; + \parse_str($query, $foo); + $this->params = $foo; + } +} From a4980e18ebfe2f55b2a30f9f4ae0c98c344a0d74 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Sep 2024 15:58:02 +0200 Subject: [PATCH 0237/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/2313 Closes https://github.com/phpstan/phpstan/issues/11655 Closes https://github.com/phpstan/phpstan/issues/2634 --- ...nexistentOffsetInArrayDimFetchRuleTest.php | 20 ++++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-11655.php | 23 +++++++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-2313.php | 22 ++++++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-2634.php | 13 +++++++++++ 4 files changed, 78 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11655.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-2313.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-2634.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index ffc6aa26d5d..2838f2cbd9f 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -878,4 +878,24 @@ public function testBug11572(): void ]); } + public function testBug2313(): void + { + $this->analyse([__DIR__ . '/data/bug-2313.php'], []); + } + + public function testBug11655(): void + { + $this->analyse([__DIR__ . '/data/bug-11655.php'], [ + [ + "Offset 3 does not exist on array{string, 'x', array{string, 'x'}}.", + 15, + ], + ]); + } + + public function testBug2634(): void + { + $this->analyse([__DIR__ . '/data/bug-2634.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11655.php b/tests/PHPStan/Rules/Arrays/data/bug-11655.php new file mode 100644 index 00000000000..04e0bc2a1ed --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11655.php @@ -0,0 +1,23 @@ + array()); + + safe_inc($data['apples']['count']); + print_r($data); + + safe_inc($data['apples']['count']); + print_r($data); +}; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-2634.php b/tests/PHPStan/Rules/Arrays/data/bug-2634.php new file mode 100644 index 00000000000..355ce896f63 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-2634.php @@ -0,0 +1,13 @@ + Date: Wed, 11 Sep 2024 09:44:54 +0200 Subject: [PATCH 0238/3097] Fix false positive when type casting in If_ statement Co-authored-by: Ondrej Mirtes --- src/Analyser/TypeSpecifier.php | 30 ++++++++++---- tests/PHPStan/Analyser/nsrt/narrow-cast.php | 20 ++++++---- .../ElseIfConstantConditionRuleTest.php | 16 ++++++++ .../Rules/Comparison/data/bug-11674.php | 40 +++++++++++++++++++ 4 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-11674.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fd243efb16f..f02e5f51d2c 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -197,15 +197,31 @@ public function specifyTypesInCondition( $context, $rootExpr, ); - } elseif ( - $expr instanceof Expr\Cast\String_ - || $expr instanceof Expr\Cast\Double - || $expr instanceof Expr\Cast\Int_ - || $expr instanceof Expr\Cast\Bool_ - ) { + } elseif ($expr instanceof Expr\Cast\Bool_) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BinaryOp\Equal($expr->expr, new ConstFetch(new Name\FullyQualified('true'))), + $context, + $rootExpr, + ); + } elseif ($expr instanceof Expr\Cast\String_) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\String_('')), + $context, + $rootExpr, + ); + } elseif ($expr instanceof Expr\Cast\Int_) { + return $this->specifyTypesInCondition( + $scope, + new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\LNumber(0)), + $context, + $rootExpr, + ); + } elseif ($expr instanceof Expr\Cast\Double) { return $this->specifyTypesInCondition( $scope, - new Node\Expr\BinaryOp\NotEqual($expr->expr, new ConstFetch(new Name\FullyQualified('false'))), + new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\DNumber(0.0)), $context, $rootExpr, ); diff --git a/tests/PHPStan/Analyser/nsrt/narrow-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-cast.php index afd5224ae0e..82e09e0bd3f 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-cast.php @@ -31,9 +31,9 @@ function doFoo(string $x, array $arr): void { /** @param int<-5, 5> $x */ function castString($x, string $s, bool $b) { if ((string) $x) { - assertType('int<-5, -1>|int<1, 5>', $x); + assertType('int<-5, 5>', $x); } else { - assertType('0', $x); + assertType('int<-5, 5>', $x); } if ((string) $b) { @@ -63,8 +63,14 @@ function castInt($x, string $s, bool $b) { assertType('false', $b); } + if ((int) $s) { + assertType('string', $s); + } else { + assertType('string', $s); + } + if ((int) strpos($s, 'xy')) { - assertType('non-falsy-string', $s); + assertType('string', $s); } else { assertType('string', $s); } @@ -73,9 +79,9 @@ function castInt($x, string $s, bool $b) { /** @param int<-5, 5> $x */ function castFloat($x, string $s, bool $b) { if ((float) $x) { - assertType('int<-5, -1>|int<1, 5>', $x); + assertType('int<-5, 5>', $x); } else { - assertType('0', $x); + assertType('int<-5, 5>', $x); } if ((float) $b) { @@ -85,8 +91,8 @@ function castFloat($x, string $s, bool $b) { } if ((float) $s) { - assertType('non-falsy-string', $s); + assertType('string', $s); } else { - assertType("''|'0'", $s); + assertType("string", $s); } } diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index 7d3b008d908..0643363ef1c 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -121,4 +122,19 @@ public function testReportPhpDoc(): void ]); } + public function testBug11674(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-11674.php'], [ + [ + 'Elseif condition is always false.', + 28, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11674.php b/tests/PHPStan/Rules/Comparison/data/bug-11674.php new file mode 100644 index 00000000000..7af6660da4d --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11674.php @@ -0,0 +1,40 @@ += 8.0 + +namespace Bug11674; + +class Test { + + private ?string $param; + + function show() : void { + if ((int) $this->param) { + echo 1; + } elseif ($this->param) { + echo 2; + } + } + + function show2() : void { + if ((float) $this->param) { + echo 1; + } elseif ($this->param) { + echo 2; + } + } + + function show3() : void { + if ((bool) $this->param) { + echo 1; + } elseif ($this->param) { + echo 2; + } + } + + function show4() : void { + if ((string) $this->param) { + echo 1; + } elseif ($this->param) { + echo 2; + } + } +} From a2548e3c8de3bd2b3d27171192ba107d29711bc2 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:53:59 +0000 Subject: [PATCH 0239/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e121521ccee..7ce4bd47b28 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.102", + "phpstan/php-8-stubs": "0.3.104", "phpstan/phpdoc-parser": "1.30.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 9fd242603f0..f8a221dffe3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "acf4480811082060c5c0e548ad4bf9bf", + "content-hash": "ad10e33b3f3e87a1ee4afc90835a8e59", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.102", + "version": "0.3.104", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "607eedcd3bf7bc7baa2bc187741d772c776cc7ee" + "reference": "a25ce7cc2c246653e8cd9c0c5669ad7465ccb297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/607eedcd3bf7bc7baa2bc187741d772c776cc7ee", - "reference": "607eedcd3bf7bc7baa2bc187741d772c776cc7ee", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/a25ce7cc2c246653e8cd9c0c5669ad7465ccb297", + "reference": "a25ce7cc2c246653e8cd9c0c5669ad7465ccb297", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.102" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.104" }, - "time": "2024-09-05T00:17:54+00:00" + "time": "2024-09-11T15:53:22+00:00" }, { "name": "phpstan/phpdoc-parser", From ae23a9201bb4a8f8d91b3e9ed40a8e0b78f300d1 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Wed, 11 Sep 2024 20:56:35 +0200 Subject: [PATCH 0240/3097] Simplify abs return type --- resources/functionMap.php | 4 +-- stubs/core.stub | 6 ++++ .../ParametersAcceptorSelectorTest.php | 29 ------------------- .../CallToFunctionParametersRuleTest.php | 9 ++++++ .../PHPStan/Rules/Functions/data/bug-9224.php | 12 ++++++++ 5 files changed, 28 insertions(+), 32 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-9224.php diff --git a/resources/functionMap.php b/resources/functionMap.php index d7f2c1f8ec9..2b9537b98e2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -57,9 +57,7 @@ return [ '_' => ['string', 'message'=>'string'], -'abs' => ['0|positive-int', 'number'=>'int'], -'abs\'1' => ['float', 'number'=>'float'], -'abs\'2' => ['float|0|positive-int', 'number'=>'string'], +'abs' => ['float|0|positive-int', 'num'=>'int|float'], 'accelerator_get_configuration' => ['array'], 'accelerator_get_scripts' => ['array'], 'accelerator_get_status' => ['array', 'fetch_scripts'=>'bool'], diff --git a/stubs/core.stub b/stubs/core.stub index 2dc82fe7aa5..652fed707d0 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -307,3 +307,9 @@ function headers_sent(?string &$filename = null, ?int &$line = null): bool {} * @return ($value is callable ? true : false) */ function is_callable(mixed $value, bool $syntax_only = false, ?string &$callable_name = null): bool {} + +/** + * @param float|int $num + * @return ($num is float ? float : $num is int ? non-negative-int : float|non-negative-int) + */ +function abs($num) {} diff --git a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php index 2b2b96c00f7..1f2e534a456 100644 --- a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php +++ b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php @@ -144,35 +144,6 @@ public function dataSelectFromTypes(): Generator ), ]; - $absVariants = $reflectionProvider->getFunction(new Name('abs'), null)->getVariants(); - yield [ - [ - new FloatType(), - new FloatType(), - ], - $absVariants, - false, - ParametersAcceptorSelector::combineAcceptors($absVariants), - ]; - yield [ - [ - new FloatType(), - new IntegerType(), - new StringType(), - ], - $absVariants, - false, - ParametersAcceptorSelector::combineAcceptors($absVariants), - ]; - yield [ - [ - new StringType(), - ], - $absVariants, - false, - $absVariants[2], - ]; - $strtokVariants = $reflectionProvider->getFunction(new Name('strtok'), null)->getVariants(); yield [ [], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c35966b09f3..a47624e8e8b 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1777,4 +1777,13 @@ public function testBug11559b(): void ]); } + public function testBug9224(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-9224.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-9224.php b/tests/PHPStan/Rules/Functions/data/bug-9224.php new file mode 100644 index 00000000000..6b784414125 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-9224.php @@ -0,0 +1,12 @@ += 8.1 + +namespace Bug3425; + +class HelloWorld +{ + /** @param array $arr */ + public function sayHello(array $arr): void + { + array_map(abs(...), $arr); + } +} From 31f6737a5b1a24a997929f83c0a5df34c5551c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Wed, 21 Aug 2024 23:36:38 +0200 Subject: [PATCH 0241/3097] Fix late static binding calls --- src/Analyser/MutatingScope.php | 24 +++ .../Analyser/nsrt/static-late-binding.php | 142 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/static-late-binding.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cb4dd6b858c..03c2109d86a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2083,6 +2083,18 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { $staticMethodCalledOnType = $this->resolveTypeByName($node->class); + if ( + $staticMethodCalledOnType instanceof StaticType + && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) + ) { + $methodReflectionCandidate = $this->getMethodReflection( + $staticMethodCalledOnType, + $node->name->name, + ); + if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { + $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); + } + } } else { $staticMethodCalledOnType = $this->getNativeType($node->class); } @@ -2108,6 +2120,18 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { $staticMethodCalledOnType = $this->resolveTypeByName($node->class); + if ( + $staticMethodCalledOnType instanceof StaticType + && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) + ) { + $methodReflectionCandidate = $this->getMethodReflection( + $staticMethodCalledOnType, + $node->name->name, + ); + if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { + $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); + } + } } else { $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); } diff --git a/tests/PHPStan/Analyser/nsrt/static-late-binding.php b/tests/PHPStan/Analyser/nsrt/static-late-binding.php new file mode 100644 index 00000000000..5421bbc1de5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/static-late-binding.php @@ -0,0 +1,142 @@ +retStaticConst()); + assertType('bool', X::retStaticConst()); + assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int + + assertType('int', A::retStaticConst(...)()); + assertType('2', B::retStaticConst(...)()); + assertType('2', self::retStaticConst(...)()); + assertType('2', static::retStaticConst(...)()); + assertType('int', parent::retStaticConst(...)()); + assertType('2', $this->retStaticConst(...)()); + assertType('bool', X::retStaticConst(...)()); + assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int + + assertType('StaticLateBinding\A', A::retStatic()); + assertType('StaticLateBinding\B', B::retStatic()); + assertType('static(StaticLateBinding\B)', self::retStatic()); + assertType('static(StaticLateBinding\B)', static::retStatic()); + assertType('static(StaticLateBinding\B)', parent::retStatic()); + assertType('static(StaticLateBinding\B)', $this->retStatic()); + assertType('bool', X::retStatic()); + assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A + + assertType('static(StaticLateBinding\B)', A::retNonStatic()); + assertType('static(StaticLateBinding\B)', B::retNonStatic()); + assertType('static(StaticLateBinding\B)', self::retNonStatic()); + assertType('static(StaticLateBinding\B)', static::retNonStatic()); + assertType('static(StaticLateBinding\B)', parent::retNonStatic()); + assertType('static(StaticLateBinding\B)', $this->retNonStatic()); + assertType('bool', X::retNonStatic()); + assertType('*ERROR*', $clUnioned->retNonStatic()); // should be bool|static(StaticLateBinding\B) + + A::outStaticConst($v); + assertType('int', $v); + B::outStaticConst($v); + assertType('2', $v); + self::outStaticConst($v); + assertType('2', $v); + static::outStaticConst($v); + assertType('2', $v); + parent::outStaticConst($v); + assertType('int', $v); + $this->outStaticConst($v); + assertType('2', $v); + X::outStaticConst($v); + assertType('bool', $v); + $clUnioned->outStaticConst($v); + assertType('bool', $v); // should be bool|int + } +} + +class X +{ + public static function retStaticConst(): bool + { + return false; + } + + /** + * @param-out bool $out + */ + public static function outStaticConst(&$out): void + { + $out = false; + } + + public static function retStatic(): bool + { + return false; + } + + public function retNonStatic(): bool + { + return false; + } +} From d047c7f8ef8da296d0498696635735eed1762a4e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 11 Sep 2024 11:19:12 +0200 Subject: [PATCH 0242/3097] Extract getMessageFromInternalError --- src/Command/AnalyseCommand.php | 57 ++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index b4d73589a51..4e9d20c150e 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -385,7 +385,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $internalErrorsTuples = array_values($internalErrorsTuples); - $bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml'; /** * Variable $internalErrors only contains non-file-specific "internal errors". @@ -396,32 +395,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - $message = sprintf('%s while %s', $internalError->getMessage(), $internalError->getContextDescription()); - if ($internalError->getTraceAsString() !== null) { - if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { - $firstTraceItem = $internalError->getTrace()[0] ?? null; - $trace = ''; - if ($firstTraceItem !== null && $firstTraceItem['file'] !== null && $firstTraceItem['line'] !== null) { - $trace = sprintf('## %s(%d)%s', $firstTraceItem['file'], $firstTraceItem['line'], "\n"); - } - $trace .= $internalError->getTraceAsString(); - - if ($internalError->shouldReportBug()) { - $message .= sprintf('%sPost the following stack trace to %s: %s%s', "\n", $bugReportUrl, "\n", $trace); - } else { - $message .= sprintf('%s%s', "\n\n", $trace); - } - } else { - if ($internalError->shouldReportBug()) { - $message .= sprintf('%sRun PHPStan with -v option and post the stack trace to:%s%s%s', "\n\n", "\n", $bugReportUrl, "\n"); - } else { - $message .= sprintf('%sRun PHPStan with -v option to see the stack trace', "\n"); - } - } - } - $internalErrors[] = new InternalError( - $message, + $this->getMessageFromInternalError($internalError, $output->getVerbosity()), $internalError->getContextDescription(), $internalError->getTrace(), $internalError->getTraceAsString(), @@ -555,6 +530,36 @@ private function createStreamOutput(): StreamOutput return new StreamOutput($resource); } + private function getMessageFromInternalError(InternalError $internalError, int $verbosity): string + { + $bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml'; + $message = sprintf('%s while %s', $internalError->getMessage(), $internalError->getContextDescription()); + if ($internalError->getTraceAsString() !== null) { + if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) { + $firstTraceItem = $internalError->getTrace()[0] ?? null; + $trace = ''; + if ($firstTraceItem !== null && $firstTraceItem['file'] !== null && $firstTraceItem['line'] !== null) { + $trace = sprintf('## %s(%d)%s', $firstTraceItem['file'], $firstTraceItem['line'], "\n"); + } + $trace .= $internalError->getTraceAsString(); + + if ($internalError->shouldReportBug()) { + $message .= sprintf('%sPost the following stack trace to %s: %s%s', "\n", $bugReportUrl, "\n", $trace); + } else { + $message .= sprintf('%s%s', "\n\n", $trace); + } + } else { + if ($internalError->shouldReportBug()) { + $message .= sprintf('%sRun PHPStan with -v option and post the stack trace to:%s%s%s', "\n\n", "\n", $bugReportUrl, "\n"); + } else { + $message .= sprintf('%sRun PHPStan with -v option to see the stack trace', "\n"); + } + } + } + + return $message; + } + private function generateBaseline(string $generateBaselineFile, InceptionResult $inceptionResult, AnalysisResult $analysisResult, OutputInterface $output, bool $allowEmptyBaseline, string $baselineExtension, bool $failWithoutResultCache): int { if (!$allowEmptyBaseline && !$analysisResult->hasErrors()) { From 475a18ce8e0e14e340d7a0939906fffa6dd28e8d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 11 Sep 2024 11:29:08 +0200 Subject: [PATCH 0243/3097] Special internal error message for Larastan+Laravel --- src/Command/AnalyseCommand.php | 55 ++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 4e9d20c150e..a112f222352 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -13,6 +13,7 @@ use PHPStan\Diagnose\DiagnoseExtension; use PHPStan\Diagnose\PHPStanDiagnoseExtension; use PHPStan\File\CouldNotWriteFileException; +use PHPStan\File\FileHelper; use PHPStan\File\FileReader; use PHPStan\File\FileWriter; use PHPStan\File\ParentDirectoryRelativePathHelper; @@ -34,6 +35,7 @@ use function array_key_exists; use function array_keys; use function array_map; +use function array_reverse; use function array_unique; use function array_values; use function count; @@ -50,12 +52,16 @@ use function pathinfo; use function rewind; use function sprintf; +use function str_contains; use function stream_get_contents; use function strlen; use function substr; use const PATHINFO_BASENAME; use const PATHINFO_EXTENSION; +/** + * @phpstan-import-type Trace from InternalError as InternalErrorTrace + */ final class AnalyseCommand extends Command { @@ -386,6 +392,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $internalErrorsTuples = array_values($internalErrorsTuples); + $fileHelper = $container->getByType(FileHelper::class); + /** * Variable $internalErrors only contains non-file-specific "internal errors". */ @@ -396,7 +404,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $internalErrors[] = new InternalError( - $this->getMessageFromInternalError($internalError, $output->getVerbosity()), + $this->getMessageFromInternalError($fileHelper, $internalError, $output->getVerbosity()), $internalError->getContextDescription(), $internalError->getTrace(), $internalError->getTraceAsString(), @@ -530,10 +538,51 @@ private function createStreamOutput(): StreamOutput return new StreamOutput($resource); } - private function getMessageFromInternalError(InternalError $internalError, int $verbosity): string + private function getMessageFromInternalError(FileHelper $fileHelper, InternalError $internalError, int $verbosity): string { - $bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml'; $message = sprintf('%s while %s', $internalError->getMessage(), $internalError->getContextDescription()); + $hasLarastan = false; + $isLaravelLast = false; + + foreach (array_reverse($internalError->getTrace()) as $traceItem) { + if ($traceItem['file'] === null) { + continue; + } + + $file = $fileHelper->normalizePath($traceItem['file'], '/'); + + if (str_contains($file, '/larastan/')) { + $hasLarastan = true; + $isLaravelLast = false; + continue; + } + + if (!str_contains($file, '/laravel/framework/')) { + continue; + } + + $isLaravelLast = true; + } + if ($hasLarastan) { + if ($isLaravelLast) { + $message .= "\n"; + $message .= "\n" . 'This message is coming from Laravel Framework itself.'; + $message .= "\n" . 'Larastan boots up your application in order to provide'; + $message .= "\n" . 'smarter static analysis of your codebase.'; + $message .= "\n"; + $message .= "\n" . 'In order to do that, the environment you run PHPStan in'; + $message .= "\n" . 'must match the environment you run your application in.'; + $message .= "\n"; + $message .= "\n" . 'Make sure you\'ve set your environment variables'; + $message .= "\n" . 'or the .env file correctly.'; + + return $message; + } + + $bugReportUrl = 'https://github.com/larastan/larastan/issues/new?template=bug-report.md'; + } else { + $bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml'; + } if ($internalError->getTraceAsString() !== null) { if (OutputInterface::VERBOSITY_VERBOSE <= $verbosity) { $firstTraceItem = $internalError->getTrace()[0] ?? null; From 7e366e08f96e2e4095b3f02b5487e8f9531f37bf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 12 Sep 2024 08:54:04 +0200 Subject: [PATCH 0244/3097] Tool to make optional parameters required across the codebase --- bin/make-optional-parameters-required.php | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 bin/make-optional-parameters-required.php diff --git a/bin/make-optional-parameters-required.php b/bin/make-optional-parameters-required.php new file mode 100755 index 00000000000..5d2dd308c0f --- /dev/null +++ b/bin/make-optional-parameters-required.php @@ -0,0 +1,51 @@ +#!/usr/bin/env php +createForHostVersion(); + $traverser = new NodeTraverser(new CloningVisitor()); + $printer = new Standard(); + $finder = new Finder(); + $finder->followLinks(); + + $removeParamDefaultTraverser = new NodeTraverser(new class () extends NodeVisitorAbstract { + + public function enterNode(Node $node) + { + if (!$node instanceof Node\Param) { + return null; + } + + $node->default = null; + + return $node; + } + + }); + foreach ($finder->files()->name('*.php')->in($dir) as $fileInfo) { + $oldStmts = $parser->parse(file_get_contents($fileInfo->getPathname())); + $oldTokens = $parser->getTokens(); + + $newStmts = $traverser->traverse($oldStmts); + $newStmts = $removeParamDefaultTraverser->traverse($newStmts); + + $newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens); + file_put_contents($fileInfo->getPathname(), $newCode); + } +})(); From c2c30d733c801a7a2142abf0060f39f1afe63b15 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 13 Sep 2024 10:21:49 +0200 Subject: [PATCH 0245/3097] [BCB] Refactor TypeSpecifier::create() method and SpecifiedTypes constructor parameters --- UPGRADING.md | 22 +- src/Analyser/SpecifiedTypes.php | 85 +++- src/Analyser/TypeSpecifier.php | 434 ++++++++---------- ...yExistsFunctionTypeSpecifyingExtension.php | 6 +- ...ySearchFunctionTypeSpecifyingExtension.php | 1 - ...sExistsFunctionTypeSpecifyingExtension.php | 2 - .../CountFunctionTypeSpecifyingExtension.php | 2 +- ...peDigitFunctionTypeSpecifyingExtension.php | 4 +- .../DefineConstantTypeSpecifyingExtension.php | 3 +- ...DefinedConstantTypeSpecifyingExtension.php | 1 - ...nExistsFunctionTypeSpecifyingExtension.php | 2 - ...InArrayFunctionTypeSpecifyingExtension.php | 9 +- .../IsAFunctionTypeSpecifyingExtension.php | 1 - ...IsArrayFunctionTypeSpecifyingExtension.php | 2 +- ...allableFunctionTypeSpecifyingExtension.php | 2 +- ...terableFunctionTypeSpecifyingExtension.php | 2 +- ...classOfFunctionTypeSpecifyingExtension.php | 1 - .../MethodExistsTypeSpecifyingExtension.php | 2 - .../Php/PregMatchTypeSpecifyingExtension.php | 11 +- .../PropertyExistsTypeSpecifyingExtension.php | 1 - ...assIsSubclassOfTypeSpecifyingExtension.php | 1 - ...SetTypeFunctionTypeSpecifyingExtension.php | 3 +- .../StrContainingTypeSpecifyingExtension.php | 18 +- ...sibleCheckTypeMethodCallRuleEqualsTest.php | 2 + .../ImpossibleCheckTypeMethodCallRuleTest.php | 2 + .../TestTypeOverwriteSpecifyingExtensions.php | 4 +- 26 files changed, 310 insertions(+), 313 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index c209e8ffb06..6baecd8e10c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -26,4 +26,24 @@ Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Th See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. -TODO +### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters + +[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): + +* `Expr $expr` +* `Type $type` +* `TypeSpecifierContext $context` +* `Scope $scope` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable). + +[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts: + +* `array $sureTypes` +* `array $sureNotTypes` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). + +### Changed `TypeSpecifier::specifyTypesInCondition()` + +This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index ca290a17c4c..fd9ddda81db 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -9,22 +9,78 @@ final class SpecifiedTypes { + private bool $overwrite = false; + + /** @var array */ + private array $newConditionalExpressionHolders = []; + + private ?Expr $rootExpr = null; + /** * @api * @param array $sureTypes * @param array $sureNotTypes - * @param array $newConditionalExpressionHolders */ public function __construct( private array $sureTypes = [], private array $sureNotTypes = [], - private bool $overwrite = false, - private array $newConditionalExpressionHolders = [], - private ?Expr $rootExpr = null, ) { } + /** + * Normally, $sureTypes in truthy context are used to intersect with the pre-existing type. + * And $sureNotTypes are used to remove type from the pre-existing type. + * + * Example: By default, non-empty-string intersected with '' (ConstantStringType) will lead to NeverType. + * Because it's not possible to narrow non-empty-string to an empty string. + * + * In rare cases, a type-specifying extension might want to overwrite the pre-existing types + * without taking the pre-existing types into consideration. + * + * In that case it should also call setAlwaysOverwriteTypes() on + * the returned object. + * + * ! Only do this if you're certain. Otherwise, this is a source of common bugs. ! + * + * @api + */ + public function setAlwaysOverwriteTypes(): self + { + $self = new self($this->sureTypes, $this->sureNotTypes); + $self->overwrite = true; + $self->newConditionalExpressionHolders = $this->newConditionalExpressionHolders; + $self->rootExpr = $this->rootExpr; + + return $self; + } + + /** + * @api + */ + public function setRootExpr(?Expr $rootExpr): self + { + $self = new self($this->sureTypes, $this->sureNotTypes); + $self->overwrite = $this->overwrite; + $self->newConditionalExpressionHolders = $this->newConditionalExpressionHolders; + $self->rootExpr = $rootExpr; + + return $self; + } + + /** + * @param array $newConditionalExpressionHolders + */ + public function setNewConditionalExpressionHolders(array $newConditionalExpressionHolders): self + { + $self = new self($this->sureTypes, $this->sureNotTypes); + $self->overwrite = $this->overwrite; + $self->newConditionalExpressionHolders = $newConditionalExpressionHolders; + $self->rootExpr = $this->rootExpr; + + return $self; + } + /** * @api * @return array @@ -90,7 +146,12 @@ public function intersectWith(SpecifiedTypes $other): self ]; } - return new self($sureTypeUnion, $sureNotTypeUnion, $this->overwrite && $other->overwrite, [], $rootExpr); + $result = new self($sureTypeUnion, $sureNotTypeUnion); + if ($this->overwrite && $other->overwrite) { + $result = $result->setAlwaysOverwriteTypes(); + } + + return $result->setRootExpr($rootExpr); } /** @api */ @@ -122,7 +183,12 @@ public function unionWith(SpecifiedTypes $other): self ]; } - return new self($sureTypeUnion, $sureNotTypeUnion, $this->overwrite || $other->overwrite, [], $rootExpr); + $result = new self($sureTypeUnion, $sureNotTypeUnion); + if ($this->overwrite || $other->overwrite) { + $result = $result->setAlwaysOverwriteTypes(); + } + + return $result->setRootExpr($rootExpr); } public function normalize(Scope $scope): self @@ -138,7 +204,12 @@ public function normalize(Scope $scope): self $sureTypes[$exprString][1] = TypeCombinator::remove($sureTypes[$exprString][1], $sureNotType); } - return new self($sureTypes, [], $this->overwrite, $this->newConditionalExpressionHolders, $this->rootExpr); + $result = new self($sureTypes, []); + if ($this->overwrite) { + $result = $result->setAlwaysOverwriteTypes(); + } + + return $result->setRootExpr($this->rootExpr); } private function mergeRootExpr(?Expr $rootExprA, ?Expr $rootExprB): ?Expr diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f02e5f51d2c..5c349ce624a 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -120,13 +120,10 @@ public function specifyTypesInCondition( Scope $scope, Expr $expr, TypeSpecifierContext $context, - ?Expr $rootExpr = null, ): SpecifiedTypes { - $rootExpr ??= $expr; - if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } if ($expr instanceof Instanceof_) { @@ -150,7 +147,7 @@ public function specifyTypesInCondition( } else { $type = new ObjectType($className); } - return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); + return $this->create($exprNode, $type, $context, $scope)->setRootExpr($expr); } $classType = $scope->getType($expr->class); @@ -176,64 +173,58 @@ public function specifyTypesInCondition( $type, new ObjectWithoutClassType(), ); - return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); + return $this->create($exprNode, $type, $context, $scope)->setRootExpr($expr); } elseif ($context->false()) { $exprType = $scope->getType($expr->expr); if (!$type->isSuperTypeOf($exprType)->yes()) { - return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); + return $this->create($exprNode, $type, $context, $scope)->setRootExpr($expr); } } } if ($context->true()) { - return $this->create($exprNode, new ObjectWithoutClassType(), $context, false, $scope, $rootExpr); + return $this->create($exprNode, new ObjectWithoutClassType(), $context, $scope)->setRootExpr($exprNode); } } elseif ($expr instanceof Node\Expr\BinaryOp\Identical) { - return $this->resolveIdentical($expr, $scope, $context, $rootExpr); + return $this->resolveIdentical($expr, $scope, $context); } elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Identical($expr->left, $expr->right)), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Expr\Cast\Bool_) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BinaryOp\Equal($expr->expr, new ConstFetch(new Name\FullyQualified('true'))), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Expr\Cast\String_) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\String_('')), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Expr\Cast\Int_) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\LNumber(0)), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Expr\Cast\Double) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BinaryOp\NotEqual($expr->expr, new Node\Scalar\DNumber(0.0)), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Node\Expr\BinaryOp\Equal) { - return $this->resolveEqual($expr, $scope, $context, $rootExpr); + return $this->resolveEqual($expr, $scope, $context); } elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) { return $this->specifyTypesInCondition( $scope, new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Equal($expr->left, $expr->right)), $context, - $rootExpr, - ); + )->setRootExpr($expr); } elseif ($expr instanceof Node\Expr\BinaryOp\Smaller || $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual) { @@ -256,14 +247,13 @@ public function specifyTypesInCondition( $scope, new Node\Expr\BooleanNot($inverseOperator), $context, - $rootExpr, - ); + )->setRootExpr($expr); } $orEqual = $expr instanceof Node\Expr\BinaryOp\SmallerOrEqual; $offset = $orEqual ? 0 : 1; $leftType = $scope->getType($expr->left); - $result = new SpecifiedTypes([], [], false, [], $rootExpr); + $result = (new SpecifiedTypes([], []))->setRootExpr($expr); if ( !$context->null() @@ -287,7 +277,7 @@ public function specifyTypesInCondition( $sizeType = $leftType; } - $narrowed = $this->narrowUnionByArraySize($expr->right, $argType, $sizeType, $context, $scope, $rootExpr); + $narrowed = $this->narrowUnionByArraySize($expr->right, $argType, $sizeType, $context, $scope, $expr); if ($narrowed !== null) { return $narrowed; } @@ -318,7 +308,7 @@ public function specifyTypesInCondition( if (count($countables) > 0) { $countableType = TypeCombinator::union(...$countables); - return $this->create($expr->right->getArgs()[0]->value, $countableType, $context, false, $scope, $rootExpr); + return $this->create($expr->right->getArgs()[0]->value, $countableType, $context, $scope)->setRootExpr($expr); } } @@ -329,7 +319,7 @@ public function specifyTypesInCondition( } $result = $result->unionWith( - $this->create($expr->right->getArgs()[0]->value, $newType, $context, false, $scope, $rootExpr), + $this->create($expr->right->getArgs()[0]->value, $newType, $context, $scope)->setRootExpr($expr), ); } } @@ -355,7 +345,7 @@ public function specifyTypesInCondition( $accessory = new AccessoryNonFalsyStringType(); } - $result = $result->unionWith($this->create($expr->right->getArgs()[0]->value, $accessory, $context, false, $scope, $rootExpr)); + $result = $result->unionWith($this->create($expr->right->getArgs()[0]->value, $accessory, $context, $scope)->setRootExpr($expr)); } } } @@ -363,21 +353,21 @@ public function specifyTypesInCondition( if ($leftType instanceof ConstantIntegerType) { if ($expr->right instanceof Expr\PostInc) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->right->var, IntegerRangeType::fromInterval($leftType->getValue(), null, $offset + 1), $context, )); } elseif ($expr->right instanceof Expr\PostDec) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->right->var, IntegerRangeType::fromInterval($leftType->getValue(), null, $offset - 1), $context, )); } elseif ($expr->right instanceof Expr\PreInc || $expr->right instanceof Expr\PreDec) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->right->var, IntegerRangeType::fromInterval($leftType->getValue(), null, $offset), $context, @@ -389,21 +379,21 @@ public function specifyTypesInCondition( if ($rightType instanceof ConstantIntegerType) { if ($expr->left instanceof Expr\PostInc) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->left->var, IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset + 1), $context, )); } elseif ($expr->left instanceof Expr\PostDec) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->left->var, IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset - 1), $context, )); } elseif ($expr->left instanceof Expr\PreInc || $expr->left instanceof Expr\PreDec) { $result = $result->unionWith($this->createRangeTypes( - $rootExpr, + $expr, $expr->left->var, IntegerRangeType::fromInterval(null, $rightType->getValue(), -$offset), $context, @@ -418,10 +408,8 @@ public function specifyTypesInCondition( $expr->left, $orEqual ? $rightType->getSmallerOrEqualType() : $rightType->getSmallerType(), TypeSpecifierContext::createTruthy(), - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } if (!$expr->right instanceof Node\Scalar) { @@ -430,10 +418,8 @@ public function specifyTypesInCondition( $expr->right, $orEqual ? $leftType->getGreaterOrEqualType() : $leftType->getGreaterType(), TypeSpecifierContext::createTruthy(), - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } } elseif ($context->false()) { @@ -443,10 +429,8 @@ public function specifyTypesInCondition( $expr->left, $orEqual ? $rightType->getGreaterType() : $rightType->getGreaterOrEqualType(), TypeSpecifierContext::createTruthy(), - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } if (!$expr->right instanceof Node\Scalar) { @@ -455,10 +439,8 @@ public function specifyTypesInCondition( $expr->right, $orEqual ? $leftType->getSmallerType() : $leftType->getSmallerOrEqualType(), TypeSpecifierContext::createTruthy(), - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } } @@ -466,10 +448,10 @@ public function specifyTypesInCondition( return $result; } elseif ($expr instanceof Node\Expr\BinaryOp\Greater) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Smaller($expr->right, $expr->left), $context, $rootExpr); + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Smaller($expr->right, $expr->left), $context)->setRootExpr($expr); } elseif ($expr instanceof Node\Expr\BinaryOp\GreaterOrEqual) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left), $context, $rootExpr); + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\SmallerOrEqual($expr->right, $expr->left), $context)->setRootExpr($expr); } elseif ($expr instanceof FuncCall && $expr->name instanceof Name) { if ($this->reflectionProvider->hasFunction($expr->name, $scope)) { @@ -510,7 +492,7 @@ public function specifyTypesInCondition( } } - return $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { $methodCalledOnType = $scope->getType($expr->var); $methodReflection = $scope->getMethodReflection($methodCalledOnType, $expr->name->name); @@ -558,7 +540,7 @@ public function specifyTypesInCondition( } } - return $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) { if ($expr->class instanceof Name) { $calleeType = $scope->resolveTypeByName($expr->class); @@ -611,28 +593,25 @@ public function specifyTypesInCondition( } } - return $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { if (!$scope instanceof MutatingScope) { throw new ShouldNotHappenException(); } - $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context, $rootExpr); + $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); $rightScope = $scope->filterByTruthyValue($expr->left); - $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context, $rootExpr); + $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); $types = $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($rightScope)); if ($context->false()) { - return new SpecifiedTypes( + return (new SpecifiedTypes( $types->getSureTypes(), $types->getSureNotTypes(), - false, - array_merge( - $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes), - $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes), - ), - $rootExpr, - ); + ))->setNewConditionalExpressionHolders(array_merge( + $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes), + $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes), + ))->setRootExpr($expr); } return $types; @@ -640,37 +619,34 @@ public function specifyTypesInCondition( if (!$scope instanceof MutatingScope) { throw new ShouldNotHappenException(); } - $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context, $rootExpr); + $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context)->setRootExpr($expr); $rightScope = $scope->filterByFalseyValue($expr->left); - $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context, $rootExpr); + $rightTypes = $this->specifyTypesInCondition($rightScope, $expr->right, $context)->setRootExpr($expr); $types = $context->true() ? $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($rightScope)) : $leftTypes->unionWith($rightTypes); if ($context->true()) { - return new SpecifiedTypes( + return (new SpecifiedTypes( $types->getSureTypes(), $types->getSureNotTypes(), - false, - array_merge( - $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes), - $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes), - ), - $rootExpr, - ); + ))->setNewConditionalExpressionHolders(array_merge( + $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes), + $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes), + $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes), + ))->setRootExpr($expr); } return $types; } elseif ($expr instanceof Node\Expr\BooleanNot && !$context->null()) { - return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate(), $rootExpr); + return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate())->setRootExpr($expr); } elseif ($expr instanceof Node\Expr\Assign) { if (!$scope instanceof MutatingScope) { throw new ShouldNotHappenException(); } if ($context->null()) { - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context, $rootExpr); + return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr); } - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context, $rootExpr); + return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr); } elseif ( $expr instanceof Expr\Isset_ && count($expr->vars) > 0 @@ -698,7 +674,7 @@ public function specifyTypesInCondition( throw new ShouldNotHappenException(); } - return $this->specifyTypesInCondition($scope, $andChain, $context, $rootExpr); + return $this->specifyTypesInCondition($scope, $andChain, $context)->setRootExpr($expr); } $issetExpr = $expr->vars[0]; @@ -720,10 +696,8 @@ public function specifyTypesInCondition( $issetExpr, new NullType(), $context->negate(), - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); if ($issetExpr instanceof Expr\Variable && is_string($issetExpr->name)) { if ($isset === true) { @@ -736,10 +710,8 @@ public function specifyTypesInCondition( new IssetExpr($issetExpr), new NullType(), $context, - false, $scope, - $rootExpr, - )); + ))->setRootExpr($expr); } if ($isNullable) { @@ -748,10 +720,8 @@ public function specifyTypesInCondition( new IssetExpr($issetExpr), new NullType(), $context->negate(), - false, $scope, - $rootExpr, - )); + ))->setRootExpr($expr); } // variable cannot exist in !isset() @@ -759,10 +729,8 @@ public function specifyTypesInCondition( new IssetExpr($issetExpr), new NullType(), $context, - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); } if ($isNullable && $isset === true) { @@ -796,7 +764,7 @@ public function specifyTypesInCondition( if ($var instanceof Expr\Variable && is_string($var->name)) { if ($scope->hasVariableType($var->name)->no()) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } } @@ -813,10 +781,8 @@ public function specifyTypesInCondition( $var->var, new HasOffsetType($dimType), $context, - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } } @@ -829,7 +795,7 @@ public function specifyTypesInCondition( $this->create($var->var, new IntersectionType([ new ObjectWithoutClassType(), new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr), + ]), TypeSpecifierContext::createTruthy(), $scope)->setRootExpr($expr), ); } elseif ( $var instanceof StaticPropertyFetch @@ -840,12 +806,12 @@ public function specifyTypesInCondition( $this->create($var->class, new IntersectionType([ new ObjectWithoutClassType(), new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr), + ]), TypeSpecifierContext::createTruthy(), $scope)->setRootExpr($expr), ); } $types = $types->unionWith( - $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr), + $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($expr), ); } @@ -869,10 +835,8 @@ public function specifyTypesInCondition( $expr->left, new NullType(), $context->negate(), - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); } if ((new ConstantBooleanType(false))->isSuperTypeOf($scope->getType($expr->right)->toBoolean())->yes()) { @@ -880,10 +844,8 @@ public function specifyTypesInCondition( $expr->left, new NullType(), TypeSpecifierContext::createFalse(), - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); } } elseif ( @@ -901,9 +863,9 @@ public function specifyTypesInCondition( return $this->specifyTypesInCondition($scope, new BooleanOr( new Expr\BooleanNot(new Expr\Isset_([$expr->expr])), new Expr\BooleanNot($expr->expr), - ), $context, $rootExpr); + ), $context)->setRootExpr($expr); } elseif ($expr instanceof Expr\ErrorSuppress) { - return $this->specifyTypesInCondition($scope, $expr->expr, $context, $rootExpr); + return $this->specifyTypesInCondition($scope, $expr->expr, $context)->setRootExpr($expr); } elseif ( $expr instanceof Expr\Ternary && !$context->null() @@ -914,7 +876,7 @@ public function specifyTypesInCondition( $conditionExpr = new BooleanAnd($conditionExpr, $expr->if); } - return $this->specifyTypesInCondition($scope, $conditionExpr, $context, $rootExpr); + return $this->specifyTypesInCondition($scope, $conditionExpr, $context)->setRootExpr($expr); } elseif ($expr instanceof Expr\NullsafePropertyFetch && !$context->null()) { $types = $this->specifyTypesInCondition( @@ -924,10 +886,9 @@ public function specifyTypesInCondition( new PropertyFetch($expr->var, $expr->name), ), $context, - $rootExpr, - ); + )->setRootExpr($expr); - $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); return $context->true() ? $types->unionWith($nullSafeTypes) : $types->normalize($scope)->intersectWith($nullSafeTypes->normalize($scope)); } elseif ($expr instanceof Expr\NullsafeMethodCall && !$context->null()) { $types = $this->specifyTypesInCondition( @@ -937,10 +898,9 @@ public function specifyTypesInCondition( new MethodCall($expr->var, $expr->name, $expr->args), ), $context, - $rootExpr, - ); + )->setRootExpr($expr); - $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + $nullSafeTypes = $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); return $context->true() ? $types->unionWith($nullSafeTypes) : $types->normalize($scope)->intersectWith($nullSafeTypes->normalize($scope)); } elseif ( $expr instanceof Expr\New_ @@ -971,10 +931,10 @@ public function specifyTypesInCondition( } } } elseif (!$context->null()) { - return $this->handleDefaultTruthyOrFalseyContext($context, $rootExpr, $expr, $scope); + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argType, ?Type $sizeType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr): ?SpecifiedTypes @@ -1017,7 +977,7 @@ private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argT $result[] = $innerType; } - return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$result), $context, false, $scope, $rootExpr); + return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$result), $context, $scope)->setRootExpr($rootExpr); } return null; @@ -1093,11 +1053,11 @@ private function specifyTypesForConstantBinaryExpression( Type $constantType, TypeSpecifierContext $context, Scope $scope, - ?Expr $rootExpr, + Expr $rootExpr, ): ?SpecifiedTypes { if (!$context->null() && $constantType->isFalse()->yes()) { - $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + $types = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; } @@ -1106,12 +1066,11 @@ private function specifyTypesForConstantBinaryExpression( $scope, $exprNode, $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate(), - $rootExpr, - )); + )->setRootExpr($rootExpr)); } if (!$context->null() && $constantType->isTrue()->yes()) { - $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + $types = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr); if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { return $types; } @@ -1120,8 +1079,7 @@ private function specifyTypesForConstantBinaryExpression( $scope, $exprNode, $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate(), - $rootExpr, - )); + )->setRootExpr($rootExpr)); } if ( @@ -1133,7 +1091,7 @@ private function specifyTypesForConstantBinaryExpression( && $constantType instanceof ConstantIntegerType ) { if ($constantType->getValue() < 0) { - return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, $scope)->setRootExpr($rootExpr); } if ($context->truthy() || $constantType->getValue() === 0) { @@ -1143,13 +1101,13 @@ private function specifyTypesForConstantBinaryExpression( } $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType->isString()->yes()) { - $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + $funcTypes = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr); $accessory = new AccessoryNonEmptyStringType(); if ($constantType->getValue() >= 2) { $accessory = new AccessoryNonFalsyStringType(); } - $valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr); + $valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, $scope)->setRootExpr($rootExpr); return $funcTypes->unionWith($valueTypes); } @@ -1165,7 +1123,7 @@ private function specifyTypesForConstantStringBinaryExpression( Type $constantType, TypeSpecifierContext $context, Scope $scope, - ?Expr $rootExpr, + Expr $rootExpr, ): ?SpecifiedTypes { $scalarValues = $constantType->getConstantScalarValues(); @@ -1207,8 +1165,8 @@ private function specifyTypesForConstantStringBinaryExpression( } if ($type !== null) { - $callType = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - $argType = $this->create($exprNode->getArgs()[0]->value, $type, $context, false, $scope, $rootExpr); + $callType = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr); + $argType = $this->create($exprNode->getArgs()[0]->value, $type, $context, $scope)->setRootExpr($rootExpr); return $callType->unionWith($argType); } } @@ -1229,9 +1187,8 @@ private function specifyTypesForConstantStringBinaryExpression( $exprNode->getArgs()[0]->value, $classStringType, $context, - false, $scope, - ); + )->setRootExpr($rootExpr); } if ($argType->isObject()->yes()) { @@ -1239,37 +1196,35 @@ private function specifyTypesForConstantStringBinaryExpression( $exprNode->getArgs()[0]->value, $objectType, $context, - false, $scope, - ); + )->setRootExpr($rootExpr); } return $this->create( $exprNode->getArgs()[0]->value, TypeCombinator::union($objectType, $classStringType), $context, - false, $scope, - ); + )->setRootExpr($rootExpr); } return null; } - private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, ?Expr $rootExpr, Expr $expr, Scope $scope): SpecifiedTypes + private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, Expr $expr, Scope $scope): SpecifiedTypes { if ($context->null()) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } if (!$context->truthy()) { $type = StaticTypeFactory::truthy(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope, $rootExpr); + return $this->create($expr, $type, TypeSpecifierContext::createFalse(), $scope)->setRootExpr($expr); } elseif (!$context->falsey()) { $type = StaticTypeFactory::falsey(); - return $this->create($expr, $type, TypeSpecifierContext::createFalse(), false, $scope, $rootExpr); + return $this->create($expr, $type, TypeSpecifierContext::createFalse(), $scope)->setRootExpr($expr); } - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } private function specifyTypesFromConditionalReturnType( @@ -1354,7 +1309,6 @@ public function getConditionalSpecifiedTypes( $argsMap[$parameterName], $targetType, $context, - false, $scope, ); @@ -1450,10 +1404,8 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai $assertExpr, $assertedType, $assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(), - false, $scope, - $containsUnresolvedTemplate || $assert->isEquality() ? $call : null, - ); + )->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : null); $types = $types !== null ? $types->unionWith($newTypes) : $newTypes; if (!$context->null() || !$assertedType instanceof ConstantBooleanType) { @@ -1650,13 +1602,11 @@ public function create( Expr $expr, Type $type, TypeSpecifierContext $context, - bool $overwrite = false, - ?Scope $scope = null, - ?Expr $rootExpr = null, + Scope $scope, ): SpecifiedTypes { if ($expr instanceof Instanceof_ || $expr instanceof Expr\List_) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } $specifiedExprs = []; @@ -1682,7 +1632,7 @@ public function create( $types = null; foreach ($specifiedExprs as $specifiedExpr) { - $newTypes = $this->createForExpr($specifiedExpr, $type, $context, $overwrite, $scope, $rootExpr); + $newTypes = $this->createForExpr($specifiedExpr, $type, $context, $scope); if ($types === null) { $types = $newTypes; @@ -1698,17 +1648,13 @@ private function createForExpr( Expr $expr, Type $type, TypeSpecifierContext $context, - bool $overwrite = false, - ?Scope $scope = null, - ?Expr $rootExpr = null, + Scope $scope, ): SpecifiedTypes { - if ($scope !== null) { - if ($context->true()) { - $containsNull = !$type->isNull()->no() && !$scope->getType($expr)->isNull()->no(); - } elseif ($context->false()) { - $containsNull = !TypeCombinator::containsNull($type) && !$scope->getType($expr)->isNull()->no(); - } + if ($context->true()) { + $containsNull = !$type->isNull()->no() && !$scope->getType($expr)->isNull()->no(); + } elseif ($context->false()) { + $containsNull = !TypeCombinator::containsNull($type) && !$scope->getType($expr)->isNull()->no(); } $originalExpr = $expr; @@ -1717,8 +1663,7 @@ private function createForExpr( } if ( - $scope !== null - && !$context->null() + !$context->null() && $expr instanceof Expr\BinaryOp\Coalesce ) { $rightIsSuperType = $type->isSuperTypeOf($scope->getType($expr->right)); @@ -1734,24 +1679,23 @@ private function createForExpr( $has = $this->reflectionProvider->hasFunction($expr->name, $scope); if (!$has) { // backwards compatibility with previous behaviour - return new SpecifiedTypes([], [], false, [], $rootExpr); + return new SpecifiedTypes([], []); } $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); $hasSideEffects = $functionReflection->hasSideEffects(); if ($hasSideEffects->yes()) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return new SpecifiedTypes([], []); } if (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no()) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return new SpecifiedTypes([], []); } } if ( $expr instanceof MethodCall && $expr->name instanceof Node\Identifier - && $scope !== null ) { $methodName = $expr->name->toString(); $calledOnType = $scope->getType($expr->var); @@ -1762,17 +1706,16 @@ private function createForExpr( || (!$this->rememberPossiblyImpureFunctionValues && !$methodReflection->hasSideEffects()->no()) ) { if (isset($containsNull) && !$containsNull) { - return $this->createNullsafeTypes($rootExpr, $originalExpr, $scope, $context, $overwrite, $type); + return $this->createNullsafeTypes($originalExpr, $scope, $context, $type); } - return new SpecifiedTypes([], [], false, [], $rootExpr); + return new SpecifiedTypes([], []); } } if ( $expr instanceof StaticCall && $expr->name instanceof Node\Identifier - && $scope !== null ) { $methodName = $expr->name->toString(); if ($expr->class instanceof Name) { @@ -1788,10 +1731,10 @@ private function createForExpr( || (!$this->rememberPossiblyImpureFunctionValues && !$methodReflection->hasSideEffects()->no()) ) { if (isset($containsNull) && !$containsNull) { - return $this->createNullsafeTypes($rootExpr, $originalExpr, $scope, $context, $overwrite, $type); + return $this->createNullsafeTypes($originalExpr, $scope, $context, $type); } - return new SpecifiedTypes([], [], false, [], $rootExpr); + return new SpecifiedTypes([], []); } } @@ -1811,61 +1754,61 @@ private function createForExpr( } } - $types = new SpecifiedTypes($sureTypes, $sureNotTypes, $overwrite, [], $rootExpr); - if ($scope !== null && isset($containsNull) && !$containsNull) { - return $this->createNullsafeTypes($rootExpr, $originalExpr, $scope, $context, $overwrite, $type)->unionWith($types); + $types = new SpecifiedTypes($sureTypes, $sureNotTypes); + if (isset($containsNull) && !$containsNull) { + return $this->createNullsafeTypes($originalExpr, $scope, $context, $type)->unionWith($types); } return $types; } - private function createNullsafeTypes(?Expr $rootExpr, Expr $expr, Scope $scope, TypeSpecifierContext $context, bool $overwrite, ?Type $type): SpecifiedTypes + private function createNullsafeTypes(Expr $expr, Scope $scope, TypeSpecifierContext $context, ?Type $type): SpecifiedTypes { if ($expr instanceof Expr\NullsafePropertyFetch) { if ($type !== null) { - $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), $type, $context, false, $scope, $rootExpr); + $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), $type, $context, $scope); } else { - $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr); + $propertyFetchTypes = $this->create(new PropertyFetch($expr->var, $expr->name), new NullType(), TypeSpecifierContext::createFalse(), $scope); } return $propertyFetchTypes->unionWith( - $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), $overwrite, $scope, $rootExpr), + $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), $scope), ); } if ($expr instanceof Expr\NullsafeMethodCall) { if ($type !== null) { - $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), $type, $context, $overwrite, $scope, $rootExpr); + $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), $type, $context, $scope); } else { - $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), new NullType(), TypeSpecifierContext::createFalse(), $overwrite, $scope, $rootExpr); + $methodCallTypes = $this->create(new MethodCall($expr->var, $expr->name, $expr->args), new NullType(), TypeSpecifierContext::createFalse(), $scope); } return $methodCallTypes->unionWith( - $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), $overwrite, $scope, $rootExpr), + $this->create($expr->var, new NullType(), TypeSpecifierContext::createFalse(), $scope), ); } if ($expr instanceof Expr\PropertyFetch) { - return $this->createNullsafeTypes($rootExpr, $expr->var, $scope, $context, $overwrite, null); + return $this->createNullsafeTypes($expr->var, $scope, $context, null); } if ($expr instanceof Expr\MethodCall) { - return $this->createNullsafeTypes($rootExpr, $expr->var, $scope, $context, $overwrite, null); + return $this->createNullsafeTypes($expr->var, $scope, $context, null); } if ($expr instanceof Expr\ArrayDimFetch) { - return $this->createNullsafeTypes($rootExpr, $expr->var, $scope, $context, $overwrite, null); + return $this->createNullsafeTypes($expr->var, $scope, $context, null); } if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { - return $this->createNullsafeTypes($rootExpr, $expr->class, $scope, $context, $overwrite, null); + return $this->createNullsafeTypes($expr->class, $scope, $context, null); } if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { - return $this->createNullsafeTypes($rootExpr, $expr->class, $scope, $context, $overwrite, null); + return $this->createNullsafeTypes($expr->class, $scope, $context, null); } - return new SpecifiedTypes([], [], $overwrite, [], $rootExpr); + return new SpecifiedTypes([], []); } private function createRangeTypes(?Expr $rootExpr, Expr $expr, Type $type, TypeSpecifierContext $context): SpecifiedTypes @@ -1882,7 +1825,7 @@ private function createRangeTypes(?Expr $rootExpr, Expr $expr, Type $type, TypeS } } - return new SpecifiedTypes([], $sureNotTypes, false, [], $rootExpr); + return (new SpecifiedTypes([], $sureNotTypes))->setRootExpr($rootExpr); } /** @@ -1944,7 +1887,7 @@ private function getTypeSpecifyingExtensionsForType(array $extensions, string $c return array_merge(...$extensionsForClass); } - public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecifierContext $context, ?Expr $rootExpr): SpecifiedTypes + public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); if ($expressions !== null) { @@ -1955,8 +1898,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif $scope, $exprNode, $context->true() ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createFalsey()->negate(), - $rootExpr, - ); + )->setRootExpr($expr); } if (!$context->null() && $constantType->getValue() === true) { @@ -1964,8 +1906,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif $scope, $exprNode, $context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate(), - $rootExpr, - ); + )->setRootExpr($expr); } if ( @@ -1975,7 +1916,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif && isset($exprNode->getArgs()[0]) && $constantType->isString()->yes() ) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context, $rootExpr); + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context)->setRootExpr($expr); } if ( @@ -1985,7 +1926,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif && $exprNode->name->toLowerString() === 'preg_match' && (new ConstantIntegerType(1))->isSuperTypeOf($constantType)->yes() ) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context, $rootExpr); + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context)->setRootExpr($expr); } } @@ -2001,8 +1942,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif $expr->right, ), $context, - $rootExpr, - ); + )->setRootExpr($expr); } $rightBooleanType = $rightType->toBoolean(); @@ -2014,8 +1954,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif new ConstFetch(new Name($rightBooleanType->getValue() ? 'true' : 'false')), ), $context, - $rootExpr, - ); + )->setRootExpr($expr); } if ( @@ -2023,7 +1962,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif && $rightType->isArray()->yes() && $leftType->isConstantArray()->yes() && $leftType->isIterableAtLeastOnce()->no() ) { - return $this->create($expr->right, new NonEmptyArrayType(), $context->negate(), false, $scope, $rootExpr); + return $this->create($expr->right, new NonEmptyArrayType(), $context->negate(), $scope)->setRootExpr($expr); } if ( @@ -2031,7 +1970,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif && $leftType->isArray()->yes() && $rightType->isConstantArray()->yes() && $rightType->isIterableAtLeastOnce()->no() ) { - return $this->create($expr->left, new NonEmptyArrayType(), $context->negate(), false, $scope, $rootExpr); + return $this->create($expr->left, new NonEmptyArrayType(), $context->negate(), $scope)->setRootExpr($expr); } if ( @@ -2040,26 +1979,26 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif || ($leftType->isFloat()->yes() && $rightType->isFloat()->yes()) || ($leftType->isEnum()->yes() && $rightType->isEnum()->yes()) ) { - return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context, $rootExpr); + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context)->setRootExpr($expr); } $leftExprString = $this->exprPrinter->printExpr($expr->left); $rightExprString = $this->exprPrinter->printExpr($expr->right); if ($leftExprString === $rightExprString) { if (!$expr->left instanceof Expr\Variable || !$expr->right instanceof Expr\Variable) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } } - $leftTypes = $this->create($expr->left, $leftType, $context, false, $scope, $rootExpr); - $rightTypes = $this->create($expr->right, $rightType, $context, false, $scope, $rootExpr); + $leftTypes = $this->create($expr->left, $leftType, $context, $scope)->setRootExpr($expr); + $rightTypes = $this->create($expr->right, $rightType, $context, $scope)->setRootExpr($expr); return $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->normalize($scope)->intersectWith($rightTypes->normalize($scope)); } - public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context, ?Expr $rootExpr): SpecifiedTypes + public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $leftExpr = $expr->left; $rightExpr = $expr->right; @@ -2085,13 +2024,13 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty && $rightType->isInteger()->yes() ) { if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($rightType)->yes()) { - return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, $scope)->setRootExpr($expr); } $argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value); $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($rightType); if ($isZero->yes()) { - $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr); if ($context->truthy() && !$argType->isArray()->yes()) { $newArgType = new UnionType([ @@ -2103,12 +2042,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } return $funcTypes->unionWith( - $this->create($unwrappedLeftExpr->getArgs()[0]->value, $newArgType, $context, false, $scope, $rootExpr), + $this->create($unwrappedLeftExpr->getArgs()[0]->value, $newArgType, $context, $scope)->setRootExpr($expr), ); } if ($argType instanceof UnionType) { - $narrowed = $this->narrowUnionByArraySize($unwrappedLeftExpr, $argType, $rightType, $context, $scope, $rootExpr); + $narrowed = $this->narrowUnionByArraySize($unwrappedLeftExpr, $argType, $rightType, $context, $scope, $expr); if ($narrowed !== null) { return $narrowed; } @@ -2120,18 +2059,18 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $argType->isConstantArray()->yes() && $rightType->isSuperTypeOf($argType->getArraySize())->no() ) { - return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, $scope)->setRootExpr($expr); } - $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr); $constArray = $this->turnListIntoConstantArray($unwrappedLeftExpr, $argType, $rightType, $scope); if ($constArray !== null) { return $funcTypes->unionWith( - $this->create($unwrappedLeftExpr->getArgs()[0]->value, $constArray, $context, false, $scope, $rootExpr), + $this->create($unwrappedLeftExpr->getArgs()[0]->value, $constArray, $context, $scope)->setRootExpr($expr), ); } elseif (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) { return $funcTypes->unionWith( - $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, false, $scope, $rootExpr), + $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope)->setRootExpr($expr), ); } @@ -2151,8 +2090,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $scope, $leftExpr, $context, - $rootExpr, - ); + )->setRootExpr($expr); } if ( @@ -2167,10 +2105,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $unwrappedLeftExpr->getArgs()[0]->value, $rightType->getClassStringObjectType(), $context, - false, $scope, - $rootExpr, - )->unionWith($this->create($leftExpr, $rightType, $context, false, $scope, $rootExpr)); + )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); } } @@ -2194,18 +2130,16 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $unwrappedLeftExpr->getArgs()[0]->value, TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), $context, - false, $scope, - ); + )->setRootExpr($expr); } return $this->create( $unwrappedLeftExpr->getArgs()[0]->value, TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), $context, - false, $scope, - ); + )->setRootExpr($expr); } } @@ -2213,9 +2147,9 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $types = null; foreach ($rightType->getFiniteTypes() as $finiteType) { if ($finiteType->isString()->yes()) { - $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); + $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $expr); } else { - $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); + $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $expr); } if ($specifiedType === null) { continue; @@ -2230,7 +2164,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($types !== null) { if ($leftExpr !== $unwrappedLeftExpr) { - $types = $types->unionWith($this->create($leftExpr, $rightType, $context, false, $scope, $rootExpr)); + $types = $types->unionWith($this->create($leftExpr, $rightType, $context, $scope)->setRootExpr($expr)); } return $types; } @@ -2246,11 +2180,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $unwrappedExprNode = $exprNode->getExpr(); } - $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedExprNode, $constantType, $context, $scope, $rootExpr); + $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedExprNode, $constantType, $context, $scope, $expr); if ($specifiedType !== null) { if ($exprNode !== $unwrappedExprNode) { $specifiedType = $specifiedType->unionWith( - $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr), + $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($expr), ); } return $specifiedType; @@ -2274,8 +2208,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty new Name($rightType->getValue()), ), $context, - $rootExpr, - )->unionWith($this->create($leftExpr, $rightType, $context, false, $scope, $rootExpr)); + )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); } $leftType = $scope->getType($leftExpr); @@ -2296,8 +2229,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty new Name($leftType->getValue()), ), $context, - $rootExpr, - )->unionWith($this->create($rightExpr, $leftType, $context, false, $scope, $rootExpr)); + )->unionWith($this->create($rightExpr, $leftType, $context, $scope)->setRootExpr($expr)); } if ($context->false()) { @@ -2305,16 +2237,16 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($identicalType instanceof ConstantBooleanType) { $never = new NeverType(); $contextForTypes = $identicalType->getValue() ? $context->negate() : $context; - $leftTypes = $this->create($leftExpr, $never, $contextForTypes, false, $scope, $rootExpr); - $rightTypes = $this->create($rightExpr, $never, $contextForTypes, false, $scope, $rootExpr); + $leftTypes = $this->create($leftExpr, $never, $contextForTypes, $scope)->setRootExpr($expr); + $rightTypes = $this->create($rightExpr, $never, $contextForTypes, $scope)->setRootExpr($expr); if ($leftExpr instanceof AlwaysRememberedExpr) { $leftTypes = $leftTypes->unionWith( - $this->create($unwrappedLeftExpr, $never, $contextForTypes, false, $scope, $rootExpr), + $this->create($unwrappedLeftExpr, $never, $contextForTypes, $scope)->setRootExpr($expr), ); } if ($rightExpr instanceof AlwaysRememberedExpr) { $rightTypes = $rightTypes->unionWith( - $this->create($unwrappedRightExpr, $never, $contextForTypes, false, $scope, $rootExpr), + $this->create($unwrappedRightExpr, $never, $contextForTypes, $scope)->setRootExpr($expr), ); } return $leftTypes->unionWith($rightTypes); @@ -2330,19 +2262,15 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $rightExpr, $leftType, $context, - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); if ($rightExpr instanceof AlwaysRememberedExpr) { $types = $types->unionWith($this->create( $unwrappedRightExpr, $leftType, $context, - false, $scope, - $rootExpr, - )); + ))->setRootExpr($expr); } } if ( @@ -2353,19 +2281,15 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $leftExpr, $rightType, $context, - false, $scope, - $rootExpr, - ); + )->setRootExpr($expr); if ($leftExpr instanceof AlwaysRememberedExpr) { $leftTypes = $leftTypes->unionWith($this->create( $unwrappedLeftExpr, $rightType, $context, - false, $scope, - $rootExpr, - )); + ))->setRootExpr($expr); } if ($types !== null) { $types = $types->unionWith($leftTypes); @@ -2382,30 +2306,30 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $rightExprString = $this->exprPrinter->printExpr($unwrappedRightExpr); if ($leftExprString === $rightExprString) { if (!$unwrappedLeftExpr instanceof Expr\Variable || !$unwrappedRightExpr instanceof Expr\Variable) { - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } } if ($context->true()) { - $leftTypes = $this->create($leftExpr, $rightType, $context, false, $scope, $rootExpr); - $rightTypes = $this->create($rightExpr, $leftType, $context, false, $scope, $rootExpr); + $leftTypes = $this->create($leftExpr, $rightType, $context, $scope)->setRootExpr($expr); + $rightTypes = $this->create($rightExpr, $leftType, $context, $scope)->setRootExpr($expr); if ($leftExpr instanceof AlwaysRememberedExpr) { $leftTypes = $leftTypes->unionWith( - $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr), + $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr), ); } if ($rightExpr instanceof AlwaysRememberedExpr) { $rightTypes = $rightTypes->unionWith( - $this->create($unwrappedRightExpr, $leftType, $context, false, $scope, $rootExpr), + $this->create($unwrappedRightExpr, $leftType, $context, $scope)->setRootExpr($expr), ); } return $leftTypes->unionWith($rightTypes); } elseif ($context->false()) { - return $this->create($leftExpr, $leftType, $context, false, $scope, $rootExpr)->normalize($scope) - ->intersectWith($this->create($rightExpr, $rightType, $context, false, $scope, $rootExpr)->normalize($scope)); + return $this->create($leftExpr, $leftType, $context, $scope)->setRootExpr($expr)->normalize($scope) + ->intersectWith($this->create($rightExpr, $rightType, $context, $scope)->setRootExpr($expr)->normalize($scope)); } - return new SpecifiedTypes([], [], false, [], $rootExpr); + return (new SpecifiedTypes([], []))->setRootExpr($expr); } } diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index d255aa8c152..35a3ecb8ea4 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -73,7 +73,6 @@ public function specifyTypes( $key, $arrayKeyType, $context, - false, $scope, ); @@ -86,10 +85,8 @@ public function specifyTypes( $arrayDimFetch, $arrayType->getIterableValueType(), $context, - false, $scope, - new Identical($arrayDimFetch, new ConstFetch(new Name('__PHPSTAN_FAUX_CONSTANT'))), - )); + ))->setRootExpr(new Identical($arrayDimFetch, new ConstFetch(new Name('__PHPSTAN_FAUX_CONSTANT')))); } return new SpecifiedTypes(); @@ -108,7 +105,6 @@ public function specifyTypes( $array, $type, $context, - false, $scope, ); } diff --git a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php index 4b974bbe1da..b382891275d 100644 --- a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php @@ -47,7 +47,6 @@ public function specifyTypes( $arrayArg, TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType()), $context, - false, $scope, ); } diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index 8cf3d88c192..e10e74a53ea 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -50,7 +50,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ]), new ConstantBooleanType(true), $context, - false, $scope, ); } @@ -64,7 +63,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[0]->value, $narrowedType, $context, - false, $scope, ); } diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index 03d938a7e46..b109b13f917 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -41,7 +41,7 @@ public function specifyTypes( return new SpecifiedTypes([], []); } - return $this->typeSpecifier->create($node->getArgs()[0]->value, new NonEmptyArrayType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index 7e705660448..837c3fb8900 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -59,7 +59,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } $unionType = TypeCombinator::union(...$types); - $specifiedTypes = $this->typeSpecifier->create($exprArg, $unionType, $context, false, $scope); + $specifiedTypes = $this->typeSpecifier->create($exprArg, $unionType, $context, $scope); if ($exprArg instanceof Cast\String_) { $castedType = new UnionType([ @@ -71,7 +71,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n new ConstantBooleanType(true), ]); $specifiedTypes = $specifiedTypes->unionWith( - $this->typeSpecifier->create($exprArg->expr, $castedType, $context, false, $scope), + $this->typeSpecifier->create($exprArg->expr, $castedType, $context, $scope), ); } diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 81a836f6202..9d4ac3d682a 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -56,9 +56,8 @@ public function specifyTypes( ), $scope->getType($node->getArgs()[1]->value), TypeSpecifierContext::createTruthy(), - true, $scope, - ); + )->setAlwaysOverwriteTypes(); } } diff --git a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php index 880a3c8c216..01c310459bc 100644 --- a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php @@ -63,7 +63,6 @@ public function specifyTypes( $expr, new MixedType(), $context, - false, $scope, ); } diff --git a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php index 51bc6c4b2dd..5d320104a72 100644 --- a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php @@ -42,7 +42,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ]), new ConstantBooleanType(true), $context, - false, $scope, ); } @@ -51,7 +50,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[0]->value, new CallableType(), $context, - false, $scope, ); } diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 1c4436ca464..a91974e4669 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -71,9 +71,9 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } if ($isStrictComparison) { - $itemTypes = $this->typeSpecifier->resolveIdentical(new Identical($needleExpr, $item->value), $scope, $context, null); + $itemTypes = $this->typeSpecifier->resolveIdentical(new Identical($needleExpr, $item->value), $scope, $context); } else { - $itemTypes = $this->typeSpecifier->resolveEqual(new Equal($needleExpr, $item->value), $scope, $context, null); + $itemTypes = $this->typeSpecifier->resolveEqual(new Equal($needleExpr, $item->value), $scope, $context); } if ($types === null) { @@ -99,7 +99,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[1]->value, TypeCombinator::intersect($arrayType, new NonEmptyArrayType()), $context, - false, $scope, ); } @@ -122,7 +121,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $needleExpr, $arrayValueType, $context, - false, $scope, ); if ($needleExpr instanceof AlwaysRememberedExpr) { @@ -130,7 +128,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $needleExpr->getExpr(), $arrayValueType, $context, - false, $scope, )); } @@ -156,7 +153,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[1]->value, new ArrayType(new MixedType(), $arrayValueType), TypeSpecifierContext::createTrue(), - false, $scope, )); } @@ -166,7 +162,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[1]->value, TypeCombinator::intersect($arrayType, new NonEmptyArrayType()), $context, - false, $scope, )); } diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index c4000b9aff0..fe6048dbb90 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -51,7 +51,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[0]->value, $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true), $context, - false, $scope, ); } diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index 20ca925c1c6..5f6c0d710e5 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -39,7 +39,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->getArgs()[0]->value, new ArrayType(new MixedType($this->explicitMixed), new MixedType($this->explicitMixed)), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new ArrayType(new MixedType($this->explicitMixed), new MixedType($this->explicitMixed)), $context, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index a571338e184..42c5fe85056 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -58,7 +58,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return $this->methodExistsExtension->specifyTypes($functionReflection, $functionCall, $scope, $context); } - return $this->typeSpecifier->create($value, new CallableType(), $context, false, $scope); + return $this->typeSpecifier->create($value, new CallableType(), $context, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php index c523fde2435..a8404ef99f7 100644 --- a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php @@ -36,7 +36,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->getArgs()[0]->value, new IterableType(new MixedType(), new MixedType()), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new IterableType(new MixedType(), new MixedType()), $context, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 2d52ee99e1b..5bf3d9df9b4 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -52,7 +52,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $node->getArgs()[0]->value, $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false), $context, - false, $scope, ); } diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index bdff31b8421..bc00486cbfe 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -61,7 +61,6 @@ public function specifyTypes( new HasMethodType($methodNameType->getValue()), ]), $context, - false, $scope, ); } @@ -79,7 +78,6 @@ public function specifyTypes( new ClassStringType(), ]), $context, - false, $scope, ); } diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 2c7cad49bee..09606087f1a 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -68,14 +68,17 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $context = $context->negate(); } - return $this->typeSpecifier->create( + $types = $this->typeSpecifier->create( $matchesArg->value, $matchedType, $context, - $overwrite, $scope, - $node, - ); + )->setRootExpr($node); + if ($overwrite) { + $types = $types->setAlwaysOverwriteTypes(); + } + + return $types; } } diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 70d08b9098f..38592e632e7 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -82,7 +82,6 @@ public function specifyTypes( new HasPropertyType($propertyNameType->getValue()), ]), $context, - false, $scope, ); } diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index fb3e577d382..df49f7cb169 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -50,7 +50,6 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod new ObjectType($valueType->getValue()), ]), $context, - false, $scope, ); } diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index 934aece46e1..a9134026eae 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -78,9 +78,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $value, TypeCombinator::union(...$types), TypeSpecifierContext::createTruthy(), - true, $scope, - ); + )->setAlwaysOverwriteTypes(); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/StrContainingTypeSpecifyingExtension.php b/src/Type/Php/StrContainingTypeSpecifyingExtension.php index 8cf678ae56d..af5f0e55b7f 100644 --- a/src/Type/Php/StrContainingTypeSpecifyingExtension.php +++ b/src/Type/Php/StrContainingTypeSpecifyingExtension.php @@ -90,18 +90,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $args[$hackstackArg]->value, new IntersectionType($accessories), $context, - false, $scope, - new BooleanAnd( - new NotIdentical( - $args[$needleArg]->value, - new String_(''), - ), - new FuncCall(new Name('FAUX_FUNCTION'), [ - new Arg($args[$needleArg]->value), - ]), + )->setRootExpr(new BooleanAnd( + new NotIdentical( + $args[$needleArg]->value, + new String_(''), ), - ); + new FuncCall(new Name('FAUX_FUNCTION'), [ + new Arg($args[$needleArg]->value), + ]), + )); } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 3aa4bf08108..fe386028a3a 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -41,10 +41,12 @@ public function testRule(): void [ 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with int will always evaluate to false.', 30, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with string will always evaluate to true.', 36, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.', diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 29f63a6bf29..206e7acc453 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -52,10 +52,12 @@ public function testRule(): void [ 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with int will always evaluate to false.', 30, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to method PHPStan\Tests\AssertionClass::assertNotInt() with string will always evaluate to true.', 36, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to method ImpossibleMethodCall\Foo::isSame() with 1 and 1 will always evaluate to true.', diff --git a/tests/PHPStan/Rules/Comparison/data/TestTypeOverwriteSpecifyingExtensions.php b/tests/PHPStan/Rules/Comparison/data/TestTypeOverwriteSpecifyingExtensions.php index 4e2acbd198f..1ba7c4855f1 100644 --- a/tests/PHPStan/Rules/Comparison/data/TestTypeOverwriteSpecifyingExtensions.php +++ b/tests/PHPStan/Rules/Comparison/data/TestTypeOverwriteSpecifyingExtensions.php @@ -51,8 +51,8 @@ public function specifyTypes( $node->var, $newType, TypeSpecifierContext::createTruthy(), - true - ); + $scope, + )->setAlwaysOverwriteTypes(); } } From da764a2292911f66fd19a1ae53e31258494842f9 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 13 Sep 2024 17:05:33 +0200 Subject: [PATCH 0246/3097] Expose Output::isDecorated and Output::isVeryVerbose --- src/Command/Output.php | 4 ++++ src/Command/Symfony/SymfonyOutput.php | 10 ++++++++++ src/Rules/Api/BcUncoveredInterface.php | 2 ++ 3 files changed, 16 insertions(+) diff --git a/src/Command/Output.php b/src/Command/Output.php index 34b26c6c186..b0efcd648a2 100644 --- a/src/Command/Output.php +++ b/src/Command/Output.php @@ -16,6 +16,10 @@ public function getStyle(): OutputStyle; public function isVerbose(): bool; + public function isVeryVerbose(): bool; + public function isDebug(): bool; + public function isDecorated(): bool; + } diff --git a/src/Command/Symfony/SymfonyOutput.php b/src/Command/Symfony/SymfonyOutput.php index 7f60d04bfc2..2d66f11a38f 100644 --- a/src/Command/Symfony/SymfonyOutput.php +++ b/src/Command/Symfony/SymfonyOutput.php @@ -44,9 +44,19 @@ public function isVerbose(): bool return $this->symfonyOutput->isVerbose(); } + public function isVeryVerbose(): bool + { + return $this->symfonyOutput->isVeryVerbose(); + } + public function isDebug(): bool { return $this->symfonyOutput->isDebug(); } + public function isDecorated(): bool + { + return $this->symfonyOutput->isDecorated(); + } + } diff --git a/src/Rules/Api/BcUncoveredInterface.php b/src/Rules/Api/BcUncoveredInterface.php index 7670f1962f2..5b508c2e075 100644 --- a/src/Rules/Api/BcUncoveredInterface.php +++ b/src/Rules/Api/BcUncoveredInterface.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Api; use PHPStan\Analyser\Scope; +use PHPStan\Command\Output; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; @@ -39,6 +40,7 @@ final class BcUncoveredInterface NonIgnorableRuleError::class, RuleError::class, TipRuleError::class, + Output::class, ]; } From 51ce513308fb5d50459f0896dd7e6421f4d3be85 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 13 Sep 2024 14:46:46 +0200 Subject: [PATCH 0247/3097] Fix --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5c349ce624a..f05f7e4358d 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1405,7 +1405,7 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai $assertedType, $assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(), $scope, - )->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : null); + )->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : $assertExpr); $types = $types !== null ? $types->unionWith($newTypes) : $newTypes; if (!$context->null() || !$assertedType instanceof ConstantBooleanType) { From 5962aa1cb9d16ae86362a8aca8a5f8eecf81ee40 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 13 Sep 2024 22:21:12 +0200 Subject: [PATCH 0248/3097] Revert "Fix" This reverts commit 51ce513308fb5d50459f0896dd7e6421f4d3be85. --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f05f7e4358d..5c349ce624a 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1405,7 +1405,7 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai $assertedType, $assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(), $scope, - )->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : $assertExpr); + )->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : null); $types = $types !== null ? $types->unionWith($newTypes) : $newTypes; if (!$context->null() || !$assertedType instanceof ConstantBooleanType) { From a2854d1c276ada4fd3a5c7348585ebfa2fac9289 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 14:19:57 +0200 Subject: [PATCH 0249/3097] [BCB] Parameter `$callableParameters` of MutatingScope::enterAnonymousFunction() and enterArrowFunction() made required --- UPGRADING.md | 4 ++++ src/Analyser/MutatingScope.php | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 6baecd8e10c..3516ec8d6b1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -47,3 +47,7 @@ If you want to change `$overwrite` or `$rootExpr` (previous parameters also used ### Changed `TypeSpecifier::specifyTypesInCondition()` This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). + +### Minor backward compatibility breaks + +* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6d0bccf7198..06974beea71 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3376,7 +3376,7 @@ public function isInClosureBind(): bool */ public function enterAnonymousFunction( Expr\Closure $closure, - ?array $callableParameters = null, + ?array $callableParameters, ): self { $anonymousFunctionReflection = $this->getType($closure); @@ -3411,7 +3411,7 @@ public function enterAnonymousFunction( */ private function enterAnonymousFunctionWithoutReflection( Expr\Closure $closure, - ?array $callableParameters = null, + ?array $callableParameters, ): self { $expressionTypes = []; @@ -3553,7 +3553,7 @@ private function invalidateStaticExpressions(array $expressionTypes): array * @api * @param ParameterReflection[]|null $callableParameters */ - public function enterArrowFunction(Expr\ArrowFunction $arrowFunction, ?array $callableParameters = null): self + public function enterArrowFunction(Expr\ArrowFunction $arrowFunction, ?array $callableParameters): self { $anonymousFunctionReflection = $this->getType($arrowFunction); if (!$anonymousFunctionReflection instanceof ClosureType) { From 2c4c0cde75e637ac323e81def57d4a2ace952429 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 14:39:53 +0200 Subject: [PATCH 0250/3097] A few more MutatingScope method parameters made required --- src/Analyser/MutatingScope.php | 38 +++++++++---------- src/Analyser/NodeScopeResolver.php | 23 +++++------ .../Operators/InvalidBinaryOperationRule.php | 5 ++- .../Operators/InvalidUnaryOperationRule.php | 3 +- ...ArrayFilterFunctionReturnTypeExtension.php | 5 ++- tests/PHPStan/Analyser/ScopeTest.php | 5 ++- .../PHPStan/Analyser/StatementResultTest.php | 9 +++-- tests/PHPStan/Analyser/TypeSpecifierTest.php | 29 +++++++------- tests/PHPStan/Type/BitwiseFlagHelperTest.php | 14 +++---- 9 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 06974beea71..6de895a04b0 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3614,7 +3614,7 @@ private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFu if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { throw new ShouldNotHappenException(); } - $arrowFunctionScope = $arrowFunctionScope->assignVariable($parameter->var->name, $parameterType, $parameterType); + $arrowFunctionScope = $arrowFunctionScope->assignVariable($parameter->var->name, $parameterType, $parameterType, TrinaryLogic::createYes()); } if ($arrowFunction->static) { @@ -3733,6 +3733,7 @@ public function enterForeach(self $originalScope, Expr $iteratee, string $valueN $valueName, $originalScope->getIterableValueType($iterateeType), $originalScope->getIterableValueType($nativeIterateeType), + TrinaryLogic::createYes(), ); if ($keyName !== null) { $scope = $scope->enterForeachKey($originalScope, $iteratee, $keyName); @@ -3749,6 +3750,7 @@ public function enterForeachKey(self $originalScope, Expr $iteratee, string $key $keyName, $originalScope->getIterableKeyType($iterateeType), $originalScope->getIterableKeyType($nativeIterateeType), + TrinaryLogic::createYes(), ); if ($iterateeType->isArray()->yes()) { @@ -3783,6 +3785,7 @@ public function enterCatchType(Type $catchType, ?string $variableName): self $variableName, TypeCombinator::intersect($catchType, new ObjectType(Throwable::class)), TypeCombinator::intersect($catchType, new ObjectType(Throwable::class)), + TrinaryLogic::createYes(), ); } @@ -3928,18 +3931,16 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions); } - public function assignVariable(string $variableName, Type $type, Type $nativeType, ?TrinaryLogic $certainty = null): self + public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self { $node = new Variable($variableName); $scope = $this->assignExpression($node, $type, $nativeType); - if ($certainty !== null) { - if ($certainty->no()) { - throw new ShouldNotHappenException(); - } elseif (!$certainty->yes()) { - $exprString = '$' . $variableName; - $scope->expressionTypes[$exprString] = new ExpressionTypeHolder($node, $type, $certainty); - $scope->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($node, $nativeType, $certainty); - } + if ($certainty->no()) { + throw new ShouldNotHappenException(); + } elseif (!$certainty->yes()) { + $exprString = '$' . $variableName; + $scope->expressionTypes[$exprString] = new ExpressionTypeHolder($node, $type, $certainty); + $scope->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($node, $nativeType, $certainty); } $parameterOriginalValueExprString = $this->getNodeKey(new ParameterVariableOriginalValueExpr($variableName)); @@ -3987,7 +3988,7 @@ public function unsetExpression(Expr $expr): self return $scope->invalidateExpression($expr); } - public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, ?TrinaryLogic $certainty = null): self + public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, TrinaryLogic $certainty): self { if ($expr instanceof ConstFetch) { $loweredConstName = strtolower($expr->name->toString()); @@ -4035,9 +4036,7 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, } } - if ($certainty === null) { - $certainty = TrinaryLogic::createYes(); - } elseif ($certainty->no()) { + if ($certainty->no()) { throw new ShouldNotHappenException(); } @@ -4073,11 +4072,8 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, return $scope; } - public function assignExpression(Expr $expr, Type $type, ?Type $nativeType = null): self + public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { - if ($nativeType === null) { - $nativeType = new MixedType(); - } $scope = $this; if ($expr instanceof PropertyFetch) { $scope = $this->invalidateExpression($expr) @@ -4088,7 +4084,7 @@ public function assignExpression(Expr $expr, Type $type, ?Type $nativeType = nul $scope = $this->invalidateExpression($expr); } - return $scope->specifyExpressionType($expr, $type, $nativeType); + return $scope->specifyExpressionType($expr, $type, $nativeType, TrinaryLogic::createYes()); } public function assignInitializedProperty(Type $fetchedOnType, string $propertyName): self @@ -4292,13 +4288,14 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); - return $this->specifyExpressionType($expr, $newType, $newType); + return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes()); } return $this->specifyExpressionType( $expr, TypeCombinator::intersect($type, $originalExprType), TypeCombinator::intersect($type, $nativeType), + TrinaryLogic::createYes(), ); } @@ -4315,6 +4312,7 @@ public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self $expr, TypeCombinator::remove($exprType, $typeToRemove), TypeCombinator::remove($this->getNativeType($expr), $typeToRemove), + TrinaryLogic::createYes(), ); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ca8d1e74584..09f53137c96 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1809,7 +1809,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { continue; } - $scope = $scope->assignVariable($var->name, new MixedType(), new MixedType()); + $scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); $vars[] = $var->name; } $scope = $this->processVarAnnotation($scope, $vars, $stmt); @@ -1842,7 +1842,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $impurePoints = array_merge($impurePoints, $varResult->getImpurePoints()); $scope = $scope->exitExpressionAssign($var->var); - $scope = $scope->assignVariable($var->var->name, new MixedType(), new MixedType()); + $scope = $scope->assignVariable($var->var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); $vars[] = $var->var->name; } @@ -2091,6 +2091,7 @@ private function ensureShallowNonNullability(MutatingScope $scope, Scope $origin $exprToSpecify, $exprTypeWithoutNull, TypeCombinator::removeNull($nativeType), + TrinaryLogic::createYes(), ); return new EnsuredNonNullabilityResult( @@ -2457,7 +2458,7 @@ static function (): void { $functionReflection !== null && in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) ) { - $scope = $scope->assignVariable('http_response_header', AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())), new ArrayType(new IntegerType(), new StringType())); + $scope = $scope->assignVariable('http_response_header', AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes()); } if ( @@ -4217,7 +4218,7 @@ private function processClosureNode( $variableNativeType = TypeCombinator::union($scope->getVariableType($inAssignRightSideVariableName), $inAssignRightSideNativeType); } } - $scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType, $variableNativeType); + $scope = $scope->assignVariable($inAssignRightSideVariableName, $variableType, $variableNativeType, TrinaryLogic::createYes()); } } $this->processExprNode($stmt, $use->var, $useScope, $nodeCallback, $context); @@ -4625,7 +4626,7 @@ private function processArgs( && !$arg->value->static ) { $restoreThisScope = $scopeToPass; - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType()); + $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); } if ($parameter !== null) { @@ -4677,7 +4678,7 @@ private function processArgs( && $parameter->getClosureThisType() !== null && !$arg->value->static ) { - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType()); + $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); } if ($parameter !== null) { @@ -4983,7 +4984,7 @@ private function processAssignVar( $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); $nodeCallback(new VariableAssignNode($var, $assignedExpr, $isAssignOp), $result->getScope()); - $scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr)); + $scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr), TrinaryLogic::createYes()); foreach ($conditionalExpressions as $exprString => $holders) { $scope = $scope->addConditionalExpressions($exprString, $holders); } @@ -5142,7 +5143,7 @@ private function processAssignVar( if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); - $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite); + $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); } else { if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); @@ -5394,7 +5395,7 @@ static function (): void { if ($var instanceof Variable && is_string($var->name)) { $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); - $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite); + $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); } else { if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); @@ -5603,13 +5604,13 @@ private function processVarAnnotation(MutatingScope $scope, array $variableNames $variableType = $varTags[$variableName]->getType(); $changed = true; - $scope = $scope->assignVariable($variableName, $variableType, new MixedType()); + $scope = $scope->assignVariable($variableName, $variableType, new MixedType(), TrinaryLogic::createYes()); } if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { $variableType = $varTags[0]->getType(); $changed = true; - $scope = $scope->assignVariable($variableNames[0], $variableType, new MixedType()); + $scope = $scope->assignVariable($variableNames[0], $variableType, new MixedType(), TrinaryLogic::createYes()); } return $scope; diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 517c41b9092..77653e1f1a0 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -10,6 +10,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -104,8 +105,8 @@ public function processNode(Node $node, Scope $scope): array } $scope = $scope - ->assignVariable($leftName, $leftType, $leftType) - ->assignVariable($rightName, $rightType, $rightType); + ->assignVariable($leftName, $leftType, $leftType, TrinaryLogic::createYes()) + ->assignVariable($rightName, $rightType, $rightType, TrinaryLogic::createYes()); if (!$scope->getType($newNode) instanceof ErrorType) { return []; diff --git a/src/Rules/Operators/InvalidUnaryOperationRule.php b/src/Rules/Operators/InvalidUnaryOperationRule.php index 9ea9c073423..cf823a82bf9 100644 --- a/src/Rules/Operators/InvalidUnaryOperationRule.php +++ b/src/Rules/Operators/InvalidUnaryOperationRule.php @@ -9,6 +9,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -69,7 +70,7 @@ public function processNode(Node $node, Scope $scope): array throw new ShouldNotHappenException(); } - $scope = $scope->assignVariable($varName, $exprType, $exprType); + $scope = $scope->assignVariable($varName, $exprType, $exprType, TrinaryLogic::createYes()); if (!$scope->getType($newNode) instanceof ErrorType) { return []; } diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php index 4672fd1a96d..8f2e5d096c1 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php @@ -18,6 +18,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantArrayType; @@ -244,7 +245,7 @@ private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type throw new ShouldNotHappenException(); } $itemVarName = $itemVar->name; - $scope = $scope->assignVariable($itemVarName, $itemType, new MixedType()); + $scope = $scope->assignVariable($itemVarName, $itemType, new MixedType(), TrinaryLogic::createYes()); } $keyVarName = null; @@ -253,7 +254,7 @@ private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type throw new ShouldNotHappenException(); } $keyVarName = $keyVar->name; - $scope = $scope->assignVariable($keyVarName, $keyType, new MixedType()); + $scope = $scope->assignVariable($keyVarName, $keyType, new MixedType(), TrinaryLogic::createYes()); } $booleanResult = $scope->getType($expr)->toBoolean(); diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 99bba5ea9bd..88d5ee4122d 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Name\FullyQualified; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -232,8 +233,8 @@ public function testGeneralize(Type $a, Type $b, string $expectedTypeDescription { /** @var ScopeFactory $scopeFactory */ $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); - $scopeA = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $a, $a); - $scopeB = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $b, $b); + $scopeA = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $a, $a, TrinaryLogic::createYes()); + $scopeB = $scopeFactory->create(ScopeContext::create('file.php'))->assignVariable('a', $b, $b, TrinaryLogic::createYes()); $resultScope = $scopeA->generalizeWith($scopeB); $this->assertSame($expectedTypeDescription, $resultScope->getVariableType('a')->describe(VerbosityLevel::precise())); } diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index fb3a9dd5b1a..afc4e3f9cae 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -5,6 +5,7 @@ use PhpParser\Node\Stmt; use PHPStan\Parser\Parser; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; @@ -395,10 +396,10 @@ public function testIsAlwaysTerminating( /** @var ScopeFactory $scopeFactory */ $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); $scope = $scopeFactory->create(ScopeContext::create('test.php')) - ->assignVariable('string', new StringType(), new StringType()) - ->assignVariable('x', new IntegerType(), new IntegerType()) - ->assignVariable('cond', new MixedType(), new MixedType()) - ->assignVariable('arr', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType())); + ->assignVariable('string', new StringType(), new StringType(), TrinaryLogic::createYes()) + ->assignVariable('x', new IntegerType(), new IntegerType(), TrinaryLogic::createYes()) + ->assignVariable('cond', new MixedType(), new MixedType(), TrinaryLogic::createYes()) + ->assignVariable('arr', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType()), TrinaryLogic::createYes()); $result = $nodeScopeResolver->processStmtNodes( new Stmt\Namespace_(null, $stmts), $stmts, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index c2b21e92c46..122160c7faf 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -21,6 +21,7 @@ use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Node\Printer\Printer; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -62,20 +63,20 @@ protected function setUp(): void $this->typeSpecifier = self::getContainer()->getService('typeSpecifier'); $this->scope = $this->createScopeFactory($reflectionProvider, $this->typeSpecifier)->create(ScopeContext::create('')); $this->scope = $this->scope->enterClass($reflectionProvider->getClass('DateTime')); - $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar'), new ObjectType('Bar')); - $this->scope = $this->scope->assignVariable('stringOrNull', new UnionType([new StringType(), new NullType()]), new UnionType([new StringType(), new NullType()])); - $this->scope = $this->scope->assignVariable('string', new StringType(), new StringType()); - $this->scope = $this->scope->assignVariable('fooOrNull', new UnionType([new ObjectType('Foo'), new NullType()]), new UnionType([new ObjectType('Foo'), new NullType()])); - $this->scope = $this->scope->assignVariable('barOrNull', new UnionType([new ObjectType('Bar'), new NullType()]), new UnionType([new ObjectType('Bar'), new NullType()])); - $this->scope = $this->scope->assignVariable('barOrFalse', new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)]), new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)])); - $this->scope = $this->scope->assignVariable('stringOrFalse', new UnionType([new StringType(), new ConstantBooleanType(false)]), new UnionType([new StringType(), new ConstantBooleanType(false)])); - $this->scope = $this->scope->assignVariable('array', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType())); - $this->scope = $this->scope->assignVariable('foo', new MixedType(), new MixedType()); - $this->scope = $this->scope->assignVariable('classString', new ClassStringType(), new ClassStringType()); - $this->scope = $this->scope->assignVariable('genericClassString', new GenericClassStringType(new ObjectType('Bar')), new GenericClassStringType(new ObjectType('Bar'))); - $this->scope = $this->scope->assignVariable('object', new ObjectWithoutClassType(), new ObjectWithoutClassType()); - $this->scope = $this->scope->assignVariable('int', new IntegerType(), new IntegerType()); - $this->scope = $this->scope->assignVariable('float', new FloatType(), new FloatType()); + $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar'), new ObjectType('Bar'), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('stringOrNull', new UnionType([new StringType(), new NullType()]), new UnionType([new StringType(), new NullType()]), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('string', new StringType(), new StringType(), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('fooOrNull', new UnionType([new ObjectType('Foo'), new NullType()]), new UnionType([new ObjectType('Foo'), new NullType()]), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('barOrNull', new UnionType([new ObjectType('Bar'), new NullType()]), new UnionType([new ObjectType('Bar'), new NullType()]), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('barOrFalse', new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)]), new UnionType([new ObjectType('Bar'), new ConstantBooleanType(false)]), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('stringOrFalse', new UnionType([new StringType(), new ConstantBooleanType(false)]), new UnionType([new StringType(), new ConstantBooleanType(false)]), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('array', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType()), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('foo', new MixedType(), new MixedType(), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('classString', new ClassStringType(), new ClassStringType(), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('genericClassString', new GenericClassStringType(new ObjectType('Bar')), new GenericClassStringType(new ObjectType('Bar')), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('object', new ObjectWithoutClassType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('int', new IntegerType(), new IntegerType(), TrinaryLogic::createYes()); + $this->scope = $this->scope->assignVariable('float', new FloatType(), new FloatType(), TrinaryLogic::createYes()); } /** diff --git a/tests/PHPStan/Type/BitwiseFlagHelperTest.php b/tests/PHPStan/Type/BitwiseFlagHelperTest.php index f2136604c35..7703524f477 100644 --- a/tests/PHPStan/Type/BitwiseFlagHelperTest.php +++ b/tests/PHPStan/Type/BitwiseFlagHelperTest.php @@ -127,13 +127,13 @@ public function testExprContainsConst(Expr $expr, string $constName, TrinaryLogi /** @var ScopeFactory $scopeFactory */ $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); $scope = $scopeFactory->create(ScopeContext::create('file.php')) - ->assignVariable('mixedVar', new MixedType(), new MixedType()) - ->assignVariable('stringVar', new StringType(), new StringType()) - ->assignVariable('integerVar', new IntegerType(), new IntegerType()) - ->assignVariable('booleanVar', new BooleanType(), new BooleanType()) - ->assignVariable('floatVar', new FloatType(), new FloatType()) - ->assignVariable('unionIntFloatVar', new UnionType([new IntegerType(), new FloatType()]), new UnionType([new IntegerType(), new FloatType()])) - ->assignVariable('unionStringFloatVar', new UnionType([new StringType(), new FloatType()]), new UnionType([new StringType(), new FloatType()])); + ->assignVariable('mixedVar', new MixedType(), new MixedType(), TrinaryLogic::createYes()) + ->assignVariable('stringVar', new StringType(), new StringType(), TrinaryLogic::createYes()) + ->assignVariable('integerVar', new IntegerType(), new IntegerType(), TrinaryLogic::createYes()) + ->assignVariable('booleanVar', new BooleanType(), new BooleanType(), TrinaryLogic::createYes()) + ->assignVariable('floatVar', new FloatType(), new FloatType(), TrinaryLogic::createYes()) + ->assignVariable('unionIntFloatVar', new UnionType([new IntegerType(), new FloatType()]), new UnionType([new IntegerType(), new FloatType()]), TrinaryLogic::createYes()) + ->assignVariable('unionStringFloatVar', new UnionType([new StringType(), new FloatType()]), new UnionType([new StringType(), new FloatType()]), TrinaryLogic::createYes()); $analyser = new BitwiseFlagHelper($this->createReflectionProvider()); $actual = $analyser->bitwiseOrContainsConstant($expr, $scope, $constName); From 1648f008c60796631c7c585d31eac2c8ed956172 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 14:57:33 +0200 Subject: [PATCH 0251/3097] Document StatementContext --- src/Analyser/StatementContext.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Analyser/StatementContext.php b/src/Analyser/StatementContext.php index e9308cfb777..5fd5381601d 100644 --- a/src/Analyser/StatementContext.php +++ b/src/Analyser/StatementContext.php @@ -2,6 +2,14 @@ namespace PHPStan\Analyser; +/** + * Object of this class is one of the parameters of `NodeScopeResolver::processStmtNodes()`. + * + * It determines whether loops will be analysed once or multiple times + * until the types "stabilize". + * + * When in doubt, use `StatementContext::createTopLevel()`. + */ final class StatementContext { @@ -11,11 +19,17 @@ private function __construct( { } + /** + * @api + */ public static function createTopLevel(): self { return new self(true); } + /** + * @api + */ public static function createDeep(): self { return new self(false); From 4b6ac9c8f7654d8f751fbb21f5d2473a621b8739 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 14:58:24 +0200 Subject: [PATCH 0252/3097] [BCB] Parameter `$context` of `NodeScopeResolver::processStmtNodes()` made required --- UPGRADING.md | 1 + src/Analyser/NodeScopeResolver.php | 5 +---- tests/PHPStan/Analyser/StatementResultTest.php | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 3516ec8d6b1..99bd8966208 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -51,3 +51,4 @@ This method now longer accepts `Expr $rootExpr`. If you want to change it, call ### Minor backward compatibility breaks * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required +* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 09f53137c96..3bd5080928b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -329,12 +329,9 @@ public function processStmtNodes( array $stmts, MutatingScope $scope, callable $nodeCallback, - ?StatementContext $context = null, + StatementContext $context, ): StatementResult { - if ($context === null) { - $context = StatementContext::createTopLevel(); - } $exitPoints = []; $throwPoints = []; $impurePoints = []; diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index afc4e3f9cae..2a44edc5467 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -406,6 +406,7 @@ public function testIsAlwaysTerminating( $scope, static function (): void { }, + StatementContext::createTopLevel(), ); $this->assertSame($expectedIsAlwaysTerminating, $result->isAlwaysTerminating()); } From f17cf9ec43111cb29dd50d620fb6259c0ab0d373 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:06:54 +0200 Subject: [PATCH 0253/3097] CommandHelper::begin() parameters made required --- src/Command/AnalyseCommand.php | 1 + src/Command/ClearResultCacheCommand.php | 1 + src/Command/CommandHelper.php | 4 ++-- src/Command/DiagnoseCommand.php | 2 ++ src/Command/DumpParametersCommand.php | 2 ++ tests/PHPStan/Command/CommandHelperTest.php | 4 ++++ 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index a112f222352..f4281c48e41 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -171,6 +171,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $level, $allowXdebug, $debugEnabled, + true, ); } catch (InceptionNotSuccessfulException $e) { return 1; diff --git a/src/Command/ClearResultCacheCommand.php b/src/Command/ClearResultCacheCommand.php index 89e3c45ee70..ac41019e2b5 100644 --- a/src/Command/ClearResultCacheCommand.php +++ b/src/Command/ClearResultCacheCommand.php @@ -81,6 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int '0', $allowXdebug, $debugEnabled, + true, ); } catch (InceptionNotSuccessfulException) { return 1; diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 55926064a2a..8b1db512f1c 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -89,8 +89,8 @@ public static function begin( ?string $generateBaselineFile, ?string $level, bool $allowXdebug, - bool $debugEnabled = false, - bool $cleanupContainerCache = true, + bool $debugEnabled, + bool $cleanupContainerCache, ): InceptionResult { $stdOutput = new SymfonyOutput($output, new SymfonyStyle(new ErrorsConsoleStyle($input, $output))); diff --git a/src/Command/DiagnoseCommand.php b/src/Command/DiagnoseCommand.php index 2f8a5a47b0c..5a9f17e0abd 100644 --- a/src/Command/DiagnoseCommand.php +++ b/src/Command/DiagnoseCommand.php @@ -79,6 +79,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int null, $level, false, + false, + false, ); } catch (InceptionNotSuccessfulException) { return 1; diff --git a/src/Command/DumpParametersCommand.php b/src/Command/DumpParametersCommand.php index b3085db0c64..ccb8188933f 100644 --- a/src/Command/DumpParametersCommand.php +++ b/src/Command/DumpParametersCommand.php @@ -81,6 +81,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int null, $level, false, + false, + false, ); } catch (InceptionNotSuccessfulException) { return 1; diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index bbb00f122cd..d5a14801990 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -124,6 +124,8 @@ public function testBegin( null, $level, false, + false, + false, ); if ($expectException) { $this->fail(); @@ -307,6 +309,8 @@ public function testResolveParameters( null, '0', false, + false, + false, ); $parameters = $result->getContainer()->getParameters(); foreach ($expectedParameters as $name => $expectedValue) { From 939a715a0636ed05752659dbe7646c1f1a574765 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:08:17 +0200 Subject: [PATCH 0254/3097] ContainerFactory - always check duplicate files --- src/Command/CommandHelper.php | 2 +- src/DependencyInjection/ContainerFactory.php | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 8b1db512f1c..4ea6fb9cd3e 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -212,7 +212,7 @@ public static function begin( $paths = array_map(static fn (string $path): string => $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($path)), $paths); $analysedPathsFromConfig = []; - $containerFactory = new ContainerFactory($currentWorkingDirectory, true); + $containerFactory = new ContainerFactory($currentWorkingDirectory); $projectConfig = null; if ($projectConfigFile !== null) { if (!is_file($projectConfigFile)) { diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 5c8dad2f238..4cd4dfc0dbe 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -71,7 +71,7 @@ class ContainerFactory private static ?int $lastInitializedContainerId = null; /** @api */ - public function __construct(private string $currentWorkingDirectory, private bool $checkDuplicateFiles = false) + public function __construct(private string $currentWorkingDirectory) { $this->fileHelper = new FileHelper($currentWorkingDirectory); @@ -277,10 +277,6 @@ private function detectDuplicateIncludedFiles( return [$normalized, $configArray]; } - if (!$this->checkDuplicateFiles) { - return [$normalized, $configArray]; - } - $duplicateFiles = array_unique(array_diff_key($normalized, $deduplicated)); throw new DuplicateIncludedFilesException($duplicateFiles); From aefd71ceb3d9cdf6823111c46a76fda29806d80a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:10:06 +0200 Subject: [PATCH 0255/3097] [BCB] ClassPropertiesNode - remove `$extensions` parameter from `getUninitializedProperties()` --- UPGRADING.md | 1 + src/Node/ClassPropertiesNode.php | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 99bd8966208..d8172940a0d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -52,3 +52,4 @@ This method now longer accepts `Expr $rootExpr`. If you want to change it, call * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required +* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index 0707a5bc6d1..df8f0f993ac 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -18,7 +18,6 @@ use PHPStan\Node\Property\PropertyWrite; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; -use PHPStan\Rules\Properties\ReadWritePropertiesExtension; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\NeverType; @@ -98,13 +97,11 @@ public function getClassReflection(): ClassReflection /** * @param string[] $constructors - * @param ReadWritePropertiesExtension[]|null $extensions * @return array{array, array, array} */ public function getUninitializedProperties( Scope $scope, array $constructors, - ?array $extensions = null, ): array { if (!$this->getClass() instanceof Class_) { @@ -116,9 +113,7 @@ public function getUninitializedProperties( $originalProperties = []; $initialInitializedProperties = []; $initializedProperties = []; - if ($extensions === null) { - $extensions = $this->readWritePropertiesExtensionProvider->getExtensions(); - } + $extensions = $this->readWritePropertiesExtensionProvider->getExtensions(); $initializedViaExtension = []; foreach ($this->getProperties() as $property) { if ($property->isStatic()) { From 61bb049ad2ab31c69b93d86f1bab3b247155d87d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:11:28 +0200 Subject: [PATCH 0256/3097] RichParser - required parameter in private method --- src/Parser/RichParser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index 0bf45b07a32..73fd744faee 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -152,7 +152,7 @@ private function getLinesToIgnore(array $tokens): array $isCurrentLine = str_contains($text, '@phpstan-ignore-line'); if ($type === T_DOC_COMMENT) { - $lines += $this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-line'); + $lines += $this->getLinesToIgnoreForTokenByIgnoreComment($text, $line, '@phpstan-ignore-line', false); if ($isNextLine) { $pattern = sprintf('~%s~si', implode('|', [self::PHPDOC_TAG_REGEX, self::PHPDOC_DOCTRINE_TAG_REGEX])); $r = preg_match_all($pattern, $text, $pregMatches, PREG_OFFSET_CAPTURE); @@ -242,7 +242,7 @@ private function getLinesToIgnoreForTokenByIgnoreComment( string $tokenText, int $tokenLine, string $ignoreComment, - bool $ignoreNextLine = false, + bool $ignoreNextLine, ): array { $lines = []; From 5b58f83e6d8b5044d742caed9729d00178c4a9de Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:11:53 +0200 Subject: [PATCH 0257/3097] MethodTag - constructor parameter `$templateTags` is required --- src/PhpDoc/Tag/MethodTag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpDoc/Tag/MethodTag.php b/src/PhpDoc/Tag/MethodTag.php index 793fd3d6d2e..85018267b19 100644 --- a/src/PhpDoc/Tag/MethodTag.php +++ b/src/PhpDoc/Tag/MethodTag.php @@ -19,7 +19,7 @@ public function __construct( private Type $returnType, private bool $isStatic, private array $parameters, - private array $templateTags = [], + private array $templateTags, ) { } From f88d9ba7f56ef6c3b783aee1c909a3422c0ef3c3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:13:24 +0200 Subject: [PATCH 0258/3097] InitializerExprTypeResolver - constructor parameter `$usePathConstantsAsConstantString` made required --- src/DependencyInjection/ValidateIgnoredErrorsExtension.php | 2 +- src/Reflection/InitializerExprTypeResolver.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 1973f7e3033..5384f86d2d5 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -108,7 +108,7 @@ public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry return new OperatorTypeSpecifyingExtensionRegistry(null, []); } - }, new OversizedArrayBuilder()), + }, new OversizedArrayBuilder(), true), ), ), ); diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 3faf85d131f..6d4734b3970 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -102,7 +102,7 @@ public function __construct( private PhpVersion $phpVersion, private OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider, private OversizedArrayBuilder $oversizedArrayBuilder, - private bool $usePathConstantsAsConstantString = false, + private bool $usePathConstantsAsConstantString, ) { } From 8bfbf8f254a68e4f1b15419eb950ea677fc2916e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:13:51 +0200 Subject: [PATCH 0259/3097] `PhpMethodReflectionFactory::create()` - all parameters are required --- src/Reflection/Php/PhpMethodReflectionFactory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 2aa2aca526b..77de1aa1b76 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -34,9 +34,9 @@ public function create( ?Type $selfOutType, ?string $phpDocComment, array $phpDocParameterOutTypes, - array $immediatelyInvokedCallableParameters = [], - array $phpDocClosureThisTypeParameters = [], - bool $acceptsNamedArguments = true, + array $immediatelyInvokedCallableParameters, + array $phpDocClosureThisTypeParameters, + bool $acceptsNamedArguments, ): PhpMethodReflection; } From 493752737c32eb878de4dfb91817761b952348e4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:14:45 +0200 Subject: [PATCH 0260/3097] FunctionCallParametersCheck - parameters `$nodeType` and `$acceptsNamedArguments` made required --- src/Rules/FunctionCallParametersCheck.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4f0d2ae447e..76b9eff3712 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -63,8 +63,8 @@ public function check( bool $isBuiltin, $funcCall, array $messages, - string $nodeType = 'function', - bool $acceptsNamedArguments = true, + string $nodeType, + bool $acceptsNamedArguments, ): array { $functionParametersMinCount = 0; From f85a500288b0b8ef9a19d405c0e3d99ab57ce797 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:15:41 +0200 Subject: [PATCH 0261/3097] MethodParameterComparisonHelper - parameter `$ignorable` of `compare()` method made required --- src/Rules/Methods/MethodParameterComparisonHelper.php | 2 +- src/Rules/Methods/OverridingMethodRule.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 2f21d52123c..284152b9c9e 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -31,7 +31,7 @@ public function __construct(private PhpVersion $phpVersion, private bool $generi /** * @return list */ - public function compare(ExtendedMethodReflection $prototype, ClassReflection $prototypeDeclaringClass, PhpMethodFromParserNodeReflection $method, bool $ignorable = false): array + public function compare(ExtendedMethodReflection $prototype, ClassReflection $prototypeDeclaringClass, PhpMethodFromParserNodeReflection $method, bool $ignorable): array { /** @var list $messages */ $messages = []; diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 9d414f6f2b0..99f3b1866e3 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -233,7 +233,7 @@ public function processNode(Node $node, Scope $scope): array } } - $messages = array_merge($messages, $this->methodParameterComparisonHelper->compare($prototype, $prototypeDeclaringClass, $method)); + $messages = array_merge($messages, $this->methodParameterComparisonHelper->compare($prototype, $prototypeDeclaringClass, $method, false)); if (!$prototypeVariant instanceof FunctionVariantWithPhpDocs) { return $this->addErrors($messages, $node, $scope); From a8cd423e842deaa7d924580665207a4b1a373115 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:16:11 +0200 Subject: [PATCH 0262/3097] Parameter `$dateTimeClass` of DateTimeModifyReturnTypeExtension constructor made required --- src/Type/Php/DateTimeModifyReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/DateTimeModifyReturnTypeExtension.php b/src/Type/Php/DateTimeModifyReturnTypeExtension.php index 6ab34811e5c..0ed2933856d 100644 --- a/src/Type/Php/DateTimeModifyReturnTypeExtension.php +++ b/src/Type/Php/DateTimeModifyReturnTypeExtension.php @@ -22,7 +22,7 @@ final class DateTimeModifyReturnTypeExtension implements DynamicMethodReturnType /** @param class-string $dateTimeClass */ public function __construct( private PhpVersion $phpVersion, - private string $dateTimeClass = DateTime::class, + private string $dateTimeClass, ) { } From bcbb5036d38e03092ac4a1294b1289ddc44174eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:24:12 +0200 Subject: [PATCH 0263/3097] Upgrading note --- UPGRADING.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index d8172940a0d..8e61c277882 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -9,7 +9,28 @@ PHPStan now requires PHP 7.4 or newer to run. ## Upgrading guide for end users -TODO +The best way do get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** +and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. + +Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: + +```json +"require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-nette": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + ... +} +``` + +Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well. + +After changing your `composer.json`, run `composer update 'phpstan/*' -W`. ## Upgrading guide for extension developers From 64ff598cd42268d2178d02efd208afe637060978 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Sep 2024 15:27:14 +0200 Subject: [PATCH 0264/3097] NativeFunctionReflection construct parameters made required --- src/Reflection/Native/NativeFunctionReflection.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 2dd98f29517..6ba0c0b7c5e 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -26,10 +26,10 @@ public function __construct( private ?Type $throwType, private TrinaryLogic $hasSideEffects, private bool $isDeprecated, - ?Assertions $assertions = null, - private ?string $phpDocComment = null, - ?TrinaryLogic $returnsByReference = null, - private bool $acceptsNamedArguments = true, + ?Assertions $assertions, + private ?string $phpDocComment, + ?TrinaryLogic $returnsByReference, + private bool $acceptsNamedArguments, ) { $this->assertions = $assertions ?? Assertions::createEmpty(); From 2507e387c57b5b9577ee8d02226650880b731697 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 13 Sep 2024 09:46:45 +0200 Subject: [PATCH 0265/3097] Do not truncate offset key When you have an array with long key names, it becomes hard / impossible to see which one errors. I think it would be better to be precise here. --- src/Type/Constant/ConstantArrayType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 5ab51870a8e..7d2d0c9bc17 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -327,7 +327,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $acceptsValue = $valueType->acceptsWithReason($otherValueType, $strictTypes)->decorateReasons( static fn (string $reason) => sprintf( 'Offset %s (%s) does not accept type %s: %s', - $keyType->describe(VerbosityLevel::value()), + $keyType->describe(VerbosityLevel::precise()), $valueType->describe($verbosity), $otherValueType->describe($verbosity), $reason, @@ -337,7 +337,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $acceptsValue = new AcceptsResult($acceptsValue->result, [ sprintf( 'Offset %s (%s) does not accept type %s.', - $keyType->describe(VerbosityLevel::value()), + $keyType->describe(VerbosityLevel::precise()), $valueType->describe($verbosity), $otherValueType->describe($verbosity), ), From c6a852acfe87d829b2d82b45964bb796a95f10c0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 13 Sep 2024 12:42:12 +0200 Subject: [PATCH 0266/3097] Simplify SubstrDynamicReturnTypeExtension --- .../Php/SubstrDynamicReturnTypeExtension.php | 90 +++++++++---------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index e90116c57a8..f40ff3900d8 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -37,71 +37,69 @@ public function getTypeFromFunctionCall( ): ?Type { $args = $functionCall->getArgs(); - if (count($args) === 0) { + if (count($args) < 2) { return null; } - if (count($args) >= 2) { - $string = $scope->getType($args[0]->value); - $offset = $scope->getType($args[1]->value); + $string = $scope->getType($args[0]->value); + $offset = $scope->getType($args[1]->value); - $negativeOffset = IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($offset)->yes(); - $zeroOffset = (new ConstantIntegerType(0))->isSuperTypeOf($offset)->yes(); - $length = null; - $positiveLength = false; - $maybeOneLength = false; + $negativeOffset = IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($offset)->yes(); + $zeroOffset = (new ConstantIntegerType(0))->isSuperTypeOf($offset)->yes(); + $length = null; + $positiveLength = false; + $maybeOneLength = false; - if (count($args) === 3) { - $length = $scope->getType($args[2]->value); - $positiveLength = IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($length)->yes(); - $maybeOneLength = !(new ConstantIntegerType(1))->isSuperTypeOf($length)->no(); - } + if (count($args) === 3) { + $length = $scope->getType($args[2]->value); + $positiveLength = IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($length)->yes(); + $maybeOneLength = !(new ConstantIntegerType(1))->isSuperTypeOf($length)->no(); + } - $constantStrings = $string->getConstantStrings(); - if ( - count($constantStrings) > 0 - && $offset instanceof ConstantIntegerType - && ($length === null || $length instanceof ConstantIntegerType) - ) { - $results = []; - foreach ($constantStrings as $constantString) { - if ($length !== null) { - if ($functionReflection->getName() === 'mb_substr') { - $substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue()); - } else { - $substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue()); - } + $constantStrings = $string->getConstantStrings(); + if ( + count($constantStrings) > 0 + && $offset instanceof ConstantIntegerType + && ($length === null || $length instanceof ConstantIntegerType) + ) { + $results = []; + foreach ($constantStrings as $constantString) { + if ($length !== null) { + if ($functionReflection->getName() === 'mb_substr') { + $substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue()); } else { - if ($functionReflection->getName() === 'mb_substr') { - $substr = mb_substr($constantString->getValue(), $offset->getValue()); - } else { - $substr = substr($constantString->getValue(), $offset->getValue()); - } + $substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue()); } - - if (is_bool($substr)) { - $results[] = new ConstantBooleanType($substr); + } else { + if ($functionReflection->getName() === 'mb_substr') { + $substr = mb_substr($constantString->getValue(), $offset->getValue()); } else { - $results[] = new ConstantStringType($substr); + $substr = substr($constantString->getValue(), $offset->getValue()); } } - return TypeCombinator::union(...$results); + if (is_bool($substr)) { + $results[] = new ConstantBooleanType($substr); + } else { + $results[] = new ConstantStringType($substr); + } } - if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { - if ($string->isNonFalsyString()->yes() && !$maybeOneLength) { - return new IntersectionType([ - new StringType(), - new AccessoryNonFalsyStringType(), - ]); + return TypeCombinator::union(...$results); + } - } + if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { + if ($string->isNonFalsyString()->yes() && !$maybeOneLength) { return new IntersectionType([ new StringType(), - new AccessoryNonEmptyStringType(), + new AccessoryNonFalsyStringType(), ]); + } + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); } return null; From cdf7d673c53b2a4f6e1d58f794b221a976c28d1e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Sep 2024 13:30:19 +0200 Subject: [PATCH 0267/3097] Update PHPStan extensions --- composer.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.lock b/composer.lock index e11ebd0c9b3..37d6db47ae9 100644 --- a/composer.lock +++ b/composer.lock @@ -2286,12 +2286,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9d57f3db5bba9b0d8d11726389eb8af3126780b4" + "reference": "f9635550d59587171fc60dc0840ee015e8a76453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9d57f3db5bba9b0d8d11726389eb8af3126780b4", - "reference": "9d57f3db5bba9b0d8d11726389eb8af3126780b4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/f9635550d59587171fc60dc0840ee015e8a76453", + "reference": "f9635550d59587171fc60dc0840ee015e8a76453", "shasum": "" }, "require": { @@ -2326,7 +2326,7 @@ "issues": "https://github.com/phpstan/phpdoc-parser/issues", "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.x" }, - "time": "2024-09-07T21:23:59+00:00" + "time": "2024-09-09T14:20:27+00:00" }, { "name": "psr/container", @@ -4669,12 +4669,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd" + "reference": "89572d5481ec1e121ac1567f689fe49a25d6cef6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd", - "reference": "398e2e2d7b71cbdd943c87bd7c7731ad1eeaf1cd", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/89572d5481ec1e121ac1567f689fe49a25d6cef6", + "reference": "89572d5481ec1e121ac1567f689fe49a25d6cef6", "shasum": "" }, "require": { @@ -4709,7 +4709,7 @@ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" }, - "time": "2024-09-06T13:40:51+00:00" + "time": "2024-09-11T15:52:56+00:00" }, { "name": "phpstan/phpstan-nette", @@ -4778,12 +4778,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "3faa60573a32522772e7cda004003b15466e2b5b" + "reference": "4d861e0843cd1f8eccacfac14e24a8629280a887" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/3faa60573a32522772e7cda004003b15466e2b5b", - "reference": "3faa60573a32522772e7cda004003b15466e2b5b", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/4d861e0843cd1f8eccacfac14e24a8629280a887", + "reference": "4d861e0843cd1f8eccacfac14e24a8629280a887", "shasum": "" }, "require": { @@ -4822,7 +4822,7 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.x" }, - "time": "2024-09-04T20:57:24+00:00" + "time": "2024-09-13T12:47:01+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4830,12 +4830,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3" + "reference": "1062d489f1d10e79df42d73fa5352a27741d65f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/5eec39fc6ef36015e6de08949c8e9ae9d64560a3", - "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1062d489f1d10e79df42d73fa5352a27741d65f1", + "reference": "1062d489f1d10e79df42d73fa5352a27741d65f1", "shasum": "" }, "require": { @@ -4871,7 +4871,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-06T18:44:39+00:00" + "time": "2024-09-13T12:49:46+00:00" }, { "name": "phpunit/php-code-coverage", From 4bb7e72d3baac0dfbd5a33905ed04f8797903da8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Sep 2024 14:54:06 +0200 Subject: [PATCH 0268/3097] Update phpdoc-parser --- composer.lock | 8 ++++---- src/Type/Constant/ConstantArrayType.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index 37d6db47ae9..6b92d5bffb6 100644 --- a/composer.lock +++ b/composer.lock @@ -2286,12 +2286,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "f9635550d59587171fc60dc0840ee015e8a76453" + "reference": "0fe292d7fa9deb3a869a4a74363497e1ae527a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/f9635550d59587171fc60dc0840ee015e8a76453", - "reference": "f9635550d59587171fc60dc0840ee015e8a76453", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/0fe292d7fa9deb3a869a4a74363497e1ae527a54", + "reference": "0fe292d7fa9deb3a869a4a74363497e1ae527a54", "shasum": "" }, "require": { @@ -2326,7 +2326,7 @@ "issues": "https://github.com/phpstan/phpdoc-parser/issues", "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.x" }, - "time": "2024-09-09T14:20:27+00:00" + "time": "2024-09-15T12:53:31+00:00" }, { "name": "psr/container", diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7d2d0c9bc17..2161d3f4a2b 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1685,7 +1685,7 @@ public function toPhpDocNode(): TypeNode ); } - return new ArrayShapeNode($exportValuesOnly ? $values : $items); + return ArrayShapeNode::createSealed($exportValuesOnly ? $values : $items); } public static function isValidIdentifier(string $value): bool From 202c2e1c84e01c011338ca01ce095ec1d03433fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Sep 2024 16:04:20 +0200 Subject: [PATCH 0269/3097] Update simple-downgrader --- composer.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 6b92d5bffb6..4162573cd2a 100644 --- a/composer.lock +++ b/composer.lock @@ -4441,12 +4441,12 @@ "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "1630e1672948a2b59955d7fa634992dc4331ac00" + "reference": "760b4c5c0b5ae631e6604bdcf074387e40e35ed1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/1630e1672948a2b59955d7fa634992dc4331ac00", - "reference": "1630e1672948a2b59955d7fa634992dc4331ac00", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/760b4c5c0b5ae631e6604bdcf074387e40e35ed1", + "reference": "760b4c5c0b5ae631e6604bdcf074387e40e35ed1", "shasum": "" }, "require": { @@ -4459,9 +4459,10 @@ }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.5.36" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^9.6" }, + "default-branch": true, "bin": [ "bin/simple-downgrade" ], @@ -4482,7 +4483,7 @@ "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.x" }, - "time": "2024-09-07T21:32:41+00:00" + "time": "2024-09-15T14:01:11+00:00" }, { "name": "phar-io/manifest", From 104b6b8ba3d20133bac61d15064cd035610bb439 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Sep 2024 20:34:48 +0200 Subject: [PATCH 0270/3097] Update nikic/php-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 30da24daf1e..33b8daac2f3 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", - "nikic/php-parser": "^5.1.0", + "nikic/php-parser": "^5.2.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.8", "phpstan/php-8-stubs": "0.3.104", diff --git a/composer.lock b/composer.lock index 4162573cd2a..90d9df7f41d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6eb11a29766ed815fc1ad8931ef7f48", + "content-hash": "2992836f30621996bc1a9f11f903c7f2", "packages": [ { "name": "clue/ndjson-react", @@ -2049,16 +2049,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", "shasum": "" }, "require": { @@ -2101,9 +2101,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-15T16:40:33+00:00" }, { "name": "ondram/ci-detector", From 10de8332f63a79a9510914a083e27b12c31182a5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0271/3097] Issue bot - let all comments about PHP-Parser 5.2 through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 3d7ed3ffaeca23a52986128edc6caa9baa3402d4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Sep 2024 20:54:47 +0200 Subject: [PATCH 0272/3097] Revert "Issue bot - let all comments about PHP-Parser 5.2 through" This reverts commit 10de8332f63a79a9510914a083e27b12c31182a5. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From b3c25b87849b56c44331b572acebb1c9c171a569 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 15 Sep 2024 20:56:22 +0200 Subject: [PATCH 0273/3097] Fix string types sorting --- src/Type/UnionTypeHelper.php | 4 ++++ tests/PHPStan/Analyser/nsrt/string-union.php | 20 ++++++++++++++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 8 ++++++++ 3 files changed, 32 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/string-union.php diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index 7f82682dc53..485c4826445 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -116,6 +116,10 @@ public static function sortTypes(array $types): array return self::compareStrings($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); } + if ($a->isString()->yes() && $b->isString()->yes()) { + return self::compareStrings($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); + } + return self::compareStrings($a->describe(VerbosityLevel::typeOnly()), $b->describe(VerbosityLevel::typeOnly())); }); return $types; diff --git a/tests/PHPStan/Analyser/nsrt/string-union.php b/tests/PHPStan/Analyser/nsrt/string-union.php new file mode 100644 index 00000000000..5308521baf3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/string-union.php @@ -0,0 +1,20 @@ + Date: Mon, 16 Sep 2024 09:20:51 +0200 Subject: [PATCH 0274/3097] Fix prefixing in PHP-Parser --- compiler/build/scoper.inc.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index fd867cbb483..b6659a93b04 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -216,6 +216,16 @@ function (string $filePath, string $prefix, string $content): string { return str_replace(sprintf('use %s\\PhpParser;', $prefix), 'use PhpParser;', $content); }, + function (string $filePath, string $prefix, string $content): string { + if (!str_starts_with($filePath, 'vendor/nikic/php-parser/lib')) { + return $content; + } + + return str_replace([ + sprintf('\\%s', $prefix), + sprintf('\\\\%s', $prefix), + ], '', $content); + }, function (string $filePath, string $prefix, string $content): string { if ( $filePath !== 'vendor/nette/utils/src/Utils/Strings.php' From 041922bf7320165bface60b047980c790238f276 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:31:55 +0200 Subject: [PATCH 0275/3097] [BE] No implicit wildcard in FileExcluder --- UPGRADING.md | 21 +++++++++++ changelog-2.0.md | 4 +-- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 -- conf/parametersSchema.neon | 1 - src/Analyser/Ignore/IgnoredError.php | 3 +- src/Command/CommandHelper.php | 2 +- .../ValidateExcludePathsExtension.php | 3 +- .../ValidateIgnoredErrorsExtension.php | 4 +-- src/File/FileExcluder.php | 35 ++++++++----------- tests/PHPStan/File/FileExcluderTest.php | 34 ++++++------------ 11 files changed, 51 insertions(+), 60 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 8e61c277882..2e9830c098f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -32,6 +32,27 @@ Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-g After changing your `composer.json`, run `composer update 'phpstan/*' -W`. +### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern + +If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: + +```neon +parameters: + excludePaths: + - tests/*/data/* + - src/broken + - node_modules (?) # optional path, might not exist +``` + +If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`. + +```neon +parameters: + reportUnmatchedIgnoredErrors: false +``` + +Appending `(?)` in `ignoreErrors` is not supported. + ## Upgrading guide for extension developers ### PHPStan now uses nikic/php-parser v5 diff --git a/changelog-2.0.md b/changelog-2.0.md index 4f9a5843f13..090eea5e3ae 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -120,9 +120,7 @@ Bleeding edge (TODO move to other sections) * Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 * Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) * Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 -* Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) * RegularExpressionPatternRule: validate preg_quote'd patterns ([#3270](https://github.com/phpstan/phpstan-src/pull/3270)), thanks @staabm! -* No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! @@ -140,6 +138,8 @@ Improvements 🔧 * Unescape strings in PHPDoc parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) * PHPDoc parser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! * InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) +* No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 +* Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index dae0bbb2abf..c5f05f82081 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -53,7 +53,6 @@ parameters: printfArrayParameters: true preciseMissingReturn: true validatePregQuote: true - noImplicitWildcard: true tooWidePropertyType: true explicitThrow: true absentTypeChecks: true diff --git a/conf/config.neon b/conf/config.neon index b004e06bbd1..552b99403a1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -89,7 +89,6 @@ parameters: printfArrayParameters: false preciseMissingReturn: false validatePregQuote: false - noImplicitWildcard: false requireFileExists: false narrowPregMatches: true tooWidePropertyType: false @@ -682,8 +681,6 @@ services: - implement: PHPStan\File\FileExcluderRawFactory - arguments: - noImplicitWildcard: %featureToggles.noImplicitWildcard% fileExcluderAnalyse: class: PHPStan\File\FileExcluder diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 0d8cfd70a7f..6b8a6c44b72 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -83,7 +83,6 @@ parametersSchema: printfArrayParameters: bool() preciseMissingReturn: bool() validatePregQuote: bool() - noImplicitWildcard: bool() narrowPregMatches: bool() tooWidePropertyType: bool() explicitThrow: bool() diff --git a/src/Analyser/Ignore/IgnoredError.php b/src/Analyser/Ignore/IgnoredError.php index e714c93f797..b420ae29ce2 100644 --- a/src/Analyser/Ignore/IgnoredError.php +++ b/src/Analyser/Ignore/IgnoredError.php @@ -4,7 +4,6 @@ use Nette\Utils\Strings; use PHPStan\Analyser\Error; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\File\FileExcluder; use PHPStan\File\FileHelper; use PHPStan\ShouldNotHappenException; @@ -86,7 +85,7 @@ public static function shouldIgnore( } if ($path !== null) { - $fileExcluder = new FileExcluder($fileHelper, [$path], BleedingEdgeToggle::isBleedingEdge()); + $fileExcluder = new FileExcluder($fileHelper, [$path]); $isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath()); if (!$isExcluded && $error->getTraitFilePath() !== null) { return $fileExcluder->isExcludedFromAnalysing($error->getTraitFilePath()); diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 4ea6fb9cd3e..c899ccc8ccd 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -609,7 +609,7 @@ public static function begin( $pathRoutingParser->setAnalysedFiles($files); - $stubFilesExcluder = new FileExcluder($currentWorkingDirectoryFileHelper, $stubFilesProvider->getProjectStubFiles(), true); + $stubFilesExcluder = new FileExcluder($currentWorkingDirectoryFileHelper, $stubFilesProvider->getProjectStubFiles()); $files = array_values(array_filter($files, static fn (string $file) => !$stubFilesExcluder->isExcludedFromAnalysing($file))); diff --git a/src/DependencyInjection/ValidateExcludePathsExtension.php b/src/DependencyInjection/ValidateExcludePathsExtension.php index b322c4b18ce..6d099d1c105 100644 --- a/src/DependencyInjection/ValidateExcludePathsExtension.php +++ b/src/DependencyInjection/ValidateExcludePathsExtension.php @@ -28,8 +28,7 @@ public function loadConfiguration(): void } $errors = []; - $noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard']; - if ($builder->parameters['__validate'] && $noImplicitWildcard) { + if ($builder->parameters['__validate']) { $paths = []; if (array_key_exists('analyse', $excludePaths)) { $paths = $excludePaths['analyse']; diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 5384f86d2d5..0f5dc0d9e20 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -59,8 +59,6 @@ public function loadConfiguration(): void return; } - $noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard']; - /** @throws void */ $parser = Llk::load(new Read(__DIR__ . '/../../resources/RegexGrammar.pp')); $reflectionProvider = new DummyReflectionProvider(); @@ -141,7 +139,7 @@ public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry $reportUnmatched = (bool) $builder->parameters['reportUnmatchedIgnoredErrors']; - if ($noImplicitWildcard && $reportUnmatched) { + if ($reportUnmatched) { foreach ($ignoreErrors as $ignoreError) { if (!is_array($ignoreError)) { continue; diff --git a/src/File/FileExcluder.php b/src/File/FileExcluder.php index 7e6fefdaca8..7ebb938302a 100644 --- a/src/File/FileExcluder.php +++ b/src/File/FileExcluder.php @@ -52,7 +52,6 @@ final class FileExcluder public function __construct( FileHelper $fileHelper, array $analyseExcludes, - private bool $noImplicitWildcard, ) { foreach ($analyseExcludes as $exclude) { @@ -68,18 +67,14 @@ public function __construct( if (self::isFnmatchPattern($normalized)) { $this->fnmatchAnalyseExcludes[] = $normalized; } else { - if ($this->noImplicitWildcard) { - if (is_file($normalized)) { - $this->literalAnalyseFilesExcludes[] = $normalized; - } elseif (is_dir($normalized)) { - if (!$trailingDirSeparator) { - $normalized .= DIRECTORY_SEPARATOR; - } - - $this->literalAnalyseDirectoryExcludes[] = $normalized; + if (is_file($normalized)) { + $this->literalAnalyseFilesExcludes[] = $normalized; + } elseif (is_dir($normalized)) { + if (!$trailingDirSeparator) { + $normalized .= DIRECTORY_SEPARATOR; } - } else { - $this->literalAnalyseExcludes[] = $fileHelper->absolutizePath($normalized); + + $this->literalAnalyseDirectoryExcludes[] = $normalized; } } } @@ -99,16 +94,14 @@ public function isExcludedFromAnalysing(string $file): bool return true; } } - if ($this->noImplicitWildcard) { - foreach ($this->literalAnalyseDirectoryExcludes as $exclude) { - if (str_starts_with($file, $exclude)) { - return true; - } + foreach ($this->literalAnalyseDirectoryExcludes as $exclude) { + if (str_starts_with($file, $exclude)) { + return true; } - foreach ($this->literalAnalyseFilesExcludes as $exclude) { - if ($file === $exclude) { - return true; - } + } + foreach ($this->literalAnalyseFilesExcludes as $exclude) { + if ($file === $exclude) { + return true; } } foreach ($this->fnmatchAnalyseExcludes as $exclude) { diff --git a/tests/PHPStan/File/FileExcluderTest.php b/tests/PHPStan/File/FileExcluderTest.php index 7477416b6cc..f5c746a3ade 100644 --- a/tests/PHPStan/File/FileExcluderTest.php +++ b/tests/PHPStan/File/FileExcluderTest.php @@ -19,7 +19,7 @@ public function testFilesAreExcludedFromAnalysingOnWindows( { $this->skipIfNotOnWindows(); - $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, false); + $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes); $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); } @@ -34,7 +34,7 @@ public function dataExcludeOnWindows(): array ], [ __DIR__ . '/data/excluded-file.php', - [__DIR__], + [__DIR__ . '/*'], true, ], [ @@ -64,7 +64,7 @@ public function dataExcludeOnWindows(): array ], [ __DIR__ . '\data\parse-error.php', - ['tests/PHPStan/File/data'], + ['tests/PHPStan/File/data/*'], true, ], [ @@ -99,7 +99,7 @@ public function dataExcludeOnWindows(): array ], [ 'c:\etc\phpstan\dummy-1.php', - ['c:\etc\phpstan\\'], + ['c:\etc\phpstan\\*'], true, ], [ @@ -109,7 +109,7 @@ public function dataExcludeOnWindows(): array ], [ 'c:\etc\phpstan-test\dummy-2.php', - ['c:\etc\phpstan'], + ['c:\etc\phpstan*'], true, ], ]; @@ -127,7 +127,7 @@ public function testFilesAreExcludedFromAnalysingOnUnix( { $this->skipIfNotOnUnix(); - $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, false); + $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes); $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); } @@ -172,7 +172,7 @@ public function dataExcludeOnUnix(): array ], [ __DIR__ . '/data/parse-error.php', - ['tests/PHPStan/File/data'], + ['*/tests/PHPStan/File/data/*'], true, ], [ @@ -192,7 +192,7 @@ public function dataExcludeOnUnix(): array ], [ '/etc/phpstan/dummy-1.php', - ['/etc/phpstan/'], + ['/etc/phpstan/*'], true, ], [ @@ -202,7 +202,7 @@ public function dataExcludeOnUnix(): array ], [ '/etc/phpstan-test/dummy-2.php', - ['/etc/phpstan'], + ['/etc/phpstan*'], true, ], ]; @@ -216,16 +216,6 @@ public function dataNoImplicitWildcard(): iterable __DIR__ . '/test', ], false, - true, - ]; - - yield [ - __DIR__ . '/tests/foo.php', - [ - __DIR__ . '/test', - ], - true, - false, ]; yield [ @@ -234,7 +224,6 @@ public function dataNoImplicitWildcard(): iterable __DIR__ . '/test', ], true, - true, ]; yield [ @@ -243,7 +232,6 @@ public function dataNoImplicitWildcard(): iterable __DIR__ . '/FileExcluderTest.php', ], true, - true, ]; yield [ @@ -252,7 +240,6 @@ public function dataNoImplicitWildcard(): iterable __DIR__ . '/test*', ], true, - true, ]; } @@ -263,13 +250,12 @@ public function dataNoImplicitWildcard(): iterable public function testNoImplicitWildcard( string $filePath, array $analyseExcludes, - bool $noImplicitWildcard, bool $isExcluded, ): void { $this->skipIfNotOnUnix(); - $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes, $noImplicitWildcard); + $fileExcluder = new FileExcluder($this->getFileHelper(), $analyseExcludes); $this->assertSame($isExcluded, $fileExcluder->isExcludedFromAnalysing($filePath)); } From 2e027658e061f647b0de5fbf5b68588ee668eae5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:46:30 +0200 Subject: [PATCH 0276/3097] [BE] Do not generalize template types, except when in `GenericObjectType` --- changelog-2.0.md | 4 ++-- src/Reflection/ResolvedFunctionVariantWithOriginal.php | 3 +-- src/Type/Generic/TemplateTypeTrait.php | 9 --------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 090eea5e3ae..628c9412d80 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -71,8 +71,6 @@ Bleeding edge (TODO move to other sections) * MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! -* Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) - * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * NoopRule - report top-level `xor` because that's probably not what the user intended to do (https://github.com/phpstan/phpstan-src/commit/a1fffb3346e09f1e8e8d987d4282263295a55142), #10267 @@ -140,6 +138,8 @@ Improvements 🔧 * InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) * No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 * Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) +* Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) + * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 Bugfixes 🐛 ===================== diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index c9f0d94ac6a..ab5b182105f 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\ErrorType; @@ -245,7 +244,7 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance }; return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references, $objectCb): Type { - if (BleedingEdgeToggle::isBleedingEdge() && $type instanceof GenericObjectType) { + if ($type instanceof GenericObjectType) { return TypeTraverser::map($type, $objectCb); } diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 66fd32a2fd3..13440a54a70 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -2,12 +2,10 @@ namespace PHPStan\Type\Generic; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; -use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; @@ -268,13 +266,6 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap TemplateTypeVariance::createStatic(), )); if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) { - if (!BleedingEdgeToggle::isBleedingEdge() && $this->shouldGeneralizeInferredType()) { - $generalizedType = $receivedType->generalize(GeneralizePrecision::templateArgument()); - if ($resolvedBound->isSuperTypeOf($generalizedType)->yes()) { - $receivedType = $generalizedType; - } - } - return (new TemplateTypeMap([ $this->name => $receivedType, ]))->union($map); From 0253a68fcbb04253c0e64efd5002c15ca80c2f5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:51:23 +0200 Subject: [PATCH 0277/3097] [BE] Non-static methods cannot be used as static callables in PHP 8+ --- changelog-2.0.md | 2 +- src/Type/Constant/ConstantArrayType.php | 4 +--- src/Type/Constant/ConstantStringType.php | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 628c9412d80..e49e16733ee 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -57,7 +57,6 @@ Bleeding edge (TODO move to other sections) * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) * More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! -* Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! * More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 @@ -140,6 +139,7 @@ Improvements 🔧 * Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) * Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 +* Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! Bugfixes 🐛 ===================== diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 2161d3f4a2b..7d16286e4fb 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -4,7 +4,6 @@ use Nette\Utils\Strings; use PHPStan\Analyser\OutOfClassScope; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Internal\CombinationsHelper; use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; @@ -579,8 +578,7 @@ public function findTypeAndMethodNames(): array } if ( - BleedingEdgeToggle::isBleedingEdge() - && $has->yes() + $has->yes() && !$phpVersion->supportsCallableInstanceMethods() ) { $methodReflection = $type->getMethod($method->getValue(), new OutOfClassScope()); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 3aa75ee5fb6..48da3f2c481 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -6,7 +6,6 @@ use Nette\Utils\Strings; use PhpParser\Node\Name; use PHPStan\Analyser\OutOfClassScope; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; @@ -217,8 +216,7 @@ public function isCallable(): TrinaryLogic if ($classRef->hasMethod($matches[2])) { $method = $classRef->getMethod($matches[2], new OutOfClassScope()); if ( - BleedingEdgeToggle::isBleedingEdge() - && !$phpVersion->supportsCallableInstanceMethods() + !$phpVersion->supportsCallableInstanceMethods() && !$method->isStatic() ) { return TrinaryLogic::createNo(); From 39b82cdd415faf7f4f62149a860e6a073770af5c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:53:53 +0200 Subject: [PATCH 0278/3097] [BE] Analysis with zero files results in non-zero exit code --- changelog-2.0.md | 2 +- src/Command/AnalyseCommand.php | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index e49e16733ee..ec3add18a15 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -59,7 +59,6 @@ Bleeding edge (TODO move to other sections) * More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! * More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! -* Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 * Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! * `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! * Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! @@ -140,6 +139,7 @@ Improvements 🔧 * Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 * Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! +* Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 Bugfixes 🐛 ===================== diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index f4281c48e41..d7ff2a61321 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -244,17 +244,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if (count($files) === 0) { - $bleedingEdge = (bool) $container->getParameter('featureToggles')['zeroFiles']; - $this->runDiagnoseExtensions($container, $inceptionResult->getErrorOutput()); - if (!$bleedingEdge) { - $inceptionResult->getErrorOutput()->getStyle()->note('No files found to analyse.'); - $inceptionResult->getErrorOutput()->getStyle()->warning('This will cause a non-zero exit code in PHPStan 2.0.'); - - return $inceptionResult->handleReturn(0, null, $this->analysisStartTime); - } - $inceptionResult->getErrorOutput()->getStyle()->error('No files found to analyse.'); return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); From 741c4011cf42e02cb1ca2da01739bf44b455891b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:54:21 +0200 Subject: [PATCH 0279/3097] [BE] Fail build when project config uses custom extensions outside of analysed paths --- changelog-2.0.md | 4 ++-- src/Command/AnalyseCommand.php | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index ec3add18a15..d1343fa30c3 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -93,8 +93,6 @@ Bleeding edge (TODO move to other sections) * Deprecated: returning plain strings as errors, use RuleErrorBuilder * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Deprecated: returning RuleError without identifier (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) -* Fail build when project config uses custom extensions outside of analysed paths - * This will only occur after a run that uses already present and valid result cache * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) @@ -140,6 +138,8 @@ Improvements 🔧 * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 * Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 +* Fail build when project config uses custom extensions outside of analysed paths + * This will only occur after a run that uses already present and valid result cache Bugfixes 🐛 ===================== diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index d7ff2a61321..5e4aa61443f 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -503,12 +503,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $errorOutput->writeLineFormatted(''); - $bleedingEdge = (bool) $container->getParameter('featureToggles')['projectServicesNotInAnalysedPaths']; - if ($bleedingEdge) { - return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes(), $this->analysisStartTime); - } - - $errorOutput->getStyle()->warning('This will cause a non-zero exit code in PHPStan 2.0.'); + return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes(), $this->analysisStartTime); } } From e4a3488fca79b8cd4aa04ea0883ed674d8d6772c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:57:07 +0200 Subject: [PATCH 0280/3097] [BE] Countable stub with `0|positive-int` --- changelog-2.0.md | 3 ++- conf/config.neon | 8 +------- src/PhpDoc/CountableStubFilesExtension.php | 21 --------------------- stubs/Countable.stub | 2 +- stubs/bleedingEdge/Countable.stub | 9 --------- 5 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 src/PhpDoc/CountableStubFilesExtension.php delete mode 100644 stubs/bleedingEdge/Countable.stub diff --git a/changelog-2.0.md b/changelog-2.0.md index d1343fa30c3..6e690a7ca4d 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -9,7 +9,6 @@ Bleeding edge (TODO move to other sections) ===================== * Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 -* Countable stub with `0|positive-int` ([#1027](https://github.com/phpstan/phpstan-src/pull/1027)), thanks @staabm! * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * Lower memory consumption thanks to breaking up of reference cycles @@ -147,5 +146,7 @@ Bugfixes 🐛 Function signature fixes 🤖 ======================= +* Countable stub with `0|positive-int` ([#1027](https://github.com/phpstan/phpstan-src/pull/1027)), thanks @staabm! + Internals 🔍 ===================== diff --git a/conf/config.neon b/conf/config.neon index 552b99403a1..38c6cb62060 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -195,6 +195,7 @@ parameters: - ../stubs/arrayFunctions.stub - ../stubs/core.stub - ../stubs/typeCheckingFunctions.stub + - ../stubs/Countable.stub earlyTerminatingMethodCalls: [] earlyTerminatingFunctionCalls: [] memoryLimitFile: %tmpDir%/.memory_limit @@ -456,13 +457,6 @@ services: arguments: duplicateStubs: %featureToggles.duplicateStubs% - - - class: PHPStan\PhpDoc\CountableStubFilesExtension - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - tags: - - phpstan.stubFilesExtension - - class: PHPStan\PhpDoc\SocketSelectStubFilesExtension tags: diff --git a/src/PhpDoc/CountableStubFilesExtension.php b/src/PhpDoc/CountableStubFilesExtension.php deleted file mode 100644 index 3fc3a156668..00000000000 --- a/src/PhpDoc/CountableStubFilesExtension.php +++ /dev/null @@ -1,21 +0,0 @@ -bleedingEdge) { - return [__DIR__ . '/../../stubs/bleedingEdge/Countable.stub']; - } - - return [__DIR__ . '/../../stubs/Countable.stub']; - } - -} diff --git a/stubs/Countable.stub b/stubs/Countable.stub index c69c9dba438..2491db1ce59 100644 --- a/stubs/Countable.stub +++ b/stubs/Countable.stub @@ -3,7 +3,7 @@ interface Countable { /** - * @return int + * @return 0|positive-int */ public function count(): int; } diff --git a/stubs/bleedingEdge/Countable.stub b/stubs/bleedingEdge/Countable.stub deleted file mode 100644 index 2491db1ce59..00000000000 --- a/stubs/bleedingEdge/Countable.stub +++ /dev/null @@ -1,9 +0,0 @@ - Date: Mon, 16 Sep 2024 10:57:46 +0200 Subject: [PATCH 0281/3097] [BE] Require identifier in custom rules --- changelog-2.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 6e690a7ca4d..f920c123b14 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -91,7 +91,6 @@ Bleeding edge (TODO move to other sections) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Deprecated: returning plain strings as errors, use RuleErrorBuilder * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) -* Deprecated: returning RuleError without identifier (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) @@ -139,6 +138,7 @@ Improvements 🔧 * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 * Fail build when project config uses custom extensions outside of analysed paths * This will only occur after a run that uses already present and valid result cache +* Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) Bugfixes 🐛 ===================== From 6a62c4e7f0a362d9169263e5d103e7cc04f78dcb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 10:59:32 +0200 Subject: [PATCH 0282/3097] [BE] Require RuleErrorBuilder and identifiers in custom rules --- UPGRADING.md | 22 ++++++++++++++++++++++ changelog-2.0.md | 4 ++-- conf/bleedingEdge.neon | 2 -- src/Rules/Rule.php | 2 +- stubs/bleedingEdge/Rule.stub | 26 -------------------------- 5 files changed, 25 insertions(+), 31 deletions(-) delete mode 100644 stubs/bleedingEdge/Rule.stub diff --git a/UPGRADING.md b/UPGRADING.md index 2e9830c098f..f31af1f3d3a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -68,6 +68,28 @@ Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Th See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. +### Returning plain strings as errors no longer supported, use RuleErrorBuilder + * +Identifiers are also required in custom rules. + +Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) + +Before: + +```php +return ['My error']; +``` + +After: + +```php +return [ + RuleErrorBuilder::mesage('My error') + ->identifier('my.error') + ->build(), +]; +``` + ### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters [`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): diff --git a/changelog-2.0.md b/changelog-2.0.md index f920c123b14..b4d4ac25c06 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -89,8 +89,6 @@ Bleeding edge (TODO move to other sections) * BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) -* Deprecated: returning plain strings as errors, use RuleErrorBuilder - * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) @@ -138,6 +136,8 @@ Improvements 🔧 * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 * Fail build when project config uses custom extensions outside of analysed paths * This will only occur after a run that uses already present and valid result cache +* Returning plain strings as errors no longer supported, use RuleErrorBuilder + * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) Bugfixes 🐛 diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index c5f05f82081..1742fadfaf8 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -57,5 +57,3 @@ parameters: explicitThrow: true absentTypeChecks: true requireFileExists: true - stubFiles: - - ../stubs/bleedingEdge/Rule.stub diff --git a/src/Rules/Rule.php b/src/Rules/Rule.php index e145e4b530f..293c06e6221 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/Rule.php @@ -32,7 +32,7 @@ public function getNodeType(): string; /** * @phpstan-param TNodeType $node - * @return (string|RuleError)[] errors + * @return list */ public function processNode(Node $node, Scope $scope): array; diff --git a/stubs/bleedingEdge/Rule.stub b/stubs/bleedingEdge/Rule.stub deleted file mode 100644 index 0a86ea9d2c3..00000000000 --- a/stubs/bleedingEdge/Rule.stub +++ /dev/null @@ -1,26 +0,0 @@ - - */ - public function getNodeType(): string; - - /** - * @phpstan-param TNodeType $node - * @return list - */ - public function processNode(Node $node, Scope $scope): array; - -} From 9437056907cedf1916a4298d2bbdd2c5ad28d27e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 16 Sep 2024 12:15:36 +0200 Subject: [PATCH 0283/3097] RegexArrayShapeMatcher - Fix matching literal dot character --- src/Type/Regex/RegexGroupParser.php | 12 ++++--- tests/PHPStan/Analyser/nsrt/bug-11699.php | 42 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11699.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 03bbcb6cceb..b4000d6ada0 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -445,14 +445,14 @@ private function walkGroupAst( foreach ($children as $child) { $oldLiterals = $onlyLiterals; - $this->getLiteralValue($child, $oldLiterals, true, $patternModifiers); + $this->getLiteralValue($child, $oldLiterals, true, $patternModifiers, true); foreach ($oldLiterals ?? [] as $oldLiteral) { $newLiterals[] = $oldLiteral; } } $onlyLiterals = $newLiterals; } elseif ($ast->getId() === 'token') { - $literalValue = $this->getLiteralValue($ast, $onlyLiterals, !$inClass, $patternModifiers); + $literalValue = $this->getLiteralValue($ast, $onlyLiterals, !$inClass, $patternModifiers, false); if ($literalValue !== null) { if (Strings::match($literalValue, '/^\d+$/') === null) { $isNumeric = TrinaryLogic::createNo(); @@ -508,7 +508,7 @@ private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool } } - $literal = $this->getLiteralValue($node, $onlyLiterals, false, $patternModifiers); + $literal = $this->getLiteralValue($node, $onlyLiterals, false, $patternModifiers, false); if ($literal !== null) { if ($literal !== '' && $literal !== '0') { $isNonFalsy = true; @@ -528,7 +528,7 @@ private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool /** * @param array|null $onlyLiterals */ - private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $appendLiterals, string $patternModifiers): ?string + private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $appendLiterals, string $patternModifiers, bool $inCharacterClass): ?string { if ($node->getId() !== 'token') { return null; @@ -551,15 +551,17 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap return null; } + $isEscaped = false; if (strlen($value) > 1 && $value[0] === '\\') { $value = substr($value, 1) ?: ''; + $isEscaped = true; } if ( $appendLiterals && in_array($token, ['literal', 'range', 'class_', '_class_literal'], true) && $onlyLiterals !== null - && !in_array($value, ['.'], true) + && (!in_array($value, ['.'], true) || $isEscaped || $inCharacterClass) ) { if ($onlyLiterals === []) { $onlyLiterals = [$value]; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11699.php b/tests/PHPStan/Analyser/nsrt/bug-11699.php new file mode 100644 index 00000000000..b79e076e418 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11699.php @@ -0,0 +1,42 @@ +[\~,\?\.])~', $string, $match); + if ($result === 1) { + assertType("','|'.'|'?'|'~'", $match['AB']); + } +} + +function doFoo1():void { + $string = 'Foo.bar'; + $match = []; + $result = preg_match('~(?[\~,\?.])~', $string, $match); // dot in character class does not need to be escaped + if ($result === 1) { + assertType("','|'.'|'?'|'~'", $match['AB']); + } +} + +function doFoo2():void { + $string = 'Foo.bar'; + $match = []; + $result = preg_match('~(?.)~', $string, $match); + if ($result === 1) { + assertType("non-empty-string", $match['AB']); + } +} + + +function doFoo3():void { + $string = 'Foo.bar'; + $match = []; + $result = preg_match('~(?\.)~', $string, $match); + if ($result === 1) { + assertType("'.'", $match['AB']); + } +} From 21050587db155d3f69d2067cf3ea2a7145f50bca Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 16 Sep 2024 12:18:34 +0200 Subject: [PATCH 0284/3097] Added regression test --- tests/PHPStan/Analyser/nsrt/bug-11699.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-11699.php b/tests/PHPStan/Analyser/nsrt/bug-11699.php index b79e076e418..65eebc78a62 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11699.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11699.php @@ -40,3 +40,12 @@ function doFoo3():void { assertType("'.'", $match['AB']); } } + +function doFoo4():void { + $string = 'Foo.bar'; + $match = []; + $result = preg_match('~(?[^\~,\?\.])~', $string, $match); + if ($result === 1) { + assertType("non-empty-string", $match['AB']); + } +} From b3be0259df63540a71ffbdf85b3afadf017a4d25 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 12:46:37 +0200 Subject: [PATCH 0285/3097] Fix tests --- tests/PHPStan/Command/CommandHelperTest.php | 1 + tests/PHPStan/Command/exclude-paths/full.neon | 2 +- tests/PHPStan/Command/exclude-paths/including-mixed.neon | 2 +- tests/PHPStan/Command/exclude-paths/including.neon | 4 ++-- tests/PHPStan/Command/exclude-paths/straightforward.neon | 2 +- tests/PHPStan/Command/exclude-paths/test/.gitkeep | 0 tests/PHPStan/Command/relative-paths/nested/nested.neon | 1 + tests/PHPStan/File/FileExcluderTest.php | 7 +------ 8 files changed, 8 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Command/exclude-paths/test/.gitkeep diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index d5a14801990..817dea23f80 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -205,6 +205,7 @@ public function dataParameters(): array __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'up.php', ], + 'reportUnmatchedIgnoredErrors' => false, 'ignoreErrors' => [ [ 'message' => '#aaa#', diff --git a/tests/PHPStan/Command/exclude-paths/full.neon b/tests/PHPStan/Command/exclude-paths/full.neon index bb813f036ea..275024f3a07 100644 --- a/tests/PHPStan/Command/exclude-paths/full.neon +++ b/tests/PHPStan/Command/exclude-paths/full.neon @@ -3,4 +3,4 @@ parameters: analyse: - test analyseAndScan: - - test2 + - test2 (?) diff --git a/tests/PHPStan/Command/exclude-paths/including-mixed.neon b/tests/PHPStan/Command/exclude-paths/including-mixed.neon index d7b9f3ae388..5cb7be683dc 100644 --- a/tests/PHPStan/Command/exclude-paths/including-mixed.neon +++ b/tests/PHPStan/Command/exclude-paths/including-mixed.neon @@ -6,4 +6,4 @@ parameters: analyse: - test analyseAndScan: - - test2 + - test2 (?) diff --git a/tests/PHPStan/Command/exclude-paths/including.neon b/tests/PHPStan/Command/exclude-paths/including.neon index 199397750d0..058059038be 100644 --- a/tests/PHPStan/Command/exclude-paths/including.neon +++ b/tests/PHPStan/Command/exclude-paths/including.neon @@ -4,6 +4,6 @@ includes: parameters: excludePaths: analyse: - - test2 + - test2 (?) analyseAndScan: - - test3 + - test3 (?) diff --git a/tests/PHPStan/Command/exclude-paths/straightforward.neon b/tests/PHPStan/Command/exclude-paths/straightforward.neon index 2b5facc315d..6a4a1bd0ab4 100644 --- a/tests/PHPStan/Command/exclude-paths/straightforward.neon +++ b/tests/PHPStan/Command/exclude-paths/straightforward.neon @@ -1,4 +1,4 @@ parameters: excludePaths: - test - - test2 + - test2 (?) diff --git a/tests/PHPStan/Command/exclude-paths/test/.gitkeep b/tests/PHPStan/Command/exclude-paths/test/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/PHPStan/Command/relative-paths/nested/nested.neon b/tests/PHPStan/Command/relative-paths/nested/nested.neon index 6e097edf212..c004fd54b65 100644 --- a/tests/PHPStan/Command/relative-paths/nested/nested.neon +++ b/tests/PHPStan/Command/relative-paths/nested/nested.neon @@ -3,6 +3,7 @@ parameters: - here.php - test/there.php - ../up.php + reportUnmatchedIgnoredErrors: false ignoreErrors: - message: '#aaa#' diff --git a/tests/PHPStan/File/FileExcluderTest.php b/tests/PHPStan/File/FileExcluderTest.php index f5c746a3ade..860b779e751 100644 --- a/tests/PHPStan/File/FileExcluderTest.php +++ b/tests/PHPStan/File/FileExcluderTest.php @@ -142,7 +142,7 @@ public function dataExcludeOnUnix(): array ], [ __DIR__ . '/data/excluded-file.php', - [__DIR__], + [__DIR__ . '/*'], true, ], [ @@ -170,11 +170,6 @@ public function dataExcludeOnUnix(): array [__DIR__ . '/data/[pP]arse-[eE]rror.ph[pP]'], true, ], - [ - __DIR__ . '/data/parse-error.php', - ['*/tests/PHPStan/File/data/*'], - true, - ], [ __DIR__ . '/data/parse-error.php', [__DIR__ . '/aaa'], From f7a73266acb96059da8a2885e3d41a8d6a4528bf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 13:02:20 +0200 Subject: [PATCH 0286/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/11126 Closes https://github.com/phpstan/phpstan/issues/11032 Closes https://github.com/phpstan/phpstan/issues/10653 --- changelog-2.0.md | 2 +- tests/PHPStan/Analyser/nsrt/bug-10653.php | 13 ++++++ .../Rules/Functions/ReturnTypeRuleTest.php | 14 +++++++ .../Rules/Functions/data/bug-11032.php | 42 +++++++++++++++++++ .../Rules/Functions/data/bug-11126.php | 41 ++++++++++++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 5 +++ .../PHPStan/Rules/Methods/data/bug-10653.php | 42 +++++++++++++++++++ 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10653.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11032.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11126.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10653.php diff --git a/changelog-2.0.md b/changelog-2.0.md index b4d4ac25c06..8407fcacd17 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -131,7 +131,7 @@ Improvements 🔧 * No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 * Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) * Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) - * This fixes following **17 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092 + * This fixes following **20 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092, #11126, #11032, #10653 * Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! * Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 * Fail build when project config uses custom extensions outside of analysed paths diff --git a/tests/PHPStan/Analyser/nsrt/bug-10653.php b/tests/PHPStan/Analyser/nsrt/bug-10653.php new file mode 100644 index 00000000000..fc6642a2299 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10653.php @@ -0,0 +1,13 @@ +mayFail(); + assertType('stdClass|false', $value); + $value = $a->throwOnFailure($value); + assertType(stdClass::class, $value); +}; diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 53476ec81bc..5ba98544fb0 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -301,4 +301,18 @@ public function testBug8881(): void $this->analyse([__DIR__ . '/data/bug-8881.php'], []); } + public function testBug11126(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-11126.php'], []); + } + + public function testBug11032(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-11032.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11032.php b/tests/PHPStan/Rules/Functions/data/bug-11032.php new file mode 100644 index 00000000000..ea967f31ee1 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11032.php @@ -0,0 +1,42 @@ + + */ + private $promise = null; + + /** + * @return PromiseInterface + */ + public function promise(): PromiseInterface + { + return $this->promise; + } +} + +/** + * @template T + * @param iterable $tasks + * @return PromiseInterface> + */ +function parallel(iterable $tasks): PromiseInterface +{ + /** @var Deferred> $deferred*/ + $deferred = new Deferred(); + + return $deferred->promise(); +} diff --git a/tests/PHPStan/Rules/Functions/data/bug-11126.php b/tests/PHPStan/Rules/Functions/data/bug-11126.php new file mode 100644 index 00000000000..8569f55ac05 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11126.php @@ -0,0 +1,41 @@ + + */ + public function map(callable $callback): Collection { + return $this; + } +} + +/** + * @param Collection> $in + * @return Collection> + */ +function foo(Collection $in): Collection { + return $in->map(static fn ($v) => $v); +} + +/** + * @param Collection> $in + * @return Collection> + */ +function bar(Collection $in): Collection { + return $in->map(value(...)); +} + +/** + * @param int<0, max> $in + * @return int<0, max> + */ +function value(int $in): int { + return $in; +} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 2730a29791a..5446ab66db1 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1081,4 +1081,9 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug10653(): void + { + $this->analyse([__DIR__ . '/data/bug-10653.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10653.php b/tests/PHPStan/Rules/Methods/data/bug-10653.php new file mode 100644 index 00000000000..3aaa3c735b4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10653.php @@ -0,0 +1,42 @@ +throwOnFailure($this->mayFail()); + } + + /** + * @template T + * + * @param T $result + * @return (T is false ? never : T) + */ + public function throwOnFailure($result) + { + if ($result === false) { + throw new Exception('Operation failed'); + } + return $result; + } + + /** + * @return stdClass|false + */ + public function mayFail() + { + $this->Counter++; + return $this->Counter % 2 ? new stdClass() : false; + } +} From 193b4f518d53eb3648b09ff131f83979067fec59 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 16:33:28 +0200 Subject: [PATCH 0287/3097] Errors with `argument.named` are ignorable now --- src/Rules/FunctionCallParametersCheck.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 4f0d2ae447e..6a1d2f16b11 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -295,7 +295,6 @@ public function check( $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('named argument $%s', $argumentName))) ->identifier('argument.named') ->line($argumentLine) - ->nonIgnorable() ->build(); } elseif ($unpack) { $unpackedArrayType = $scope->getType($argumentValue); @@ -304,7 +303,6 @@ public function check( $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('unpacked array with %s', $hasStringKey->yes() ? 'string key' : 'possibly string key'))) ->identifier('argument.named') ->line($argumentLine) - ->nonIgnorable() ->build(); } } From 2d613997f5a9298b2446d1a0b2f01a565ed8a457 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 16:42:23 +0200 Subject: [PATCH 0288/3097] CollectedDataNode is VirtualNode --- src/Node/CollectedDataNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/CollectedDataNode.php b/src/Node/CollectedDataNode.php index c02175b246a..7588947625f 100644 --- a/src/Node/CollectedDataNode.php +++ b/src/Node/CollectedDataNode.php @@ -12,7 +12,7 @@ * @api * @final */ -class CollectedDataNode extends NodeAbstract +class CollectedDataNode extends NodeAbstract implements VirtualNode { /** From 92a951f7f7aea828474d7a4a882804262a6603a2 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:41:00 +0000 Subject: [PATCH 0289/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 7ce4bd47b28..1939d5df29d 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.104", + "phpstan/php-8-stubs": "0.3.105", "phpstan/phpdoc-parser": "1.30.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index f8a221dffe3..bed27023af2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ad10e33b3f3e87a1ee4afc90835a8e59", + "content-hash": "76c100fc288f1215d63ee88908164a3a", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.104", + "version": "0.3.105", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "a25ce7cc2c246653e8cd9c0c5669ad7465ccb297" + "reference": "16dafadf67f56e715a4e8d5093a28435640d5df8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/a25ce7cc2c246653e8cd9c0c5669ad7465ccb297", - "reference": "a25ce7cc2c246653e8cd9c0c5669ad7465ccb297", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/16dafadf67f56e715a4e8d5093a28435640d5df8", + "reference": "16dafadf67f56e715a4e8d5093a28435640d5df8", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.104" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.105" }, - "time": "2024-09-11T15:53:22+00:00" + "time": "2024-09-16T14:40:26+00:00" }, { "name": "phpstan/phpdoc-parser", From 5d17773d65ce7f528d076ff61d4c240e560b034e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 17:21:18 +0200 Subject: [PATCH 0290/3097] RuleErrorTransformer - accept only IdentifierRuleError --- src/Analyser/RuleErrorTransformer.php | 62 +++++++++++---------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 0cb9f9ef98a..916f284505b 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -8,9 +8,7 @@ use PHPStan\Rules\LineRuleError; use PHPStan\Rules\MetadataRuleError; use PHPStan\Rules\NonIgnorableRuleError; -use PHPStan\Rules\RuleError; use PHPStan\Rules\TipRuleError; -use function is_string; final class RuleErrorTransformer { @@ -19,7 +17,7 @@ final class RuleErrorTransformer * @param class-string $nodeType */ public function transform( - string|RuleError $ruleError, + IdentifierRuleError $ruleError, Scope $scope, string $nodeType, int $nodeLine, @@ -31,7 +29,6 @@ public function transform( $filePath = $scope->getFile(); $traitFilePath = null; $tip = null; - $identifier = null; $metadata = []; if ($scope->isInTrait()) { $traitReflection = $scope->getTraitReflection(); @@ -39,43 +36,36 @@ public function transform( $traitFilePath = $traitReflection->getFileName(); } } - if (is_string($ruleError)) { - $message = $ruleError; - } else { - $message = $ruleError->getMessage(); - if ( - $ruleError instanceof LineRuleError - && $ruleError->getLine() !== -1 - ) { - $line = $ruleError->getLine(); - } - if ( - $ruleError instanceof FileRuleError - && $ruleError->getFile() !== '' - ) { - $fileName = $ruleError->getFileDescription(); - $filePath = $ruleError->getFile(); - $traitFilePath = null; - } - if ($ruleError instanceof TipRuleError) { - $tip = $ruleError->getTip(); - } + if ( + $ruleError instanceof LineRuleError + && $ruleError->getLine() !== -1 + ) { + $line = $ruleError->getLine(); + } + if ( + $ruleError instanceof FileRuleError + && $ruleError->getFile() !== '' + ) { + $fileName = $ruleError->getFileDescription(); + $filePath = $ruleError->getFile(); + $traitFilePath = null; + } - if ($ruleError instanceof IdentifierRuleError) { - $identifier = $ruleError->getIdentifier(); - } + if ($ruleError instanceof TipRuleError) { + $tip = $ruleError->getTip(); + } - if ($ruleError instanceof MetadataRuleError) { - $metadata = $ruleError->getMetadata(); - } + if ($ruleError instanceof MetadataRuleError) { + $metadata = $ruleError->getMetadata(); + } - if ($ruleError instanceof NonIgnorableRuleError) { - $canBeIgnored = false; - } + if ($ruleError instanceof NonIgnorableRuleError) { + $canBeIgnored = false; } + return new Error( - $message, + $ruleError->getMessage(), $fileName, $line, $canBeIgnored, @@ -84,7 +74,7 @@ public function transform( $tip, $nodeLine, $nodeType, - $identifier, + $ruleError->getIdentifier(), $metadata, ); } From 9b91afb90d918aeec808f49fa7d5c8f46307d99a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 13:12:26 +0200 Subject: [PATCH 0291/3097] [BE] List type --- changelog-2.0.md | 5 ++-- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/Analyser/MutatingScope.php | 17 ++++++------- src/Analyser/NodeScopeResolver.php | 6 ++--- src/Analyser/TypeSpecifier.php | 2 +- src/DependencyInjection/ContainerFactory.php | 2 -- src/PhpDoc/TypeNodeResolver.php | 12 ++++------ .../InitializerExprTypeResolver.php | 4 ++-- src/Rules/Functions/ArrayValuesRule.php | 5 ---- src/Type/Accessory/AccessoryArrayListType.php | 21 ---------------- src/Type/ArrayType.php | 6 ++--- src/Type/Constant/ConstantArrayType.php | 6 ++--- .../Constant/ConstantArrayTypeBuilder.php | 2 +- src/Type/Constant/OversizedArrayBuilder.php | 2 +- src/Type/MixedType.php | 6 ++--- .../ArrayChunkFunctionReturnTypeExtension.php | 4 ++-- ...ArrayColumnFunctionReturnTypeExtension.php | 2 +- .../ArrayFillFunctionReturnTypeExtension.php | 2 +- .../ArrayMapFunctionReturnTypeExtension.php | 2 +- ...ergeFunctionDynamicReturnTypeExtension.php | 2 +- ...lodeFunctionDynamicReturnTypeExtension.php | 2 +- ...lterVarArrayDynamicReturnTypeExtension.php | 4 ++-- ...atorToArrayFunctionReturnTypeExtension.php | 3 ++- .../PregSplitDynamicReturnTypeExtension.php | 2 +- .../Php/RangeFunctionReturnTypeExtension.php | 24 ++++++++++--------- src/Type/Php/RegexArrayShapeMatcher.php | 6 ++--- .../StrSplitFunctionReturnTypeExtension.php | 2 +- src/Type/TypeCombinator.php | 4 ++-- stubs/arrayFunctions.stub | 2 +- .../Php8SignatureMapProviderTest.php | 6 +++-- 32 files changed, 69 insertions(+), 97 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 8407fcacd17..643a7a532fd 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -5,6 +5,9 @@ When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](h Major new features 🚀 ===================== +* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! + * Lists are arrays with sequential integer keys starting at 0 + Bleeding edge (TODO move to other sections) ===================== @@ -30,8 +33,6 @@ Bleeding edge (TODO move to other sections) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! -* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! - * Lists are arrays with sequential integer keys starting at 0 * Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1742fadfaf8..177fbdc689b 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -19,7 +19,6 @@ parameters: runtimeReflectionRules: true notAnalysedTrait: true curlSetOptTypes: true - listType: true abstractTraitMethod: true missingMagicSerializationRule: true nullContextForVoidReturningFunctions: true diff --git a/conf/config.neon b/conf/config.neon index 38c6cb62060..b9623abaa3a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -54,7 +54,6 @@ parameters: runtimeReflectionRules: false notAnalysedTrait: false curlSetOptTypes: false - listType: false abstractTraitMethod: false missingMagicSerializationRule: false nullContextForVoidReturningFunctions: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 6b8a6c44b72..f5b9ed153d3 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: runtimeReflectionRules: bool() notAnalysedTrait: bool() curlSetOptTypes: bool() - listType: bool() abstractTraitMethod: bool() missingMagicSerializationRule: bool() nullContextForVoidReturningFunctions: bool() diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6de895a04b0..441778bf219 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -526,10 +526,11 @@ public function getVariableType(string $variableName): Type return IntegerRangeType::fromInterval(1, null); } if ($variableName === 'argv') { - return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( + return TypeCombinator::intersect( new ArrayType(new IntegerType(), new StringType()), new NonEmptyArrayType(), - )); + new AccessoryArrayListType(), + ); } if ($this->canAnyVariableExist()) { return new MixedType(); @@ -3175,7 +3176,7 @@ private function enterFunctionLike( if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType); } else { - $parameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $parameterType)); + $parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType()); } } $parameterNode = new Variable($parameter->getName()); @@ -3190,7 +3191,7 @@ private function enterFunctionLike( if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType); } else { - $nativeParameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $nativeParameterType)); + $nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType()); } } $nativeExpressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $nativeParameterType); @@ -3670,11 +3671,11 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type )); } - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $this->getFunctionType( + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getFunctionType( $type, false, false, - ))); + )), new AccessoryArrayListType()); } if ($type instanceof Name) { @@ -5079,7 +5080,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); } if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) { - $resultType = AccessoryArrayListType::intersectWith($resultType); + $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType()); } $resultTypes[] = $resultType; } @@ -5122,7 +5123,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); } if ($generalArraysA->isList()->yes() && $generalArraysB->isList()->yes()) { - $resultType = AccessoryArrayListType::intersectWith($resultType); + $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType()); } if ($generalArraysA->isOversizedArray()->yes() && $generalArraysB->isOversizedArray()->yes()) { $resultType = TypeCombinator::intersect($resultType, new OversizedArrayType()); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3bd5080928b..8bcb6c1af23 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2455,7 +2455,7 @@ static function (): void { $functionReflection !== null && in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true) ) { - $scope = $scope->assignVariable('http_response_header', AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes()); + $scope = $scope->assignVariable('http_response_header', TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes()); } if ( @@ -3841,7 +3841,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra ? TypeCombinator::intersect($array, new NonEmptyArrayType()) : $array; $constantArray = $isList - ? AccessoryArrayListType::intersectWith($constantArray) + ? TypeCombinator::intersect($constantArray, new AccessoryArrayListType()) : $constantArray; } @@ -3884,7 +3884,7 @@ private function getArraySortPreserveListFunctionType(Type $type): Type return $type; } - $newArrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $type->getIterableValueType())); + $newArrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $type->getIterableValueType()), new AccessoryArrayListType()); if ($isIterableAtLeastOnce->yes()) { $newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); } diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5c349ce624a..327acb7cf73 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -315,7 +315,7 @@ public function specifyTypesInCondition( if ($argType->isArray()->yes()) { $newType = new NonEmptyArrayType(); if ($context->true() && $argType->isList()->yes()) { - $newType = AccessoryArrayListType::intersectWith($newType); + $newType = TypeCombinator::intersect($newType, new AccessoryArrayListType()); } $result = $result->unionWith( diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 4cd4dfc0dbe..1e980d2f320 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -31,7 +31,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\ObjectType; use Symfony\Component\Finder\Finder; @@ -192,7 +191,6 @@ public static function postInitializeContainer(Container $container): void $container->getService('typeSpecifier'); BleedingEdgeToggle::setBleedingEdge($container->getParameter('featureToggles')['bleedingEdge']); - AccessoryArrayListType::setListTypeEnabled($container->getParameter('featureToggles')['listType']); TemplateTypeVariance::setInvarianceCompositionEnabled($container->getParameter('featureToggles')['invarianceComposition']); } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index ccfd16f32ac..20df016e0c5 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -409,15 +409,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new NonAcceptingNeverType(); case 'list': - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType())); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType()), new AccessoryArrayListType()); case 'non-empty-list': - return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( - new ArrayType(new IntegerType(), new MixedType()), - new NonEmptyArrayType(), - )); - case '__always-list': return TypeCombinator::intersect( new ArrayType(new IntegerType(), new MixedType()), + new NonEmptyArrayType(), new AccessoryArrayListType(), ); @@ -657,7 +653,7 @@ static function (string $variance): TemplateTypeVariance { return $arrayType; } elseif (in_array($mainTypeName, ['list', 'non-empty-list'], true)) { if (count($genericTypes) === 1) { // list - $listType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $genericTypes[0])); + $listType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $genericTypes[0]), new AccessoryArrayListType()); if ($mainTypeName === 'non-empty-list') { return TypeCombinator::intersect($listType, new NonEmptyArrayType()); } @@ -995,7 +991,7 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name $arrayType = $builder->getArray(); if ($typeNode->kind === ArrayShapeNode::KIND_LIST) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 6d4734b3970..b4beb587bfa 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -568,7 +568,7 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type $arrayType = $arrayBuilder->getArray(); if ($isList === true) { - return AccessoryArrayListType::intersectWith($arrayType); + return TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; @@ -1041,7 +1041,7 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } if ($leftType->isList()->yes() && $rightType->isList()->yes()) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; diff --git a/src/Rules/Functions/ArrayValuesRule.php b/src/Rules/Functions/ArrayValuesRule.php index cf058b54a87..e2b44cf3992 100644 --- a/src/Rules/Functions/ArrayValuesRule.php +++ b/src/Rules/Functions/ArrayValuesRule.php @@ -10,7 +10,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\VerbosityLevel; use function count; use function sprintf; @@ -39,10 +38,6 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (AccessoryArrayListType::isListTypeEnabled() === false) { - return []; - } - if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { return []; } diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index f80ada4ad90..472bdeb1c1b 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -39,8 +39,6 @@ class AccessoryArrayListType implements CompoundType, AccessoryType use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; - private static bool $enabled = false; - public function __construct() { } @@ -468,25 +466,6 @@ public static function __set_state(array $properties): Type return new self(); } - public static function setListTypeEnabled(bool $enabled): void - { - self::$enabled = $enabled; - } - - public static function isListTypeEnabled(): bool - { - return self::$enabled; - } - - public static function intersectWith(Type $type): Type - { - if (self::$enabled) { - return TypeCombinator::intersect($type, new self()); - } - - return $type; - } - public function exponentiate(Type $exponent): Type { return new ErrorType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 061e002a46d..33c3cdc4399 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -198,12 +198,12 @@ public function generalizeValues(): self public function getKeysArray(): Type { - return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->getIterableKeyType())); + return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType()); } public function getValuesArray(): Type { - return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->itemType)); + return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType()); } public function isIterable(): TrinaryLogic @@ -569,7 +569,7 @@ public function shiftArray(): Type public function shuffleArray(): Type { - return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->itemType)); + return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType()); } public function isCallable(): TrinaryLogic diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7d16286e4fb..2df98dd2193 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -904,7 +904,7 @@ public function shuffleArray(): Type $generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType()); } if ($valuesArray->isList->yes()) { - $generalizedArray = AccessoryArrayListType::intersectWith($generalizedArray); + $generalizedArray = TypeCombinator::intersect($generalizedArray, new AccessoryArrayListType()); } return $generalizedArray; @@ -1267,7 +1267,7 @@ public function generalize(GeneralizePrecision $precision): Type } if ($this->isList()->yes()) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } if (count($accessoryTypes) > 0) { @@ -1304,7 +1304,7 @@ public function generalizeToArray(): Type $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } if ($this->isList->yes()) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index a306833e8d0..952254e1b9d 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -306,7 +306,7 @@ public function getArray(): Type } if ($this->isList->yes()) { - $array = AccessoryArrayListType::intersectWith($array); + $array = TypeCombinator::intersect($array, new AccessoryArrayListType()); } return $array; diff --git a/src/Type/Constant/OversizedArrayBuilder.php b/src/Type/Constant/OversizedArrayBuilder.php index e6ac71ded58..026e305361d 100644 --- a/src/Type/Constant/OversizedArrayBuilder.php +++ b/src/Type/Constant/OversizedArrayBuilder.php @@ -92,7 +92,7 @@ public function build(Array_ $expr, callable $getTypeCallback): Type $arrayType = new ArrayType($keyType, $valueType); if ($isList) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return TypeCombinator::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType()); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index c45642f0d2b..77d3f55bbc0 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -177,7 +177,7 @@ public function getKeysArray(): Type return new ErrorType(); } - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new UnionType([new IntegerType(), new StringType()]))); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new UnionType([new IntegerType(), new StringType()])), new AccessoryArrayListType()); } public function getValuesArray(): Type @@ -186,7 +186,7 @@ public function getValuesArray(): Type return new ErrorType(); } - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed))); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()); } public function fillKeysArray(Type $valueType): Type @@ -258,7 +258,7 @@ public function shuffleArray(): Type return new ErrorType(); } - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed))); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()); } public function isCallable(): TrinaryLogic diff --git a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php index 1c8530ec3bb..fbec2c7582a 100644 --- a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php @@ -83,7 +83,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $chunkType = self::getChunkType($arrayType, $preserveKeys); - $resultType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $chunkType)); + $resultType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $chunkType), new AccessoryArrayListType()); if ($arrayType->isIterableAtLeastOnce()->yes()) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); } @@ -99,7 +99,7 @@ private static function getChunkType(Type $type, ?bool $preserveKeys): Type $chunkType = $type; } else { $chunkType = new ArrayType(new IntegerType(), $type->getIterableValueType()); - $chunkType = AccessoryArrayListType::intersectWith($chunkType); + $chunkType = TypeCombinator::intersect($chunkType, new AccessoryArrayListType()); } return TypeCombinator::intersect($chunkType, new NonEmptyArrayType()); diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 7bca91ccd97..51d8999c2fc 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -99,7 +99,7 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); } if ($indexType === null) { - $returnType = AccessoryArrayListType::intersectWith($returnType); + $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); } return $returnType; diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index 33ff1509e0b..fbadbac5938 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -78,7 +78,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $resultType = new ArrayType(new IntegerType(), $valueType); if ((new ConstantIntegerType(0))->isSuperTypeOf($startIndexType)->yes()) { - $resultType = AccessoryArrayListType::intersectWith($resultType); + $resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType()); } if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($numberType)->yes()) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e8e9e3a4575..5f8f7d1066f 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -94,7 +94,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $returnedArray = $returnedArrayBuilder->getArray(); if ($constantArray->isList()->yes()) { - $returnedArray = AccessoryArrayListType::intersectWith($returnedArray); + $returnedArray = TypeCombinator::intersect($returnedArray, new AccessoryArrayListType()); } $arrayTypes[] = $returnedArray; } diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 5b177a9f686..58bb5fe1ad3 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -132,7 +132,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } if ($isList) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index 9927cc252e5..73f074aca66 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -54,7 +54,7 @@ public function getTypeFromFunctionCall( return new ConstantBooleanType(false); } - $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + $returnType = TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()); if ( !isset($args[2]) || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($args[2]->value))->yes() diff --git a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php index 2eaa4308b4f..9f9a51cad3a 100644 --- a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php @@ -88,7 +88,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); $arrayType = new ArrayType($inputArgType->getIterableKeyType(), $valueType); - return $isList ? AccessoryArrayListType::intersectWith($arrayType) : $arrayType; + return $isList ? TypeCombinator::intersect($arrayType, new AccessoryArrayListType()) : $arrayType; } // Override $add_empty option @@ -116,7 +116,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $addEmpty ? TypeCombinator::addNull($valueType) : $valueType, ); - return $isList ? AccessoryArrayListType::intersectWith($arrayType) : $arrayType; + return $isList ? TypeCombinator::intersect($arrayType, new AccessoryArrayListType()) : $arrayType; } return null; diff --git a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php index 3eba7891757..3d23ca59b2d 100644 --- a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php +++ b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php @@ -10,6 +10,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function strtolower; final class IteratorToArrayFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -47,7 +48,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); if ($isList) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return $arrayType; diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index d898085584b..d51b5314b08 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -43,7 +43,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, new IntegerType(), new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), IntegerRangeType::fromInterval(0, null)], [2], [], TrinaryLogic::createYes()), ); - return TypeCombinator::union(AccessoryArrayListType::intersectWith($type), new ConstantBooleanType(false)); + return TypeCombinator::union(TypeCombinator::intersect($type, new AccessoryArrayListType()), new ConstantBooleanType(false)); } return null; diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index ed43f64f203..eec795c7eec 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -85,16 +85,17 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $startConstant = $endConstant; $endConstant = $tmp; } - return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( + return TypeCombinator::intersect( new ArrayType( new IntegerType(), IntegerRangeType::fromInterval($startConstant->getValue(), $endConstant->getValue()), ), new NonEmptyArrayType(), - )); + new AccessoryArrayListType(), + ); } - return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( + return TypeCombinator::intersect( new ArrayType( new IntegerType(), TypeCombinator::union( @@ -103,7 +104,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ), ), new NonEmptyArrayType(), - )); + new AccessoryArrayListType(), + ); } $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach ($rangeValues as $value) { @@ -125,30 +127,30 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if ($isInteger && $isStepInteger) { if ($argType instanceof IntegerRangeType) { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $argType)); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $argType), new AccessoryArrayListType()); } - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new IntegerType())); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new IntegerType()), new AccessoryArrayListType()); } if ($argType->isFloat()->yes()) { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new FloatType())); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new FloatType()), new AccessoryArrayListType()); } $numberType = new UnionType([new IntegerType(), new FloatType()]); $isNumber = $numberType->isSuperTypeOf($argType)->yes(); $isNumericString = $argType->isNumericString()->yes(); if ($isNumber || $isNumericString) { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $numberType)); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), $numberType), new AccessoryArrayListType()); } if ($argType->isString()->yes()) { - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()); } - return AccessoryArrayListType::intersectWith(new ArrayType( + return TypeCombinator::intersect(new ArrayType( new IntegerType(), new BenevolentUnionType([new IntegerType(), new FloatType(), new StringType()]), - )); + ), new AccessoryArrayListType()); } } diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 63e44b3ea26..7923e9744c4 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -354,7 +354,7 @@ private function buildArrayType( } if ($matchesAll && $this->containsSetOrder($flags)) { - $arrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $builder->getArray())); + $arrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $builder->getArray()), new AccessoryArrayListType()); if (!$wasMatched->yes()) { $arrayType = TypeCombinator::union( new ConstantArrayType([], []), @@ -382,7 +382,7 @@ private function createSubjectValueType(int $flags, bool $matchesAll): Type if ($matchesAll) { if ($this->containsPatternOrder($flags)) { - $subjectValueType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $subjectValueType)); + $subjectValueType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $subjectValueType), new AccessoryArrayListType()); } } @@ -433,7 +433,7 @@ private function createGroupValueType(RegexCapturingGroup $captureGroup, Trinary } if ($this->containsPatternOrder($flags)) { - $groupValueType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $groupValueType)); + $groupValueType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $groupValueType), new AccessoryArrayListType()); } return $groupValueType; diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 34d58152dcd..9c28bc3c694 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -103,7 +103,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return TypeCombinator::union(...$results); } - $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + $returnType = TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()); return $encoding === null && !$this->phpVersion->strSplitReturnsEmptyArray() ? TypeCombinator::intersect($returnType, new NonEmptyArrayType()) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 745c1c25276..3d2edcc4196 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -615,7 +615,7 @@ private static function processArrayAccessoryTypes(array $arrayTypes): array $constantArrays = $arrayType->getConstantArrays(); foreach ($constantArrays as $constantArray) { - if ($constantArray->isList()->yes() && AccessoryArrayListType::isListTypeEnabled()) { + if ($constantArray->isList()->yes()) { $list = new AccessoryArrayListType(); $accessoryTypes[$list->describe(VerbosityLevel::cache())][$i] = $list; } @@ -822,7 +822,7 @@ private static function optimizeConstantArrays(array $types): array $arrayType = new ArrayType($keyType, $valueType); if ($isList) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); } return TypeCombinator::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType()); diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 25c73249ec8..4c284613917 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -64,6 +64,6 @@ function array_udiff( /** * @param array $value - * @return ($value is __always-list ? true : false) + * @return ($value is list ? true : false) */ function array_is_list(array $value): bool {} diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index 57cb091e199..f5511bfc335 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; @@ -22,6 +23,7 @@ use PHPStan\Type\FileTypeMapper; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -98,9 +100,9 @@ public function dataFunctions(): array new ConstantStringType('errors'), ], [ IntegerRangeType::fromInterval(0, null), - new ArrayType(new IntegerType(), new StringType()), + new IntersectionType([new ArrayType(IntegerRangeType::fromInterval(0, null), new StringType()), new AccessoryArrayListType()]), IntegerRangeType::fromInterval(0, null), - new ArrayType(new IntegerType(), new StringType()), + new IntersectionType([new ArrayType(IntegerRangeType::fromInterval(0, null), new StringType()), new AccessoryArrayListType()]), ]), ]), new UnionType([ From 83bf3abd885baf88051da651bef4f531ccaebe2d Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 16 Sep 2024 17:35:16 +0200 Subject: [PATCH 0292/3097] Optimize NodeScopeResolverTest --- src/Testing/TypeInferenceTestCase.php | 23 +- .../Analyser/NodeScopeResolverTest.php | 255 +++++++++++------- 2 files changed, 173 insertions(+), 105 deletions(-) diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 4194d4f4d3e..a80e510da35 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -278,6 +278,21 @@ public static function gatherAssertTypes(string $file): array * @return array */ public static function gatherAssertTypesFromDirectory(string $directory): array + { + $asserts = []; + foreach (self::findTestDataFilesFromDirectory($directory) as $path) { + foreach (self::gatherAssertTypes($path) as $key => $assert) { + $asserts[$key] = $assert; + } + } + + return $asserts; + } + + /** + * @return list + */ + public static function findTestDataFilesFromDirectory(string $directory): array { if (!is_dir($directory)) { self::fail(sprintf('Directory %s does not exist.', $directory)); @@ -285,18 +300,16 @@ public static function gatherAssertTypesFromDirectory(string $directory): array $finder = new Finder(); $finder->followLinks(); - $asserts = []; + $files = []; foreach ($finder->files()->name('*.php')->in($directory) as $fileInfo) { $path = $fileInfo->getPathname(); if (self::isFileLintSkipped($path)) { continue; } - foreach (self::gatherAssertTypes($path) as $key => $assert) { - $asserts[$key] = $assert; - } + $files[] = $path; } - return $asserts; + return $files; } /** diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ec6abc47e77..2b8b3b42841 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5,75 +5,87 @@ use EnumTypeAssertions\Foo; use PHPStan\Testing\TypeInferenceTestCase; use stdClass; +use function array_shift; use function define; +use function dirname; +use function implode; +use function sprintf; +use function str_starts_with; +use function strlen; +use function substr; use const PHP_INT_SIZE; use const PHP_VERSION_ID; class NodeScopeResolverTest extends TypeInferenceTestCase { - public function dataFileAsserts(): iterable + /** + * @return iterable + */ + private static function findTestFiles(): iterable { - yield from $this->gatherAssertTypesFromDirectory(__DIR__ . '/nsrt'); + foreach (self::findTestDataFilesFromDirectory(__DIR__ . '/nsrt') as $testFile) { + yield $testFile; + } if (PHP_VERSION_ID < 80200 && PHP_VERSION_ID >= 80100) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/enum-reflection-php81.php'); + yield __DIR__ . '/data/enum-reflection-php81.php'; } if (PHP_VERSION_ID < 80000 && PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php'); + yield __DIR__ . '/data/bug-4902.php'; } if (PHP_VERSION_ID < 80300) { if (PHP_VERSION_ID >= 80200) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php82.php'); + yield __DIR__ . '/data/mb-strlen-php82.php'; } elseif (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php8.php'); + yield __DIR__ . '/data/mb-strlen-php8.php'; } elseif (PHP_VERSION_ID < 70300) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php72.php'); + yield __DIR__ . '/data/mb-strlen-php72.php'; } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php73.php'); + yield __DIR__ . '/data/mb-strlen-php73.php'; } } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-6856.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-6856.php'; if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/unionTypes.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/mixedType.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/staticReturnType.php'); + yield __DIR__ . '/../Reflection/data/unionTypes.php'; + yield __DIR__ . '/../Reflection/data/mixedType.php'; + yield __DIR__ . '/../Reflection/data/staticReturnType.php'; } if (PHP_INT_SIZE === 8) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-64bit.php'); + yield __DIR__ . '/data/predefined-constants-64bit.php'; } else { - yield from $this->gatherAssertTypes(__DIR__ . '/data/predefined-constants-32bit.php'); + yield __DIR__ . '/data/predefined-constants-32bit.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-10577.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-10610.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-2550.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-3777.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4552.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Generics/data/bug-3769.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Generics/data/bug-6301.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-4643.php'); + yield __DIR__ . '/../Rules/Variables/data/bug-10577.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-10610.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-2550.php'; + yield __DIR__ . '/../Rules/Properties/data/bug-3777.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-4552.php'; + yield __DIR__ . '/../Rules/Methods/data/infer-array-key.php'; + yield __DIR__ . '/../Rules/Generics/data/bug-3769.php'; + yield __DIR__ . '/../Rules/Generics/data/bug-6301.php'; + yield __DIR__ . '/../Rules/PhpDoc/data/bug-4643.php'; if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-4857.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-4857.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5089.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/unable-to-resolve-callback-parameter-type.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-5089.php'; + yield __DIR__ . '/../Rules/Methods/data/unable-to-resolve-callback-parameter-type.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/varying-acceptor.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); + yield __DIR__ . '/../Rules/Functions/data/varying-acceptor.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-4415.php'; if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-5372.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5372_2.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); + yield __DIR__ . '/../Rules/Arrays/data/bug-5372_2.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-5562.php'; if (PHP_VERSION_ID >= 80100) { define('TEST_OBJECT_CONSTANT', new stdClass()); @@ -82,124 +94,167 @@ public function dataFileAsserts(): iterable define('TEST_FALSE_CONSTANT', false); define('TEST_ARRAY_CONSTANT', [true, false, null]); define('TEST_ENUM_CONSTANT', Foo::ONE); - yield from $this->gatherAssertTypes(__DIR__ . '/data/new-in-initializers-runtime.php'); + yield __DIR__ . '/data/new-in-initializers-runtime.php'; } if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6473.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-6473.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php'); + yield __DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5749.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5757.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-5749.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-5757.php'; if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-6635.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-6635.php'; } if (PHP_VERSION_ID >= 80300) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Constants/data/bug-10212.php'); + yield __DIR__ . '/../Rules/Constants/data/bug-10212.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-3284.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-3284.php'; if (PHP_VERSION_ID >= 80300) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/return-type-class-constant.php'); + yield __DIR__ . '/../Rules/Methods/data/return-type-class-constant.php'; } //define('ALREADY_DEFINED_CONSTANT', true); //yield from $this->gatherAssertTypes(__DIR__ . '/data/already-defined-constant.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/conditional-complex-templates.php'); + yield __DIR__ . '/../Rules/Methods/data/conditional-complex-templates.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-7511.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/trait-mixin.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/trait-mixin.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-4708.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-7156.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6364.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5758.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-3931.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-7417.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-7469.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-3391.php'); + yield __DIR__ . '/../Rules/Methods/data/bug-7511.php'; + yield __DIR__ . '/../Rules/Properties/data/trait-mixin.php'; + yield __DIR__ . '/../Rules/Methods/data/trait-mixin.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-4708.php'; + yield __DIR__ . '/../Rules/Functions/data/bug-7156.php'; + yield __DIR__ . '/../Rules/Arrays/data/bug-6364.php'; + yield __DIR__ . '/../Rules/Arrays/data/bug-5758.php'; + yield __DIR__ . '/../Rules/Functions/data/bug-3931.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-7417.php'; + yield __DIR__ . '/../Rules/Arrays/data/bug-7469.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-3391.php'; if (PHP_VERSION_ID >= 70400) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-anonymous-function-method-constant.php'); + yield __DIR__ . '/../Rules/Functions/data/bug-anonymous-function-method-constant.php'; } if (PHP_VERSION_ID >= 80200) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/true-typehint.php'); + yield __DIR__ . '/../Rules/Methods/data/true-typehint.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6000.php'); + yield __DIR__ . '/../Rules/Arrays/data/bug-6000.php'; - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/slevomat-foreach-unset-bug.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php'); + yield __DIR__ . '/../Rules/Arrays/data/slevomat-foreach-unset-bug.php'; + yield __DIR__ . '/../Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php'; if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-7898.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-7898.php'; } if (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-7823.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/../Analyser/data/is-resource-specified.php'); - - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-7954.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/docblock-assert-equality.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-7839.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-5333.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-8174.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8169.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-8280.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8277.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-8113.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-8389.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-8467a.php'); + yield __DIR__ . '/../Rules/Functions/data/bug-7823.php'; + } + + yield __DIR__ . '/../Analyser/data/is-resource-specified.php'; + + yield __DIR__ . '/../Rules/Arrays/data/bug-7954.php'; + yield __DIR__ . '/../Rules/Comparison/data/docblock-assert-equality.php'; + yield __DIR__ . '/../Rules/Properties/data/bug-7839.php'; + yield __DIR__ . '/../Rules/Classes/data/bug-5333.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-8174.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-8169.php'; + yield __DIR__ . '/../Rules/Functions/data/bug-8280.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-8277.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-8113.php'; + yield __DIR__ . '/../Rules/Functions/data/bug-8389.php'; + yield __DIR__ . '/../Rules/Arrays/data/bug-8467a.php'; if (PHP_VERSION_ID >= 80100) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-8485.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-8485.php'; } if (PHP_VERSION_ID >= 80100) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-9007.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-9007.php'; } - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/DeadCode/data/bug-8620.php'); + yield __DIR__ . '/../Rules/DeadCode/data/bug-8620.php'; if (PHP_VERSION_ID >= 80200) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Constants/data/bug-8957.php'); + yield __DIR__ . '/../Rules/Constants/data/bug-8957.php'; } if (PHP_VERSION_ID >= 80100) { - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-9499.php'); - } - - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-5365.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-6551.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-9403.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-9542.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-9803.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'); + yield __DIR__ . '/../Rules/Comparison/data/bug-9499.php'; + } + + yield __DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-5365.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-6551.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-9403.php'; + yield __DIR__ . '/../Rules/Methods/data/bug-9542.php'; + yield __DIR__ . '/../Rules/Functions/data/bug-9803.php'; + yield __DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'; + yield __DIR__ . '/../Rules/Classes/data/bug-11591.php'; + yield __DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'; + yield __DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'; + yield __DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'; } /** - * @dataProvider dataFileAsserts - * @param mixed ...$args + * @return iterable */ - public function testFileAsserts( - string $assertType, - string $file, - ...$args, - ): void + public static function dataFile(): iterable { - $this->assertFileAsserts($assertType, $file, ...$args); + $base = dirname(__DIR__, 3) . '/'; + $baseLength = strlen($base); + + foreach (self::findTestFiles() as $file) { + $testName = $file; + if (str_starts_with($file, $base)) { + $testName = substr($file, $baseLength); + } + + yield $testName => [$file]; + } + } + + /** + * @dataProvider dataFile + */ + public function testFile(string $file): void + { + $asserts = $this->gatherAssertTypes($file); + $this->assertNotCount(0, $asserts, sprintf('File %s has no asserts.', $file)); + $failures = []; + + foreach ($asserts as $args) { + $assertType = array_shift($args); + $file = array_shift($args); + + if ($assertType === 'type') { + $expected = $args[0]; + $actual = $args[1]; + + if ($expected !== $actual) { + $failures[] = sprintf("Line %d:\nExpected: %s\nActual: %s\n", $args[2], $expected, $actual); + } + } elseif ($assertType === 'variableCertainty') { + $expectedCertainty = $args[0]; + $actualCertainty = $args[1]; + $variableName = $args[2]; + + if ($expectedCertainty->equals($actualCertainty) !== true) { + $failures[] = sprintf("Certainty of variable \$%s on line %d:\nExpected: %s\nActual: %s\n", $variableName, $args[3], $expectedCertainty->describe(), $actualCertainty->describe()); + } + } + } + + if ($failures === []) { + return; + } + + self::fail(sprintf("Failed assertions in %s:\n\n%s", $file, implode("\n", $failures))); } public static function getAdditionalConfigFiles(): array From 90da2bf1fb9477a47dd591018eddf555234f3234 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 17:43:45 +0200 Subject: [PATCH 0293/3097] Fix --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 6ae400e8020..84544357441 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -33,14 +33,14 @@ private static function findTestFiles(): iterable } if (PHP_VERSION_ID < 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php'); + yield __DIR__ . '/data/bug-4902.php'; } if (PHP_VERSION_ID < 80300) { if (PHP_VERSION_ID >= 80200) { yield __DIR__ . '/data/mb-strlen-php82.php'; } elseif (PHP_VERSION_ID >= 80000) { - yield from $this->gatherAssertTypes(__DIR__ . '/data/mb-strlen-php8.php'); + yield __DIR__ . '/data/mb-strlen-php8.php'; } else { yield __DIR__ . '/data/mb-strlen-php73.php'; } From 1d517de1b9297a0e3cef0e10589458aad89d8cbe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0294/3097] Issue bot - let all comments about list type through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From e5ad342b06374cf56e798a86b631bf9d25e18faa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Sep 2024 17:48:19 +0200 Subject: [PATCH 0295/3097] Revert "Issue bot - let all comments about list type through" This reverts commit 1d517de1b9297a0e3cef0e10589458aad89d8cbe. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From b3aec7cd39bd1c00ed718cf909b38862def03487 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 17 Sep 2024 00:16:34 +0000 Subject: [PATCH 0296/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1939d5df29d..af64be29c09 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.105", + "phpstan/php-8-stubs": "0.3.106", "phpstan/phpdoc-parser": "1.30.1", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index bed27023af2..9d353e42bbe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "76c100fc288f1215d63ee88908164a3a", + "content-hash": "6d693958743075cac1ba17147e8d55b4", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.105", + "version": "0.3.106", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "16dafadf67f56e715a4e8d5093a28435640d5df8" + "reference": "32a289268a0bbd2ee64f060ecd74bb6eb345738d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/16dafadf67f56e715a4e8d5093a28435640d5df8", - "reference": "16dafadf67f56e715a4e8d5093a28435640d5df8", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/32a289268a0bbd2ee64f060ecd74bb6eb345738d", + "reference": "32a289268a0bbd2ee64f060ecd74bb6eb345738d", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.105" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.106" }, - "time": "2024-09-16T14:40:26+00:00" + "time": "2024-09-17T00:15:59+00:00" }, { "name": "phpstan/phpdoc-parser", From 46f343ed70cba1d15c5632f26a7ab8b7c16e5a61 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 Sep 2024 09:17:37 +0200 Subject: [PATCH 0297/3097] Normalize path in TypeInferenceTestCase --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2b8b3b42841..a4bfe753641 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use EnumTypeAssertions\Foo; +use PHPStan\File\FileHelper; use PHPStan\Testing\TypeInferenceTestCase; use stdClass; use function array_shift; @@ -209,7 +210,10 @@ public static function dataFile(): iterable $base = dirname(__DIR__, 3) . '/'; $baseLength = strlen($base); + $fileHelper = new FileHelper($base); foreach (self::findTestFiles() as $file) { + $file = $fileHelper->normalizePath($file); + $testName = $file; if (str_starts_with($file, $base)) { $testName = substr($file, $baseLength); From f0b3d52184aa22e81c53ba6762c073f78be8374c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Sep 2024 09:59:41 +0200 Subject: [PATCH 0298/3097] Dial back a bit --- src/Analyser/RuleErrorTransformer.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 916f284505b..b45ce15acde 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -8,6 +8,7 @@ use PHPStan\Rules\LineRuleError; use PHPStan\Rules\MetadataRuleError; use PHPStan\Rules\NonIgnorableRuleError; +use PHPStan\Rules\RuleError; use PHPStan\Rules\TipRuleError; final class RuleErrorTransformer @@ -17,7 +18,7 @@ final class RuleErrorTransformer * @param class-string $nodeType */ public function transform( - IdentifierRuleError $ruleError, + RuleError $ruleError, Scope $scope, string $nodeType, int $nodeLine, @@ -29,6 +30,7 @@ public function transform( $filePath = $scope->getFile(); $traitFilePath = null; $tip = null; + $identifier = null; $metadata = []; if ($scope->isInTrait()) { $traitReflection = $scope->getTraitReflection(); @@ -56,6 +58,10 @@ public function transform( $tip = $ruleError->getTip(); } + if ($ruleError instanceof IdentifierRuleError) { + $identifier = $ruleError->getIdentifier(); + } + if ($ruleError instanceof MetadataRuleError) { $metadata = $ruleError->getMetadata(); } @@ -74,7 +80,7 @@ public function transform( $tip, $nodeLine, $nodeType, - $ruleError->getIdentifier(), + $identifier, $metadata, ); } From aefc87a6cf9969ac1a361d5037981b78117b4f37 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 Sep 2024 10:18:33 +0200 Subject: [PATCH 0299/3097] Fix FileExcluder on Windows --- src/File/FileExcluder.php | 4 +++- tests/PHPStan/File/FileExcluderTest.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/File/FileExcluder.php b/src/File/FileExcluder.php index 7ebb938302a..ba314cbb67d 100644 --- a/src/File/FileExcluder.php +++ b/src/File/FileExcluder.php @@ -50,7 +50,7 @@ final class FileExcluder * @param string[] $analyseExcludes */ public function __construct( - FileHelper $fileHelper, + private FileHelper $fileHelper, array $analyseExcludes, ) { @@ -89,6 +89,8 @@ public function __construct( public function isExcludedFromAnalysing(string $file): bool { + $file = $this->fileHelper->normalizePath($file); + foreach ($this->literalAnalyseExcludes as $exclude) { if (str_starts_with($file, $exclude)) { return true; diff --git a/tests/PHPStan/File/FileExcluderTest.php b/tests/PHPStan/File/FileExcluderTest.php index 860b779e751..3c2d1502906 100644 --- a/tests/PHPStan/File/FileExcluderTest.php +++ b/tests/PHPStan/File/FileExcluderTest.php @@ -64,7 +64,7 @@ public function dataExcludeOnWindows(): array ], [ __DIR__ . '\data\parse-error.php', - ['tests/PHPStan/File/data/*'], + ['*/tests/PHPStan/File/data/*'], true, ], [ From 84a7397193c250444681437670e3e095eb389787 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Sep 2024 13:26:23 +0200 Subject: [PATCH 0300/3097] Fix duplicate paths in composerAutoloaderProjectPaths on Windows --- bin/phpstan | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/phpstan b/bin/phpstan index bb97758ff6b..8d6cd25cd5d 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -159,7 +159,11 @@ use Symfony\Component\Console\Helper\ProgressBar; $application->setDefaultCommand('analyse'); ProgressBar::setFormatDefinition('file_download', ' [%bar%] %percent:3s%% %fileSize%'); + $composerAutoloaderProjectPaths = array_map(function(string $s): string { + return str_replace(DIRECTORY_SEPARATOR, '/', $s); + }, $composerAutoloaderProjectPaths); $reversedComposerAutoloaderProjectPaths = array_values(array_unique(array_reverse($composerAutoloaderProjectPaths))); + $application->add(new AnalyseCommand($reversedComposerAutoloaderProjectPaths, $analysisStartTime)); $application->add(new WorkerCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new ClearResultCacheCommand($reversedComposerAutoloaderProjectPaths)); From 05630e67fa3809191253e660765573263daab2b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Sep 2024 13:28:49 +0200 Subject: [PATCH 0301/3097] Update nikic/php-parser to 4.19.2 --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 9d353e42bbe..3684cf73d2a 100644 --- a/composer.lock +++ b/composer.lock @@ -2048,16 +2048,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v4.19.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "0ed4c8949a32986043e977dbe14776c14d644c45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ed4c8949a32986043e977dbe14776c14d644c45", + "reference": "0ed4c8949a32986043e977dbe14776c14d644c45", "shasum": "" }, "require": { @@ -2098,9 +2098,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.2" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-09-17T19:36:00+00:00" }, { "name": "ondram/ci-detector", From 988f058478eeb00548d6e1a1e84a629c7934ff93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Wed, 18 Sep 2024 21:22:48 +0200 Subject: [PATCH 0302/3097] Fix infer new templated type from initial assign into static property --- src/Parser/NewAssignedToPropertyVisitor.php | 5 +- .../TypesAssignedToPropertiesRuleTest.php | 28 +++ .../Rules/Properties/data/bug-10686.php | 33 ++++ .../Rules/Properties/data/bug-3777-static.php | 172 ++++++++++++++++++ 4 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-10686.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-3777-static.php diff --git a/src/Parser/NewAssignedToPropertyVisitor.php b/src/Parser/NewAssignedToPropertyVisitor.php index 05df87b423c..209e72730d7 100644 --- a/src/Parser/NewAssignedToPropertyVisitor.php +++ b/src/Parser/NewAssignedToPropertyVisitor.php @@ -13,7 +13,10 @@ final class NewAssignedToPropertyVisitor extends NodeVisitorAbstract public function enterNode(Node $node): ?Node { if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef) { - if ($node->var instanceof Node\Expr\PropertyFetch && $node->expr instanceof Node\Expr\New_) { + if ( + ($node->var instanceof Node\Expr\PropertyFetch || $node->var instanceof Node\Expr\StaticPropertyFetch) + && $node->expr instanceof Node\Expr\New_ + ) { $node->expr->setAttribute(self::ATTRIBUTE_NAME, $node->var); } } diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index ee0bd80321e..ff9f5be5aeb 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -223,6 +223,29 @@ public function testBug3777(): void 168, ], ]); + + $this->analyse([__DIR__ . '/data/bug-3777-static.php'], [ + [ + 'Static property Bug3777Static\Bar::$foo (Bug3777Static\Foo) does not accept Bug3777Static\Fooo.', + 58, + ], + [ + 'Static property Bug3777Static\Ipsum::$ipsum (Bug3777Static\Lorem) does not accept Bug3777Static\Lorem.', + 95, + ], + [ + 'Static property Bug3777Static\Ipsum2::$lorem2 (Bug3777Static\Lorem2) does not accept Bug3777Static\Lorem2.', + 129, + ], + [ + 'Static property Bug3777Static\Ipsum2::$ipsum2 (Bug3777Static\Lorem2) does not accept Bug3777Static\Lorem2.', + 131, + ], + [ + 'Static property Bug3777Static\Ipsum3::$ipsum3 (Bug3777Static\Lorem3) does not accept Bug3777Static\Lorem3.', + 168, + ], + ]); } public function testAppendendArrayKey(): void @@ -629,6 +652,11 @@ public function testGenericsInCallableInConstructor(): void $this->analyse([__DIR__ . '/data/generics-in-callable-in-constructor.php'], []); } + public function testBug10686(): void + { + $this->analyse([__DIR__ . '/data/bug-10686.php'], []); + } + public function testBug11275(): void { if (PHP_VERSION_ID < 80000) { diff --git a/tests/PHPStan/Rules/Properties/data/bug-10686.php b/tests/PHPStan/Rules/Properties/data/bug-10686.php new file mode 100644 index 00000000000..0d6922d5da6 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-10686.php @@ -0,0 +1,33 @@ += 7.4 + +namespace Bug10686; + +class Model {} + +/** + * @template T of object|array + */ +class WeakAnalysingMap +{ + /** @var list */ + public array $values = []; +} + +class Reference +{ + /** @var WeakAnalysingMap */ + private static WeakAnalysingMap $analysingTheirModelMap; + + public function createAnalysingTheirModel(): Model + { + if ((self::$analysingTheirModelMap ?? null) === null) { + self::$analysingTheirModelMap = new WeakAnalysingMap(); + } + + $theirModel = new Model(); + + self::$analysingTheirModelMap->values[] = $theirModel; + + return end(self::$analysingTheirModelMap->values); + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-3777-static.php b/tests/PHPStan/Rules/Properties/data/bug-3777-static.php new file mode 100644 index 00000000000..0cc9c7b9303 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-3777-static.php @@ -0,0 +1,172 @@ + + */ + public static $dates; + + public function __construct() + { + static::$dates = new \SplObjectStorage(); + assertType('SplObjectStorage', static::$dates); + } +} + +/** @template T of object */ +class Foo +{ + + public function __construct() + { + + } + +} + +/** @template T of object */ +class Fooo +{ + +} + +class Bar +{ + + /** @var Foo<\stdClass> */ + private static $foo; + + /** @var Fooo<\stdClass> */ + private static $fooo; + + public function __construct() + { + static::$foo = new Foo(); + assertType('Bug3777Static\Foo', static::$foo); + + static::$fooo = new Fooo(); + assertType('Bug3777Static\Fooo', static::$fooo); + } + + public function doBar() + { + static::$foo = new Fooo(); + assertType('Bug3777Static\Fooo', static::$foo); + } + +} + +/** + * @template T of object + * @template U of object + */ +class Lorem +{ + + /** + * @param T $t + * @param U $u + */ + public function __construct($t, $u) + { + + } + +} + +class Ipsum +{ + + /** @var Lorem<\stdClass, \Exception> */ + private static $lorem; + + /** @var Lorem<\stdClass, \Exception> */ + private static $ipsum; + + public function __construct() + { + static::$lorem = new Lorem(new \stdClass, new \Exception()); + assertType('Bug3777Static\Lorem', static::$lorem); + static::$ipsum = new Lorem(new \Exception(), new \stdClass); + assertType('Bug3777Static\Lorem', static::$ipsum); + } + +} + +/** + * @template T of object + * @template U of object + */ +class Lorem2 +{ + + /** + * @param T $t + */ + public function __construct($t) + { + + } + +} + +class Ipsum2 +{ + + /** @var Lorem2<\stdClass, \Exception> */ + private static $lorem2; + + /** @var Lorem2<\stdClass, \Exception> */ + private static $ipsum2; + + public function __construct() + { + static::$lorem2 = new Lorem2(new \stdClass); + assertType('Bug3777Static\Lorem2', static::$lorem2); + static::$ipsum2 = new Lorem2(new \Exception()); + assertType('Bug3777Static\Lorem2', static::$ipsum2); + } + +} + +/** + * @template T of object + * @template U of object + */ +class Lorem3 +{ + + /** + * @param T $t + * @param U $u + */ + public function __construct($t, $u) + { + + } + +} + +class Ipsum3 +{ + + /** @var Lorem3<\stdClass, \Exception> */ + private static $lorem3; + + /** @var Lorem3<\stdClass, \Exception> */ + private static $ipsum3; + + public function __construct() + { + static::$lorem3 = new Lorem3(new \stdClass, new \Exception()); + assertType('Bug3777Static\Lorem3', static::$lorem3); + static::$ipsum3 = new Lorem3(new \Exception(), new \stdClass()); + assertType('Bug3777Static\Lorem3', static::$ipsum3); + } + +} From 7a6a0fa20110a99adb61748d97b3f9e0f9dbef8a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 19 Sep 2024 09:14:56 +0200 Subject: [PATCH 0303/3097] `range()` with float step should return an array of floats --- .../Php/RangeFunctionReturnTypeExtension.php | 18 +++++++++++++- tests/PHPStan/Analyser/nsrt/bug-11692.php | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11692.php diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index ed43f64f203..aceaebf6700 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -79,12 +79,17 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } if (count($rangeValues) > self::RANGE_LENGTH_THRESHOLD) { - if ($startConstant instanceof ConstantIntegerType && $endConstant instanceof ConstantIntegerType) { + if ( + $startConstant instanceof ConstantIntegerType + && $endConstant instanceof ConstantIntegerType + && $stepConstant instanceof ConstantIntegerType + ) { if ($startConstant->getValue() > $endConstant->getValue()) { $tmp = $startConstant; $startConstant = $endConstant; $endConstant = $tmp; } + return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( new ArrayType( new IntegerType(), @@ -94,12 +99,23 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )); } + if ($stepType->isFloat()->yes()) { + return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( + new ArrayType( + new IntegerType(), + new FloatType(), + ), + new NonEmptyArrayType(), + )); + } + return AccessoryArrayListType::intersectWith(TypeCombinator::intersect( new ArrayType( new IntegerType(), TypeCombinator::union( $startConstant->generalize(GeneralizePrecision::moreSpecific()), $endConstant->generalize(GeneralizePrecision::moreSpecific()), + $stepType->generalize(GeneralizePrecision::moreSpecific()), ), ), new NonEmptyArrayType(), diff --git a/tests/PHPStan/Analyser/nsrt/bug-11692.php b/tests/PHPStan/Analyser/nsrt/bug-11692.php new file mode 100644 index 00000000000..c1edbba1fdf --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11692.php @@ -0,0 +1,24 @@ +', range(1, 9, .01)); + assertType('array{1, 4, 7}', range(1, 9, 3)); + + assertType('non-empty-list', range(1, 9999, .01)); + assertType('non-empty-list>', range(1, 9999, 3)); + + assertType('list', range(1, 9999, $floatOrInt)); + assertType('list', range(1, 9999, $floatOrInt)); + + assertType('list', range(1, 3, $i)); + assertType('list', range(1, 3, $f)); + + assertType('list', range(1, 9999, $i)); + assertType('list', range(1, 9999, $f)); +} + From 558280316de22a60fcc5850dcce3a094a32ea760 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Wed, 18 Sep 2024 14:13:08 +0200 Subject: [PATCH 0304/3097] Allow toggling `treatPhpDocTypesAsCertain` tip Fixes phpstan/phpstan#11689 --- conf/config.level4.neon | 19 +++++++++++++++++++ conf/config.level5.neon | 2 ++ conf/config.neon | 2 ++ conf/parametersSchema.neon | 3 +++ .../Classes/ImpossibleInstanceOfRule.php | 5 +++++ .../BooleanAndConstantConditionRule.php | 10 ++++++++++ .../BooleanNotConstantConditionRule.php | 4 ++++ .../BooleanOrConstantConditionRule.php | 10 ++++++++++ .../ConstantLooseComparisonRule.php | 4 ++++ .../DoWhileLoopConstantConditionRule.php | 4 ++++ .../ElseIfConstantConditionRule.php | 4 ++++ .../Comparison/IfConstantConditionRule.php | 4 ++++ .../ImpossibleCheckTypeFunctionCallRule.php | 4 ++++ .../ImpossibleCheckTypeMethodCallRule.php | 4 ++++ ...mpossibleCheckTypeStaticMethodCallRule.php | 4 ++++ .../LogicalXorConstantConditionRule.php | 7 +++++++ ...mparisonOperatorsConstantConditionRule.php | 4 ++++ .../StrictComparisonOfDifferentTypesRule.php | 4 ++++ .../TernaryOperatorConstantConditionRule.php | 4 ++++ .../Comparison/UnreachableIfBranchesRule.php | 4 ++++ .../UnreachableTernaryElseBranchRule.php | 4 ++++ .../WhileLoopAlwaysFalseConditionRule.php | 4 ++++ .../WhileLoopAlwaysTrueConditionRule.php | 4 ++++ src/Rules/Functions/ArrayFilterRule.php | 7 ++++--- src/Rules/Functions/ArrayValuesRule.php | 5 +++-- .../Classes/ImpossibleInstanceOfRuleTest.php | 7 ++++++- .../BooleanAndConstantConditionRuleTest.php | 1 + .../BooleanNotConstantConditionRuleTest.php | 1 + .../BooleanOrConstantConditionRuleTest.php | 1 + .../ConstantLooseComparisonRuleTest.php | 7 ++++++- .../DoWhileLoopConstantConditionRuleTest.php | 1 + .../ElseIfConstantConditionRuleTest.php | 1 + .../IfConstantConditionRuleTest.php | 1 + ...mpossibleCheckTypeFunctionCallRuleTest.php | 1 + ...sibleCheckTypeGenericOverwriteRuleTest.php | 1 + ...sibleCheckTypeMethodCallRuleEqualsTest.php | 1 + .../ImpossibleCheckTypeMethodCallRuleTest.php | 1 + ...sibleCheckTypeStaticMethodCallRuleTest.php | 1 + .../LogicalXorConstantConditionRuleTest.php | 1 + ...isonOperatorsConstantConditionRuleTest.php | 5 ++++- ...rictComparisonOfDifferentTypesRuleTest.php | 7 ++++++- ...rnaryOperatorConstantConditionRuleTest.php | 1 + .../UnreachableIfBranchesRuleTest.php | 1 + .../UnreachableTernaryElseBranchRuleTest.php | 1 + .../WhileLoopAlwaysFalseConditionRuleTest.php | 1 + .../WhileLoopAlwaysTrueConditionRuleTest.php | 1 + .../Rules/Functions/ArrayFilterRuleTest.php | 6 +++++- .../Rules/Functions/ArrayValuesRuleTest.php | 6 +++++- 48 files changed, 174 insertions(+), 11 deletions(-) diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 8f3ecc86bcc..7d4441fcf97 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -67,6 +67,7 @@ services: checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -76,6 +77,7 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% bleedingEdge: %featureToggles.bleedingEdge% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -85,6 +87,7 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% bleedingEdge: %featureToggles.bleedingEdge% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -93,6 +96,7 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -149,6 +153,7 @@ services: class: PHPStan\Rules\Comparison\DoWhileLoopConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -157,6 +162,7 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -164,6 +170,7 @@ services: class: PHPStan\Rules\Comparison\IfConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -173,6 +180,7 @@ services: checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -182,6 +190,7 @@ services: checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -191,6 +200,7 @@ services: checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -199,6 +209,7 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - class: PHPStan\Rules\DeadCode\BetterNoopRule @@ -217,6 +228,7 @@ services: class: PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -226,6 +238,7 @@ services: checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -235,11 +248,13 @@ services: checkAlwaysTrueLooseComparison: %checkAlwaysTrueLooseComparison% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - class: PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -248,6 +263,7 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% disable: %featureToggles.disableUnreachableBranchesRules% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -256,6 +272,7 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% disable: %featureToggles.disableUnreachableBranchesRules% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -263,6 +280,7 @@ services: class: PHPStan\Rules\Comparison\WhileLoopAlwaysFalseConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule @@ -270,6 +288,7 @@ services: class: PHPStan\Rules\Comparison\WhileLoopAlwaysTrueConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 184cee83b8b..7b4f758f440 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -34,11 +34,13 @@ services: class: PHPStan\Rules\Functions\ArrayFilterRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - class: PHPStan\Rules\Functions\ArrayValuesRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - class: PHPStan\Rules\Functions\CallUserFuncRule diff --git a/conf/config.neon b/conf/config.neon index 05a71dbe97d..6fa0145f19c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -161,6 +161,8 @@ parameters: treatPhpDocTypesAsCertain: true usePathConstantsAsConstantString: false rememberPossiblyImpureFunctionValues: true + tips: + treatPhpDocTypesAsCertain: true tipsOfTheDay: true reportMagicMethods: false reportMagicProperties: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 05d6d79f0cf..d3359c68678 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -129,6 +129,9 @@ parametersSchema: deprecationRulesInstalled: bool() inferPrivatePropertyTypeFromConstructor: bool() + tips: structure([ + treatPhpDocTypesAsCertain: bool() + ]) tipsOfTheDay: bool() reportMaybes: bool() reportMaybesInMethodSignatures: bool() diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index cf4df676e9c..b6d9c84ee35 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -25,6 +25,7 @@ public function __construct( private bool $checkAlwaysTrueInstanceof, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -71,6 +72,10 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } + return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/BooleanAndConstantConditionRule.php b/src/Rules/Comparison/BooleanAndConstantConditionRule.php index 8d680e93068..5c05e8ac09e 100644 --- a/src/Rules/Comparison/BooleanAndConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanAndConstantConditionRule.php @@ -23,6 +23,7 @@ public function __construct( private bool $treatPhpDocTypesAsCertain, private bool $bleedingEdge, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -52,6 +53,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; @@ -90,6 +94,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; @@ -122,6 +129,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/BooleanNotConstantConditionRule.php b/src/Rules/Comparison/BooleanNotConstantConditionRule.php index f107804843d..2b04d48f807 100644 --- a/src/Rules/Comparison/BooleanNotConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanNotConstantConditionRule.php @@ -20,6 +20,7 @@ public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -45,6 +46,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/BooleanOrConstantConditionRule.php b/src/Rules/Comparison/BooleanOrConstantConditionRule.php index 02f3db65905..f728505cad0 100644 --- a/src/Rules/Comparison/BooleanOrConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanOrConstantConditionRule.php @@ -23,6 +23,7 @@ public function __construct( private bool $treatPhpDocTypesAsCertain, private bool $bleedingEdge, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -52,6 +53,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; @@ -90,6 +94,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; @@ -122,6 +129,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 477210064ad..d6fa6c6aac0 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -21,6 +21,7 @@ public function __construct( private bool $checkAlwaysTrueLooseComparison, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -50,6 +51,9 @@ public function processNode(Node $node, Scope $scope): array if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php index 3777b5d6e59..9c270afa44c 100644 --- a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php +++ b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php @@ -22,6 +22,7 @@ final class DoWhileLoopConstantConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -70,6 +71,9 @@ public function processNode(Node $node, Scope $scope): array if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/ElseIfConstantConditionRule.php b/src/Rules/Comparison/ElseIfConstantConditionRule.php index 80444eb3359..a3c0bfab647 100644 --- a/src/Rules/Comparison/ElseIfConstantConditionRule.php +++ b/src/Rules/Comparison/ElseIfConstantConditionRule.php @@ -20,6 +20,7 @@ public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -45,6 +46,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/IfConstantConditionRule.php b/src/Rules/Comparison/IfConstantConditionRule.php index f0eb0e338c8..f0d71c3e015 100644 --- a/src/Rules/Comparison/IfConstantConditionRule.php +++ b/src/Rules/Comparison/IfConstantConditionRule.php @@ -18,6 +18,7 @@ final class IfConstantConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -43,6 +44,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index a52cc8d718e..9033aa38658 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -21,6 +21,7 @@ public function __construct( private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -54,6 +55,9 @@ public function processNode(Node $node, Scope $scope): array if ($isAlways !== null) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index 5387a18bfcf..7bbcecefd7a 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -23,6 +23,7 @@ public function __construct( private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -52,6 +53,9 @@ public function processNode(Node $node, Scope $scope): array if ($isAlways !== null) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index 9ac004779d6..df4504cb0e2 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -23,6 +23,7 @@ public function __construct( private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -52,6 +53,9 @@ public function processNode(Node $node, Scope $scope): array if ($isAlways !== null) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/LogicalXorConstantConditionRule.php b/src/Rules/Comparison/LogicalXorConstantConditionRule.php index 971868f1c4f..c7531c4196a 100644 --- a/src/Rules/Comparison/LogicalXorConstantConditionRule.php +++ b/src/Rules/Comparison/LogicalXorConstantConditionRule.php @@ -21,6 +21,7 @@ public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -44,6 +45,9 @@ public function processNode(Node $node, Scope $scope): array if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; @@ -77,6 +81,9 @@ public function processNode(Node $node, Scope $scope): array if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php index b208766609d..a35a1c740e1 100644 --- a/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php +++ b/src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php @@ -21,6 +21,7 @@ final class NumberComparisonOperatorsConstantConditionRule implements Rule public function __construct( private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -55,6 +56,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index 6780fb91d32..f4ea23258b3 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -21,6 +21,7 @@ public function __construct( private bool $checkAlwaysTrueStrictComparison, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -53,6 +54,9 @@ public function processNode(Node $node, Scope $scope): array if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php index adbe33ac835..10a359ea4b7 100644 --- a/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php +++ b/src/Rules/Comparison/TernaryOperatorConstantConditionRule.php @@ -18,6 +18,7 @@ final class TernaryOperatorConstantConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -43,6 +44,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/UnreachableIfBranchesRule.php b/src/Rules/Comparison/UnreachableIfBranchesRule.php index 7b3682f213b..5d6b001e88a 100644 --- a/src/Rules/Comparison/UnreachableIfBranchesRule.php +++ b/src/Rules/Comparison/UnreachableIfBranchesRule.php @@ -18,6 +18,7 @@ public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, private bool $disable, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -48,6 +49,9 @@ public function processNode(Node $node, Scope $scope): array if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php index 0ce221250b0..63ea467ce4c 100644 --- a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php +++ b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php @@ -18,6 +18,7 @@ public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, private bool $disable, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -49,6 +50,9 @@ public function processNode(Node $node, Scope $scope): array if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php index b6eaa9d7904..2b9fbdbdac8 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php @@ -18,6 +18,7 @@ final class WhileLoopAlwaysFalseConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -43,6 +44,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php index 68ac27fbf2c..74b46b3d692 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php @@ -21,6 +21,7 @@ final class WhileLoopAlwaysTrueConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -70,6 +71,9 @@ public function processNode( if ($booleanNativeType instanceof ConstantBooleanType) { return $ruleErrorBuilder; } + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; diff --git a/src/Rules/Functions/ArrayFilterRule.php b/src/Rules/Functions/ArrayFilterRule.php index 7eeb304c3d6..abcc1e9dc8e 100644 --- a/src/Rules/Functions/ArrayFilterRule.php +++ b/src/Rules/Functions/ArrayFilterRule.php @@ -24,6 +24,7 @@ final class ArrayFilterRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, private bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -79,7 +80,7 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('arrayFilter.empty'); if ($this->treatPhpDocTypesAsCertain) { $nativeArrayType = $scope->getNativeType($args[0]->value); - if (!$nativeArrayType->isIterableAtLeastOnce()->no()) { + if ($this->treatPhpDocTypesAsCertainTip && !$nativeArrayType->isIterableAtLeastOnce()->no()) { $errorBuilder->treatPhpDocTypesAsCertainTip(); } } @@ -101,7 +102,7 @@ public function processNode(Node $node, Scope $scope): array if ($this->treatPhpDocTypesAsCertain) { $nativeArrayType = $scope->getNativeType($args[0]->value); $isNativeSuperType = $falsyType->isSuperTypeOf($nativeArrayType->getIterableValueType()); - if (!$isNativeSuperType->no()) { + if ($this->treatPhpDocTypesAsCertainTip && !$isNativeSuperType->no()) { $errorBuilder->treatPhpDocTypesAsCertainTip(); } } @@ -121,7 +122,7 @@ public function processNode(Node $node, Scope $scope): array if ($this->treatPhpDocTypesAsCertain) { $nativeArrayType = $scope->getNativeType($args[0]->value); $isNativeSuperType = $falsyType->isSuperTypeOf($nativeArrayType->getIterableValueType()); - if (!$isNativeSuperType->yes()) { + if ($this->treatPhpDocTypesAsCertainTip && !$isNativeSuperType->yes()) { $errorBuilder->treatPhpDocTypesAsCertainTip(); } } diff --git a/src/Rules/Functions/ArrayValuesRule.php b/src/Rules/Functions/ArrayValuesRule.php index cf058b54a87..2b969a42aaf 100644 --- a/src/Rules/Functions/ArrayValuesRule.php +++ b/src/Rules/Functions/ArrayValuesRule.php @@ -24,6 +24,7 @@ final class ArrayValuesRule implements Rule public function __construct( private readonly ReflectionProvider $reflectionProvider, private readonly bool $treatPhpDocTypesAsCertain, + private bool $treatPhpDocTypesAsCertainTip, ) { } @@ -84,7 +85,7 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('arrayValues.empty'); if ($this->treatPhpDocTypesAsCertain) { $nativeArrayType = $scope->getNativeType($args[0]->value); - if (!$nativeArrayType->isIterableAtLeastOnce()->no()) { + if ($this->treatPhpDocTypesAsCertainTip && !$nativeArrayType->isIterableAtLeastOnce()->no()) { $errorBuilder->treatPhpDocTypesAsCertainTip(); } } @@ -102,7 +103,7 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('arrayValues.list'); if ($this->treatPhpDocTypesAsCertain) { $nativeArrayType = $scope->getNativeType($args[0]->value); - if (!$nativeArrayType->isList()->yes()) { + if ($this->treatPhpDocTypesAsCertainTip && !$nativeArrayType->isList()->yes()) { $errorBuilder->treatPhpDocTypesAsCertainTip(); } } diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 562401245b7..fde28cab1a6 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -20,7 +20,12 @@ class ImpossibleInstanceOfRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ImpossibleInstanceOfRule($this->checkAlwaysTrueInstanceOf, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition); + return new ImpossibleInstanceOfRule( + $this->checkAlwaysTrueInstanceOf, + $this->treatPhpDocTypesAsCertain, + $this->reportAlwaysTrueInLastCondition, + true, + ); } protected function shouldTreatPhpDocTypesAsCertain(): bool diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index d8a3d75a45d..b0d674c7167 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, $this->bleedingEdge, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php index 43ecf911a65..f8bb760dcf3 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): Rule ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index ee616efbc5c..38092ce153e 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -35,6 +35,7 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, $this->bleedingEdge, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index f8a0b7d87d7..f34dd4876bc 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -20,7 +20,12 @@ class ConstantLooseComparisonRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ConstantLooseComparisonRule($this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition); + return new ConstantLooseComparisonRule( + $this->checkAlwaysTrueStrictComparison, + $this->treatPhpDocTypesAsCertain, + $this->reportAlwaysTrueInLastCondition, + true, + ); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php index 703c2e5a870..7d05804a468 100644 --- a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php @@ -28,6 +28,7 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index 0643363ef1c..bf671466842 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): Rule ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index a7c2629d161..479f5db928b 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -29,6 +29,7 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 88d9315d848..16529f3a744 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -36,6 +36,7 @@ protected function getRule(): Rule $this->checkAlwaysTrueCheckTypeFunctionCall, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php index 9be90b80542..152cbaa4a49 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php @@ -24,6 +24,7 @@ public function getRule(): Rule true, true, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 3aa4bf08108..79e7dfb00e0 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -24,6 +24,7 @@ public function getRule(): Rule true, true, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 7a697e6ffa1..5f14946fbbb 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -28,6 +28,7 @@ public function getRule(): Rule true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index a93147ab835..b173c04eea5 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -28,6 +28,7 @@ public function getRule(): Rule true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php index 24080f2e008..7e6c6015ca3 100644 --- a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php @@ -31,6 +31,7 @@ protected function getRule(): TRule ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php index 4d89a240be1..4d9733a493c 100644 --- a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php @@ -16,7 +16,10 @@ class NumberComparisonOperatorsConstantConditionRuleTest extends RuleTestCase protected function getRule(): Rule { - return new NumberComparisonOperatorsConstantConditionRule($this->treatPhpDocTypesAsCertain); + return new NumberComparisonOperatorsConstantConditionRule( + $this->treatPhpDocTypesAsCertain, + true, + ); } public function testBug8277(): void diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index e1c99901dee..2bfd60e993f 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -21,7 +21,12 @@ class StrictComparisonOfDifferentTypesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new StrictComparisonOfDifferentTypesRule($this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition); + return new StrictComparisonOfDifferentTypesRule( + $this->checkAlwaysTrueStrictComparison, + $this->treatPhpDocTypesAsCertain, + $this->reportAlwaysTrueInLastCondition, + true, + ); } protected function shouldTreatPhpDocTypesAsCertain(): bool diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index 71fce9241dc..fa4a13a729b 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -28,6 +28,7 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php index 7843308281b..f31bbe5023c 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php @@ -29,6 +29,7 @@ protected function getRule(): Rule ), $this->treatPhpDocTypesAsCertain, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php index c145cc7cfa3..00175cc6fcf 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php @@ -29,6 +29,7 @@ protected function getRule(): Rule ), $this->treatPhpDocTypesAsCertain, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php index 42675f9d8ac..349eb489ed0 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php @@ -28,6 +28,7 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php index e6f158f91af..2be9972f15b 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php @@ -28,6 +28,7 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, + true, ); } diff --git a/tests/PHPStan/Rules/Functions/ArrayFilterRuleTest.php b/tests/PHPStan/Rules/Functions/ArrayFilterRuleTest.php index 95ac9ed295a..e0a51fc2a8e 100644 --- a/tests/PHPStan/Rules/Functions/ArrayFilterRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrayFilterRuleTest.php @@ -15,7 +15,11 @@ class ArrayFilterRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ArrayFilterRule($this->createReflectionProvider(), $this->treatPhpDocTypesAsCertain); + return new ArrayFilterRule( + $this->createReflectionProvider(), + $this->treatPhpDocTypesAsCertain, + true, + ); } public function testFile(): void diff --git a/tests/PHPStan/Rules/Functions/ArrayValuesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrayValuesRuleTest.php index c945b1efea6..ec6ed43e382 100644 --- a/tests/PHPStan/Rules/Functions/ArrayValuesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrayValuesRuleTest.php @@ -16,7 +16,11 @@ class ArrayValuesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ArrayValuesRule($this->createReflectionProvider(), $this->treatPhpDocTypesAsCertain); + return new ArrayValuesRule( + $this->createReflectionProvider(), + $this->treatPhpDocTypesAsCertain, + true, + ); } public function testFile(): void From e629cee8cf75224db509eade67cee3b58c545011 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 19 Sep 2024 09:21:31 +0200 Subject: [PATCH 0305/3097] Narrow string on `*strlen()` with positive-int --- src/Analyser/TypeSpecifier.php | 80 +++++++++---------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 2 +- .../Analyser/nsrt/strlen-int-range.php | 14 +++- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f02e5f51d2c..9f23cf64720 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1124,39 +1124,6 @@ private function specifyTypesForConstantBinaryExpression( )); } - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->getArgs()) === 1 - && $exprNode->name instanceof Name - && in_array(strtolower((string) $exprNode->name), ['strlen', 'mb_strlen'], true) - && $constantType instanceof ConstantIntegerType - ) { - if ($constantType->getValue() < 0) { - return $this->create($exprNode->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); - } - - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - $argType = $scope->getType($exprNode->getArgs()[0]->value); - if ($argType->isString()->yes()) { - $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - - $accessory = new AccessoryNonEmptyStringType(); - if ($constantType->getValue() >= 2) { - $accessory = new AccessoryNonFalsyStringType(); - } - $valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr); - - return $funcTypes->unionWith($valueTypes); - } - } - - } - return null; } @@ -2140,6 +2107,42 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + if ( + !$context->null() + && $unwrappedLeftExpr instanceof FuncCall + && count($unwrappedLeftExpr->getArgs()) === 1 + && $unwrappedLeftExpr->name instanceof Name + && in_array(strtolower((string) $unwrappedLeftExpr->name), ['strlen', 'mb_strlen'], true) + && $rightType->isInteger()->yes() + ) { + if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($rightType)->yes()) { + return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, false, $scope, $rootExpr); + } + + $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($rightType); + if ($isZero->yes()) { + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + return $funcTypes->unionWith( + $this->create($unwrappedLeftExpr->getArgs()[0]->value, new ConstantStringType(''), $context, false, $scope, $rootExpr), + ); + } + + if ($context->truthy() && IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) { + $argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value); + if ($argType->isString()->yes()) { + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, false, $scope, $rootExpr); + + $accessory = new AccessoryNonEmptyStringType(); + if (IntegerRangeType::fromInterval(2, null)->isSuperTypeOf($rightType)->yes()) { + $accessory = new AccessoryNonFalsyStringType(); + } + $valueTypes = $this->create($unwrappedLeftExpr->getArgs()[0]->value, $accessory, $context, false, $scope, $rootExpr); + + return $funcTypes->unionWith($valueTypes); + } + } + } + if ( $context->true() && $unwrappedLeftExpr instanceof FuncCall @@ -2209,14 +2212,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } - if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) { + if ($rightType->isString()->yes()) { $types = null; - foreach ($rightType->getFiniteTypes() as $finiteType) { - if ($finiteType->isString()->yes()) { - $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); - } else { - $specifiedType = $this->specifyTypesForConstantBinaryExpression($unwrappedLeftExpr, $finiteType, $context, $scope, $rootExpr); - } + foreach ($rightType->getConstantStrings() as $constantString) { + $specifiedType = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $constantString, $context, $scope, $rootExpr); + if ($specifiedType === null) { continue; } diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index c2b21e92c46..37fbd12ccf2 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1283,7 +1283,7 @@ public function dataCondition(): iterable ), ), [ - '$foo' => 'non-empty-string', + '$foo' => "string & ~''", 'strlen($foo)' => '~0', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php index f66d50c140d..f83e19e984a 100644 --- a/tests/PHPStan/Analyser/nsrt/strlen-int-range.php +++ b/tests/PHPStan/Analyser/nsrt/strlen-int-range.php @@ -69,10 +69,10 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, } if (strlen($s) == $oneOrMore) { - assertType('string', $s); // could be non-empty-string + assertType('non-empty-string', $s); } if (strlen($s) === $oneOrMore) { - assertType('string', $s); // could be non-empty-string + assertType('non-empty-string', $s); } if (strlen($s) == $tenOrEleven) { @@ -118,7 +118,7 @@ function doFoo(string $s, $zeroToThree, $twoOrThree, $twoOrMore, int $maxThree, * @param int<1, max> $oneOrMore * @param int<2, max> $twoOrMore */ -function doFooBar(array $arr, int $oneOrMore, int $twoOrMore): void +function doFooBar(string $s, array $arr, int $oneOrMore, int $twoOrMore): void { if (count($arr) == $oneOrMore) { assertType('non-empty-array', $arr); @@ -126,4 +126,12 @@ function doFooBar(array $arr, int $oneOrMore, int $twoOrMore): void if (count($arr) === $twoOrMore) { assertType('non-empty-array', $arr); } + + if (strlen($s) == $twoOrMore) { + assertType('non-falsy-string', $s); + } + if (strlen($s) === $twoOrMore) { + assertType('non-falsy-string', $s); + } + } From 9263039c312f14097dc46fe844c474f3a87eb911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Thu, 19 Sep 2024 09:22:41 +0200 Subject: [PATCH 0306/3097] Fix late static binding calls for first class callable --- src/Analyser/MutatingScope.php | 50 +++++++++---------- .../Analyser/nsrt/static-late-binding.php | 17 +++++-- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 03c2109d86a..7ac09d1e92e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1202,7 +1202,7 @@ private function resolveType(string $exprString, Expr $node): Type return new ObjectType(Closure::class); } - $classType = $this->resolveTypeByName($node->class); + $classType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); $methodName = $node->name->toString(); if (!$classType->hasMethod($methodName)->yes()) { return new ObjectType(Closure::class); @@ -2082,19 +2082,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($this->nativeTypesPromoted) { $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - if ( - $staticMethodCalledOnType instanceof StaticType - && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) - ) { - $methodReflectionCandidate = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { - $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); - } - } + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); } else { $staticMethodCalledOnType = $this->getNativeType($node->class); } @@ -2119,19 +2107,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - if ( - $staticMethodCalledOnType instanceof StaticType - && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) - ) { - $methodReflectionCandidate = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { - $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); - } - } + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); } else { $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); } @@ -2780,6 +2756,26 @@ public function resolveTypeByName(Name $name): TypeWithClassName return new ObjectType($originalClass); } + private function resolveTypeByNameWithLateStaticBinding(Name $class, Node\Identifier $name): TypeWithClassName + { + $classType = $this->resolveTypeByName($class); + + if ( + $classType instanceof StaticType + && !in_array($class->toLowerString(), ['self', 'static', 'parent'], true) + ) { + $methodReflectionCandidate = $this->getMethodReflection( + $classType, + $name->name, + ); + if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { + $classType = $classType->getStaticObjectType(); + } + } + + return $classType; + } + /** * @api * @param mixed $value diff --git a/tests/PHPStan/Analyser/nsrt/static-late-binding.php b/tests/PHPStan/Analyser/nsrt/static-late-binding.php index 5421bbc1de5..53313ceddd6 100644 --- a/tests/PHPStan/Analyser/nsrt/static-late-binding.php +++ b/tests/PHPStan/Analyser/nsrt/static-late-binding.php @@ -67,7 +67,7 @@ public function foo(): void assertType('int', parent::retStaticConst()); assertType('2', $this->retStaticConst()); assertType('bool', X::retStaticConst()); - assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int + assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int https://github.com/phpstan/phpstan/issues/11687 assertType('int', A::retStaticConst(...)()); assertType('2', B::retStaticConst(...)()); @@ -76,7 +76,7 @@ public function foo(): void assertType('int', parent::retStaticConst(...)()); assertType('2', $this->retStaticConst(...)()); assertType('bool', X::retStaticConst(...)()); - assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int + assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int https://github.com/phpstan/phpstan/issues/11687 assertType('StaticLateBinding\A', A::retStatic()); assertType('StaticLateBinding\B', B::retStatic()); @@ -85,7 +85,16 @@ public function foo(): void assertType('static(StaticLateBinding\B)', parent::retStatic()); assertType('static(StaticLateBinding\B)', $this->retStatic()); assertType('bool', X::retStatic()); - assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A + assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A https://github.com/phpstan/phpstan/issues/11687 + + assertType('StaticLateBinding\A', A::retStatic(...)()); + assertType('StaticLateBinding\B', B::retStatic(...)()); + assertType('static(StaticLateBinding\B)', self::retStatic(...)()); + assertType('static(StaticLateBinding\B)', static::retStatic(...)()); + assertType('static(StaticLateBinding\B)', parent::retStatic(...)()); + assertType('static(StaticLateBinding\B)', $this->retStatic(...)()); + assertType('bool', X::retStatic(...)()); + assertType('mixed', $clUnioned::retStatic(...)()); // should be bool|StaticLateBinding\A https://github.com/phpstan/phpstan/issues/11687 assertType('static(StaticLateBinding\B)', A::retNonStatic()); assertType('static(StaticLateBinding\B)', B::retNonStatic()); @@ -94,7 +103,7 @@ public function foo(): void assertType('static(StaticLateBinding\B)', parent::retNonStatic()); assertType('static(StaticLateBinding\B)', $this->retNonStatic()); assertType('bool', X::retNonStatic()); - assertType('*ERROR*', $clUnioned->retNonStatic()); // should be bool|static(StaticLateBinding\B) + assertType('*ERROR*', $clUnioned->retNonStatic()); // should be bool|static(StaticLateBinding\B) https://github.com/phpstan/phpstan/issues/11687 A::outStaticConst($v); assertType('int', $v); From 8f2307e83c80a095af7554631da0c7876565a41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Thu, 19 Sep 2024 09:22:18 +0200 Subject: [PATCH 0307/3097] Display parent class name for anonymous class like native php does --- .../BetterReflectionProvider.php | 16 +++++- .../Analyser/AnalyserIntegrationTest.php | 4 +- .../PHPStan/Levels/data/stubValidator-0.json | 4 +- .../AnonymousClassReflectionTest.php | 49 +++++++++++++++++++ .../Reflection/data/anonymous-classes.php | 20 ++++++++ .../Classes/RequireImplementsRuleTest.php | 2 +- .../MissingMethodImplementationRuleTest.php | 2 +- .../MissingReadOnlyPropertyAssignRuleTest.php | 2 +- 8 files changed, 90 insertions(+), 9 deletions(-) diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 54b26ec9f83..be27dd03a06 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -51,6 +51,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Type; use function array_key_exists; +use function array_key_first; use function array_map; use function base64_decode; use function in_array; @@ -217,12 +218,23 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ null, ); + $displayParentName = $reflectionClass->getParentClassName(); + if ($displayParentName === null) { + // https://3v4l.org/6FBuP + $classInterfaceNames = $reflectionClass->getInterfaceNames(); + if ($classInterfaceNames !== []) { + $displayParentName = $classInterfaceNames[array_key_first($classInterfaceNames)]; + } else { + $displayParentName = 'class'; + } + } + /** @var int|null $classLineIndex */ $classLineIndex = $classNode->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX); if ($classLineIndex === null) { - $displayName = sprintf('class@anonymous/%s:%s', $filename, $classNode->getStartLine()); + $displayName = sprintf('%s@anonymous/%s:%s', $displayParentName, $filename, $classNode->getStartLine()); } else { - $displayName = sprintf('class@anonymous/%s:%s:%d', $filename, $classNode->getStartLine(), $classLineIndex); + $displayName = sprintf('%s@anonymous/%s:%s:%d', $displayParentName, $filename, $classNode->getStartLine(), $classLineIndex); } self::$anonymousClasses[$className] = new ClassReflection( diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 6fb9c0f411b..207ff22e0bd 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -541,9 +541,9 @@ public function testBug6442(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-6442.php'); $this->assertCount(2, $errors); - $this->assertSame('Dumped type: \'Bug6442\\\A\'', $errors[0]->getMessage()); + $this->assertSame('Dumped type: \'Bug6442\\\B\'', $errors[0]->getMessage()); $this->assertSame(9, $errors[0]->getLine()); - $this->assertSame('Dumped type: \'Bug6442\\\B\'', $errors[1]->getMessage()); + $this->assertSame('Dumped type: \'Bug6442\\\A\'', $errors[1]->getMessage()); $this->assertSame(9, $errors[1]->getLine()); } diff --git a/tests/PHPStan/Levels/data/stubValidator-0.json b/tests/PHPStan/Levels/data/stubValidator-0.json index 0517d298af2..ce13d6e9971 100644 --- a/tests/PHPStan/Levels/data/stubValidator-0.json +++ b/tests/PHPStan/Levels/data/stubValidator-0.json @@ -20,12 +20,12 @@ "ignorable": false }, { - "message": "Method class@anonymous/stubValidator/stubs.php:27::doFoo() has no return type specified.", + "message": "Method ArrayIterator@anonymous/stubValidator/stubs.php:27::doFoo() has no return type specified.", "line": 30, "ignorable": false }, { - "message": "Parameter $foo of method class@anonymous/stubValidator/stubs.php:27::doFoo() has invalid type StubValidator\\Foooooooo.", + "message": "Parameter $foo of method ArrayIterator@anonymous/stubValidator/stubs.php:27::doFoo() has invalid type StubValidator\\Foooooooo.", "line": 30, "ignorable": false } diff --git a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php index 9310f617f17..b6ee4130323 100644 --- a/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php +++ b/tests/PHPStan/Reflection/AnonymousClassReflectionTest.php @@ -101,6 +101,55 @@ public function testReflection(): void ]), 9, ], + [ + implode("\n", [ + 'name: AnonymousClass1d622e3ff3a656e68d55eafbd25eaef1', + 'display name: AnonymousClassReflectionTest\A@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:17:1', + ]), + 17, + ], + [ + implode("\n", [ + 'name: AnonymousClass6e1acc8e948827c8d0439a2225fdbdd0', + 'display name: AnonymousClassReflectionTest\A@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:17:2', + ]), + 17, + ], + [ + implode("\n", [ + 'name: AnonymousClass2a49db3d44479dddd8beaea4ea8131fb', + 'display name: AnonymousClassReflectionTest\A@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:19:1', + ]), + 19, + ], + [ + implode("\n", [ + 'name: AnonymousClass337463cf86ee25e526f445630960b336', + 'display name: AnonymousClassReflectionTest\A@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:19:2', + ]), + 19, + ], + [ + implode("\n", [ + 'name: AnonymousClassda3e79cc45f826d60295f848abab37e7', + 'display name: AnonymousClassReflectionTest\U@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:29', + ]), + 29, + ], + [ + implode("\n", [ + 'name: AnonymousClassc06612bf3776bbe5e50870a8c3151186', + 'display name: AnonymousClassReflectionTest\U@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:31', + ]), + 31, + ], + [ + implode("\n", [ + 'name: AnonymousClassbee6eba8c721d73d649fcc9d361f5902', + 'display name: AnonymousClassReflectionTest\V@anonymous/tests/PHPStan/Reflection/data/anonymous-classes.php:33', + ]), + 33, + ], ]); } diff --git a/tests/PHPStan/Reflection/data/anonymous-classes.php b/tests/PHPStan/Reflection/data/anonymous-classes.php index 9336316ff98..4024445aeca 100644 --- a/tests/PHPStan/Reflection/data/anonymous-classes.php +++ b/tests/PHPStan/Reflection/data/anonymous-classes.php @@ -11,3 +11,23 @@ public function __construct(object $object) { } }; + +class A {} + +new class extends A {}; new class extends A {}; + +new class (new class extends A {}) extends A { + public function __construct(object $object) + { + } +}; + +interface U {} + +interface V {} + +new class implements U {}; + +new class implements U, V {}; + +new class implements V, U {}; diff --git a/tests/PHPStan/Rules/Classes/RequireImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/RequireImplementsRuleTest.php index a2fe9cf7a38..6a147e9398c 100644 --- a/tests/PHPStan/Rules/Classes/RequireImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/RequireImplementsRuleTest.php @@ -57,7 +57,7 @@ public function testRule(): void 137, ], [ - 'Trait IncompatibleRequireImplements\ValidPsalmTrait requires using class to implement IncompatibleRequireImplements\RequiredInterface2, but class@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php:164 does not.', + 'Trait IncompatibleRequireImplements\ValidPsalmTrait requires using class to implement IncompatibleRequireImplements\RequiredInterface2, but IncompatibleRequireImplements\RequiredInterface@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php:164 does not.', 164, ], [ diff --git a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php index 82240f467e2..babaadaae2a 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodImplementationRuleTest.php @@ -29,7 +29,7 @@ public function testRule(): void 24, ], [ - 'Non-abstract class class@anonymous/tests/PHPStan/Rules/Methods/data/missing-method-impl.php:41 contains abstract method doFoo() from interface MissingMethodImpl\Foo.', + 'Non-abstract class MissingMethodImpl\Foo@anonymous/tests/PHPStan/Rules/Methods/data/missing-method-impl.php:41 contains abstract method doFoo() from interface MissingMethodImpl\Foo.', 41, ], ]); diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php index 02e81f3f550..3b481b8f14d 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php @@ -307,7 +307,7 @@ public function testRedeclaredReadonlyProperties(): void 70, ], [ - 'Readonly property class@anonymous/tests/PHPStan/Rules/Properties/data/redeclare-readonly-property.php:117::$myProp is already assigned.', + 'Readonly property RedeclareReadonlyProperty\A@anonymous/tests/PHPStan/Rules/Properties/data/redeclare-readonly-property.php:117::$myProp is already assigned.', 121, ], [ From cb8f9103f4191f176d1b52cc45f661c3326f194f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Sep 2024 09:42:46 +0200 Subject: [PATCH 0308/3097] E_ALL value is different on PHP 8.4 --- conf/config.neon | 1 + tests/PHPStan/Analyser/nsrt/predefined-constants.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/config.neon b/conf/config.neon index 6fa0145f19c..67351dc4dad 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -260,6 +260,7 @@ parameters: - OPENSSL_VERSION_NUMBER - ZEND_DEBUG_BUILD - ZEND_THREAD_SAFE + - E_ALL # different on PHP 8.4 customRulesetUsed: null editorUrl: null editorUrlTitle: null diff --git a/tests/PHPStan/Analyser/nsrt/predefined-constants.php b/tests/PHPStan/Analyser/nsrt/predefined-constants.php index ed7d7a85777..70dceda6537 100644 --- a/tests/PHPStan/Analyser/nsrt/predefined-constants.php +++ b/tests/PHPStan/Analyser/nsrt/predefined-constants.php @@ -45,7 +45,7 @@ assertType('4096', E_RECOVERABLE_ERROR); assertType('8192', E_DEPRECATED); assertType('16384', E_USER_DEPRECATED); -assertType('32767', E_ALL); +assertType('int', E_ALL); assertType('2048', E_STRICT); assertType('int<1, max>', __COMPILER_HALT_OFFSET__); assertType('true', true); From 06ab32096388b46b51f586e484823ea148ff536f Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 20 Sep 2024 12:33:03 +0200 Subject: [PATCH 0309/3097] add generic types for array_values --- stubs/arrayFunctions.stub | 8 ++++++++ tests/PHPStan/Analyser/nsrt/array_values.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 25c73249ec8..8124ffe56a6 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -16,6 +16,14 @@ function array_reduce( $three = null ) {} +/** + * @template T of mixed + * + * @param array $array + * @return ($array is non-empty-array ? non-empty-list : list) + */ +function array_values(array $array): array {} + /** * @template TKey as (int|string) * @template T diff --git a/tests/PHPStan/Analyser/nsrt/array_values.php b/tests/PHPStan/Analyser/nsrt/array_values.php index d32544f3e6b..a9fc01c9470 100644 --- a/tests/PHPStan/Analyser/nsrt/array_values.php +++ b/tests/PHPStan/Analyser/nsrt/array_values.php @@ -37,4 +37,12 @@ public function constantArrayType(): void ); assertType("array{0?: 'a'|'b'|'c', 1?: 'b'|'c', 2?: 'c'}", array_values($numbers)); } + + /** + * @param array> $a + */ + public function arrayMap(array $a): void + { + assertType('array>', array_map(array_values(...), $a)); + } } From 3e5195b87620163b0becedf9ddce43030c7f2838 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 08:48:49 +0200 Subject: [PATCH 0310/3097] Support IntegerRangeType in ConstantStringType offset-value-type handling --- src/Type/Constant/ConstantStringType.php | 36 ++++++++++++++----- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../PHPStan/Analyser/nsrt/string-offsets.php | 32 +++++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/string-offsets.php diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index fbdbaf2f97b..fcd467fbbba 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -345,10 +345,9 @@ public function isLiteralString(): TrinaryLogic public function hasOffsetValueType(Type $offsetType): TrinaryLogic { - if ($offsetType instanceof ConstantIntegerType) { - return TrinaryLogic::createFromBoolean( - $offsetType->getValue() < strlen($this->value), - ); + if ($offsetType->isInteger()->yes()) { + $strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1); + return $strLenType->isSuperTypeOf($offsetType); } return parent::hasOffsetValueType($offsetType); @@ -356,12 +355,33 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { - if ($offsetType instanceof ConstantIntegerType) { - if ($offsetType->getValue() < strlen($this->value)) { - return new self($this->value[$offsetType->getValue()]); + if ($offsetType->isInteger()->yes()) { + if ($offsetType instanceof ConstantIntegerType) { + if ($offsetType->getValue() < strlen($this->value)) { + return new self($this->value[$offsetType->getValue()]); + } + + return new ErrorType(); } - return new ErrorType(); + $strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1); + $intersected = TypeCombinator::intersect($strLenType, $offsetType); + if ($intersected instanceof IntegerRangeType) { + $finiteTypes = $intersected->getFiniteTypes(); + if ($finiteTypes === []) { + return parent::getOffsetValueType($offsetType); + } + + $chars = []; + foreach ($finiteTypes as $constantInteger) { + $chars[] = new self($this->value[$constantInteger->getValue()]); + } + if (!$strLenType->isSuperTypeOf($offsetType)->yes()) { + $chars[] = new self(''); + } + + return TypeCombinator::union(...$chars); + } } return parent::getOffsetValueType($offsetType); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 0a9beb6fb81..234bbb82d3b 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2904,7 +2904,7 @@ public function dataBinaryOperations(): array '$fooString[4]', ], [ - 'non-empty-string', + "''|'f'|'o'", '$fooString[$integer]', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/string-offsets.php b/tests/PHPStan/Analyser/nsrt/string-offsets.php new file mode 100644 index 00000000000..6a2c2728824 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/string-offsets.php @@ -0,0 +1,32 @@ + $oneToThree + * @param int<3, 10> $threeToTen + * @param int<10, max> $tenOrMore + * @param int<-10, -5> $negative + * + * @return void + */ +function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i) { + $s = "world"; + if (rand(0, 1)) { + $s = "hello"; + } + + assertType("''|'d'|'e'|'h'|'l'|'o'|'r'|'w'", $s[$i]); + + assertType("'h'|'w'", $s[0]); + + assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]); + assertType('*ERROR*', $s[$tenOrMore]); + assertType("''|'d'|'l'|'o'", $s[$threeToTen]); + assertType("*ERROR*", $s[$negative]); + + $longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR"; + assertType("non-empty-string", $longString[$i]); +} From c1076529ae1a3417258c35168f3a1f7df85104c0 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 22 Sep 2024 11:11:52 +0200 Subject: [PATCH 0311/3097] Add `Type::chunkArray()` --- phpstan-baseline.neon | 5 -- src/Type/Accessory/AccessoryArrayListType.php | 5 ++ src/Type/Accessory/HasOffsetType.php | 5 ++ src/Type/Accessory/HasOffsetValueType.php | 5 ++ src/Type/Accessory/NonEmptyArrayType.php | 5 ++ src/Type/Accessory/OversizedArrayType.php | 5 ++ src/Type/ArrayType.php | 14 ++++ src/Type/Constant/ConstantArrayType.php | 36 +++++++++- src/Type/IntersectionType.php | 5 ++ src/Type/MixedType.php | 9 +++ src/Type/NeverType.php | 5 ++ .../ArrayChunkFunctionReturnTypeExtension.php | 68 ++----------------- src/Type/StaticType.php | 5 ++ src/Type/Traits/LateResolvableTypeTrait.php | 5 ++ src/Type/Traits/MaybeArrayTypeTrait.php | 5 ++ src/Type/Traits/NonArrayTypeTrait.php | 5 ++ src/Type/Type.php | 2 + src/Type/UnionType.php | 5 ++ tests/PHPStan/Analyser/nsrt/array-chunk.php | 21 ++++++ 19 files changed, 147 insertions(+), 68 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d06f9c49232..79f1b546ea2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1283,11 +1283,6 @@ parameters: count: 4 path: src/Type/ObjectWithoutClassType.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index f80ada4ad90..511087818d1 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -194,6 +194,11 @@ public function getValuesArray(): Type return $this; } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function fillKeysArray(Type $valueType): Type { return new MixedType(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index ac4b6e402b5..5ed39d97970 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -175,6 +175,11 @@ public function unsetOffset(Type $offsetType): Type return $this; } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new NonEmptyArrayType(); + } + public function fillKeysArray(Type $valueType): Type { return new NonEmptyArrayType(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 59a2a26b217..ae1ef84f564 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -209,6 +209,11 @@ public function getValuesArray(): Type return new NonEmptyArrayType(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new NonEmptyArrayType(); + } + public function fillKeysArray(Type $valueType): Type { return new NonEmptyArrayType(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 8f2ccac6894..1f2abd69b69 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -179,6 +179,11 @@ public function getValuesArray(): Type return $this; } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function fillKeysArray(Type $valueType): Type { return $this; diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index ad8a1c2a139..d710a178581 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -175,6 +175,11 @@ public function getValuesArray(): Type return $this; } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function fillKeysArray(Type $valueType): Type { return $this; diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 061e002a46d..d2eaf1bc242 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -513,6 +513,20 @@ public function unsetOffset(Type $offsetType): Type return $this; } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + $chunkType = $preserveKeys->yes() + ? $this + : AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $this->getIterableValueType())); + $chunkType = TypeCombinator::intersect($chunkType, new NonEmptyArrayType()); + + $arrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $chunkType)); + + return $this->isIterableAtLeastOnce()->yes() + ? TypeCombinator::intersect($arrayType, new NonEmptyArrayType()) + : $arrayType; + } + public function fillKeysArray(Type $valueType): Type { $itemType = $this->getItemType(); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7d2d0c9bc17..384e9327963 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -73,6 +73,7 @@ class ConstantArrayType extends ArrayType implements ConstantType { private const DESCRIBE_LIMIT = 8; + private const CHUNK_FINITE_TYPES_LIMIT = 5; private TrinaryLogic $isList; @@ -780,6 +781,36 @@ public function unsetOffset(Type $offsetType): Type return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $isList); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + $biggerOne = IntegerRangeType::fromInterval(1, null); + $finiteTypes = $lengthType->getFiniteTypes(); + if ($biggerOne->isSuperTypeOf($lengthType)->yes() && count($finiteTypes) < self::CHUNK_FINITE_TYPES_LIMIT) { + $results = []; + foreach ($finiteTypes as $finiteType) { + if (!$finiteType instanceof ConstantIntegerType || $finiteType->getValue() < 1) { + return parent::chunkArray($lengthType, $preserveKeys); + } + + $length = $finiteType->getValue(); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + + $keyTypesCount = count($this->keyTypes); + for ($i = 0; $i < $keyTypesCount; $i += $length) { + $chunk = $this->slice($i, $length, true); + $builder->setOffsetValueType(null, $preserveKeys->yes() ? $chunk : $chunk->getValuesArray()); + } + + $results[] = $builder->getArray(); + } + + return TypeCombinator::union(...$results); + } + + return parent::chunkArray($lengthType, $preserveKeys); + } + public function fillKeysArray(Type $valueType): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -1185,7 +1216,10 @@ public function reverse(bool $preserveKeys = false): self return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys)); } - /** @param positive-int $length */ + /** + * @deprecated Use chunkArray() instead + * @param positive-int $length + */ public function chunk(int $length, bool $preserveKeys = false): self { $builder = ConstantArrayTypeBuilder::createEmpty(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 08ff4070334..8bf38bdddaa 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -724,6 +724,11 @@ public function getValuesArray(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->getValuesArray()); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->chunkArray($lengthType, $preserveKeys)); + } + public function fillKeysArray(Type $valueType): Type { return $this->intersectTypes(static fn (Type $type): Type => $type->fillKeysArray($valueType)); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index c45642f0d2b..00d256addf3 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -189,6 +189,15 @@ public function getValuesArray(): Type return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed))); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ($this->isArray()->no()) { + return new ErrorType(); + } + + return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed))); + } + public function fillKeysArray(Type $valueType): Type { if ($this->isArray()->no()) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index a21dcb7d236..5d488d319c5 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -296,6 +296,11 @@ public function getValuesArray(): Type return new NeverType(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new NeverType(); + } + public function fillKeysArray(Type $valueType): Type { return new NeverType(); diff --git a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php index 1c8530ec3bb..d17122bbecc 100644 --- a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php @@ -6,25 +6,17 @@ use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Accessory\AccessoryArrayListType; -use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\IntegerType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; final class ArrayChunkFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - private const FINITE_TYPES_LIMIT = 5; - public function __construct(private PhpVersion $phpVersion) { } @@ -41,68 +33,20 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $arrayType = $scope->getType($functionCall->getArgs()[0]->value); - $lengthType = $scope->getType($functionCall->getArgs()[1]->value); - if (isset($functionCall->getArgs()[2])) { - $preserveKeysType = $scope->getType($functionCall->getArgs()[2]->value); - $preserveKeys = $preserveKeysType instanceof ConstantBooleanType ? $preserveKeysType->getValue() : null; - } else { - $preserveKeys = false; + if ($arrayType->isArray()->no()) { + return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } + $lengthType = $scope->getType($functionCall->getArgs()[1]->value); $negativeOrZero = IntegerRangeType::fromInterval(null, 0); if ($negativeOrZero->isSuperTypeOf($lengthType)->yes()) { return $this->phpVersion->throwsValueErrorForInternalFunctions() ? new NeverType() : new NullType(); } - if (!$arrayType->isArray()->yes()) { - return null; - } - - if ($preserveKeys !== null) { - $constantArrays = $arrayType->getConstantArrays(); - $biggerOne = IntegerRangeType::fromInterval(1, null); - $finiteTypes = $lengthType->getFiniteTypes(); - if (count($constantArrays) > 0 - && $biggerOne->isSuperTypeOf($lengthType)->yes() - && count($finiteTypes) < self::FINITE_TYPES_LIMIT - ) { - $results = []; - foreach ($constantArrays as $constantArray) { - foreach ($finiteTypes as $finiteType) { - if (!$finiteType instanceof ConstantIntegerType || $finiteType->getValue() < 1) { - return null; - } - - $results[] = $constantArray->chunk($finiteType->getValue(), $preserveKeys); - } - } - - return TypeCombinator::union(...$results); - } - } - - $chunkType = self::getChunkType($arrayType, $preserveKeys); - - $resultType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $chunkType)); - if ($arrayType->isIterableAtLeastOnce()->yes()) { - $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); - } - - return $resultType; - } - - private static function getChunkType(Type $type, ?bool $preserveKeys): Type - { - if ($preserveKeys === null) { - $chunkType = new ArrayType(TypeCombinator::union($type->getIterableKeyType(), new IntegerType()), $type->getIterableValueType()); - } elseif ($preserveKeys) { - $chunkType = $type; - } else { - $chunkType = new ArrayType(new IntegerType(), $type->getIterableValueType()); - $chunkType = AccessoryArrayListType::intersectWith($chunkType); - } + $preserveKeysType = isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : new ConstantBooleanType(false); + $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType); - return TypeCombinator::intersect($chunkType, new NonEmptyArrayType()); + return $arrayType->chunkArray($lengthType, $preserveKeys); } } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 9db6c41cd0e..bb55a6c9fa0 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -415,6 +415,11 @@ public function getValuesArray(): Type return $this->getStaticObjectType()->getValuesArray(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->getStaticObjectType()->chunkArray($lengthType, $preserveKeys); + } + public function fillKeysArray(Type $valueType): Type { return $this->getStaticObjectType()->fillKeysArray($valueType); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index bea1d953a01..bff88f730eb 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -262,6 +262,11 @@ public function getValuesArray(): Type return $this->resolve()->getValuesArray(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->resolve()->chunkArray($lengthType, $preserveKeys); + } + public function fillKeysArray(Type $valueType): Type { return $this->resolve()->fillKeysArray($valueType); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index 7d258975537..a68b3577225 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -49,6 +49,11 @@ public function getValuesArray(): Type return new ErrorType(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + public function fillKeysArray(Type $valueType): Type { return new ErrorType(); diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 8deb186895f..d6f332d2f23 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -49,6 +49,11 @@ public function getValuesArray(): Type return new ErrorType(); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + public function fillKeysArray(Type $valueType): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 60e1046d1bf..727e2f8a312 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -151,6 +151,8 @@ public function getKeysArray(): Type; public function getValuesArray(): Type; + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type; + public function fillKeysArray(Type $valueType): Type; public function flipArray(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f79dbe88a41..af4ede1b405 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -701,6 +701,11 @@ public function getValuesArray(): Type return $this->unionTypes(static fn (Type $type): Type => $type->getValuesArray()); } + public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->chunkArray($lengthType, $preserveKeys)); + } + public function fillKeysArray(Type $valueType): Type { return $this->unionTypes(static fn (Type $type): Type => $type->fillKeysArray($valueType)); diff --git a/tests/PHPStan/Analyser/nsrt/array-chunk.php b/tests/PHPStan/Analyser/nsrt/array-chunk.php index a1452bd6c3d..8e6fa67cf29 100644 --- a/tests/PHPStan/Analyser/nsrt/array-chunk.php +++ b/tests/PHPStan/Analyser/nsrt/array-chunk.php @@ -74,5 +74,26 @@ function testLimits(array $arr, int $oneToFour, int $tooBig) { assertType('non-empty-list>', array_chunk($arr, $tooBig)); } + /** @param array $map */ + public function offsets(array $arr, array $map): void + { + if (array_key_exists('foo', $arr)) { + assertType('non-empty-list>', array_chunk($arr, 2)); + assertType('non-empty-list', array_chunk($arr, 2, true)); + } + if (array_key_exists('foo', $arr) && $arr['foo'] === 'bar') { + assertType('non-empty-list>', array_chunk($arr, 2)); + assertType('non-empty-list', array_chunk($arr, 2, true)); + } + + if (array_key_exists('foo', $map)) { + assertType('non-empty-list>', array_chunk($map, 2)); + assertType('non-empty-list>', array_chunk($map, 2, true)); + } + if (array_key_exists('foo', $map) && $map['foo'] === 'bar') { + assertType('non-empty-list>', array_chunk($map, 2)); + assertType('non-empty-list>', array_chunk($map, 2, true)); + } + } } From 8d87c671c74eb440442fe095db58f66b8e1f849e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 21 Sep 2024 16:40:14 +0200 Subject: [PATCH 0312/3097] Solve 11703 --- src/Type/Accessory/OversizedArrayType.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-11703.php | 99 +++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11703.php diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index d710a178581..2e9da3ae05e 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -227,12 +227,12 @@ public function isIterable(): TrinaryLogic public function isIterableAtLeastOnce(): TrinaryLogic { - return TrinaryLogic::createYes(); + return TrinaryLogic::createMaybe(); } public function getArraySize(): Type { - return IntegerRangeType::fromInterval(1, null); + return IntegerRangeType::fromInterval(0, null); } public function getIterableKeyType(): Type diff --git a/tests/PHPStan/Analyser/nsrt/bug-11703.php b/tests/PHPStan/Analyser/nsrt/bug-11703.php new file mode 100644 index 00000000000..ae1a91127b2 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11703.php @@ -0,0 +1,99 @@ + 'Some message about the alert.', + 'duration' => $duration, + 'severity' => 100, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert2.', + 'duration' => $duration, + 'severity' => 99, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert3.', + 'duration' => $duration, + 'severity' => 75, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert4.', + 'duration' => $duration, + 'severity' => 60, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert5.', + 'duration' => null, + 'severity' => 25, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert6.', + 'duration' => $duration, + 'severity' => 24, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert7.', + 'duration' => $duration, + 'severity' => 24, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert8.', + 'duration' => $duration, + 'severity' => 24, + ]; + } + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert9.', + 'duration' => $duration, + 'severity' => 24, + ]; + } + + assertType('int<0, 9>', count($alerts)); + + if (mt_rand(0, 1)) { + $alerts[] = [ + 'message' => 'Some message about the alert10.', + 'duration' => $duration, + 'severity' => 23, + ]; + } + + assertType('int<0, max>', count($alerts)); + if (count($alerts) === 0) { + return null; + } + + return true; +} From 7b0d777fad1b093c4c25a2032922d6dc9b757063 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 11:16:02 +0200 Subject: [PATCH 0313/3097] Fix ErrorType after ArrayDimFetch --- src/Type/Constant/ConstantArrayType.php | 22 +++++++++++++++---- .../Rules/Variables/data/bug-10577.php | 12 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 384e9327963..27d874deab8 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -652,12 +652,26 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { + if (count($this->keyTypes) === 0) { + return new ErrorType(); + } + $offsetType = $offsetType->toArrayKey(); $matchingValueTypes = []; $all = true; + $maybeAll = true; foreach ($this->keyTypes as $i => $keyType) { if ($keyType->isSuperTypeOf($offsetType)->no()) { $all = false; + + if ( + $keyType instanceof ConstantIntegerType + && !$offsetType->isString()->no() + && $offsetType->isConstantScalarValue()->no() + ) { + continue; + } + $maybeAll = false; continue; } @@ -665,10 +679,6 @@ public function getOffsetValueType(Type $offsetType): Type } if ($all) { - if (count($this->keyTypes) === 0) { - return new ErrorType(); - } - return $this->getIterableValueType(); } @@ -681,6 +691,10 @@ public function getOffsetValueType(Type $offsetType): Type return $type; } + if ($maybeAll) { + return $this->getIterableValueType(); + } + return new ErrorType(); // undefined offset } diff --git a/tests/PHPStan/Rules/Variables/data/bug-10577.php b/tests/PHPStan/Rules/Variables/data/bug-10577.php index c84a6897ff6..36ecae0c593 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-10577.php +++ b/tests/PHPStan/Rules/Variables/data/bug-10577.php @@ -20,10 +20,22 @@ public function validate(string $value): void throw new \RuntimeException(); } + assertType("non-empty-string", $value); + assertType("'Test1'|'Test2'", self::MAP[$value]); + $value = self::MAP[$value] ?? $value; + assertType("non-empty-string", $value); assertType("'Test1'|'Test2'", self::MAP[$value]); // ... } + + public function validateNumericString(string $value): void + { + if (!is_numeric($value)) return; + + assertType("numeric-string", $value); + assertType("'Test1'|'Test2'", self::MAP[$value]); + } } From d517bb46ea8609f0f0106d97f1d502358db91118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Fri, 20 Sep 2024 20:35:08 +0200 Subject: [PATCH 0314/3097] implement ClosureType::getReferencedTemplateTypes() --- src/Type/ClosureType.php | 20 +++++++++++++++++-- .../MethodSignatureVarianceRuleTest.php | 10 ++++++++++ .../PHPStan/Rules/Generics/data/bug-10609.php | 16 +++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Generics/data/bug-10609.php diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 6d987f63421..967b850525c 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -36,10 +36,10 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; -use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -53,7 +53,6 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor { use NonArrayTypeTrait; - use NonGenericTypeTrait; use NonIterableTypeTrait; use UndecidedComparisonTypeTrait; use NonOffsetAccessibleTypeTrait; @@ -540,6 +539,23 @@ private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $para return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); } + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $references = $this->getReturnType()->getReferencedTemplateTypes( + $positionVariance->compose(TemplateTypeVariance::createCovariant()), + ); + + $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant()); + + foreach ($this->getParameters() as $param) { + foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) { + $references[] = $reference; + } + } + + return $references; + } + public function traverse(callable $cb): Type { if ($this->isCommonCallable) { diff --git a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php index 8f743f4dc71..f01b15ba3d6 100644 --- a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php @@ -234,4 +234,14 @@ public function testPr2465(): void ]); } + public function testBug10609(): void + { + $this->analyse([__DIR__ . '/data/bug-10609.php'], [ + [ + 'Template type A is declared as covariant, but occurs in contravariant position in parameter fn of method Bug10609\Collection::tap().', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Generics/data/bug-10609.php b/tests/PHPStan/Rules/Generics/data/bug-10609.php new file mode 100644 index 00000000000..c62a9e0a10a --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/bug-10609.php @@ -0,0 +1,16 @@ + Date: Sun, 22 Sep 2024 11:26:05 +0200 Subject: [PATCH 0315/3097] Fix substracted union type describe --- src/Type/MixedType.php | 8 +++++-- src/Type/ObjectType.php | 8 +++++-- src/Type/ObjectWithoutClassType.php | 4 +++- tests/PHPStan/Analyser/TypeSpecifierTest.php | 24 +++++++++---------- tests/PHPStan/Analyser/nsrt/assert-empty.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-10189.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-3991.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-3993.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4117.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7176.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-pr-339.php | 4 ++-- .../Analyser/nsrt/callsite-cast-narrowing.php | 2 +- .../Analyser/nsrt/composer-array-bug.php | 2 +- .../composer-non-empty-array-after-unset.php | 2 +- tests/PHPStan/Analyser/nsrt/ctype-digit.php | 4 ++-- tests/PHPStan/Analyser/nsrt/more-types.php | 2 +- .../Analyser/nsrt/this-subtractable.php | 10 ++++---- .../Analyser/nsrt/throw-points/try-catch.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 10 ++++---- 19 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 00d256addf3..55953f7f285 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -447,7 +447,9 @@ public function describe(VerbosityLevel $level): string function () use ($level): string { $description = 'mixed'; if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe($level)) + : sprintf('~%s', $this->subtractedType->describe($level)); } return $description; @@ -455,7 +457,9 @@ function () use ($level): string { function () use ($level): string { $description = 'mixed'; if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe($level)) + : sprintf('~%s', $this->subtractedType->describe($level)); } if ($this->isExplicitMixed) { diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 45a04563c90..1732c3c6277 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -487,7 +487,9 @@ public function describe(VerbosityLevel $level): string $preciseWithSubtracted = function () use ($level): string { $description = $this->className; if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe($level)) + : sprintf('~%s', $this->subtractedType->describe($level)); } return $description; @@ -538,7 +540,9 @@ private function describeCache(): string } if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache())) + : sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); } $reflection = $this->classReflection; diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 2d0cc64c1e4..1144acd2f83 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -132,7 +132,9 @@ public function describe(VerbosityLevel $level): string function () use ($level): string { $description = 'object'; if ($this->subtractedType !== null) { - $description .= sprintf('~%s', $this->subtractedType->describe($level)); + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe($level)) + : sprintf('~%s', $this->subtractedType->describe($level)); } return $description; diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 37fbd12ccf2..ced1959085a 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -44,7 +44,7 @@ class TypeSpecifierTest extends PHPStanTestCase { private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array{}|false|null'; - private const TRUTHY_TYPE_DESCRIPTION = 'mixed~' . self::FALSEY_TYPE_DESCRIPTION; + private const TRUTHY_TYPE_DESCRIPTION = 'mixed~(' . self::FALSEY_TYPE_DESCRIPTION . ')'; private const SURE_NOT_FALSEY = '~' . self::FALSEY_TYPE_DESCRIPTION; private const SURE_NOT_TRUTHY = '~' . self::TRUTHY_TYPE_DESCRIPTION; @@ -819,10 +819,10 @@ public function dataCondition(): iterable new LNumber(3), ), [ - '$n' => 'mixed~int<3, max>|true', + '$n' => 'mixed~(int<3, max>|true)', ], [ - '$n' => 'mixed~0.0|int|false|null', + '$n' => 'mixed~(0.0|int|false|null)', ], ], [ @@ -831,10 +831,10 @@ public function dataCondition(): iterable new LNumber(PHP_INT_MIN), ), [ - '$n' => 'mixed~int<' . PHP_INT_MIN . ', max>|true', + '$n' => 'mixed~(int<' . PHP_INT_MIN . ', max>|true)', ], [ - '$n' => 'mixed~0.0|false|null', + '$n' => 'mixed~(0.0|false|null)', ], ], [ @@ -843,7 +843,7 @@ public function dataCondition(): iterable new LNumber(PHP_INT_MAX), ), [ - '$n' => 'mixed~0.0|bool|int|null', + '$n' => 'mixed~(0.0|bool|int|null)', ], [ '$n' => 'mixed', @@ -858,7 +858,7 @@ public function dataCondition(): iterable '$n' => 'mixed~int<' . (PHP_INT_MIN + 1) . ', max>', ], [ - '$n' => 'mixed~0.0|bool|int|null', + '$n' => 'mixed~(0.0|bool|int|null)', ], ], [ @@ -867,10 +867,10 @@ public function dataCondition(): iterable new LNumber(PHP_INT_MAX), ), [ - '$n' => 'mixed~0.0|int|false|null', + '$n' => 'mixed~(0.0|int|false|null)', ], [ - '$n' => 'mixed~int<' . PHP_INT_MAX . ', max>|true', + '$n' => 'mixed~(int<' . PHP_INT_MAX . ', max>|true)', ], ], [ @@ -885,10 +885,10 @@ public function dataCondition(): iterable ), ), [ - '$n' => 'mixed~0.0|int|int<6, max>|false|null', + '$n' => 'mixed~(0.0|int|int<6, max>|false|null)', ], [ - '$n' => 'mixed~int<3, 5>|true', + '$n' => 'mixed~(int<3, 5>|true)', ], ], [ @@ -1250,7 +1250,7 @@ public function dataCondition(): iterable ), [ '$foo' => 'non-empty-array', - 'count($foo)' => 'mixed~0.0|int|false|null', + 'count($foo)' => 'mixed~(0.0|int|false|null)', ], [], ], diff --git a/tests/PHPStan/Analyser/nsrt/assert-empty.php b/tests/PHPStan/Analyser/nsrt/assert-empty.php index 12176791a3a..236e2b43d01 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-empty.php +++ b/tests/PHPStan/Analyser/nsrt/assert-empty.php @@ -25,5 +25,5 @@ function ($var) { function ($var) { assertNotEmpty($var); - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $var); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $var); }; diff --git a/tests/PHPStan/Analyser/nsrt/bug-10189.php b/tests/PHPStan/Analyser/nsrt/bug-10189.php index 6d3d0e86451..6cb97fa4de1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10189.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10189.php @@ -20,7 +20,7 @@ function file_managed_file_save_upload(): array { } assertType('non-empty-array|Bug10189\SomeInterface', $files); $files = array_filter($files); - assertType("array", $files); + assertType("array", $files); return empty($files) ? [] : [1,2]; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-3991.php b/tests/PHPStan/Analyser/nsrt/bug-3991.php index 3a01a2d27b8..e1a79f6937d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3991.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3991.php @@ -26,7 +26,7 @@ public static function email($config = null) assertType('array{}|null', $config); $config = new \stdClass(); } elseif (! (is_array($config) || $config instanceof \stdClass)) { - assertNativeType('mixed~0|0.0|\'\'|\'0\'|array{}|stdClass|false|null', $config); + assertNativeType('mixed~(0|0.0|\'\'|\'0\'|array{}|stdClass|false|null)', $config); assertType('*NEVER*', $config); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-3993.php b/tests/PHPStan/Analyser/nsrt/bug-3993.php index 38b1884bf5d..a1a24e380dc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3993.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3993.php @@ -13,7 +13,7 @@ public function doFoo($arguments) return; } - assertType('mixed~array{}|null', $arguments); + assertType('mixed~(array{}|null)', $arguments); array_shift($arguments); diff --git a/tests/PHPStan/Analyser/nsrt/bug-4117.php b/tests/PHPStan/Analyser/nsrt/bug-4117.php index d1bca857c38..f1f2f60f409 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4117.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4117.php @@ -32,7 +32,7 @@ public function broken(int $key) $item = $this->items[$key] ?? null; assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item); if ($item) { - assertType("T of mixed~0|0.0|''|'0'|array{}|false|null (class Bug4117Types\GenericList, argument)", $item); + assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item); } else { assertType("(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|null", $item); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7176.php b/tests/PHPStan/Analyser/nsrt/bug-7176.php index 6be67ffaba2..29a5f83184e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7176.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7176.php @@ -25,7 +25,7 @@ function test(Suit $x): string { assertType('Bug7176Types\Suit::Spades', $x); return 'DOES NOT WORK'; } - assertType('Bug7176Types\Suit~Bug7176Types\Suit::Clubs|Bug7176Types\Suit::Spades', $x); + assertType('Bug7176Types\Suit~(Bug7176Types\Suit::Clubs|Bug7176Types\Suit::Spades)', $x); return match ($x) { Suit::Hearts => 'a', diff --git a/tests/PHPStan/Analyser/nsrt/bug-pr-339.php b/tests/PHPStan/Analyser/nsrt/bug-pr-339.php index 768efbc147e..4d841ad783e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-pr-339.php +++ b/tests/PHPStan/Analyser/nsrt/bug-pr-339.php @@ -17,14 +17,14 @@ assertType('mixed', $a); assertType('mixed', $c); if ($a) { - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $a); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $a); assertType('mixed', $c); assertVariableCertainty(TrinaryLogic::createYes(), $a); } if ($c) { assertType('mixed', $a); - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $c); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $c); assertVariableCertainty(TrinaryLogic::createYes(), $c); } } else { diff --git a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php index ae067d82f13..239e49d53a9 100644 --- a/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php +++ b/tests/PHPStan/Analyser/nsrt/callsite-cast-narrowing.php @@ -15,7 +15,7 @@ public function sayHello($mixed, int $int, string $string, $numericString, $nonE if (ctype_digit((string) $mixed)) { assertType('int<0, max>|numeric-string|true', $mixed); } else { - assertType('mixed~int<0, max>|numeric-string|true', $mixed); + assertType('mixed~(int<0, max>|numeric-string|true)', $mixed); } assertType('mixed', $mixed); diff --git a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php index 42733805c31..fbbcff38a81 100644 --- a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php +++ b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php @@ -51,7 +51,7 @@ public function doFoo(): void unset($this->config['authors']); assertType("array", $this->config); } else { - assertType("array&hasOffsetValue('authors', mixed~0|0.0|''|'0'|array{}|false|null)", $this->config); + assertType("array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config); } assertType('array', $this->config); diff --git a/tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php b/tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php index 31914d185e1..0cec47c79a6 100644 --- a/tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php +++ b/tests/PHPStan/Analyser/nsrt/composer-non-empty-array-after-unset.php @@ -13,7 +13,7 @@ class Foo public function doFoo() { if (!empty($this->config['authors'])) { - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $this->config['authors']); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $this->config['authors']); foreach ($this->config['authors'] as $key => $author) { assertType("mixed", $this->config['authors']); if (!is_array($author)) { diff --git a/tests/PHPStan/Analyser/nsrt/ctype-digit.php b/tests/PHPStan/Analyser/nsrt/ctype-digit.php index 835ba4fdccd..6d013f52e45 100644 --- a/tests/PHPStan/Analyser/nsrt/ctype-digit.php +++ b/tests/PHPStan/Analyser/nsrt/ctype-digit.php @@ -20,7 +20,7 @@ public function foo(mixed $foo): void if (is_int($foo) && ctype_digit($foo)) { assertType('int<48, 57>|int<256, max>', $foo); } else { - assertType('mixed~int<48, 57>|int<256, max>', $foo); + assertType('mixed~(int<48, 57>|int<256, max>)', $foo); } if (ctype_digit($foo)) { @@ -28,6 +28,6 @@ public function foo(mixed $foo): void return; } - assertType('mixed~int<48, 57>|int<256, max>', $foo); // not all numeric strings are covered by ctype_digit + assertType('mixed~(int<48, 57>|int<256, max>)', $foo); // not all numeric strings are covered by ctype_digit } } diff --git a/tests/PHPStan/Analyser/nsrt/more-types.php b/tests/PHPStan/Analyser/nsrt/more-types.php index f1b742f170e..70dc86d3d9e 100644 --- a/tests/PHPStan/Analyser/nsrt/more-types.php +++ b/tests/PHPStan/Analyser/nsrt/more-types.php @@ -38,7 +38,7 @@ public function doFoo( assertType('literal-string&non-empty-string', $nonEmptyLiteralString); assertType('float|int|int<1, max>|non-falsy-string|true', $nonEmptyScalar); assertType("0|0.0|''|'0'|false", $emptyScalar); - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $nonEmptyMixed); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $nonEmptyMixed); } } diff --git a/tests/PHPStan/Analyser/nsrt/this-subtractable.php b/tests/PHPStan/Analyser/nsrt/this-subtractable.php index a087e3468b7..a3d9a57554e 100644 --- a/tests/PHPStan/Analyser/nsrt/this-subtractable.php +++ b/tests/PHPStan/Analyser/nsrt/this-subtractable.php @@ -12,7 +12,7 @@ public function doFoo() assertType('$this(ThisSubtractable\Foo)', $this); if (!$this instanceof Bar && !$this instanceof Baz) { - assertType('$this(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $this); + assertType('$this(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz))', $this); } else { assertType('($this(ThisSubtractable\Foo)&ThisSubtractable\Bar)|($this(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $this); } @@ -26,7 +26,7 @@ public function doBar() assertType('static(ThisSubtractable\Foo)', $s); if (!$s instanceof Bar && !$s instanceof Baz) { - assertType('static(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $s); + assertType('static(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz))', $s); } else { assertType('(static(ThisSubtractable\Foo)&ThisSubtractable\Bar)|(static(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $s); } @@ -52,7 +52,7 @@ public function doBazz(self $s) assertType('ThisSubtractable\Foo', $s); if (!$s instanceof Bar && !$s instanceof Baz) { - assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz', $s); + assertType('ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)', $s); } else { assertType('ThisSubtractable\Bar|ThisSubtractable\Baz', $s); } @@ -70,12 +70,12 @@ public function doBazzz(self $s) assertType('ThisSubtractable\Foo&hasMethod(test123)', $s); if (!$s instanceof Bar && !$s instanceof Baz) { - assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123)', $s); + assertType('ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)&hasMethod(test123)', $s); } else { assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))', $s); } - assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123))', $s); + assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~(ThisSubtractable\Bar|ThisSubtractable\Baz)&hasMethod(test123))', $s); } /** diff --git a/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php b/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php index f39b3e4fac3..87c0be66eb3 100644 --- a/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php +++ b/tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php @@ -76,7 +76,7 @@ function (): void { assertVariableCertainty(TrinaryLogic::createMaybe(), $baz); assertType('1|2', $baz); } catch (\Throwable $e) { - assertType('Throwable~InvalidArgumentException|RuntimeException', $e); + assertType('Throwable~(InvalidArgumentException|RuntimeException)', $e); assertVariableCertainty(TrinaryLogic::createNo(), $foo); assertVariableCertainty(TrinaryLogic::createYes(), $bar); assertVariableCertainty(TrinaryLogic::createNo(), $baz); diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 5a64d209676..95e11da9738 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1886,7 +1886,7 @@ public function dataUnion(): iterable StaticTypeFactory::truthy(), ], MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array{}|false|null=implicit', + 'mixed~(0|0.0|\'\'|\'0\'|array{}|false|null)=implicit', ], [ [ @@ -3325,7 +3325,7 @@ public function dataIntersect(): iterable new MixedType(false, new IntegerType()), ], MixedType::class, - 'mixed~int|string=implicit', + 'mixed~(int|string)=implicit', ], [ [ @@ -4494,7 +4494,7 @@ public function dataRemove(): array StaticTypeFactory::truthy(), StaticTypeFactory::falsey(), MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array{}|false|null', + 'mixed~(0|0.0|\'\'|\'0\'|array{}|false|null)', ], [ StaticTypeFactory::falsey(), @@ -4670,7 +4670,7 @@ public function dataRemove(): array new MixedType(false, new IntegerType()), new StringType(), MixedType::class, - 'mixed~int|string', + 'mixed~(int|string)', ], [ new MixedType(false), @@ -4706,7 +4706,7 @@ public function dataRemove(): array new ObjectType('Exception', new ObjectType('InvalidArgumentException')), new ObjectType('LengthException'), ObjectType::class, - 'Exception~InvalidArgumentException|LengthException', + 'Exception~(InvalidArgumentException|LengthException)', ], [ new ObjectType('Exception'), From 105b9faa81e0b75483ddcfecdbbeca1ee7289b2c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 13:01:31 +0200 Subject: [PATCH 0316/3097] Fix build --- .../Rules/Generics/MethodSignatureVarianceRuleTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php index f01b15ba3d6..ec893f09479 100644 --- a/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -236,6 +237,10 @@ public function testPr2465(): void public function testBug10609(): void { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-10609.php'], [ [ 'Template type A is declared as covariant, but occurs in contravariant position in parameter fn of method Bug10609\Collection::tap().', From c7b3443f9258c115c9d76d98c9c2c9c082a5ef88 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:33:28 +0000 Subject: [PATCH 0317/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index af64be29c09..c30f4a3827c 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", "phpstan/php-8-stubs": "0.3.106", - "phpstan/phpdoc-parser": "1.30.1", + "phpstan/phpdoc-parser": "1.31.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 3684cf73d2a..ddf91ae047a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d693958743075cac1ba17147e8d55b4", + "content-hash": "ea2c2c535b8c0031d60c5ef66f124cf0", "packages": [ { "name": "clue/ndjson-react", @@ -2280,16 +2280,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.1", + "version": "1.31.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e" + "reference": "249f15fb843bf240cf058372dad29e100cee6c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/249f15fb843bf240cf058372dad29e100cee6c17", + "reference": "249f15fb843bf240cf058372dad29e100cee6c17", "shasum": "" }, "require": { @@ -2321,9 +2321,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.31.0" }, - "time": "2024-09-07T20:13:05+00:00" + "time": "2024-09-22T11:32:18+00:00" }, { "name": "psr/container", From 920f87202feedb824274c9fe26546cbc4104135c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 13:58:58 +0200 Subject: [PATCH 0318/3097] More precise mixed-type subtraction in `toInteger()` --- src/Type/MixedType.php | 23 ++++++++++- .../Analyser/nsrt/integer-range-types.php | 41 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 55953f7f285..1539b6d3f4b 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -27,6 +27,7 @@ use PHPStan\Type\Accessory\OversizedArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -484,10 +485,10 @@ public function toBoolean(): BooleanType public function toNumber(): Type { - return new UnionType([ + return TypeCombinator::union( $this->toInteger(), $this->toFloat(), - ]); + ); } public function toAbsoluteNumber(): Type @@ -497,6 +498,24 @@ public function toAbsoluteNumber(): Type public function toInteger(): Type { + $castsToZero = new UnionType([ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantArrayType([], []), + new StringType(), + new FloatType(), + ]); + if ( + $this->subtractedType !== null + && $this->subtractedType->isSuperTypeOf($castsToZero)->yes() + ) { + return new UnionType([ + IntegerRangeType::fromInterval(null, -1), + IntegerRangeType::fromInterval(1, null), + ]); + } + return new IntegerType(); } diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index c769890c9e3..308064e2a41 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -446,3 +446,44 @@ public function zeroIssues($positive, $negative) } } + +function subtract($m) { + if ($m != 0) { + assertType("mixed", $m); // could be "mixed~0|0.0|''|'0'|array{}|false|null" + assertType('int', (int) $m); + } + if ($m !== 0) { + assertType("mixed~0", $m); + assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0 + } + if (!is_int($m)) { + assertType("mixed~int", $m); + assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0 + } + + if ($m != true) { + assertType("0|0.0|''|'0'|array{}|false|null", $m); + assertType('0', (int) $m); + } + if ($m !== true) { + assertType("mixed~true", $m); + assertType('int', (int) $m); + } + + if ($m != false) { + assertType("mixed~0|0.0|''|'0'|array{}|false|null", $m); + assertType('int', (int) $m); + } + if ($m !== false) { + assertType("mixed~false", $m); + assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0 + } + if (!is_string($m) && !is_float($m)) { + assertType("mixed~float|string", $m); + assertType('int', (int) $m); + if ($m != false) { + assertType("mixed~0|array{}|float|string|false|null", $m); + assertType('int|int<1, max>', (int) $m); + } + } +} From 38ac7ad444d94862fccc6f6992332d445b559ff3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 22 Sep 2024 16:38:47 +0200 Subject: [PATCH 0319/3097] Fix test --- tests/PHPStan/Analyser/nsrt/integer-range-types.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index 308064e2a41..cd35c779530 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -471,7 +471,7 @@ function subtract($m) { } if ($m != false) { - assertType("mixed~0|0.0|''|'0'|array{}|false|null", $m); + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m); assertType('int', (int) $m); } if ($m !== false) { @@ -479,10 +479,10 @@ function subtract($m) { assertType('int', (int) $m); // mixed could still contain falsey values, which cast to 0 } if (!is_string($m) && !is_float($m)) { - assertType("mixed~float|string", $m); + assertType("mixed~(float|string)", $m); assertType('int', (int) $m); if ($m != false) { - assertType("mixed~0|array{}|float|string|false|null", $m); + assertType("mixed~(0|array{}|float|string|false|null)", $m); assertType('int|int<1, max>', (int) $m); } } From 60e4151e7d9dbabc2c75e0ca7139af45b3c18ebd Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 22 Sep 2024 17:07:18 +0200 Subject: [PATCH 0320/3097] Add missing namespace in test --- tests/PHPStan/Analyser/nsrt/bug-10834.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10834.php b/tests/PHPStan/Analyser/nsrt/bug-10834.php index 69efb186356..8e9050ac4ee 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10834.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10834.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace Bug10834; + use function PHPStan\Testing\assertType; /** From fac4b0af6dd70d2fffc4563750d612cb0de17430 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 17:39:41 +0200 Subject: [PATCH 0321/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/11709 Closes https://github.com/phpstan/phpstan/issues/9524 --- .../Analyser/AnalyserIntegrationTest.php | 10 +++++++ tests/PHPStan/Analyser/data/bug-11709.php | 28 +++++++++++++++++++ .../Methods/OverridingMethodRuleTest.php | 6 ++++ tests/PHPStan/Rules/Methods/data/bug-9524.php | 11 ++++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-11709.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-9524.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 207ff22e0bd..38afaf662da 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1455,6 +1455,16 @@ public function testBug11640(): void $this->assertNoErrors($errors); } + public function testBug11709(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11709.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-11709.php b/tests/PHPStan/Analyser/data/bug-11709.php new file mode 100644 index 00000000000..2515e3dcb82 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11709.php @@ -0,0 +1,28 @@ + : mixed) $value + * @phpstan-assert array $value + */ +function isArrayWithStringKeys(mixed $value): void +{ + if (!is_array($value)) { + throw new \Exception('Not an array'); + } + + foreach (array_keys($value) as $key) { + if (!is_string($key)) { + throw new \Exception('Non-string key'); + } + } +} + +function ($m): void { + isArrayWithStringKeys($m); + assertType('array', $m); +}; diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 916532a7b9a..dd90432f8f4 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -808,4 +808,10 @@ public function testBug10165(): void $this->analyse([__DIR__ . '/data/bug-10165.php'], []); } + public function testBug9524(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-9524.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-9524.php b/tests/PHPStan/Rules/Methods/data/bug-9524.php new file mode 100644 index 00000000000..9713e71a617 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-9524.php @@ -0,0 +1,11 @@ + Date: Sun, 22 Sep 2024 17:00:17 +0200 Subject: [PATCH 0322/3097] Improve loose comparison for integer ranges --- src/Type/IntegerRangeType.php | 10 +++ src/Type/NullType.php | 4 ++ src/Type/Traits/ConstantScalarTypeTrait.php | 4 ++ .../ConstantLooseComparisonRuleTest.php | 67 +++++++++++++++++++ .../Rules/Comparison/data/bug-11694.php | 45 +++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-11694.php diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index b7962f71ae1..534be0ebbbf 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; @@ -689,6 +690,15 @@ public function toPhpDocNode(): TypeNode return new GenericTypeNode(new IdentifierTypeNode('int'), [$min, $max]); } + public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType + { + if ($this->isSmallerThan($type)->yes() || $this->isGreaterThan($type)->yes()) { + return new ConstantBooleanType(false); + } + + return parent::looseCompare($type, $phpVersion); + } + /** * @param mixed[] $properties */ diff --git a/src/Type/NullType.php b/src/Type/NullType.php index e72cf25be21..a59f86e868f 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -331,6 +331,10 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new ConstantBooleanType($this->getValue() == []); // phpcs:ignore } + if ($type instanceof CompoundType) { + return $type->looseCompare($this, $phpVersion); + } + return new BooleanType(); } diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 5a8d6dcfc5d..527d10b68a1 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -66,6 +66,10 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new ConstantBooleanType($this->getValue() == []); // phpcs:ignore } + if ($type instanceof CompoundType) { + return $type->looseCompare($this, $phpVersion); + } + return parent::looseCompare($type, $phpVersion); } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index f34dd4876bc..213b9df52de 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -171,4 +171,71 @@ public function testTreatPhpDocTypesAsCertain(bool $treatPhpDocTypesAsCertain, a $this->analyse([__DIR__ . '/data/loose-comparison-treat-phpdoc-types.php'], $expectedErrors); } + public function testBug11694(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-11694.php'], [ + [ + 'Loose comparison using == between 3 and int<10, 20> will always evaluate to false.', + 17, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and 3 will always evaluate to false.', + 18, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between 23 and int<10, 20> will always evaluate to false.', + 23, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and 23 will always evaluate to false.', + 24, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between null and int<10, 20> will always evaluate to false.', + 26, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and null will always evaluate to false.', + 27, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between \' 3\' and int<10, 20> will always evaluate to false.', + 32, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and \' 3\' will always evaluate to false.', + 33, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between \' 23\' and int<10, 20> will always evaluate to false.', + 38, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and \' 23\' will always evaluate to false.', + 39, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between false and int<10, 20> will always evaluate to false.', + 44, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Loose comparison using == between int<10, 20> and false will always evaluate to false.', + 45, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11694.php b/tests/PHPStan/Rules/Comparison/data/bug-11694.php new file mode 100644 index 00000000000..5c8566f788a --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11694.php @@ -0,0 +1,45 @@ + + */ +function test(int $value) : int { + if ($value > 5) { + return 10; + } + + return 20; +} + +if (3 == test(3)) {} +if (test(3) == 3) {} + +if (13 == test(3)) {} +if (test(3) == 13) {} + +if (23 == test(3)) {} +if (test(3) == 23) {} + +if (null == test(3)) {} +if (test(3) == null) {} + +if ('13foo' == test(3)) {} +if (test(3) == '13foo') {} + +if (' 3' == test(3)) {} +if (test(3) == ' 3') {} + +if (' 13' == test(3)) {} +if (test(3) == ' 13') {} + +if (' 23' == test(3)) {} +if (test(3) == ' 23') {} + +if (true == test(3)) {} +if (test(3) == true) {} + +if (false == test(3)) {} +if (test(3) == false) {} From 80fdfab466730c0ca06886090300b542a9d5e481 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 20:44:11 +0200 Subject: [PATCH 0323/3097] Truthy `isset($arr[$k])` should narrow `$k` --- src/Analyser/TypeSpecifier.php | 54 ++++++ tests/PHPStan/Analyser/nsrt/bug-11716.php | 214 ++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-8559.php | 40 ++++ 3 files changed, 308 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11716.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8559.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9f23cf64720..a5dc7bc6a58 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -818,6 +818,60 @@ public function specifyTypesInCondition( $rootExpr, ), ); + } else { + $varType = $scope->getType($var->var); + if ($varType->isArray()->yes() && !$varType->isIterableAtLeastOnce()->no()) { + $varIterableKeyType = $varType->getIterableKeyType(); + + if ($varIterableKeyType->isConstantScalarValue()->yes()) { + $narrowedKey = TypeCombinator::union( + $varIterableKeyType, + TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')), + ); + + if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) { + $narrowedKey = TypeCombinator::union( + $narrowedKey, + new ConstantBooleanType(false), + ); + } + + if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) { + $narrowedKey = TypeCombinator::union( + $narrowedKey, + new ConstantBooleanType(true), + ); + } + + if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) { + $narrowedKey = TypeCombinator::addNull($narrowedKey); + } + + if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) { + $narrowedKey = TypeCombinator::union($narrowedKey, new FloatType()); + } + } else { + $narrowedKey = new MixedType( + false, + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new ObjectWithoutClassType(), + new ResourceType(), + ]), + ); + } + + $types = $types->unionWith( + $this->create( + $var->dim, + $narrowedKey, + $context, + false, + $scope, + $rootExpr, + ), + ); + } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php new file mode 100644 index 00000000000..2394e8a175b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -0,0 +1,214 @@ += 8.0 + +namespace Bug11716; + +use function PHPStan\Testing\assertType; + +class TypeExpression +{ + /** + * @return '&'|'|' + */ + public function parse(string $glue): string + { + $seenGlues = ['|' => false, '&' => false]; + + assertType("array{|: false, &: false}", $seenGlues); + + if ($glue !== '') { + assertType('non-empty-string', $glue); + + \assert(isset($seenGlues[$glue])); + $seenGlues[$glue] = true; + + assertType("'&'|'|'", $glue); + assertType('array{|: bool, &: bool}', $seenGlues); + } else { + assertType("''", $glue); + } + + assertType("''|'&'|'|'", $glue); + assertType("array{|: bool, &: bool}", $seenGlues); + + return array_key_first($seenGlues); + } +} + +/** + * @param array $arr + */ +function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void { + if (isset($generalArr[$mixed])) { + assertType('mixed~(array|object|resource)', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($generalArr[$i])) { + assertType('int', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + + if (isset($generalArr[$s])) { + assertType('string', $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (isset($arr[$mixed])) { + assertType('mixed~(array|object|resource)', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($arr[$i])) { + assertType('int', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + + if (isset($arr[$s])) { + assertType('string', $s); + } else { + assertType('string', $s); + } + assertType('string', $s); +} + +/** + * @param array> $arr + */ +function multiDim($mixed, $mixed2, array $arr) { + if (isset($arr[$mixed])) { + assertType('mixed~(array|object|resource)', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($arr[$mixed]) && isset($arr[$mixed][$mixed2])) { + assertType('mixed~(array|object|resource)', $mixed); + assertType('mixed~(array|object|resource)', $mixed2); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($arr[$mixed][$mixed2])) { + assertType('mixed~(array|object|resource)', $mixed); + assertType('mixed~(array|object|resource)', $mixed2); + } else { + assertType('mixed', $mixed); + assertType('mixed', $mixed2); + } + assertType('mixed', $mixed); + assertType('mixed', $mixed2); +} + +/** + * @param array $arr + */ +function emptyArrr($mixed, array $arr) +{ + if (count($arr) !== 0) { + return; + } + + assertType('array{}', $arr); + if (isset($arr[$mixed])) { + assertType('mixed', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); +} + +function emptyString($mixed) +{ + // see https://3v4l.org/XHZdr + $arr = ['' => 1, 'a' => 2]; + if (isset($arr[$mixed])) { + assertType("''|'a'|null", $mixed); + } else { + assertType('mixed', $mixed); // could be mixed~(''|'a'|null) + } + assertType('mixed', $mixed); +} + +function numericString($mixed, int $i, string $s) +{ + $arr = ['1' => 1, '2' => 2]; + if (isset($arr[$mixed])) { + assertType("1|2|'1'|'2'|float|true", $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + $arr = ['0' => 1, '2' => 2]; + if (isset($arr[$mixed])) { + assertType("0|2|'0'|'2'|float|false", $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + $arr = ['1' => 1, '2' => 2]; + if (isset($arr[$i])) { + assertType("1|2", $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + + $arr = ['1' => 1, '2' => 2, 3 => 3]; + if (isset($arr[$s])) { + assertType("'1'|'2'|'3'", $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + $arr = ['1' => 1, '2' => 2, 3 => 3]; + if (isset($arr[substr($s, 10)])) { + assertType("string", $s); + assertType("'1'|'2'|'3'", substr($s, 10)); + } else { + assertType('string', $s); + } + assertType('string', $s); +} + +function intKeys($mixed) +{ + $arr = [1 => 1, 2 => 2]; + if (isset($arr[$mixed])) { + assertType("1|2|'1'|'2'|float|true", $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + $arr = [0 => 0, 1 => 1, 2 => 2]; + if (isset($arr[$mixed])) { + assertType("0|1|2|'0'|'1'|'2'|bool|float", $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); +} + +function arrayAccess(\ArrayAccess $arr, $mixed) { + if (isset($arr[$mixed])) { + assertType("mixed", $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-8559.php b/tests/PHPStan/Analyser/nsrt/bug-8559.php new file mode 100644 index 00000000000..ee68b2fff07 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8559.php @@ -0,0 +1,40 @@ + 1, 'b' => 2]; + + /** + * @phpstan-assert key-of $key + * @return value-of + */ + public static function get(string $key): int + { + assert(isset(self::KEYS[$key])); + assertType("'a'|'b'", $key); + return self::KEYS[$key]; + } + + /** + * @phpstan-assert key-of $key + * @return value-of + */ + public static function get2(string $key): int + { + assert(in_array($key, array_keys(self::KEYS), true)); + assertType("'a'|'b'", $key); + return self::KEYS[$key]; + } +} + +$key = 'x'; +$v = X::get($key); +assertType("*NEVER*", $key); + +$key = 'a'; +$v = X::get($key); +assertType("'a'", $key); From b40a1f632e6feb1bc885ef87270a894c7b9cb122 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 21:01:31 +0200 Subject: [PATCH 0324/3097] More precise `MixedType::toString()` with subtracted type --- src/Type/MixedType.php | 30 +++++++++++++++- .../Analyser/nsrt/non-empty-string.php | 36 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1539b6d3f4b..86c5ee66266 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -27,7 +27,9 @@ use PHPStan\Type\Accessory\OversizedArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -504,7 +506,7 @@ public function toInteger(): Type new ConstantIntegerType(0), new ConstantArrayType([], []), new StringType(), - new FloatType(), + new FloatType(), // every 0.x float casts to int(0) ]); if ( $this->subtractedType !== null @@ -526,6 +528,32 @@ public function toFloat(): Type public function toString(): Type { + if ($this->subtractedType !== null) { + $castsToEmptyString = new UnionType([ + new NullType(), + new ConstantBooleanType(false), + new ConstantStringType(''), + ]); + if ($this->subtractedType->isSuperTypeOf($castsToEmptyString)->yes()) { + $accessories = [ + new StringType(), + new AccessoryNonEmptyStringType(), + ]; + + $castsToZeroString = new UnionType([ + new ConstantFloatType(0.0), + new ConstantStringType('0'), + new ConstantIntegerType(0), + ]); + if ($this->subtractedType->isSuperTypeOf($castsToZeroString)->yes()) { + $accessories[] = new AccessoryNonFalsyStringType(); + } + return new IntersectionType( + $accessories, + ); + } + } + return new StringType(); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 976071cb84f..78e536a735a 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -408,4 +408,40 @@ function multiplesPrintfFormats(string $s) { assertType('non-empty-string', sprintf($nonEmpty, $s)); assertType('non-falsy-string', sprintf($nonFalsy, $s)); } + + function subtract($m) { + if ($m) { + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m); + assertType('non-falsy-string', (string) $m); + } + if ($m != '') { + assertType("mixed", $m); + assertType('string', (string) $m); + } + if ($m !== '') { + assertType("mixed~''", $m); + assertType('string', (string) $m); + } + if (!is_string($m)) { + assertType("mixed~string", $m); + assertType('string', (string) $m); + } + + if ($m !== true) { + assertType("mixed~true", $m); + assertType('string', (string) $m); + } + if ($m !== false) { + assertType("mixed~false", $m); + assertType('string', (string) $m); + } + if ($m !== false && $m !== '' && $m !== null) { + assertType("mixed~(''|false|null)", $m); + assertType('non-empty-string', (string) $m); + } + if (!is_bool($m) && $m !== '' && $m !== null) { + assertType("mixed~(''|bool|null)", $m); + assertType('non-empty-string', (string) $m); + } + } } From acc22e6b17e9c35dfd209de1c6662d29a8cde1a4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 20:50:12 +0200 Subject: [PATCH 0325/3097] [BE] New RuleLevelHelper behaviour --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 - conf/parametersSchema.neon | 1 - phpstan-baseline.neon | 5 - src/Rules/RuleLevelHelper.php | 309 ++++-------------- .../Analyser/Bug9307CallMethodsRuleTest.php | 2 +- .../Arrays/AppendedArrayItemTypeRuleTest.php | 2 +- .../Arrays/ArrayDestructuringRuleTest.php | 2 +- .../Rules/Arrays/ArrayUnpackingRuleTest.php | 2 +- .../InvalidKeyInArrayDimFetchRuleTest.php | 2 +- .../Arrays/IterableInForeachRuleTest.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- .../Arrays/OffsetAccessAssignOpRuleTest.php | 2 +- .../Arrays/OffsetAccessAssignmentRuleTest.php | 2 +- .../OffsetAccessValueAssignmentRuleTest.php | 2 +- .../Arrays/UnpackIterableInArrayRuleTest.php | 2 +- tests/PHPStan/Rules/Cast/EchoRuleTest.php | 2 +- .../Rules/Cast/InvalidCastRuleTest.php | 2 +- .../InvalidPartOfEncapsedStringRuleTest.php | 2 +- tests/PHPStan/Rules/Cast/PrintRuleTest.php | 2 +- .../Rules/Classes/ClassAttributesRuleTest.php | 2 +- .../ClassConstantAttributesRuleTest.php | 2 +- .../Rules/Classes/ClassConstantRuleTest.php | 2 +- .../ForbiddenNameCheckExtensionRuleTest.php | 2 +- .../Rules/Classes/InstantiationRuleTest.php | 2 +- .../DynamicClassConstantFetchRuleTest.php | 2 +- .../EnumCases/EnumCaseAttributesRuleTest.php | 2 +- .../Exceptions/ThrowExprTypeRuleTest.php | 2 +- .../ArrowFunctionAttributesRuleTest.php | 2 +- .../ArrowFunctionReturnTypeRuleTest.php | 1 - .../Rules/Functions/CallCallablesRuleTest.php | 2 +- .../CallToFunctionParametersRuleTest.php | 2 +- .../Rules/Functions/CallUserFuncRuleTest.php | 2 +- .../Functions/ClosureAttributesRuleTest.php | 2 +- .../Functions/ClosureReturnTypeRuleTest.php | 2 +- .../Functions/FunctionAttributesRuleTest.php | 2 +- .../Functions/FunctionCallableRuleTest.php | 2 +- ...plodeParameterCastableToStringRuleTest.php | 2 +- .../Functions/ParamAttributesRuleTest.php | 2 +- .../ParameterCastableToStringRuleTest.php | 2 +- .../Rules/Functions/ReturnTypeRuleTest.php | 2 +- .../SortParameterCastableToStringRuleTest.php | 2 +- .../Generators/YieldFromTypeRuleTest.php | 2 +- .../Rules/Generators/YieldTypeRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 2 +- .../Methods/CallStaticMethodsRuleTest.php | 2 +- ...hodStatementWithoutSideEffectsRuleTest.php | 2 +- ...hodStatementWithoutSideEffectsRuleTest.php | 2 +- .../Methods/MethodAttributesRuleTest.php | 2 +- .../Rules/Methods/MethodCallableRuleTest.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 2 +- .../Methods/StaticMethodCallableRuleTest.php | 2 +- .../InvalidBinaryOperationRuleTest.php | 2 +- .../InvalidComparisonOperationRuleTest.php | 2 +- .../InvalidIncDecOperationRuleTest.php | 2 +- .../InvalidUnaryOperationRuleTest.php | 2 +- .../AccessPropertiesInAssignRuleTest.php | 2 +- .../Properties/AccessPropertiesRuleTest.php | 2 +- ...AccessStaticPropertiesInAssignRuleTest.php | 2 +- .../AccessStaticPropertiesRuleTest.php | 2 +- .../PHPStan/Rules/Properties/Bug7074Test.php | 2 +- ...ValueTypesAssignedToPropertiesRuleTest.php | 2 +- .../Properties/PropertyAttributesRuleTest.php | 2 +- .../ReadingWriteOnlyPropertiesRuleTest.php | 2 +- .../TypesAssignedToPropertiesRuleTest.php | 2 +- .../WritingToReadOnlyPropertiesRuleTest.php | 2 +- .../ParameterOutAssignedTypeRuleTest.php | 2 +- .../ParameterOutExecutionEndTypeRuleTest.php | 2 +- .../Variables/VariableCloningRuleTest.php | 2 +- 70 files changed, 120 insertions(+), 327 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 643a7a532fd..8538dba4fe4 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -45,7 +45,6 @@ Bleeding edge (TODO move to other sections) * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) -* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) * Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) * Stricter function signature map (https://github.com/phpstan/phpstan-src/commit/06b746d8e72cc0843707896ec161559bb6a81137, [#2163](https://github.com/phpstan/phpstan-src/pull/2163)), #7239, thanks @staabm! @@ -140,6 +139,7 @@ Improvements 🔧 * Returning plain strings as errors no longer supported, use RuleErrorBuilder * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) +* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 177fbdc689b..246fdc091a8 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -32,7 +32,6 @@ parameters: disableUnreachableBranchesRules: true varTagType: true closureDefaultParameterTypeRule: true - newRuleLevelHelper: true instanceofType: true paramOutVariance: true strictStaticMethodTemplateTypeVariance: true diff --git a/conf/config.neon b/conf/config.neon index 88e6800df0f..f2904ef641e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -67,7 +67,6 @@ parameters: disableUnreachableBranchesRules: false varTagType: false closureDefaultParameterTypeRule: false - newRuleLevelHelper: false instanceofType: false paramOutVariance: false @@ -1124,7 +1123,6 @@ services: checkUnionTypes: %checkUnionTypes% checkExplicitMixed: %checkExplicitMixed% checkImplicitMixed: %checkImplicitMixed% - newRuleLevelHelper: %featureToggles.newRuleLevelHelper% checkBenevolentUnionTypes: %checkBenevolentUnionTypes% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index b54bad125dd..c5caacdd840 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -62,7 +62,6 @@ parametersSchema: disableUnreachableBranchesRules: bool() varTagType: bool() closureDefaultParameterTypeRule: bool() - newRuleLevelHelper: bool() instanceofType: bool() paramOutVariance: bool() strictStaticMethodTemplateTypeVariance: bool() diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dc567692e90..69cef6f30ee 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -621,11 +621,6 @@ parameters: count: 1 path: src/Rules/RuleLevelHelper.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" - count: 2 - path: src/Rules/RuleLevelHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 1 diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 0f156836060..9869c16c62e 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -15,7 +15,6 @@ use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\StaticType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -23,7 +22,6 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -use function array_merge; use function count; use function sprintf; use function str_contains; @@ -38,7 +36,6 @@ public function __construct( private bool $checkUnionTypes, private bool $checkExplicitMixed, private bool $checkImplicitMixed, - private bool $newRuleLevelHelper, private bool $checkBenevolentUnionTypes, ) { @@ -64,10 +61,6 @@ private function transformCommonType(Type $type): Type return TypeTraverser::map($type, function (Type $type, callable $traverse) { if ($type instanceof TemplateMixedType) { - if (!$this->newRuleLevelHelper) { - return $type->toStrictMixedType(); - } - if ($this->checkExplicitMixed) { return $type->toStrictMixedType(); } @@ -154,148 +147,10 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType): public function acceptsWithReason(Type $acceptingType, Type $acceptedType, bool $strictTypes): RuleLevelHelperAcceptsResult { - if ($this->newRuleLevelHelper) { - [$acceptedType, $checkForUnion] = $this->transformAcceptedType($acceptingType, $acceptedType); - $acceptingType = $this->transformCommonType($acceptingType); - - $accepts = $acceptingType->acceptsWithReason($acceptedType, $strictTypes); - - return new RuleLevelHelperAcceptsResult( - $checkForUnion ? $accepts->yes() : !$accepts->no(), - $accepts->reasons, - ); - } - - $checkForUnion = $this->checkUnionTypes; - - if ($this->checkBenevolentUnionTypes) { - $traverse = static function (Type $type, callable $traverse) use (&$checkForUnion): Type { - if ($type instanceof BenevolentUnionType) { - $checkForUnion = true; - return TypeUtils::toStrictUnion($type); - } - - return $traverse($type); - }; - - $acceptedType = TypeTraverser::map($acceptedType, $traverse); - } - - if ( - $this->checkExplicitMixed - ) { - $traverse = static function (Type $type, callable $traverse): Type { - if ($type instanceof TemplateMixedType) { - return $type->toStrictMixedType(); - } - if ( - $type instanceof MixedType - && $type->isExplicitMixed() - ) { - return new StrictMixedType(); - } - - return $traverse($type); - }; - $acceptingType = TypeTraverser::map($acceptingType, $traverse); - $acceptedType = TypeTraverser::map($acceptedType, $traverse); - } - - if ( - $this->checkImplicitMixed - ) { - $traverse = static function (Type $type, callable $traverse): Type { - if ($type instanceof TemplateMixedType) { - return $type->toStrictMixedType(); - } - if ( - $type instanceof MixedType - && !$type->isExplicitMixed() - ) { - return new StrictMixedType(); - } - - return $traverse($type); - }; - $acceptingType = TypeTraverser::map($acceptingType, $traverse); - $acceptedType = TypeTraverser::map($acceptedType, $traverse); - } - - if ( - !$this->checkNullables - && !$acceptingType instanceof NullType - && !$acceptedType instanceof NullType - && !$acceptedType instanceof BenevolentUnionType - ) { - $acceptedType = TypeCombinator::removeNull($acceptedType); - } + [$acceptedType, $checkForUnion] = $this->transformAcceptedType($acceptingType, $acceptedType); + $acceptingType = $this->transformCommonType($acceptingType); $accepts = $acceptingType->acceptsWithReason($acceptedType, $strictTypes); - if ($accepts->yes()) { - return new RuleLevelHelperAcceptsResult(true, $accepts->reasons); - } - if ($acceptingType instanceof UnionType) { - $reasons = []; - foreach ($acceptingType->getTypes() as $innerType) { - $accepts = self::acceptsWithReason($innerType, $acceptedType, $strictTypes); - if ($accepts->result) { - return $accepts; - } - - $reasons = array_merge($reasons, $accepts->reasons); - } - - return new RuleLevelHelperAcceptsResult(false, $reasons); - } - - if ( - $acceptedType->isArray()->yes() - && $acceptingType->isArray()->yes() - && ( - $acceptedType->isConstantArray()->no() - || !$acceptedType->isIterableAtLeastOnce()->no() - ) - && $acceptingType->isConstantArray()->no() - ) { - if ($acceptingType->isIterableAtLeastOnce()->yes() && !$acceptedType->isIterableAtLeastOnce()->yes()) { - $verbosity = VerbosityLevel::getRecommendedLevelByType($acceptingType, $acceptedType); - return new RuleLevelHelperAcceptsResult(false, [ - sprintf( - '%s %s empty.', - $acceptedType->describe($verbosity), - $acceptedType->isIterableAtLeastOnce()->no() ? 'is' : 'might be', - ), - ]); - } - - if ( - $acceptingType->isList()->yes() - && !$acceptedType->isList()->yes() - ) { - $report = $checkForUnion || $acceptedType->isList()->no(); - - if ($report) { - $verbosity = VerbosityLevel::getRecommendedLevelByType($acceptingType, $acceptedType); - return new RuleLevelHelperAcceptsResult(false, [ - sprintf( - '%s %s a list.', - $acceptedType->describe($verbosity), - $acceptedType->isList()->no() ? 'is not' : 'might not be', - ), - ]); - } - } - - return self::acceptsWithReason( - $acceptingType->getIterableKeyType(), - $acceptedType->getIterableKeyType(), - $strictTypes, - )->and(self::acceptsWithReason( - $acceptingType->getIterableValueType(), - $acceptedType->getIterableValueType(), - $strictTypes, - )); - } return new RuleLevelHelperAcceptsResult( $checkForUnion ? $accepts->yes() : !$accepts->no(), @@ -336,49 +191,24 @@ private function findTypeToCheckImplementation( $type = TypeCombinator::removeNull($type); } - if ($this->newRuleLevelHelper) { - if ( - ($this->checkExplicitMixed || $this->checkImplicitMixed) - && $type instanceof MixedType - && ($type->isExplicitMixed() ? $this->checkExplicitMixed : $this->checkImplicitMixed) - ) { - return new FoundTypeResult( - $type instanceof TemplateMixedType - ? $type->toStrictMixedType() - : new StrictMixedType(), - [], - [], - null, - ); - } - } else { - if ( - $this->checkExplicitMixed - && $type instanceof MixedType - && !$type instanceof TemplateMixedType - && $type->isExplicitMixed() - ) { - return new FoundTypeResult(new StrictMixedType(), [], [], null); - } - - if ( - $this->checkImplicitMixed - && $type instanceof MixedType - && !$type instanceof TemplateMixedType - && !$type->isExplicitMixed() - ) { - return new FoundTypeResult(new StrictMixedType(), [], [], null); - } + if ( + ($this->checkExplicitMixed || $this->checkImplicitMixed) + && $type instanceof MixedType + && ($type->isExplicitMixed() ? $this->checkExplicitMixed : $this->checkImplicitMixed) + ) { + return new FoundTypeResult( + $type instanceof TemplateMixedType + ? $type->toStrictMixedType() + : new StrictMixedType(), + [], + [], + null, + ); } if ($type instanceof MixedType || $type instanceof NeverType) { return new FoundTypeResult(new ErrorType(), [], [], null); } - if (!$this->newRuleLevelHelper) { - if ($isTopLevel && $type instanceof StaticType) { - $type = $type->getStaticObjectType(); - } - } $errors = []; $hasClassExistsClass = false; @@ -415,85 +245,58 @@ private function findTypeToCheckImplementation( return new FoundTypeResult(new ErrorType(), [], [], null); } - if ($this->newRuleLevelHelper) { - if ($type instanceof UnionType) { - $shouldFilterUnion = ( - !$this->checkUnionTypes - && !$type instanceof BenevolentUnionType - ) || ( - !$this->checkBenevolentUnionTypes - && $type instanceof BenevolentUnionType - ); - - $newTypes = []; + if ($type instanceof UnionType) { + $shouldFilterUnion = ( + !$this->checkUnionTypes + && !$type instanceof BenevolentUnionType + ) || ( + !$this->checkBenevolentUnionTypes + && $type instanceof BenevolentUnionType + ); - foreach ($type->getTypes() as $innerType) { - if ($shouldFilterUnion && !$unionTypeCriteriaCallback($innerType)) { - continue; - } + $newTypes = []; - $newTypes[] = $this->findTypeToCheckImplementation( - $scope, - $var, - $innerType, - $unknownClassErrorPattern, - $unionTypeCriteriaCallback, - )->getType(); + foreach ($type->getTypes() as $innerType) { + if ($shouldFilterUnion && !$unionTypeCriteriaCallback($innerType)) { + continue; } - if (count($newTypes) > 0) { - $newUnion = TypeCombinator::union(...$newTypes); - if ( - !$this->checkBenevolentUnionTypes - && $type instanceof BenevolentUnionType - ) { - $newUnion = TypeUtils::toBenevolentUnion($newUnion); - } - - return new FoundTypeResult($newUnion, $directClassNames, [], null); - } + $newTypes[] = $this->findTypeToCheckImplementation( + $scope, + $var, + $innerType, + $unknownClassErrorPattern, + $unionTypeCriteriaCallback, + )->getType(); } - if ($type instanceof IntersectionType) { - $newTypes = []; - - foreach ($type->getTypes() as $innerType) { - $newTypes[] = $this->findTypeToCheckImplementation( - $scope, - $var, - $innerType, - $unknownClassErrorPattern, - $unionTypeCriteriaCallback, - )->getType(); - } - - return new FoundTypeResult(TypeCombinator::intersect(...$newTypes), $directClassNames, [], null); - } - } else { - if ( - ( - !$this->checkUnionTypes - && $type instanceof UnionType - && !$type instanceof BenevolentUnionType - ) || ( + if (count($newTypes) > 0) { + $newUnion = TypeCombinator::union(...$newTypes); + if ( !$this->checkBenevolentUnionTypes && $type instanceof BenevolentUnionType - ) - ) { - $newTypes = []; - - foreach ($type->getTypes() as $innerType) { - if (!$unionTypeCriteriaCallback($innerType)) { - continue; - } - - $newTypes[] = $innerType; + ) { + $newUnion = TypeUtils::toBenevolentUnion($newUnion); } - if (count($newTypes) > 0) { - return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, [], null); - } + return new FoundTypeResult($newUnion, $directClassNames, [], null); + } + } + + if ($type instanceof IntersectionType) { + $newTypes = []; + + foreach ($type->getTypes() as $innerType) { + $newTypes[] = $this->findTypeToCheckImplementation( + $scope, + $var, + $innerType, + $unknownClassErrorPattern, + $unionTypeCriteriaCallback, + )->getType(); } + + return new FoundTypeResult(TypeCombinator::intersect(...$newTypes), $directClassNames, [], null); } $tip = null; diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index ff0a0daa9e7..c7b4f688a35 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -23,7 +23,7 @@ class Bug9307CallMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php index ea09ce0cb4c..3a6c6dfb4fa 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php @@ -17,7 +17,7 @@ protected function getRule(): Rule { return new AppendedArrayItemTypeRule( new PropertyReflectionFinder(), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php index a536ce6cf5f..840da52b49e 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php @@ -17,7 +17,7 @@ class ArrayDestructuringRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false); return new ArrayDestructuringRule( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php index b733df51cde..cb14b900fe5 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php @@ -22,7 +22,7 @@ protected function getRule(): Rule { return new ArrayUnpackingRule( self::getContainer()->getByType(PhpVersion::class), - new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, true, $this->checkBenevolentUnions), + new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, $this->checkBenevolentUnions), ); } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index 70c671135a7..df337d9bdf4 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -15,7 +15,7 @@ class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false); return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true); } diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index 31407e99be0..c83001b6a8f 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -21,7 +21,7 @@ class IterableInForeachRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false)); + return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); } public function testCheckWithMaybes(): void diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 8e0127cf5d0..f7fa3224f9b 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -25,7 +25,7 @@ class NonexistentOffsetInArrayDimFetchRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false); return new NonexistentOffsetInArrayDimFetchRule( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php index 2daf7b7ed4b..45750ac2a4a 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php @@ -17,7 +17,7 @@ class OffsetAccessAssignOpRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, false); return new OffsetAccessAssignOpRule($ruleLevelHelper); } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index 86420630c2e..9049635db0c 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -17,7 +17,7 @@ class OffsetAccessAssignmentRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, false, false, false); return new OffsetAccessAssignmentRule($ruleLevelHelper); } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php index 0129923ae89..38afbe61379 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php @@ -15,7 +15,7 @@ class OffsetAccessValueAssignmentRuleTest extends RuleTestCase protected function getRule(): Rule { - return new OffsetAccessValueAssignmentRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new OffsetAccessValueAssignmentRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php index c7de4bc7bff..ba43922b08d 100644 --- a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php @@ -21,7 +21,7 @@ class UnpackIterableInArrayRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnpackIterableInArrayRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false)); + return new UnpackIterableInArrayRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Cast/EchoRuleTest.php b/tests/PHPStan/Rules/Cast/EchoRuleTest.php index 81289f82f0b..5804527769c 100644 --- a/tests/PHPStan/Rules/Cast/EchoRuleTest.php +++ b/tests/PHPStan/Rules/Cast/EchoRuleTest.php @@ -16,7 +16,7 @@ class EchoRuleTest extends RuleTestCase protected function getRule(): Rule { return new EchoRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index bc7cd35acb7..ee36958b199 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -22,7 +22,7 @@ class InvalidCastRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false)); + return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php index 3ba7d5ce61c..4503a788e8f 100644 --- a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php @@ -19,7 +19,7 @@ protected function getRule(): Rule { return new InvalidPartOfEncapsedStringRule( new ExprPrinter(new Printer()), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/Cast/PrintRuleTest.php b/tests/PHPStan/Rules/Cast/PrintRuleTest.php index c7a52e8123b..b3a71307ec9 100644 --- a/tests/PHPStan/Rules/Cast/PrintRuleTest.php +++ b/tests/PHPStan/Rules/Cast/PrintRuleTest.php @@ -16,7 +16,7 @@ class PrintRuleTest extends RuleTestCase protected function getRule(): Rule { return new PrintRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 6fa6252277f..e261724d7dd 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index b132e3fe08a..69fd7a2c2b4 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 42378de1f22..d8bb82d1417 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new ClassConstantRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 9d3ea640348..fa8b767c4bf 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 2f4b45065f4..4271153e362 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php b/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php index 28069352264..16dbd9dfbfd 100644 --- a/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php +++ b/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): TRule { return new DynamicClassConstantFetchRule( self::getContainer()->getByType(PhpVersion::class), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 26643e506c7..bb81a1157e8 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80100), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php index 9ece8025a0a..b9ac08e1bde 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php @@ -15,7 +15,7 @@ class ThrowExprTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ThrowExprTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new ThrowExprTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 7f3aab6ac29..44d564b8ff9 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php index 307db4e0a9e..a3ba642464e 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php @@ -23,7 +23,6 @@ protected function getRule(): Rule true, false, false, - true, false, ))); } diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index bcd1c9ee874..97260fb244e 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -22,7 +22,7 @@ class CallCallablesRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false); return new CallCallablesRule( new FunctionCallParametersCheck( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 802b0e11a8f..a996359363e 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index d10eee8e48c..4744b8f1ce9 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -21,7 +21,7 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true)); + return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index f4a3d5a1bbb..5827cc181c6 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php index 2a7a309e1af..6ffeadca252 100644 --- a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php @@ -15,7 +15,7 @@ class ClosureReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false))); + return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false))); } public function testClosureReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 553ab6c462d..5fe8fe58f80 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php index 2a92e5f5a2e..e13fc184fc0 100644 --- a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new FunctionCallableRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new PhpVersion(PHP_VERSION_ID), true, true, diff --git a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php index 25f0facf276..e5003028bc1 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php @@ -17,7 +17,7 @@ class ImplodeParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, true, false))); + return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); } public function testNamedArguments(): void diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 60d620474cb..3298bd5bb0c 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php index 8619497c4f9..e577240cb8d 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class ParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, true, false))); + return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 5ba98544fb0..bd993f3bca6 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -20,7 +20,7 @@ class ReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), $this->checkNullables, false, true, $this->checkExplicitMixed, false, true, false))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), $this->checkNullables, false, true, $this->checkExplicitMixed, false, false))); } public function testReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php index 98076839d19..f0350b51791 100644 --- a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class SortParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, true, false))); + return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php index 681d9abab1f..5c84c216a6e 100644 --- a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php @@ -14,7 +14,7 @@ class YieldFromTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new YieldFromTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), true); + return new YieldFromTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), true); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php index deec20a074e..d658d3aeb41 100644 --- a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php @@ -14,7 +14,7 @@ class YieldTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new YieldTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new YieldTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 0436117153a..845c826e5c9 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -33,7 +33,7 @@ class CallMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index d46969bdf3e..f13767c4ed9 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -32,7 +32,7 @@ class CallStaticMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false); return new CallStaticMethodsRule( new StaticMethodCallCheck( $reflectionProvider, diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php index a75873ffd52..f9ca07781c5 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php @@ -16,7 +16,7 @@ class CallToMethodStatementWithoutSideEffectsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new CallToMethodStatementWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new CallToMethodStatementWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index 953ca5f9823..90564ff6d2c 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new CallToStaticMethodStatementWithoutSideEffectsRule( - new RuleLevelHelper($broker, true, false, true, false, false, true, false), + new RuleLevelHelper($broker, true, false, true, false, false, false), $broker, ); } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 9ce75af6e6e..03d672afd00 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php index b5bc1f8efb2..5058756f1c1 100644 --- a/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php @@ -19,7 +19,7 @@ class MethodCallableRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false); return new MethodCallableRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 5446ab66db1..150b170ca32 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -22,7 +22,7 @@ class ReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed, false, true, $this->checkBenevolentUnionTypes))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed, false, $this->checkBenevolentUnionTypes))); } public function testReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php index c726ab7fdb1..169acc6693a 100644 --- a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php @@ -22,7 +22,7 @@ class StaticMethodCallableRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false); return new StaticMethodCallableRule( new StaticMethodCallCheck( diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 190b83798bb..625e2d615ef 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule { return new InvalidBinaryOperationRule( new ExprPrinter(new Printer()), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), true, ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php index 3221658bc46..dc9d57f88ef 100644 --- a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php @@ -16,7 +16,7 @@ class InvalidComparisonOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidComparisonOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php index a70d03a6e77..5dd8aae36fa 100644 --- a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php @@ -20,7 +20,7 @@ class InvalidIncDecOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidIncDecOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), true, false, ); diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index ddc41ed3371..909472e0916 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -20,7 +20,7 @@ class InvalidUnaryOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidUnaryOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), true, ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index 853f053bc74..b6ea917903a 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), true, true), + new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), true, true), ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 2809d62a9a5..7e5d97a44b0 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -23,7 +23,7 @@ class AccessPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, true, false), true, $this->checkDynamicProperties); + return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), true, $this->checkDynamicProperties); } public function testAccessProperties(): void diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index 593aa13f974..ea4b27f4f89 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -21,7 +21,7 @@ protected function getRule(): Rule return new AccessStaticPropertiesInAssignRule( new AccessStaticPropertiesRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 0ae3edbc3c2..822a048e1ec 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -21,7 +21,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new AccessStaticPropertiesRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Properties/Bug7074Test.php b/tests/PHPStan/Rules/Properties/Bug7074Test.php index 37486754903..d3398ed7e7f 100644 --- a/tests/PHPStan/Rules/Properties/Bug7074Test.php +++ b/tests/PHPStan/Rules/Properties/Bug7074Test.php @@ -15,7 +15,7 @@ class Bug7074Test extends RuleTestCase protected function getRule(): Rule { - return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php index 7f99a5ae721..eae03f029cf 100644 --- a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php @@ -14,7 +14,7 @@ class DefaultValueTypesAssignedToPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testDefaultValueTypesAssignedToProperties(): void diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index b6d394d30b2..26f89696866 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php index d5834f40030..f3ba064bac2 100644 --- a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php @@ -16,7 +16,7 @@ class ReadingWriteOnlyPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReadingWriteOnlyPropertiesRule(new PropertyDescriptor(), new PropertyReflectionFinder(), new RuleLevelHelper($this->createReflectionProvider(), true, $this->checkThisOnly, true, false, false, true, false), $this->checkThisOnly); + return new ReadingWriteOnlyPropertiesRule(new PropertyDescriptor(), new PropertyReflectionFinder(), new RuleLevelHelper($this->createReflectionProvider(), true, $this->checkThisOnly, true, false, false, false), $this->checkThisOnly); } public function testPropertyMustBeReadableInAssignOp(): void diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index d5e84b66e1a..59eb242e7c6 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -17,7 +17,7 @@ class TypesAssignedToPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, true, false), new PropertyReflectionFinder()); + return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false), new PropertyReflectionFinder()); } public function testTypesAssignedToProperties(): void diff --git a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php index d3196aba89a..fb64553a3b2 100644 --- a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php @@ -16,7 +16,7 @@ class WritingToReadOnlyPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new WritingToReadOnlyPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); + return new WritingToReadOnlyPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); } public function testCheckThisOnlyProperties(): void diff --git a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php index ed68b380b11..95e94df1687 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php @@ -15,7 +15,7 @@ class ParameterOutAssignedTypeRuleTest extends RuleTestCase protected function getRule(): TRule { return new ParameterOutAssignedTypeRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false), ); } diff --git a/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php index 26157f4ffb9..8f3b40c7b6a 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php @@ -15,7 +15,7 @@ class ParameterOutExecutionEndTypeRuleTest extends RuleTestCase protected function getRule(): Rule { return new ParameterOutExecutionEndTypeRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, true, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false), ); } diff --git a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php index 801d2b31f46..f1c3af3cc09 100644 --- a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php @@ -15,7 +15,7 @@ class VariableCloningRuleTest extends RuleTestCase protected function getRule(): Rule { - return new VariableCloningRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, true, false)); + return new VariableCloningRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); } public function testClone(): void From 12879e4d02567fbbba927162629a7f2219e3ca62 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:04:19 +0200 Subject: [PATCH 0326/3097] Fix build --- src/Analyser/TypeSpecifier.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index e58f023c485..8c40e83826c 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -832,10 +832,8 @@ public function specifyTypesInCondition( $var->dim, $narrowedKey, $context, - false, $scope, - $rootExpr, - ), + )->setRootExpr($expr), ); } } From 5834cf5b6e6ff9ca76c468d15cc2931d2b729c3e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:07:48 +0200 Subject: [PATCH 0327/3097] [BE] Infer explicit mixed when instantiating generic class with unknown template types --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/Analyser/DirectInternalScopeFactory.php | 2 - src/Analyser/LazyInternalScopeFactory.php | 4 -- src/Analyser/MutatingScope.php | 52 +++++---------------- src/Testing/PHPStanTestCase.php | 1 - 8 files changed, 13 insertions(+), 51 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 8538dba4fe4..a95e3c79e66 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -11,7 +11,6 @@ Major new features 🚀 Bleeding edge (TODO move to other sections) ===================== -* Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * Lower memory consumption thanks to breaking up of reference cycles @@ -140,6 +139,7 @@ Improvements 🔧 * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) * New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) +* Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 246fdc091a8..119065aaf69 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses!: [] - explicitMixedInUnknownGenericNew: true explicitMixedForGlobalVariables: true explicitMixedViaIsArray: true arrayFilter: true diff --git a/conf/config.neon b/conf/config.neon index f2904ef641e..cfc414dd360 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -36,7 +36,6 @@ parameters: - CachingIterator - RegexIterator - ReflectionEnum - explicitMixedInUnknownGenericNew: false explicitMixedForGlobalVariables: false explicitMixedViaIsArray: false arrayFilter: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index c5caacdd840..11267d2474e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -31,7 +31,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), - explicitMixedInUnknownGenericNew: bool(), explicitMixedForGlobalVariables: bool(), explicitMixedViaIsArray: bool(), arrayFilter: bool(), diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 49a3395d2e6..ed36ee647ef 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -35,7 +35,6 @@ public function __construct( private Parser $parser, private NodeScopeResolver $nodeScopeResolver, private PhpVersion $phpVersion, - private bool $explicitMixedInUnknownGenericNew, private bool $explicitMixedForGlobalVariables, private ConstantResolver $constantResolver, ) @@ -103,7 +102,6 @@ public function create( $afterExtractCall, $parentScope, $nativeTypesPromoted, - $this->explicitMixedInUnknownGenericNew, $this->explicitMixedForGlobalVariables, ); } diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index fec15217689..d079e204e26 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -20,8 +20,6 @@ final class LazyInternalScopeFactory implements InternalScopeFactory { - private bool $explicitMixedInUnknownGenericNew; - private bool $explicitMixedForGlobalVariables; /** @@ -32,7 +30,6 @@ public function __construct( private Container $container, ) { - $this->explicitMixedInUnknownGenericNew = $this->container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew']; $this->explicitMixedForGlobalVariables = $this->container->getParameter('featureToggles')['explicitMixedForGlobalVariables']; } @@ -97,7 +94,6 @@ public function create( $afterExtractCall, $parentScope, $nativeTypesPromoted, - $this->explicitMixedInUnknownGenericNew, $this->explicitMixedForGlobalVariables, ); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 395a0c41c22..17c0c34828a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -220,7 +220,6 @@ public function __construct( private bool $afterExtractCall = false, private ?Scope $parentScope = null, private bool $nativeTypesPromoted = false, - private bool $explicitMixedInUnknownGenericNew = false, private bool $explicitMixedForGlobalVariables = false, ) { @@ -5549,49 +5548,22 @@ private function exactInstantiation(New_ $node, string $className): ?Type $constructorMethod->getNamedArgumentsVariants(), ); - if ($this->explicitMixedInUnknownGenericNew) { - $resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); - return TypeTraverser::map(new GenericObjectType( - $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), - ), static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type { - if ($type instanceof TemplateType && !$type->isArgument()) { - $newType = $resolvedTemplateTypeMap->getType($type->getName()); - if ($newType === null || $newType instanceof ErrorType) { - return $type->getBound(); - } - - return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType); + $resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); + return TypeTraverser::map(new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), + ), static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type { + if ($type instanceof TemplateType && !$type->isArgument()) { + $newType = $resolvedTemplateTypeMap->getType($type->getName()); + if ($newType === null || $newType instanceof ErrorType) { + return $type->getBound(); } - return $traverse($type); - }); - } - - $resolvedPhpDoc = $classReflection->getResolvedPhpDoc(); - if ($resolvedPhpDoc === null) { - return $objectType; - } - - $list = []; - $typeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); - foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $templateType = $typeMap->getType($tag->getName()); - if ($templateType !== null) { - $list[] = $templateType; - continue; + return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType); } - $bound = $tag->getBound(); - if ($bound instanceof MixedType && $bound->isExplicitMixed()) { - $bound = new MixedType(false); - } - $list[] = $bound; - } - return new GenericObjectType( - $resolvedClassName, - $list, - ); + return $traverse($type); + }); } private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 14efcc7480e..8238b6ead9e 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -188,7 +188,6 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider self::getParser(), $container->getByType(NodeScopeResolver::class), $container->getByType(PhpVersion::class), - $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], $container->getParameter('featureToggles')['explicitMixedForGlobalVariables'], $constantResolver, ), From 9db564b03a68adb8fefc9d9d771220c312126481 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:09:43 +0200 Subject: [PATCH 0328/3097] [BE] Fix invariance composition --- changelog-2.0.md | 3 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/DependencyInjection/ContainerFactory.php | 2 - src/Type/Generic/TemplateTypeVariance.php | 9 +- .../Type/Generic/GenericObjectTypeTest.php | 318 +----------------- 7 files changed, 20 insertions(+), 315 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index a95e3c79e66..579cf43f380 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -37,7 +37,6 @@ Bleeding edge (TODO move to other sections) * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) -* Fix invariance composition ([#2054](https://github.com/phpstan/phpstan-src/pull/2054)), thanks @jiripudil! * Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! @@ -144,6 +143,8 @@ Improvements 🔧 Bugfixes 🐛 ===================== +* Fix invariance composition ([#2054](https://github.com/phpstan/phpstan-src/pull/2054)), thanks @jiripudil! + Function signature fixes 🤖 ======================= diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 119065aaf69..e011ee0bf7f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -26,7 +26,6 @@ parameters: duplicateStubs: true logicalXor: true betterNoop: true - invarianceComposition: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true varTagType: true diff --git a/conf/config.neon b/conf/config.neon index cfc414dd360..3593b506da6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -61,7 +61,6 @@ parameters: duplicateStubs: false logicalXor: false betterNoop: false - invarianceComposition: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false varTagType: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 11267d2474e..234a86f02fe 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -56,7 +56,6 @@ parametersSchema: duplicateStubs: bool() logicalXor: bool() betterNoop: bool() - invarianceComposition: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() varTagType: bool() diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 1e980d2f320..2a491725bb5 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -31,7 +31,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\ObjectType; use Symfony\Component\Finder\Finder; use function array_diff_key; @@ -191,7 +190,6 @@ public static function postInitializeContainer(Container $container): void $container->getService('typeSpecifier'); BleedingEdgeToggle::setBleedingEdge($container->getParameter('featureToggles')['bleedingEdge']); - TemplateTypeVariance::setInvarianceCompositionEnabled($container->getParameter('featureToggles')['invarianceComposition']); } public function clearOldContainers(string $tempDirectory): void diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 786c61302c6..b3089b62eb7 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -28,8 +28,6 @@ class TemplateTypeVariance /** @var self[] */ private static array $registry; - private static bool $invarianceCompositionEnabled = false; - private function __construct(private int $value) { } @@ -118,7 +116,7 @@ public function compose(self $other): self return self::createInvariant(); } - if (self::$invarianceCompositionEnabled && $this->invariant()) { + if ($this->invariant()) { return self::createInvariant(); } @@ -253,9 +251,4 @@ public static function __set_state(array $properties): self return new self($properties['value']); } - public static function setInvarianceCompositionEnabled(bool $enabled): void - { - self::$invarianceCompositionEnabled = $enabled; - } - } diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 1012234740e..74b558e8ca3 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -477,7 +477,6 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -485,42 +484,11 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Invariant' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ], null, null, [ - TemplateTypeVariance::createCovariant(), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], - 'param: Invariant' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ], null, null, [ - TemplateTypeVariance::createContravariant(), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], 'param: Out' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Out::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -535,7 +503,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -552,7 +519,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -565,7 +531,6 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\In::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -580,7 +545,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -597,7 +561,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -612,7 +575,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -627,7 +589,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -635,106 +596,11 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Out>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant(), - ), - ], - ], - 'param: In>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\In::class, [ - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant(), - ), - ], - ], - 'param: Invariant>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], - 'param: Invariant>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\In::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], - 'param: In>>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\In::class, [ - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], - 'param: Out>>' => [ - TemplateTypeVariance::createContravariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\In::class, [ - $templateType('T'), - ]), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], 'return: Invariant' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -742,42 +608,11 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Invariant' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ], null, null, [ - TemplateTypeVariance::createCovariant(), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], - 'return: Invariant' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ], null, null, [ - TemplateTypeVariance::createContravariant(), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], 'return: Out' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Out::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -792,7 +627,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -809,7 +643,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -822,7 +655,6 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\In::class, [ $templateType('T'), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -837,7 +669,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -854,7 +685,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -869,7 +699,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -884,101 +713,6 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], - 'return: Out>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant(), - ), - ], - ], - 'return: In>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\In::class, [ - new GenericObjectType(D\Invariant::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createInvariant(), - ), - ], - ], - 'return: Invariant>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], - 'return: Invariant>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\In::class, [ - $templateType('T'), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createContravariant(), - ), - ], - ], - 'return: In>>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\In::class, [ - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\Out::class, [ - $templateType('T'), - ]), - ]), - ]), - false, - [ - new TemplateTypeReference( - $templateType('T'), - TemplateTypeVariance::createCovariant(), - ), - ], - ], - 'return: Out>>' => [ - TemplateTypeVariance::createCovariant(), - new GenericObjectType(D\Out::class, [ - new GenericObjectType(D\Invariant::class, [ - new GenericObjectType(D\In::class, [ - $templateType('T'), - ]), - ]), - ]), - false, [ new TemplateTypeReference( $templateType('T'), @@ -986,14 +720,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Out> (with invariance composition)' => [ + 'param: Out>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Out::class, [ new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1001,14 +734,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: In> (with invariance composition)' => [ + 'param: In>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\In::class, [ new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1016,14 +748,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Invariant> (with invariance composition)' => [ + 'param: Invariant>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ new GenericObjectType(D\Out::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1031,14 +762,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Invariant> (with invariance composition)' => [ + 'param: Invariant>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ new GenericObjectType(D\In::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1046,7 +776,7 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: In>> (with invariance composition)' => [ + 'param: In>>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\In::class, [ new GenericObjectType(D\Invariant::class, [ @@ -1055,7 +785,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1063,7 +792,7 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Out>> (with invariance composition)' => [ + 'param: Out>>' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Out::class, [ new GenericObjectType(D\Invariant::class, [ @@ -1072,7 +801,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1080,14 +808,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Invariant (with invariance composition)' => [ + 'param: Invariant' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), ], null, null, [ TemplateTypeVariance::createCovariant(), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1095,14 +822,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'param: Invariant (with invariance composition)' => [ + 'param: Invariant' => [ TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), ], null, null, [ TemplateTypeVariance::createContravariant(), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1110,14 +836,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Out> (with invariance composition)' => [ + 'return: Out>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Out::class, [ new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1125,14 +850,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: In> (with invariance composition)' => [ + 'return: In>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\In::class, [ new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1140,14 +864,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Invariant> (with invariance composition)' => [ + 'return: Invariant>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ new GenericObjectType(D\Out::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1155,14 +878,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Invariant> (with invariance composition)' => [ + 'return: Invariant>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ new GenericObjectType(D\In::class, [ $templateType('T'), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1170,7 +892,7 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: In>> (with invariance composition)' => [ + 'return: In>>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\In::class, [ new GenericObjectType(D\Invariant::class, [ @@ -1179,7 +901,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1187,7 +908,7 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Out>> (with invariance composition)' => [ + 'return: Out>>' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Out::class, [ new GenericObjectType(D\Invariant::class, [ @@ -1196,7 +917,6 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1204,14 +924,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Invariant (with invariance composition)' => [ + 'return: Invariant' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), ], null, null, [ TemplateTypeVariance::createCovariant(), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1219,14 +938,13 @@ public function dataGetReferencedTypeArguments(): array ), ], ], - 'return: Invariant (with invariance composition)' => [ + 'return: Invariant' => [ TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), ], null, null, [ TemplateTypeVariance::createContravariant(), ]), - true, [ new TemplateTypeReference( $templateType('T'), @@ -1242,10 +960,8 @@ public function dataGetReferencedTypeArguments(): array * * @param array $expectedReferences */ - public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, bool $invarianceComposition, array $expectedReferences): void + public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, array $expectedReferences): void { - TemplateTypeVariance::setInvarianceCompositionEnabled($invarianceComposition); - $result = []; foreach ($type->getReferencedTemplateTypes($positionVariance) as $r) { $result[] = $r; From ae5f990c9ced8e656c5eade5230c8321a3cd3b66 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:25:25 +0200 Subject: [PATCH 0329/3097] [BE] Use explicit mixed for global array variables --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/Analyser/DirectInternalScopeFactory.php | 2 -- src/Analyser/LazyInternalScopeFactory.php | 4 ---- src/Analyser/MutatingScope.php | 3 +-- src/Testing/PHPStanTestCase.php | 1 - 8 files changed, 2 insertions(+), 13 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 579cf43f380..c106985a823 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -30,7 +30,6 @@ Bleeding edge (TODO move to other sections) * ApiInstanceofRule * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) -* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! @@ -139,6 +138,7 @@ Improvements 🔧 * Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) * New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) * Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 +* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index e011ee0bf7f..44d842cb000 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses!: [] - explicitMixedForGlobalVariables: true explicitMixedViaIsArray: true arrayFilter: true arrayUnpacking: true diff --git a/conf/config.neon b/conf/config.neon index 3593b506da6..8953eab15b5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -36,7 +36,6 @@ parameters: - CachingIterator - RegexIterator - ReflectionEnum - explicitMixedForGlobalVariables: false explicitMixedViaIsArray: false arrayFilter: false arrayUnpacking: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 234a86f02fe..2381e94d6ec 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -31,7 +31,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), - explicitMixedForGlobalVariables: bool(), explicitMixedViaIsArray: bool(), arrayFilter: bool(), arrayUnpacking: bool(), diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index ed36ee647ef..c48074c1dd9 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -35,7 +35,6 @@ public function __construct( private Parser $parser, private NodeScopeResolver $nodeScopeResolver, private PhpVersion $phpVersion, - private bool $explicitMixedForGlobalVariables, private ConstantResolver $constantResolver, ) { @@ -102,7 +101,6 @@ public function create( $afterExtractCall, $parentScope, $nativeTypesPromoted, - $this->explicitMixedForGlobalVariables, ); } diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index d079e204e26..0c904d0d3bd 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -20,8 +20,6 @@ final class LazyInternalScopeFactory implements InternalScopeFactory { - private bool $explicitMixedForGlobalVariables; - /** * @param class-string $scopeClass */ @@ -30,7 +28,6 @@ public function __construct( private Container $container, ) { - $this->explicitMixedForGlobalVariables = $this->container->getParameter('featureToggles')['explicitMixedForGlobalVariables']; } /** @@ -94,7 +91,6 @@ public function create( $afterExtractCall, $parentScope, $nativeTypesPromoted, - $this->explicitMixedForGlobalVariables, ); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 17c0c34828a..62786fe77a9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -220,7 +220,6 @@ public function __construct( private bool $afterExtractCall = false, private ?Scope $parentScope = null, private bool $nativeTypesPromoted = false, - private bool $explicitMixedForGlobalVariables = false, ) { if ($namespace === '') { @@ -537,7 +536,7 @@ public function getVariableType(string $variableName): Type } if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType($this->explicitMixedForGlobalVariables)); + return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); } if ($this->hasVariableType($variableName)->no()) { diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 8238b6ead9e..f4ab2a470d4 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -188,7 +188,6 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider self::getParser(), $container->getByType(NodeScopeResolver::class), $container->getByType(PhpVersion::class), - $container->getParameter('featureToggles')['explicitMixedForGlobalVariables'], $constantResolver, ), ); From a63334e9245150924f0be509af5038262dd23ab1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:26:34 +0200 Subject: [PATCH 0330/3097] Fix build --- tests/PHPStan/Type/Generic/GenericObjectTypeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 74b558e8ca3..273394c6b63 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -461,7 +461,7 @@ public function testResolveTemplateTypes(Type $received, Type $template, array $ ); } - /** @return array}> */ + /** @return array}> */ public function dataGetReferencedTypeArguments(): array { $templateType = static fn ($name, ?Type $bound = null): Type => TemplateTypeFactory::create( From d7246a08e4a0f6687f7dce817cdd0e1b82643397 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Sep 2024 21:29:27 +0200 Subject: [PATCH 0331/3097] [BE] Consider implicit throw points when the only explicit one is `Throw_` --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Analyser/NodeScopeResolver.php | 6 +----- src/Testing/RuleTestCase.php | 1 - src/Testing/TypeInferenceTestCase.php | 1 - tests/PHPStan/Analyser/AnalyserTest.php | 1 - 8 files changed, 2 insertions(+), 13 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index c106985a823..baf8f62323b 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -92,7 +92,6 @@ Bleeding edge (TODO move to other sections) * More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) -* Consider implicit throw points when the only explicit one is Throw_ (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) * Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 * Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 * Check invalid `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/95c0a5806c65c975201b9d3a464873f75a04c8b8), #10932 @@ -139,6 +138,7 @@ Improvements 🔧 * New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) * Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 * Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! +* Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 44d842cb000..cbd47d326c6 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -49,6 +49,5 @@ parameters: preciseMissingReturn: true validatePregQuote: true tooWidePropertyType: true - explicitThrow: true absentTypeChecks: true requireFileExists: true diff --git a/conf/config.neon b/conf/config.neon index 8953eab15b5..48d550549f4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -87,7 +87,6 @@ parameters: requireFileExists: false narrowPregMatches: true tooWidePropertyType: false - explicitThrow: false absentTypeChecks: false fileExtensions: - php @@ -534,7 +533,6 @@ services: universalObjectCratesClasses: %universalObjectCratesClasses% paramOutType: %featureToggles.paramOutType% preciseMissingReturn: %featureToggles.preciseMissingReturn% - explicitThrow: %featureToggles.explicitThrow% - class: PHPStan\Analyser\ConstantResolver diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 2381e94d6ec..a42dd03e8aa 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -80,7 +80,6 @@ parametersSchema: validatePregQuote: bool() narrowPregMatches: bool() tooWidePropertyType: bool() - explicitThrow: bool() absentTypeChecks: bool() requireFileExists: bool() ]) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8bcb6c1af23..f80ae029459 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -262,7 +262,6 @@ public function __construct( private readonly bool $detectDeadTypeInMultiCatch, private readonly bool $paramOutType, private readonly bool $preciseMissingReturn, - private readonly bool $explicitThrow, ) { $earlyTerminatingMethodNames = []; @@ -1565,10 +1564,7 @@ private function processStmtNode( } // implicit only - if ( - count($matchingThrowPoints) === 0 - || ($this->explicitThrow && $onlyExplicitIsThrow) - ) { + if (count($matchingThrowPoints) === 0 || $onlyExplicitIsThrow) { foreach ($throwPoints as $throwPointIndex => $throwPoint) { if ($throwPoint->isExplicit()) { continue; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 6e88b1ab684..58143571920 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -108,7 +108,6 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], - self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index ca350a16b30..a87e4b47e42 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -88,7 +88,6 @@ public static function processFile( self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], - self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 39155bdb6fc..3be28182531 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -731,7 +731,6 @@ private function createAnalyser(): Analyser self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], - self::getContainer()->getParameter('featureToggles')['explicitThrow'], ); $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( From 457d73e9062d6b4614ee9a23c635f42ba28dcd21 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:21:31 +0000 Subject: [PATCH 0332/3097] Update issue-bot --- issue-bot/composer.lock | 120 ++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index af4a00fca88..9f4f4c42a5a 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -1868,16 +1868,16 @@ }, { "name": "symfony/console", - "version": "v6.4.11", + "version": "v6.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998" + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/42686880adaacdad1835ee8fc2a9ec5b7bd63998", - "reference": "42686880adaacdad1835ee8fc2a9ec5b7bd63998", + "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", + "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", "shasum": "" }, "require": { @@ -1942,7 +1942,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.11" + "source": "https://github.com/symfony/console/tree/v6.4.12" }, "funding": [ { @@ -1958,7 +1958,7 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:29+00:00" + "time": "2024-09-20T08:15:52+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2160,20 +2160,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2219,7 +2219,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -2235,24 +2235,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -2297,7 +2297,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -2313,24 +2313,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -2378,7 +2378,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -2394,24 +2394,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2458,7 +2458,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -2474,24 +2474,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -2538,7 +2538,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -2554,7 +2554,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/service-contracts", @@ -2641,16 +2641,16 @@ }, { "name": "symfony/string", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -2708,7 +2708,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.4" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -2724,7 +2724,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-09-20T08:28:38+00:00" } ], "packages-dev": [ @@ -2860,16 +2860,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", "shasum": "" }, "require": { @@ -2912,9 +2912,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-15T16:40:33+00:00" }, { "name": "phar-io/manifest", @@ -3355,16 +3355,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", "shasum": "" }, "require": { @@ -3379,7 +3379,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -3438,7 +3438,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" }, "funding": [ { @@ -3454,7 +3454,7 @@ "type": "tidelift" } ], - "time": "2024-07-10T11:45:39+00:00" + "time": "2024-09-19T10:50:18+00:00" }, { "name": "sebastian/cli-parser", From c5035effbb89d96ba813250b595be266e147eadd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:21:15 +0000 Subject: [PATCH 0333/3097] Update crate-ci/typos action to v1.24.6 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index e87b7b38c85..10b814e08a0 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.24.5" + uses: "crate-ci/typos@v1.24.6" with: files: "README.md src/" From 975486ec2a011569993ad90a5bc676e0f7da644d Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:20:08 +0000 Subject: [PATCH 0334/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c30f4a3827c..deb73479df8 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.106", + "phpstan/php-8-stubs": "0.3.108", "phpstan/phpdoc-parser": "1.31.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index ddf91ae047a..cb42d7d1e42 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ea2c2c535b8c0031d60c5ef66f124cf0", + "content-hash": "c5763f95f6bf96a666d820e5e34d8e56", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.106", + "version": "0.3.108", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "32a289268a0bbd2ee64f060ecd74bb6eb345738d" + "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/32a289268a0bbd2ee64f060ecd74bb6eb345738d", - "reference": "32a289268a0bbd2ee64f060ecd74bb6eb345738d", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/59ee6d5256a0bb43debf7d131441edf598b155fa", + "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.106" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.108" }, - "time": "2024-09-17T00:15:59+00:00" + "time": "2024-09-23T00:19:30+00:00" }, { "name": "phpstan/phpdoc-parser", From 707ec771ec4875dd583f97cbadd76189297d5f3d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:11:40 +0200 Subject: [PATCH 0335/3097] [BE] `@param-out` changes --- changelog-2.0.md | 8 ++++---- conf/bleedingEdge.neon | 1 - conf/config.level3.neon | 12 ++---------- conf/config.level4.neon | 11 ++--------- conf/config.level6.neon | 16 ++-------------- conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Analyser/NodeScopeResolver.php | 3 --- src/PhpDoc/StubValidator.php | 4 ++-- .../MissingFunctionParameterTypehintRule.php | 4 ---- .../MissingMethodParameterTypehintRule.php | 4 ---- src/Testing/RuleTestCase.php | 1 - src/Testing/TypeInferenceTestCase.php | 1 - tests/PHPStan/Analyser/AnalyserTest.php | 1 - .../MissingFunctionParameterTypehintRuleTest.php | 2 +- .../MissingMethodParameterTypehintRuleTest.php | 2 +- 16 files changed, 14 insertions(+), 59 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index baf8f62323b..7b7dfedae21 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -7,6 +7,9 @@ Major new features 🚀 * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 +* **Enhancements in Handling Parameters Passed by Reference** + * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) + * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! Bleeding edge (TODO move to other sections) ===================== @@ -71,9 +74,6 @@ Bleeding edge (TODO move to other sections) * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) * Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) -* **Enhancements in Handling Parameters Passed by Reference** - * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) - * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * Add option `reportAnyTypeWideningInVarTag` ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! * `array_values` rule (report when a `list` type is always passed in) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! * Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! @@ -84,7 +84,6 @@ Bleeding edge (TODO move to other sections) * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 -* Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) @@ -139,6 +138,7 @@ Improvements 🔧 * Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 * Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! * Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) +* Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index cbd47d326c6..b3e0abb5c3c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -41,7 +41,6 @@ parameters: callUserFunc: true finalByPhpDoc: true magicConstantOutOfContext: true - paramOutType: true pure: true checkParameterCastableToStringFunctions: true uselessReturnValue: true diff --git a/conf/config.level3.neon b/conf/config.level3.neon index f205db23b6e..dfbfc00454d 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -8,10 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule: - phpstan.rules.rule: %featureToggles.paramOutType% - PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule: - phpstan.rules.rule: %featureToggles.paramOutType% rules: - PHPStan\Rules\Arrays\ArrayDestructuringRule @@ -30,6 +26,8 @@ rules: - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule + - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule + - PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule - PHPStan\Rules\Variables\VariableCloningRule parameters: @@ -95,9 +93,3 @@ services: - class: PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule - - - - class: PHPStan\Rules\Variables\ParameterOutAssignedTypeRule - - - - class: PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 349523c3a04..199a794a530 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -14,6 +14,8 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWideArrowFunctionReturnTypehintRule - PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule + - PHPStan\Rules\TooWideTypehints\TooWideFunctionParameterOutTypeRule + - PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule conditionalTags: PHPStan\Rules\Comparison\ConstantLooseComparisonRule: @@ -28,10 +30,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.logicalXor% PHPStan\Rules\DeadCode\BetterNoopRule: phpstan.rules.rule: %featureToggles.betterNoop% - PHPStan\Rules\TooWideTypehints\TooWideFunctionParameterOutTypeRule: - phpstan.rules.rule: %featureToggles.paramOutType% - PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule: - phpstan.rules.rule: %featureToggles.paramOutType% PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureNewCollector: @@ -322,11 +320,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\TooWideTypehints\TooWideFunctionParameterOutTypeRule - - - - class: PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule - class: PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule diff --git a/conf/config.level6.neon b/conf/config.level6.neon index 1029bcdba03..2b50d58d83b 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -9,7 +9,9 @@ parameters: rules: - PHPStan\Rules\Constants\MissingClassConstantTypehintRule + - PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule - PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule + - PHPStan\Rules\Methods\MissingMethodParameterTypehintRule - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule - PHPStan\Rules\Properties\MissingPropertyTypehintRule @@ -18,19 +20,5 @@ conditionalTags: phpstan.rules.rule: %featureToggles.absentTypeChecks% services: - - - class: PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule - arguments: - paramOut: %featureToggles.paramOutType% - tags: - - phpstan.rules.rule - - - - class: PHPStan\Rules\Methods\MissingMethodParameterTypehintRule - arguments: - paramOut: %featureToggles.paramOutType% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule diff --git a/conf/config.neon b/conf/config.neon index 48d550549f4..f21201c707b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -77,7 +77,6 @@ parameters: callUserFunc: false finalByPhpDoc: false magicConstantOutOfContext: false - paramOutType: false pure: false checkParameterCastableToStringFunctions: false uselessReturnValue: false @@ -531,7 +530,6 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch% universalObjectCratesClasses: %universalObjectCratesClasses% - paramOutType: %featureToggles.paramOutType% preciseMissingReturn: %featureToggles.preciseMissingReturn% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index a42dd03e8aa..96140b32969 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -71,7 +71,6 @@ parametersSchema: callUserFunc: bool() finalByPhpDoc: bool() magicConstantOutOfContext: bool() - paramOutType: bool() pure: bool() checkParameterCastableToStringFunctions: bool() uselessReturnValue: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f80ae029459..4e7f170897a 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -260,7 +260,6 @@ public function __construct( private readonly bool $implicitThrows, private readonly bool $treatPhpDocTypesAsCertain, private readonly bool $detectDeadTypeInMultiCatch, - private readonly bool $paramOutType, private readonly bool $preciseMissingReturn, ) { @@ -4748,13 +4747,11 @@ private function processArgs( } elseif ( $calleeReflection instanceof MethodReflection && !$calleeReflection->getDeclaringClass()->isBuiltin() - && $this->paramOutType ) { $byRefType = $currentParameter->getType(); } elseif ( $calleeReflection instanceof FunctionReflection && !$calleeReflection->isBuiltin() - && $this->paramOutType ) { $byRefType = $currentParameter->getType(); } diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 0c5d975f2f9..e866f7081b3 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -228,9 +228,9 @@ private function getRuleRegistry(Container $container): RuleRegistry new InvalidThrowsPhpDocValueRule($fileTypeMapper), // level 6 - new MissingFunctionParameterTypehintRule($missingTypehintCheck, $container->getParameter('featureToggles')['paramOutType']), + new MissingFunctionParameterTypehintRule($missingTypehintCheck), new MissingFunctionReturnTypehintRule($missingTypehintCheck), - new MissingMethodParameterTypehintRule($missingTypehintCheck, $container->getParameter('featureToggles')['paramOutType']), + new MissingMethodParameterTypehintRule($missingTypehintCheck), new MissingMethodReturnTypehintRule($missingTypehintCheck), new MissingPropertyTypehintRule($missingTypehintCheck), ]; diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php index 73782fcf533..3b7a2646566 100644 --- a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -25,7 +25,6 @@ final class MissingFunctionParameterTypehintRule implements Rule public function __construct( private MissingTypehintCheck $missingTypehintCheck, - private bool $paramOut, ) { } @@ -51,9 +50,6 @@ public function processNode(Node $node, Scope $scope): array } } - if (!$this->paramOut) { - continue; - } if ($parameterReflection->getOutType() === null) { continue; } diff --git a/src/Rules/Methods/MissingMethodParameterTypehintRule.php b/src/Rules/Methods/MissingMethodParameterTypehintRule.php index 5ef4db93d13..8fe57003b71 100644 --- a/src/Rules/Methods/MissingMethodParameterTypehintRule.php +++ b/src/Rules/Methods/MissingMethodParameterTypehintRule.php @@ -25,7 +25,6 @@ final class MissingMethodParameterTypehintRule implements Rule public function __construct( private MissingTypehintCheck $missingTypehintCheck, - private bool $paramOut, ) { } @@ -51,9 +50,6 @@ public function processNode(Node $node, Scope $scope): array } } - if (!$this->paramOut) { - continue; - } if ($parameterReflection->getOutType() === null) { continue; } diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 58143571920..802be9e5992 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -106,7 +106,6 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $fileAnalyser = new FileAnalyser( diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index a87e4b47e42..9089e0dd371 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -86,7 +86,6 @@ public static function processFile( self::getContainer()->getParameter('exceptions')['implicitThrows'], self::getContainer()->getParameter('treatPhpDocTypesAsCertain'), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 3be28182531..e693b1beb91 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -729,7 +729,6 @@ private function createAnalyser(): Analyser true, $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['paramOutType'], self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $lexer = new Lexer(); diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index a12ee381e9a..73a197f3a59 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingFunctionParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, []), true); + return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index fecb8a1045a..48c0c6acde0 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, []), true); + return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); } public function testRule(): void From a72f752d8e4613ec77bb8647b4c41237f97127cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 22:23:48 +0200 Subject: [PATCH 0336/3097] Add more mixed-type bool subtraction tests --- .../PHPStan/Analyser/nsrt/mixed-subtract.php | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/mixed-subtract.php diff --git a/tests/PHPStan/Analyser/nsrt/mixed-subtract.php b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php new file mode 100644 index 00000000000..31c201d1650 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php @@ -0,0 +1,56 @@ += 8.0 + +namespace SubtractMixed; + +use function PHPStan\Testing\assertType; + +/** + * @param int|0.0|''|'0'|array{}|false|null $moreThenFalsy + */ +function subtract(mixed $m, $moreThenFalsy) { + if ($m !== true) { + assertType("mixed~true", $m); + assertType('bool', (bool) $m); // mixed could still contain something truthy + } + if ($m !== false) { + assertType("mixed~false", $m); + assertType('bool', (bool) $m); // mixed could still contain something falsy + } + if (!is_bool($m)) { + assertType('mixed~bool', $m); + assertType('bool', (bool) $m); + } + if (!is_array($m)) { + assertType('mixed~array', $m); + assertType('bool', (bool) $m); + } + + if ($m) { + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m); + assertType('true', (bool) $m); + } + if (!$m) { + assertType("0|0.0|''|'0'|array{}|false|null", $m); + assertType('false', (bool) $m); + } + if (!$m) { + if (!is_int($m)) { + assertType("0.0|''|'0'|array{}|false|null", $m); + assertType('false', (bool)$m); + } + if (!is_bool($m)) { + assertType("0|0.0|''|'0'|array{}|null", $m); + assertType('false', (bool)$m); + } + } + + if (!$m || is_int($m)) { + assertType("0.0|''|'0'|array{}|int|false|null", $m); + assertType('bool', (bool) $m); + } + + if ($m !== $moreThenFalsy) { + assertType('mixed', $m); + assertType('bool', (bool) $m); // could be true + } +} From 5d4eefce786073c800011121c07b965a7e9de5ba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:20:49 +0200 Subject: [PATCH 0337/3097] [BE] Precise missing return --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Analyser/NodeScopeResolver.php | 3 +-- src/Testing/RuleTestCase.php | 1 - src/Testing/TypeInferenceTestCase.php | 1 - tests/PHPStan/Analyser/AnalyserTest.php | 1 - 8 files changed, 2 insertions(+), 10 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 7b7dfedae21..1352fbce3da 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -110,7 +110,6 @@ Bleeding edge (TODO move to other sections) * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! -* Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) * Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3! @@ -139,6 +138,7 @@ Improvements 🔧 * Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! * Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) * Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) +* Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index b3e0abb5c3c..c476e5106c1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -45,7 +45,6 @@ parameters: checkParameterCastableToStringFunctions: true uselessReturnValue: true printfArrayParameters: true - preciseMissingReturn: true validatePregQuote: true tooWidePropertyType: true absentTypeChecks: true diff --git a/conf/config.neon b/conf/config.neon index f21201c707b..6cca06d1090 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -81,7 +81,6 @@ parameters: checkParameterCastableToStringFunctions: false uselessReturnValue: false printfArrayParameters: false - preciseMissingReturn: false validatePregQuote: false requireFileExists: false narrowPregMatches: true @@ -530,7 +529,6 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch% universalObjectCratesClasses: %universalObjectCratesClasses% - preciseMissingReturn: %featureToggles.preciseMissingReturn% - class: PHPStan\Analyser\ConstantResolver diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 96140b32969..447f4eedb6c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -75,7 +75,6 @@ parametersSchema: checkParameterCastableToStringFunctions: bool() uselessReturnValue: bool() printfArrayParameters: bool() - preciseMissingReturn: bool() validatePregQuote: bool() narrowPregMatches: bool() tooWidePropertyType: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 4e7f170897a..8ee2e80fcde 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -260,7 +260,6 @@ public function __construct( private readonly bool $implicitThrows, private readonly bool $treatPhpDocTypesAsCertain, private readonly bool $detectDeadTypeInMultiCatch, - private readonly bool $preciseMissingReturn, ) { $earlyTerminatingMethodNames = []; @@ -359,7 +358,7 @@ public function processStmtNodes( $parentNode = $parentNode; $endStatements = $statementResult->getEndStatements(); - if ($this->preciseMissingReturn && count($endStatements) > 0) { + if (count($endStatements) > 0) { foreach ($endStatements as $endStatement) { $endStatementResult = $endStatement->getResult(); $nodeCallback(new ExecutionEndNode( diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 802be9e5992..716f13bc793 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -106,7 +106,6 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 9089e0dd371..d406cc4bf5c 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -86,7 +86,6 @@ public static function processFile( self::getContainer()->getParameter('exceptions')['implicitThrows'], self::getContainer()->getParameter('treatPhpDocTypesAsCertain'), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index e693b1beb91..ee8c645bd02 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -729,7 +729,6 @@ private function createAnalyser(): Analyser true, $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], - self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'], ); $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( From ee565dbd2f2a31811e19e7fbc181526d33b3b4d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:24:56 +0200 Subject: [PATCH 0338/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/11119 Closes https://github.com/phpstan/phpstan/issues/4174 Closes https://github.com/phpstan/phpstan/issues/7082 Closes https://github.com/phpstan/phpstan/issues/4912 Closes https://github.com/phpstan/phpstan/issues/1953 --- changelog-2.0.md | 6 +- .../IfConstantConditionRuleTest.php | 6 ++ .../Rules/Comparison/data/bug-4912.php | 27 ++++++ .../CallToFunctionParametersRuleTest.php | 11 +++ .../PHPStan/Rules/Functions/data/bug-7082.php | 12 +++ .../Rules/Methods/CallMethodsRuleTest.php | 14 +++ tests/PHPStan/Rules/Methods/data/bug-1953.php | 13 +++ .../InvalidComparisonOperationRuleTest.php | 5 + .../Rules/Operators/data/bug-11119.php | 17 ++++ .../TypesAssignedToPropertiesRuleTest.php | 6 ++ .../Rules/Properties/data/bug-4174.php | 95 +++++++++++++++++++ 11 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-4912.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-7082.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-1953.php create mode 100644 tests/PHPStan/Rules/Operators/data/bug-11119.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-4174.php diff --git a/changelog-2.0.md b/changelog-2.0.md index 1352fbce3da..31fb869728e 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -133,10 +133,10 @@ Improvements 🔧 * Returning plain strings as errors no longer supported, use RuleErrorBuilder * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) * Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) -* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23) +* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23), #11119, #4174 * Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 -* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm! -* Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4) +* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), #7082, thanks @herndlm! +* Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4), #4912 * Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) * Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 479f5db928b..840839ac9b3 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -169,4 +169,10 @@ public function testBug10561(): void $this->analyse([__DIR__ . '/data/bug-10561.php'], []); } + public function testBug4912(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4912.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4912.php b/tests/PHPStan/Rules/Comparison/data/bug-4912.php new file mode 100644 index 00000000000..cdbb585f9ae --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-4912.php @@ -0,0 +1,27 @@ +analyse([__DIR__ . '/data/bug-9224.php'], []); } + public function testBug7082(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-7082.php'], [ + [ + 'Parameter #1 $val of function Bug7082\takesStr expects string, mixed given.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-7082.php b/tests/PHPStan/Rules/Functions/data/bug-7082.php new file mode 100644 index 00000000000..0984dce665e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-7082.php @@ -0,0 +1,12 @@ +analyse([__DIR__ . '/data/bug-10159.php'], []); } + public function testBug1953(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-1953.php'], [ + [ + 'Cannot call method bar() on string.', + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-1953.php b/tests/PHPStan/Rules/Methods/data/bug-1953.php new file mode 100644 index 00000000000..c70978996c9 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-1953.php @@ -0,0 +1,13 @@ +bar(); +}; diff --git a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php index dc9d57f88ef..c6879f2dc17 100644 --- a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php @@ -168,4 +168,9 @@ public function testRuleWithNullsafeVariant(): void ]); } + public function testBug11119(): void + { + $this->analyse([__DIR__ . '/data/bug-11119.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-11119.php b/tests/PHPStan/Rules/Operators/data/bug-11119.php new file mode 100644 index 00000000000..c0fe6b5feb4 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-11119.php @@ -0,0 +1,17 @@ + ($carry instanceof DateTime && $carry < $time) ? $carry : $time, + null + ); +}; diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 59eb242e7c6..3616824c7f2 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -670,4 +670,10 @@ public function testBug11617(): void ]); } + public function testBug4174(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-4174.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-4174.php b/tests/PHPStan/Rules/Properties/data/bug-4174.php new file mode 100644 index 00000000000..9e4d4e7b093 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-4174.php @@ -0,0 +1,95 @@ + + */ + public static function getArrayConstAsKey(): array { + return [ + self::NUMBER_TYPE_OFF => 'Off', + self::NUMBER_TYPE_HEAD => 'Head', + self::NUMBER_TYPE_POSITION => 'Position', + ]; + } + + /** + * @return list + */ + public static function getArrayConstAsValue(): array { + return [ + self::NUMBER_TYPE_OFF, + self::NUMBER_TYPE_HEAD, + self::NUMBER_TYPE_POSITION, + ]; + } + + public function checkConstViaArrayKey(): void + { + $numberArray = self::getArrayConstAsKey(); + + // --- + + $newvalue = $this->getIntFromPost('newValue'); + + if ($newvalue && array_key_exists($newvalue, $numberArray)) { + $this->newValue = $newvalue; + } + + if (isset($numberArray[$newvalue])) { + $this->newValue = $newvalue; + } + + // --- + + $newvalue = $this->getIntFromPostWithoutNull('newValue'); + + if ($newvalue && array_key_exists($newvalue, $numberArray)) { + $this->newValue = $newvalue; + } + + if (isset($numberArray[$newvalue])) { + $this->newValue = $newvalue; + } + } + + public function checkConstViaArrayValue(): void + { + $numberArray = self::getArrayConstAsValue(); + + // --- + + $newvalue = $this->getIntFromPost('newValue'); + + if ($newvalue && in_array($newvalue, $numberArray, true)) { + $this->newValue = $newvalue; + } + + // --- + + $newvalue = $this->getIntFromPostWithoutNull('newValue'); + + if ($newvalue && in_array($newvalue, $numberArray, true)) { + $this->newValue = $newvalue; + } + } + + public function getIntFromPost(string $key): ?int { + return isset($_POST[$key]) ? (int)$_POST[$key] : null; + } + + public function getIntFromPostWithoutNull(string $key): int { + return isset($_POST[$key]) ? (int)$_POST[$key] : 0; + } +} From 84308056ae9cf2e0b4f9eca2d6b5cbbcd9f7f780 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:44:14 +0200 Subject: [PATCH 0339/3097] [BE] Report dead types even in multi-exception catch --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 - conf/parametersSchema.neon | 1 - src/Analyser/NodeScopeResolver.php | 14 ++---- src/Testing/RuleTestCase.php | 1 - src/Testing/TypeInferenceTestCase.php | 1 - tests/PHPStan/Analyser/AnalyserTest.php | 1 - ...xceptionRuleWithDisabledMultiCatchTest.php | 48 ------------------- .../disable-detect-multi-catch.neon | 3 -- 10 files changed, 5 insertions(+), 69 deletions(-) delete mode 100644 tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleWithDisabledMultiCatchTest.php delete mode 100644 tests/PHPStan/Rules/Exceptions/disable-detect-multi-catch.neon diff --git a/changelog-2.0.md b/changelog-2.0.md index 31fb869728e..8af379cecfc 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -58,7 +58,6 @@ Bleeding edge (TODO move to other sections) * More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! * More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! -* Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! * `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! * Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! * More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! @@ -139,6 +138,7 @@ Improvements 🔧 * Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4), #4912 * Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) * Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) +* Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index c476e5106c1..bd47059f3c6 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -35,7 +35,6 @@ parameters: propertyVariance: true genericPrototypeMessage: true stricterFunctionMap: true - detectDeadTypeInMultiCatch: true zeroFiles: true projectServicesNotInAnalysedPaths: true callUserFunc: true diff --git a/conf/config.neon b/conf/config.neon index 6cca06d1090..5fe45a5ef6f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -71,7 +71,6 @@ parameters: propertyVariance: false genericPrototypeMessage: false stricterFunctionMap: false - detectDeadTypeInMultiCatch: false zeroFiles: false projectServicesNotInAnalysedPaths: false callUserFunc: false @@ -527,7 +526,6 @@ services: earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% implicitThrows: %exceptions.implicitThrows% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch% universalObjectCratesClasses: %universalObjectCratesClasses% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 447f4eedb6c..61294c35f5e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -65,7 +65,6 @@ parametersSchema: propertyVariance: bool() genericPrototypeMessage: bool() stricterFunctionMap: bool() - detectDeadTypeInMultiCatch: bool() zeroFiles: bool() projectServicesNotInAnalysedPaths: bool() callUserFunc: bool() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8ee2e80fcde..ba87e0f1647 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -259,7 +259,6 @@ public function __construct( private readonly array $universalObjectCratesClasses, private readonly bool $implicitThrows, private readonly bool $treatPhpDocTypesAsCertain, - private readonly bool $detectDeadTypeInMultiCatch, ) { $earlyTerminatingMethodNames = []; @@ -1593,19 +1592,14 @@ private function processStmtNode( } // emit error - if ($this->detectDeadTypeInMultiCatch) { - foreach ($matchingCatchTypes as $catchTypeIndex => $matched) { - if ($matched) { - continue; - } - $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchTypes[$catchTypeIndex], $originalCatchTypes[$catchTypeIndex]), $scope); + foreach ($matchingCatchTypes as $catchTypeIndex => $matched) { + if ($matched) { + continue; } + $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchTypes[$catchTypeIndex], $originalCatchTypes[$catchTypeIndex]), $scope); } if (count($matchingThrowPoints) === 0) { - if (!$this->detectDeadTypeInMultiCatch) { - $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope); - } continue; } diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 716f13bc793..5aac77f7727 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -105,7 +105,6 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('universalObjectCratesClasses'), self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), - self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], ); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index d406cc4bf5c..02fea01cc0a 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -85,7 +85,6 @@ public static function processFile( self::getContainer()->getParameter('universalObjectCratesClasses'), self::getContainer()->getParameter('exceptions')['implicitThrows'], self::getContainer()->getParameter('treatPhpDocTypesAsCertain'), - self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index ee8c645bd02..0a27ee28896 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -728,7 +728,6 @@ private function createAnalyser(): Analyser [stdClass::class], true, $this->shouldTreatPhpDocTypesAsCertain(), - self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], ); $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleWithDisabledMultiCatchTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleWithDisabledMultiCatchTest.php deleted file mode 100644 index debb459f031..00000000000 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleWithDisabledMultiCatchTest.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -class CatchWithUnthrownExceptionRuleWithDisabledMultiCatchTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new CatchWithUnthrownExceptionRule(new DefaultExceptionTypeResolver( - $this->createReflectionProvider(), - [], - [], - [], - [], - ), true); - } - - public static function getAdditionalConfigFiles(): array - { - return array_merge( - parent::getAdditionalConfigFiles(), - [__DIR__ . '/disable-detect-multi-catch.neon'], - ); - } - - public function testMultiCatchBackwardCompatible(): void - { - $this->analyse([__DIR__ . '/data/unthrown-exception-multi.php'], [ - [ - 'Dead catch - InvalidArgumentException is already caught above.', - 145, - ], - [ - 'Dead catch - InvalidArgumentException is already caught above.', - 156, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Exceptions/disable-detect-multi-catch.neon b/tests/PHPStan/Rules/Exceptions/disable-detect-multi-catch.neon deleted file mode 100644 index e7635572051..00000000000 --- a/tests/PHPStan/Rules/Exceptions/disable-detect-multi-catch.neon +++ /dev/null @@ -1,3 +0,0 @@ -parameters: - featureToggles: - detectDeadTypeInMultiCatch: false From 7a701eab2c3b23f5f468907eb017b9826c638e38 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:47:38 +0200 Subject: [PATCH 0340/3097] [BE] MethodSignatureRule - look at abstract trait method --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 - conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 2 +- src/Rules/Methods/MethodSignatureRule.php | 43 +++++++++---------- .../Rules/Methods/MethodSignatureRuleTest.php | 2 +- .../Methods/OverridingMethodRuleTest.php | 2 +- 8 files changed, 24 insertions(+), 31 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 8af379cecfc..98d047542b9 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -64,7 +64,6 @@ Bleeding edge (TODO move to other sections) * More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! * More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! * Detect overriding `@final` method in OverridingMethodRule, #9135 -* MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) @@ -139,6 +138,7 @@ Improvements 🔧 * Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) * Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) * Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! +* MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index bd47059f3c6..589c51b4c2f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -17,7 +17,6 @@ parameters: runtimeReflectionRules: true notAnalysedTrait: true curlSetOptTypes: true - abstractTraitMethod: true missingMagicSerializationRule: true nullContextForVoidReturningFunctions: true unescapeStrings: true diff --git a/conf/config.neon b/conf/config.neon index 5fe45a5ef6f..81d10a16245 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -52,7 +52,6 @@ parameters: runtimeReflectionRules: false notAnalysedTrait: false curlSetOptTypes: false - abstractTraitMethod: false missingMagicSerializationRule: false nullContextForVoidReturningFunctions: false unescapeStrings: false @@ -1046,7 +1045,6 @@ services: arguments: reportMaybes: %reportMaybesInMethodSignatures% reportStatic: %reportStaticMethodSignatures% - abstractTraitMethod: %featureToggles.abstractTraitMethod% - class: PHPStan\Rules\Methods\MethodParameterComparisonHelper diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 61294c35f5e..6685779c137 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -47,7 +47,6 @@ parametersSchema: runtimeReflectionRules: bool() notAnalysedTrait: bool() curlSetOptTypes: bool() - abstractTraitMethod: bool() missingMagicSerializationRule: bool() nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index e866f7081b3..dd1461ecf4b 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -196,7 +196,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true, $container->getParameter('featureToggles')['abstractTraitMethod']), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $phpClassReflectionExtension, $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), + new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $phpClassReflectionExtension, $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 1e9d6b1ba27..194a41d74cc 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -44,7 +44,6 @@ public function __construct( private PhpClassReflectionExtension $phpClassReflectionExtension, private bool $reportMaybes, private bool $reportStatic, - private bool $abstractTraitMethod, ) { } @@ -169,30 +168,28 @@ private function collectParentMethods(string $methodName, ClassReflection $class $parentMethods[] = [$method, $method->getDeclaringClass()]; } - if ($this->abstractTraitMethod) { - foreach ($class->getTraits(true) as $trait) { - $nativeTraitReflection = $trait->getNativeReflection(); - if (!$nativeTraitReflection->hasMethod($methodName)) { - continue; - } - - $methodReflection = $nativeTraitReflection->getMethod($methodName); - $isAbstract = $methodReflection->isAbstract(); - if (!$isAbstract) { - continue; - } + foreach ($class->getTraits(true) as $trait) { + $nativeTraitReflection = $trait->getNativeReflection(); + if (!$nativeTraitReflection->hasMethod($methodName)) { + continue; + } - $declaringTrait = $trait->getNativeMethod($methodName)->getDeclaringClass(); - $parentMethods[] = [ - $this->phpClassReflectionExtension->createUserlandMethodReflection( - $trait, - $class, - new NativeBuiltinMethodReflection($methodReflection), - $declaringTrait->getName(), - ), - $declaringTrait, - ]; + $methodReflection = $nativeTraitReflection->getMethod($methodName); + $isAbstract = $methodReflection->isAbstract(); + if (!$isAbstract) { + continue; } + + $declaringTrait = $trait->getNativeMethod($methodName)->getDeclaringClass(); + $parentMethods[] = [ + $this->phpClassReflectionExtension->createUserlandMethodReflection( + $trait, + $class, + new NativeBuiltinMethodReflection($methodReflection), + $declaringTrait->getName(), + ), + $declaringTrait, + ]; } return $parentMethods; diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index cbdac548bcc..6fd66f7e288 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule($phpClassReflectionExtension, $this->reportMaybes, $this->reportStatic, true), + new MethodSignatureRule($phpClassReflectionExtension, $this->reportMaybes, $this->reportStatic), true, new MethodParameterComparisonHelper($phpVersion, true), $phpClassReflectionExtension, diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index dd90432f8f4..722dcf77476 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule return new OverridingMethodRule( $phpVersion, - new MethodSignatureRule($phpClassReflectionExtension, true, true, true), + new MethodSignatureRule($phpClassReflectionExtension, true, true), false, new MethodParameterComparisonHelper($phpVersion, true), $phpClassReflectionExtension, From 870aa060438b91cbeb71a16c47e6d4d7d8a9aad5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 09:50:48 +0200 Subject: [PATCH 0341/3097] [BE] OverridingMethodRule - include template types in prototype declaring class description --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 2 +- .../MethodParameterComparisonHelper.php | 22 ++++++++-------- src/Rules/Methods/OverridingMethodRule.php | 25 +++++++++---------- .../Rules/Methods/MethodSignatureRuleTest.php | 3 +-- .../Methods/OverridingMethodRuleTest.php | 3 +-- 10 files changed, 27 insertions(+), 36 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 98d047542b9..ac4b73f38c5 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -51,7 +51,6 @@ Bleeding edge (TODO move to other sections) * Specify `Imagick` parameter types ([#2334](https://github.com/phpstan/phpstan-src/pull/2334)), thanks @zonuexe! * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! -* OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) @@ -139,6 +138,7 @@ Improvements 🔧 * Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) * Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! * MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) +* OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 589c51b4c2f..543fb682a39 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -32,7 +32,6 @@ parameters: paramOutVariance: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true - genericPrototypeMessage: true stricterFunctionMap: true zeroFiles: true projectServicesNotInAnalysedPaths: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1382d99ee16..efa06ccb5d5 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -178,7 +178,6 @@ services: class: PHPStan\Rules\Methods\OverridingMethodRule arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% - genericPrototypeMessage: %featureToggles.genericPrototypeMessage% finalByPhpDoc: %featureToggles.finalByPhpDoc% checkMissingOverrideMethodAttribute: %checkMissingOverrideMethodAttribute% tags: diff --git a/conf/config.neon b/conf/config.neon index 81d10a16245..de2f0d05a95 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -68,7 +68,6 @@ parameters: strictStaticMethodTemplateTypeVariance: false propertyVariance: false - genericPrototypeMessage: false stricterFunctionMap: false zeroFiles: false projectServicesNotInAnalysedPaths: false @@ -1048,8 +1047,6 @@ services: - class: PHPStan\Rules\Methods\MethodParameterComparisonHelper - arguments: - genericPrototypeMessage: %featureToggles.genericPrototypeMessage% - class: PHPStan\Rules\MissingTypehintCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 6685779c137..8b7291a6d77 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -62,7 +62,6 @@ parametersSchema: paramOutVariance: bool() strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() - genericPrototypeMessage: bool() stricterFunctionMap: bool() zeroFiles: bool() projectServicesNotInAnalysedPaths: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index dd1461ecf4b..04223a005ed 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -196,7 +196,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion, $container->getParameter('featureToggles')['genericPrototypeMessage']), $phpClassReflectionExtension, $container->getParameter('featureToggles')['genericPrototypeMessage'], $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), + new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 284152b9c9e..a9b741979a6 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -24,7 +24,7 @@ final class MethodParameterComparisonHelper { - public function __construct(private PhpVersion $phpVersion, private bool $genericPrototypeMessage) + public function __construct(private PhpVersion $phpVersion) { } @@ -47,7 +47,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr 'Method %s::%s() overrides method %s::%s() but misses parameter #%d $%s.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), $i + 1, $prototypeParameter->getName(), @@ -73,7 +73,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $method->getName(), $i + 1, $prototypeParameter->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('parameter.byRef'); @@ -92,7 +92,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $method->getName(), $i + 1, $prototypeParameter->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('parameter.notByRef'); @@ -133,7 +133,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $method->getName(), $i + 1, $prototypeParameter->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('parameter.notVariadic'); @@ -178,7 +178,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $i + $j + 1, $remainingPrototypeParameter->getName(), $remainingPrototypeParameter->getNativeType()->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.childParameterType'); @@ -198,7 +198,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $method->getName(), $i + 1, $prototypeParameter->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('parameter.variadic'); @@ -220,7 +220,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $method->getName(), $i + 1, $prototypeParameter->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('parameter.notOptional'); @@ -246,7 +246,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $i + 1, $prototypeParameter->getName(), $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.childParameterType'); @@ -274,7 +274,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $i + 1, $prototypeParameter->getName(), $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.childParameterType'); @@ -294,7 +294,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $i + 1, $prototypeParameter->getName(), $prototypeParameterType->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.childParameterType'); diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 99f3b1866e3..da41d2b685c 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -38,7 +38,6 @@ public function __construct( private bool $checkPhpDocMethodSignatures, private MethodParameterComparisonHelper $methodParameterComparisonHelper, private PhpClassReflectionExtension $phpClassReflectionExtension, - private bool $genericPrototypeMessage, private bool $finalByPhpDoc, private bool $checkMissingOverrideMethodAttribute, ) @@ -65,7 +64,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() overrides final method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $parent->getDisplayName($this->genericPrototypeMessage), + $parent->getDisplayName(true), $parentConstructor->getName(), )) ->nonIgnorable() @@ -79,7 +78,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() overrides @final method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $parent->getDisplayName($this->genericPrototypeMessage), + $parent->getDisplayName(true), $parentConstructor->getName(), ))->identifier('method.parentMethodFinalByPhpDoc') ->build(), @@ -116,7 +115,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() overrides method %s::%s() but is missing the #[\Override] attribute.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.missingOverride')->build(); } @@ -125,7 +124,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() overrides final method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -136,7 +135,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() overrides @final method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), ))->identifier('method.parentMethodFinalByPhpDoc') ->build(); @@ -148,7 +147,7 @@ public function processNode(Node $node, Scope $scope): array 'Non-static method %s::%s() overrides static method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -160,7 +159,7 @@ public function processNode(Node $node, Scope $scope): array 'Static method %s::%s() overrides non-static method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -176,7 +175,7 @@ public function processNode(Node $node, Scope $scope): array $method->isPrivate() ? 'Private' : 'Protected', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -188,7 +187,7 @@ public function processNode(Node $node, Scope $scope): array 'Private method %s::%s() overriding protected method %s::%s() should be protected or public.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -223,7 +222,7 @@ public function processNode(Node $node, Scope $scope): array $method->getDeclaringClass()->getDisplayName(), $method->getName(), $realPrototype->getTentativeReturnType()->describe(VerbosityLevel::typeOnly()), - $realPrototype->getDeclaringClass()->getDisplayName($this->genericPrototypeMessage), + $realPrototype->getDeclaringClass()->getDisplayName(true), $realPrototype->getName(), )) ->tip('Make it covariant, or use the #[\ReturnTypeWillChange] attribute to temporarily suppress the error.') @@ -273,7 +272,7 @@ public function processNode(Node $node, Scope $scope): array $method->getDeclaringClass()->getDisplayName(), $method->getName(), $prototypeReturnType->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() @@ -286,7 +285,7 @@ public function processNode(Node $node, Scope $scope): array $method->getDeclaringClass()->getDisplayName(), $method->getName(), $prototypeReturnType->describe(VerbosityLevel::typeOnly()), - $prototypeDeclaringClass->getDisplayName($this->genericPrototypeMessage), + $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), )) ->nonIgnorable() diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 6fd66f7e288..5ad57bd6c5c 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -28,10 +28,9 @@ protected function getRule(): Rule $phpVersion, new MethodSignatureRule($phpClassReflectionExtension, $this->reportMaybes, $this->reportStatic), true, - new MethodParameterComparisonHelper($phpVersion, true), + new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, true, - true, false, ); } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 722dcf77476..abae064fb75 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -30,10 +30,9 @@ protected function getRule(): Rule $phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), false, - new MethodParameterComparisonHelper($phpVersion, true), + new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, true, - true, $this->checkMissingOverrideMethodAttribute, ); } From 84ab800f690237eeb3fe2f42cba58d50ff187fb2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 10:00:06 +0200 Subject: [PATCH 0342/3097] [BE] Detect overriding `@final` method in OverridingMethodRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 2 +- src/Rules/Methods/OverridingMethodRule.php | 5 ++--- tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 1 - tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php | 1 - 9 files changed, 4 insertions(+), 11 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index ac4b73f38c5..4467e39234a 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -62,7 +62,6 @@ Bleeding edge (TODO move to other sections) * More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! * More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! * More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! -* Detect overriding `@final` method in OverridingMethodRule, #9135 * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) @@ -139,6 +138,7 @@ Improvements 🔧 * Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! * MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) * OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) +* Detect overriding `@final` method in OverridingMethodRule, #9135 Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 543fb682a39..a4e2926ed4c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -36,7 +36,6 @@ parameters: zeroFiles: true projectServicesNotInAnalysedPaths: true callUserFunc: true - finalByPhpDoc: true magicConstantOutOfContext: true pure: true checkParameterCastableToStringFunctions: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index efa06ccb5d5..7a9c5c6c42b 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -178,7 +178,6 @@ services: class: PHPStan\Rules\Methods\OverridingMethodRule arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% - finalByPhpDoc: %featureToggles.finalByPhpDoc% checkMissingOverrideMethodAttribute: %checkMissingOverrideMethodAttribute% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index de2f0d05a95..1c97e2d9c25 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -72,7 +72,6 @@ parameters: zeroFiles: false projectServicesNotInAnalysedPaths: false callUserFunc: false - finalByPhpDoc: false magicConstantOutOfContext: false pure: false checkParameterCastableToStringFunctions: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 8b7291a6d77..10db8d09109 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -66,7 +66,6 @@ parametersSchema: zeroFiles: bool() projectServicesNotInAnalysedPaths: bool() callUserFunc: bool() - finalByPhpDoc: bool() magicConstantOutOfContext: bool() pure: bool() checkParameterCastableToStringFunctions: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 04223a005ed..dc930e386dd 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -196,7 +196,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, $container->getParameter('featureToggles')['finalByPhpDoc'], $container->getParameter('checkMissingOverrideMethodAttribute')), + new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, $container->getParameter('checkMissingOverrideMethodAttribute')), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index da41d2b685c..01fbaff3dcf 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -38,7 +38,6 @@ public function __construct( private bool $checkPhpDocMethodSignatures, private MethodParameterComparisonHelper $methodParameterComparisonHelper, private PhpClassReflectionExtension $phpClassReflectionExtension, - private bool $finalByPhpDoc, private bool $checkMissingOverrideMethodAttribute, ) { @@ -72,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array ->build(), ], $node, $scope); } - if ($parentConstructor->isFinal()->yes() && $this->finalByPhpDoc) { + if ($parentConstructor->isFinal()->yes()) { return $this->addErrors([ RuleErrorBuilder::message(sprintf( 'Method %s::%s() overrides @final method %s::%s().', @@ -130,7 +129,7 @@ public function processNode(Node $node, Scope $scope): array ->nonIgnorable() ->identifier('method.parentMethodFinal') ->build(); - } elseif ($prototype->isFinal()->yes() && $this->finalByPhpDoc) { + } elseif ($prototype->isFinal()->yes()) { $messages[] = RuleErrorBuilder::message(sprintf( 'Method %s::%s() overrides @final method %s::%s().', $method->getDeclaringClass()->getDisplayName(), diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 5ad57bd6c5c..5f75cd05541 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -30,7 +30,6 @@ protected function getRule(): Rule true, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, - true, false, ); } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index abae064fb75..5a8b6772292 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule false, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, - true, $this->checkMissingOverrideMethodAttribute, ); } From 5b34cdba40e3a8aebd868689a3466bc322aab9c8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 10:02:37 +0200 Subject: [PATCH 0343/3097] [BE] Absent type checks --- changelog-2.0.md | 32 +++++------ conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 6 +-- conf/config.level2.neon | 53 ++++--------------- conf/config.level6.neon | 9 +--- conf/config.neon | 5 -- conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 33 +++++------- src/Rules/Classes/LocalTypeAliasesCheck.php | 5 -- src/Rules/Classes/MixinCheck.php | 5 -- src/Rules/FunctionDefinitionCheck.php | 15 +++--- src/Rules/Generics/GenericAncestorsCheck.php | 15 ++---- .../Classes/LocalTypeAliasesRuleTest.php | 1 - .../Classes/LocalTypeTraitAliasesRuleTest.php | 1 - .../LocalTypeTraitUseAliasesRuleTest.php | 1 - tests/PHPStan/Rules/Classes/MixinRuleTest.php | 1 - .../Rules/Classes/MixinTraitRuleTest.php | 1 - .../Rules/Classes/MixinTraitUseRuleTest.php | 1 - ...lassesInArrowFunctionTypehintsRuleTest.php | 1 - ...stingClassesInClosureTypehintsRuleTest.php | 1 - .../ExistingClassesInTypehintsRuleTest.php | 1 - .../Rules/Generics/ClassAncestorsRuleTest.php | 1 - .../Rules/Generics/EnumAncestorsRuleTest.php | 1 - .../Generics/InterfaceAncestorsRuleTest.php | 1 - .../Rules/Generics/UsedTraitsRuleTest.php | 1 - .../ExistingClassesInTypehintsRuleTest.php | 1 - 26 files changed, 52 insertions(+), 142 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 4467e39234a..37f6429f1b7 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -10,6 +10,23 @@ Major new features 🚀 * **Enhancements in Handling Parameters Passed by Reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! +* Added previously absent type checks (level 0) + * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) + * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) + * Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) + * Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) + * Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 + * Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 +* Added previously absent type checks (level 2) + * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) + * Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 + * Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) + * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) + * Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 +* Added previously absent type checks (level 6) + * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) + * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) + * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) Bleeding edge (TODO move to other sections) ===================== @@ -82,25 +99,10 @@ Bleeding edge (TODO move to other sections) * BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! -* Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) -* Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) * More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) -* Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 -* Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 -* Check invalid `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/95c0a5806c65c975201b9d3a464873f75a04c8b8), #10932 * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 -* Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) -* Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) -* Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) -* Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) -* Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) -* Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) -* Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) -* Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 -* Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) -* Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 * RegularExpressionPatternRule: validate preg_quote'd patterns ([#3270](https://github.com/phpstan/phpstan-src/pull/3270)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index a4e2926ed4c..d0b7eba94be 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -43,5 +43,4 @@ parameters: printfArrayParameters: true validatePregQuote: true tooWidePropertyType: true - absentTypeChecks: true requireFileExists: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 7a9c5c6c42b..60f2e7e5fc7 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -32,8 +32,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.validatePregQuote% PHPStan\Rules\Keywords\RequireFileExistsRule: phpstan.rules.rule: %featureToggles.requireFileExists% - PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% rules: - PHPStan\Rules\Api\ApiInstantiationRule @@ -63,6 +61,7 @@ rules: - PHPStan\Rules\Classes\InstantiationCallableRule - PHPStan\Rules\Classes\InvalidPromotedPropertiesRule - PHPStan\Rules\Classes\LocalTypeAliasesRule + - PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule - PHPStan\Rules\Classes\LocalTypeTraitAliasesRule - PHPStan\Rules\Classes\NewStaticRule - PHPStan\Rules\Classes\NonClassAttributeClassRule @@ -150,9 +149,6 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% - - - class: PHPStan\Rules\Classes\LocalTypeTraitUseAliasesRule - - class: PHPStan\Rules\Exceptions\CaughtExceptionExistenceRule tags: diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 29234ebe71a..6ff60ddf877 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -12,6 +12,14 @@ rules: - PHPStan\Rules\Cast\InvalidPartOfEncapsedStringRule - PHPStan\Rules\Cast\PrintRule - PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule + - PHPStan\Rules\Classes\MethodTagRule + - PHPStan\Rules\Classes\MethodTagTraitRule + - PHPStan\Rules\Classes\MethodTagTraitUseRule + - PHPStan\Rules\Classes\PropertyTagRule + - PHPStan\Rules\Classes\PropertyTagTraitRule + - PHPStan\Rules\Classes\PropertyTagTraitUseRule + - PHPStan\Rules\Classes\MixinTraitRule + - PHPStan\Rules\Classes\MixinTraitUseRule - PHPStan\Rules\Comparison\UsageOfVoidMatchExpressionRule - PHPStan\Rules\Constants\ValueAssignedToClassConstantRule - PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule @@ -25,6 +33,7 @@ rules: - PHPStan\Rules\Generics\InterfaceTemplateTypeRule - PHPStan\Rules\Generics\MethodTemplateTypeRule - PHPStan\Rules\Generics\MethodTagTemplateTypeRule + - PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule - PHPStan\Rules\Generics\MethodSignatureVarianceRule - PHPStan\Rules\Generics\TraitTemplateTypeRule - PHPStan\Rules\Generics\UsedTraitsRule @@ -49,28 +58,10 @@ rules: - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionTraitRule conditionalTags: - PHPStan\Rules\Classes\MethodTagRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\MethodTagTraitRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\MethodTagTraitUseRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\MixinTraitRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\MixinTraitUseRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\PropertyTagRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\PropertyTagTraitRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - PHPStan\Rules\Classes\PropertyTagTraitUseRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% - PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: @@ -90,30 +81,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Classes\MixinTraitRule - - - - class: PHPStan\Rules\Classes\MixinTraitUseRule - - - - class: PHPStan\Rules\Classes\MethodTagRule - - - - class: PHPStan\Rules\Classes\MethodTagTraitRule - - - - class: PHPStan\Rules\Classes\MethodTagTraitUseRule - - - - class: PHPStan\Rules\Classes\PropertyTagRule - - - - class: PHPStan\Rules\Classes\PropertyTagTraitRule - - - - class: PHPStan\Rules\Classes\PropertyTagTraitUseRule - - class: PHPStan\Rules\PhpDoc\RequireExtendsCheck arguments: @@ -137,8 +104,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule - class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule - diff --git a/conf/config.level6.neon b/conf/config.level6.neon index 2b50d58d83b..e49ad444b90 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -13,12 +13,5 @@ rules: - PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule - PHPStan\Rules\Methods\MissingMethodParameterTypehintRule - PHPStan\Rules\Methods\MissingMethodReturnTypehintRule + - PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule - PHPStan\Rules\Properties\MissingPropertyTypehintRule - -conditionalTags: - PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule: - phpstan.rules.rule: %featureToggles.absentTypeChecks% - -services: - - - class: PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule diff --git a/conf/config.neon b/conf/config.neon index 1c97e2d9c25..1e7fc5ed63c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -81,7 +81,6 @@ parameters: requireFileExists: false narrowPregMatches: true tooWidePropertyType: false - absentTypeChecks: false fileExtensions: - php checkAdvancedIsset: false @@ -906,7 +905,6 @@ services: globalTypeAliases: %typeAliases% checkMissingTypehints: %checkMissingTypehints% checkClassCaseSensitivity: %checkClassCaseSensitivity% - absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\Classes\MethodTagCheck @@ -918,7 +916,6 @@ services: class: PHPStan\Rules\Classes\MixinCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% - absentTypeChecks: %featureToggles.absentTypeChecks% checkMissingTypehints: %checkMissingTypehints% - @@ -984,7 +981,6 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% - absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\FunctionReturnTypeCheck @@ -999,7 +995,6 @@ services: arguments: checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% - absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\Generics\GenericObjectTypeCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 10db8d09109..f797b4608be 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -74,7 +74,6 @@ parametersSchema: validatePregQuote: bool() narrowPregMatches: bool() tooWidePropertyType: bool() - absentTypeChecks: bool() requireFileExists: bool() ]) fileExtensions: listOf(string()) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index dc930e386dd..e37624c68b2 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -186,6 +186,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class); $methodTagTemplateTypeCheck = $container->getByType(MethodTagTemplateTypeCheck::class); $mixinCheck = $container->getByType(MixinCheck::class); + $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); + $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $rules = [ // level 0 @@ -200,6 +202,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), + new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck), // level 2 new ClassAncestorsRule($genericAncestorsCheck, $crossCheckInterfacesHelper), @@ -226,6 +229,16 @@ private function getRuleRegistry(Container $container): RuleRegistry $container->getByType(PhpDocParser::class), ), new InvalidThrowsPhpDocValueRule($fileTypeMapper), + new MixinTraitRule($mixinCheck, $reflectionProvider), + new MixinRule($mixinCheck), + new MixinTraitUseRule($mixinCheck), + new MethodTagRule($methodTagCheck), + new MethodTagTraitRule($methodTagCheck, $reflectionProvider), + new MethodTagTraitUseRule($methodTagCheck), + new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider), + new PropertyTagRule($propertyTagCheck), + new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider), + new PropertyTagTraitUseRule($propertyTagCheck), // level 6 new MissingFunctionParameterTypehintRule($missingTypehintCheck), @@ -233,6 +246,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new MissingMethodParameterTypehintRule($missingTypehintCheck), new MissingMethodReturnTypehintRule($missingTypehintCheck), new MissingPropertyTypehintRule($missingTypehintCheck), + new MissingMethodSelfOutTypeRule($missingTypehintCheck), ]; if ($this->duplicateStubs) { @@ -242,25 +256,6 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules[] = new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper); } - if ((bool) $container->getParameter('featureToggles')['absentTypeChecks']) { - $rules[] = new MissingMethodSelfOutTypeRule($missingTypehintCheck); - - $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); - $rules[] = new MethodTagRule($methodTagCheck); - $rules[] = new MethodTagTraitRule($methodTagCheck, $reflectionProvider); - $rules[] = new MethodTagTraitUseRule($methodTagCheck); - - $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); - $rules[] = new PropertyTagRule($propertyTagCheck); - $rules[] = new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider); - $rules[] = new PropertyTagTraitUseRule($propertyTagCheck); - $rules[] = new MixinRule($mixinCheck); - $rules[] = new MixinTraitRule($mixinCheck, $reflectionProvider); - $rules[] = new MixinTraitUseRule($mixinCheck); - $rules[] = new LocalTypeTraitUseAliasesRule($localTypeAliasesCheck); - $rules[] = new MethodTagTemplateTypeTraitRule($methodTagTemplateTypeCheck, $reflectionProvider); - } - return new DirectRuleRegistry($rules); } diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 5347681a906..fdc99bed077 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -44,7 +44,6 @@ public function __construct( private GenericObjectTypeCheck $genericObjectTypeCheck, private bool $checkMissingTypehints, private bool $checkClassCaseSensitivity, - private bool $absentTypeChecks, ) { } @@ -182,10 +181,6 @@ public function checkInTraitDefinitionContext(ClassReflection $reflection): arra continue; } - if (!$this->absentTypeChecks) { - continue; - } - if (!$this->checkMissingTypehints) { continue; } diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index a17ef3d2001..6e5611b0f81 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -27,7 +27,6 @@ public function __construct( private MissingTypehintCheck $missingTypehintCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, - private bool $absentTypeChecks, private bool $checkMissingTypehints, ) { @@ -65,10 +64,6 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): continue; } - if (!$this->absentTypeChecks) { - continue; - } - if (!$this->checkMissingTypehints) { continue; } diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 3942cc1b405..9fcea279641 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -55,7 +55,6 @@ public function __construct( private PhpVersion $phpVersion, private bool $checkClassCaseSensitivity, private bool $checkThisOnly, - private bool $absentTypeChecks, ) { } @@ -274,7 +273,7 @@ public function checkClassMethod( ); $selfOutType = $methodReflection->getSelfOutType(); - if ($selfOutType !== null && $this->absentTypeChecks) { + if ($selfOutType !== null) { $selfOutTypeReferencedClasses = $selfOutType->getReferencedClasses(); foreach ($selfOutTypeReferencedClasses as $class) { @@ -646,13 +645,11 @@ private function getParameterReferencedClasses(ParameterReflection $parameter): } $moreClasses = []; - if ($this->absentTypeChecks) { - if ($parameter->getOutType() !== null) { - $moreClasses = array_merge($moreClasses, $parameter->getOutType()->getReferencedClasses()); - } - if ($parameter->getClosureThisType() !== null) { - $moreClasses = array_merge($moreClasses, $parameter->getClosureThisType()->getReferencedClasses()); - } + if ($parameter->getOutType() !== null) { + $moreClasses = array_merge($moreClasses, $parameter->getOutType()->getReferencedClasses()); + } + if ($parameter->getClosureThisType() !== null) { + $moreClasses = array_merge($moreClasses, $parameter->getClosureThisType()->getReferencedClasses()); } return array_merge( diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index ef9ce469b57..201b5d3dc26 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -35,7 +35,6 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkGenericClassInNonGenericObjectType, private array $skipCheckGenericClasses, - private bool $absentTypeChecks, ) { } @@ -103,12 +102,10 @@ public function check( ); $messages = array_merge($messages, $genericObjectTypeCheckMessages); - if ($this->absentTypeChecks) { - if ($this->unresolvableTypeHelper->containsUnresolvableType($ancestorType)) { - $messages[] = RuleErrorBuilder::message($unresolvableTypeMessage) - ->identifier('generics.unresolvable') - ->build(); - } + if ($this->unresolvableTypeHelper->containsUnresolvableType($ancestorType)) { + $messages[] = RuleErrorBuilder::message($unresolvableTypeMessage) + ->identifier('generics.unresolvable') + ->build(); } foreach ($ancestorType->getReferencedClasses() as $referencedClass) { @@ -119,10 +116,6 @@ public function check( continue; } - if (!$this->absentTypeChecks) { - continue; - } - if ($referencedClass === $ancestorType->getClassName()) { continue; } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index e8c07ca171b..7455afc8cc0 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, - true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index fb443854dfb..a598bbd9df9 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -36,7 +36,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, - true, ), $this->createReflectionProvider(), ); diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php index 58ddda39a42..05f8fe03ff8 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -36,7 +36,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, - true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index b1a1bb39ca5..acaf1974b05 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, - true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index a16f6ac23ff..f23e120458d 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -33,7 +33,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, - true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index 08d3bb1c027..dbc7906da5a 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -33,7 +33,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, - true, ), ); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index a409df43919..5b603724ec3 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, - true, ), new PhpVersion(PHP_VERSION_ID), ); diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 657dc19df2e..db804026330 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, - true, ), ); } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 06957666f54..46e872ec74f 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, - true, ), ); } diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 05963f5576b..4fc2eb30e81 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, [], - true, ), new CrossCheckInterfacesHelper(), ); diff --git a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php index 017065a4a88..034d8044b87 100644 --- a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php @@ -23,7 +23,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, [], - true, ), new CrossCheckInterfacesHelper(), ); diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 3a9d430ec07..1c46e94d0c0 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, [], - true, ), new CrossCheckInterfacesHelper(), ); diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 196e663c7b4..4656dd37c62 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, [], - true, ), ); } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 359d220880e..4ca35ed5e0c 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule new PhpVersion($this->phpVersionId), true, false, - true, ), ); } From bea577235633b57143dbbf74987d8cb0f9841eee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 10:50:52 +0200 Subject: [PATCH 0344/3097] [BE] Improve error wording --- changelog-2.0.md | 2 +- conf/config.level4.neon | 2 - conf/config.neon | 1 - .../NonexistentOffsetInArrayDimFetchCheck.php | 10 +- .../BooleanAndConstantConditionRule.php | 3 +- .../BooleanOrConstantConditionRule.php | 3 +- .../Arrays/ArrayDestructuringRuleTest.php | 4 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 152 +----------------- .../BooleanAndConstantConditionRuleTest.php | 87 ---------- .../BooleanOrConstantConditionRuleTest.php | 78 --------- 10 files changed, 9 insertions(+), 333 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 37f6429f1b7..c2df639285e 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -51,7 +51,6 @@ Bleeding edge (TODO move to other sections) * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! -* Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! @@ -141,6 +140,7 @@ Improvements 🔧 * MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) * OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) * Detect overriding `@final` method in OverridingMethodRule, #9135 +* Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! Bugfixes 🐛 ===================== diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 199a794a530..327f98e1130 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -73,7 +73,6 @@ services: class: PHPStan\Rules\Comparison\BooleanAndConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - bleedingEdge: %featureToggles.bleedingEdge% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: @@ -83,7 +82,6 @@ services: class: PHPStan\Rules\Comparison\BooleanOrConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - bleedingEdge: %featureToggles.bleedingEdge% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: diff --git a/conf/config.neon b/conf/config.neon index 1e7fc5ed63c..cb61959d662 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -884,7 +884,6 @@ services: class: PHPStan\Rules\Arrays\NonexistentOffsetInArrayDimFetchCheck arguments: reportMaybes: %reportMaybes% - bleedingEdge: %featureToggles.bleedingEdge% reportPossiblyNonexistentGeneralArrayOffset: %reportPossiblyNonexistentGeneralArrayOffset% reportPossiblyNonexistentConstantArrayOffset: %reportPossiblyNonexistentConstantArrayOffset% diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 7b329d01a4a..8f78c9023bb 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -22,7 +22,6 @@ final class NonexistentOffsetInArrayDimFetchCheck public function __construct( private RuleLevelHelper $ruleLevelHelper, private bool $reportMaybes, - private bool $bleedingEdge, private bool $reportPossiblyNonexistentGeneralArrayOffset, private bool $reportPossiblyNonexistentConstantArrayOffset, ) @@ -104,15 +103,8 @@ public function check( } if ($report) { - if ($this->bleedingEdge || $this->reportPossiblyNonexistentGeneralArrayOffset || $this->reportPossiblyNonexistentConstantArrayOffset) { - return [ - RuleErrorBuilder::message(sprintf('Offset %s might not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) - ->identifier('offsetAccess.notFound') - ->build(), - ]; - } return [ - RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) + RuleErrorBuilder::message(sprintf('Offset %s might not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) ->identifier('offsetAccess.notFound') ->build(), ]; diff --git a/src/Rules/Comparison/BooleanAndConstantConditionRule.php b/src/Rules/Comparison/BooleanAndConstantConditionRule.php index 5c05e8ac09e..013e6b4b81a 100644 --- a/src/Rules/Comparison/BooleanAndConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanAndConstantConditionRule.php @@ -21,7 +21,6 @@ final class BooleanAndConstantConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, - private bool $bleedingEdge, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, ) @@ -40,7 +39,7 @@ public function processNode( { $errors = []; $originalNode = $node->getOriginalNode(); - $nodeText = $this->bleedingEdge ? $originalNode->getOperatorSigil() : '&&'; + $nodeText = $originalNode->getOperatorSigil(); $leftType = $this->helper->getBooleanType($scope, $originalNode->left); $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanAnd ? 'booleanAnd' : 'logicalAnd'; if ($leftType instanceof ConstantBooleanType) { diff --git a/src/Rules/Comparison/BooleanOrConstantConditionRule.php b/src/Rules/Comparison/BooleanOrConstantConditionRule.php index f728505cad0..b991f45981e 100644 --- a/src/Rules/Comparison/BooleanOrConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanOrConstantConditionRule.php @@ -21,7 +21,6 @@ final class BooleanOrConstantConditionRule implements Rule public function __construct( private ConstantConditionRuleHelper $helper, private bool $treatPhpDocTypesAsCertain, - private bool $bleedingEdge, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, ) @@ -39,7 +38,7 @@ public function processNode( ): array { $originalNode = $node->getOriginalNode(); - $nodeText = $this->bleedingEdge ? $originalNode->getOperatorSigil() : '||'; + $nodeText = $originalNode->getOperatorSigil(); $messages = []; $leftType = $this->helper->getBooleanType($scope, $originalNode->left); $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanOr ? 'booleanOr' : 'logicalOr'; diff --git a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php index 840da52b49e..d37f09084b1 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php @@ -13,15 +13,13 @@ class ArrayDestructuringRuleTest extends RuleTestCase { - private bool $bleedingEdge = false; - protected function getRule(): Rule { $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false); return new ArrayDestructuringRule( $ruleLevelHelper, - new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true, $this->bleedingEdge, false, false), + new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true, false, false), ); } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index f7fa3224f9b..3862059b021 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -17,8 +17,6 @@ class NonexistentOffsetInArrayDimFetchRuleTest extends RuleTestCase private bool $checkImplicitMixed = false; - private bool $bleedingEdge = false; - private bool $reportPossiblyNonexistentGeneralArrayOffset = false; private bool $reportPossiblyNonexistentConstantArrayOffset = false; @@ -29,154 +27,13 @@ protected function getRule(): Rule return new NonexistentOffsetInArrayDimFetchRule( $ruleLevelHelper, - new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true, $this->bleedingEdge, $this->reportPossiblyNonexistentGeneralArrayOffset, $this->reportPossiblyNonexistentConstantArrayOffset), + new NonexistentOffsetInArrayDimFetchCheck($ruleLevelHelper, true, $this->reportPossiblyNonexistentGeneralArrayOffset, $this->reportPossiblyNonexistentConstantArrayOffset), true, ); } public function testRule(): void { - $this->analyse([__DIR__ . '/data/nonexistent-offset.php'], [ - [ - 'Offset \'b\' does not exist on array{a: stdClass, 0: 2}.', - 17, - ], - [ - 'Offset 1 does not exist on array{a: stdClass, 0: 2}.', - 18, - ], - [ - 'Offset \'a\' does not exist on array{b: 1}.', - 55, - ], - [ - 'Access to offset \'bar\' on an unknown class NonexistentOffset\Bar.', - 101, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an offset on an unknown class NonexistentOffset\Bar.', - 102, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Offset 0 does not exist on array.', - 111, - ], - [ - 'Offset \'0\' does not exist on array.', - 112, - ], - [ - 'Offset int does not exist on array.', - 114, - ], - [ - 'Offset \'test\' does not exist on null.', - 126, - ], - [ - 'Cannot access offset 42 on int.', - 142, - ], - [ - 'Cannot access offset 42 on float.', - 143, - ], - [ - 'Cannot access offset 42 on bool.', - 144, - ], - [ - 'Cannot access offset 42 on resource.', - 145, - ], - [ - 'Offset \'c\' does not exist on array{c: false}|array{c: true}|array{e: true}.', - 171, - ], - [ - 'Offset int does not exist on array{}|array{1: 1, 2: 2}|array{3: 3, 4: 4}.', - 190, - ], - [ - 'Offset int does not exist on array{}|array{1: 1, 2: 2}|array{3: 3, 4: 4}.', - 193, - ], - [ - 'Offset \'b\' does not exist on array{a: \'blabla\'}.', - 225, - ], - [ - 'Offset \'b\' does not exist on array{a: \'blabla\'}.', - 228, - ], - [ - 'Cannot access offset \'a\' on Closure(): void.', - 253, - ], - [ - 'Cannot access offset \'a\' on array{a: 1, b: 1}|(Closure(): void).', - 258, - ], - [ - 'Offset null does not exist on array.', - 310, - ], - [ - 'Offset int does not exist on array.', - 312, - ], - [ - 'Offset \'baz\' does not exist on array{bar: 1, baz?: 2}.', - 344, - ], - [ - 'Offset \'foo\' does not exist on ArrayAccess.', - 411, - ], - [ - 'Cannot access offset \'foo\' on stdClass.', - 423, - ], - [ - 'Cannot access offset \'foo\' on true.', - 426, - ], - [ - 'Cannot access offset \'foo\' on false.', - 429, - ], - [ - 'Cannot access offset \'foo\' on resource.', - 433, - ], - [ - 'Cannot access offset \'foo\' on 42.', - 436, - ], - [ - 'Cannot access offset \'foo\' on 4.141.', - 439, - ], - [ - 'Cannot access offset \'foo\' on array|int.', - 443, - ], - [ - 'Offset \'feature_pretty…\' does not exist on array{version: non-falsy-string, commit: string|null, pretty_version: string|null, feature_version: non-falsy-string, feature_pretty_version?: string|null}.', - 504, - ], - [ - "Cannot access offset 'foo' on bool.", - 517, - ], - ]); - } - - public function testRuleBleedingEdge(): void - { - $this->bleedingEdge = true; $this->analyse([__DIR__ . '/data/nonexistent-offset.php'], [ [ 'Offset \'b\' does not exist on array{a: stdClass, 0: 2}.', @@ -327,11 +184,11 @@ public function testStrings(): void 13, ], [ - 'Offset \'foo\' does not exist on array|string.', + 'Offset \'foo\' might not exist on array|string.', 24, ], [ - 'Offset 12.34 does not exist on array|string.', + 'Offset 12.34 might not exist on array|string.', 28, ], ]); @@ -531,7 +388,7 @@ public function testBug7000(): void { $this->analyse([__DIR__ . '/data/bug-7000.php'], [ [ - "Offset 'require'|'require-dev' does not exist on array{require?: array, require-dev?: array}.", + "Offset 'require'|'require-dev' might not exist on array{require?: array, require-dev?: array}.", 16, ], ]); @@ -692,7 +549,6 @@ public function testBug6243(): void public function testBug8356(): void { - $this->bleedingEdge = true; $this->analyse([__DIR__ . '/data/bug-8356.php'], [ [ "Offset 'x' might not exist on array|string.", diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index b0d674c7167..e84e4956c70 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -13,8 +13,6 @@ class BooleanAndConstantConditionRuleTest extends RuleTestCase private bool $treatPhpDocTypesAsCertain; - private bool $bleedingEdge = false; - private bool $reportAlwaysTrueInLastCondition = false; protected function getRule(): Rule @@ -32,7 +30,6 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, - $this->bleedingEdge, $this->reportAlwaysTrueInLastCondition, true, ); @@ -140,90 +137,6 @@ public function testRuleLogicalAnd(): void { $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-logical-and.php'], [ - [ - 'Left side of && is always true.', - 15, - ], - [ - 'Right side of && is always true.', - 19, - ], - [ - 'Left side of && is always false.', - 24, - ], - [ - 'Right side of && is always false.', - 27, - ], - [ - 'Result of && is always false.', - 30, - ], - [ - 'Right side of && is always true.', - 33, - ], - [ - 'Right side of && is always true.', - 36, - ], - [ - 'Right side of && is always true.', - 39, - ], - [ - 'Result of && is always false.', - 50, - ], - [ - 'Result of && is always true.', - 54, - $tipText, - ], - [ - 'Result of && is always false.', - 60, - ], - [ - 'Result of && is always true.', - 64, - //$tipText, - ], - [ - 'Result of && is always false.', - 66, - //$tipText, - ], - [ - 'Result of && is always false.', - 125, - ], - [ - 'Left side of && is always false.', - 139, - ], - [ - 'Right side of && is always false.', - 141, - ], - [ - 'Left side of && is always true.', - 145, - ], - [ - 'Right side of && is always true.', - 147, - ], - ]); - } - - public function testRuleLogicalAndBleedingEdge(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->bleedingEdge = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/data/boolean-logical-and.php'], [ [ 'Left side of and is always true.', diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index 38092ce153e..b5d52ba9a0c 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -14,8 +14,6 @@ class BooleanOrConstantConditionRuleTest extends RuleTestCase private bool $treatPhpDocTypesAsCertain; - private bool $bleedingEdge = false; - private bool $reportAlwaysTrueInLastCondition = false; protected function getRule(): Rule @@ -33,7 +31,6 @@ protected function getRule(): Rule true, ), $this->treatPhpDocTypesAsCertain, - $this->bleedingEdge, $this->reportAlwaysTrueInLastCondition, true, ); @@ -132,81 +129,6 @@ public function testRuleLogicalOr(): void { $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/boolean-logical-or.php'], [ - [ - 'Left side of || is always true.', - 15, - ], - [ - 'Right side of || is always true.', - 19, - ], - [ - 'Left side of || is always false.', - 24, - ], - [ - 'Right side of || is always false.', - 27, - ], - [ - 'Right side of || is always true.', - 30, - ], - [ - 'Result of || is always true.', - 33, - ], - [ - 'Right side of || is always false.', - 36, - ], - [ - 'Right side of || is always false.', - 39, - ], - [ - 'Result of || is always true.', - 50, - $tipText, - ], - [ - 'Result of || is always true.', - 54, - $tipText, - ], - [ - 'Result of || is always true.', - 61, - ], - [ - 'Result of || is always true.', - 65, - ], - [ - 'Left side of || is always false.', - 77, - ], - [ - 'Right side of || is always false.', - 79, - ], - [ - 'Left side of || is always true.', - 83, - ], - [ - 'Right side of || is always true.', - 85, - ], - ]); - } - - public function testRuleLogicalOrBleedingEdge(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->bleedingEdge = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/data/boolean-logical-or.php'], [ [ 'Left side of or is always true.', From 3d61987490899e76cb7d23a06b0b099693445aba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 10:57:36 +0200 Subject: [PATCH 0345/3097] [BE] Stricter ++/-- operator check --- changelog-2.0.md | 2 +- conf/config.level0.neon | 9 +---- .../Operators/InvalidIncDecOperationRule.php | 34 +++++-------------- .../InvalidIncDecOperationRuleTest.php | 2 -- 4 files changed, 11 insertions(+), 36 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index c2df639285e..2027c9081c8 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -107,7 +107,6 @@ Bleeding edge (TODO move to other sections) * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! -* Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3! * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! @@ -141,6 +140,7 @@ Improvements 🔧 * OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) * Detect overriding `@final` method in OverridingMethodRule, #9135 * Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! +* Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! Bugfixes 🐛 ===================== diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 60f2e7e5fc7..1d5325aefa9 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -106,6 +106,7 @@ rules: - PHPStan\Rules\Methods\StaticMethodCallableRule - PHPStan\Rules\Names\UsedNamesRule - PHPStan\Rules\Operators\InvalidAssignVarRule + - PHPStan\Rules\Operators\InvalidIncDecOperationRule - PHPStan\Rules\Properties\AccessPropertiesInAssignRule - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule - PHPStan\Rules\Properties\InvalidCallablePropertyTypeRule @@ -203,14 +204,6 @@ services: arguments: checkFunctionNameCase: %checkFunctionNameCase% - - - class: PHPStan\Rules\Operators\InvalidIncDecOperationRule - tags: - - phpstan.rules.rule - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - checkThisOnly: %checkThisOnly% - - class: PHPStan\Rules\Properties\AccessPropertiesRule tags: diff --git a/src/Rules/Operators/InvalidIncDecOperationRule.php b/src/Rules/Operators/InvalidIncDecOperationRule.php index 6f3382d829d..8283936e73a 100644 --- a/src/Rules/Operators/InvalidIncDecOperationRule.php +++ b/src/Rules/Operators/InvalidIncDecOperationRule.php @@ -29,8 +29,6 @@ final class InvalidIncDecOperationRule implements Rule public function __construct( private RuleLevelHelper $ruleLevelHelper, - private bool $bleedingEdge, - private bool $checkThisOnly, ) { } @@ -87,30 +85,16 @@ public function processNode(Node $node, Scope $scope): array ]; } - if (!$this->bleedingEdge) { - if ($this->checkThisOnly) { - return []; - } + $allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType(), new ObjectType('SimpleXMLElement')]); + $varType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + '', + static fn (Type $type): bool => $allowedTypes->isSuperTypeOf($type)->yes(), + )->getType(); - $varType = $scope->getType($node->var); - if (!$varType->toString() instanceof ErrorType) { - return []; - } - if (!$varType->toNumber() instanceof ErrorType) { - return []; - } - } else { - $allowedTypes = new UnionType([new BooleanType(), new FloatType(), new IntegerType(), new StringType(), new NullType(), new ObjectType('SimpleXMLElement')]); - $varType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->var, - '', - static fn (Type $type): bool => $allowedTypes->isSuperTypeOf($type)->yes(), - )->getType(); - - if ($varType instanceof ErrorType || $allowedTypes->isSuperTypeOf($varType)->yes()) { - return []; - } + if ($varType instanceof ErrorType || $allowedTypes->isSuperTypeOf($varType)->yes()) { + return []; } return [ diff --git a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php index 5dd8aae36fa..63c757ecc8a 100644 --- a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php @@ -21,8 +21,6 @@ protected function getRule(): Rule { return new InvalidIncDecOperationRule( new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), - true, - false, ); } From f9cd2a061d0b73c665c3c9b1d300091edf9b6a36 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:01:18 +0200 Subject: [PATCH 0346/3097] [BE] Check mixed in binary and unary operators --- changelog-2.0.md | 4 +- conf/config.level2.neon | 14 +---- .../Operators/InvalidBinaryOperationRule.php | 5 -- .../Operators/InvalidUnaryOperationRule.php | 53 +++++++++---------- .../InvalidBinaryOperationRuleTest.php | 1 - .../InvalidUnaryOperationRuleTest.php | 1 - 6 files changed, 28 insertions(+), 50 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 2027c9081c8..64b626d8af3 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -104,9 +104,7 @@ Bleeding edge (TODO move to other sections) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 * RegularExpressionPatternRule: validate preg_quote'd patterns ([#3270](https://github.com/phpstan/phpstan-src/pull/3270)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! -* Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! -* Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3! * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! @@ -141,6 +139,8 @@ Improvements 🔧 * Detect overriding `@final` method in OverridingMethodRule, #9135 * Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! * Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! +* Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! +* Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! Bugfixes 🐛 ===================== diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 6ff60ddf877..715f1089a82 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -39,7 +39,9 @@ rules: - PHPStan\Rules\Generics\UsedTraitsRule - PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule + - PHPStan\Rules\Operators\InvalidBinaryOperationRule - PHPStan\Rules\Operators\InvalidComparisonOperationRule + - PHPStan\Rules\Operators\InvalidUnaryOperationRule - PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule - PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule - PHPStan\Rules\PhpDoc\FunctionAssertRule @@ -140,15 +142,3 @@ services: - class: PHPStan\Rules\Pure\PureMethodRule - - - class: PHPStan\Rules\Operators\InvalidBinaryOperationRule - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - tags: - - phpstan.rules.rule - - - class: PHPStan\Rules\Operators\InvalidUnaryOperationRule - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - tags: - - phpstan.rules.rule diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 77653e1f1a0..e44b2178d58 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -27,7 +27,6 @@ final class InvalidBinaryOperationRule implements Rule public function __construct( private ExprPrinter $exprPrinter, private RuleLevelHelper $ruleLevelHelper, - private bool $bleedingEdge, ) { } @@ -46,10 +45,6 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (!$scope->getType($node) instanceof ErrorType && !$this->bleedingEdge) { - return []; - } - $leftName = '__PHPSTAN__LEFT__'; $rightName = '__PHPSTAN__RIGHT__'; $leftVariable = new Node\Expr\Variable($leftName); diff --git a/src/Rules/Operators/InvalidUnaryOperationRule.php b/src/Rules/Operators/InvalidUnaryOperationRule.php index cf823a82bf9..6600cce4ad3 100644 --- a/src/Rules/Operators/InvalidUnaryOperationRule.php +++ b/src/Rules/Operators/InvalidUnaryOperationRule.php @@ -23,7 +23,6 @@ final class InvalidUnaryOperationRule implements Rule public function __construct( private RuleLevelHelper $ruleLevelHelper, - private bool $bleedingEdge, ) { } @@ -43,38 +42,34 @@ public function processNode(Node $node, Scope $scope): array return []; } - if ($this->bleedingEdge) { - $varName = '__PHPSTAN__LEFT__'; - $variable = new Node\Expr\Variable($varName); - $newNode = clone $node; - $newNode->setAttribute('phpstan_cache_printer', null); - $newNode->expr = $variable; + $varName = '__PHPSTAN__LEFT__'; + $variable = new Node\Expr\Variable($varName); + $newNode = clone $node; + $newNode->setAttribute('phpstan_cache_printer', null); + $newNode->expr = $variable; - if ($node instanceof Node\Expr\BitwiseNot) { - $callback = static fn (Type $type): bool => $type->isString()->yes() || $type->isInteger()->yes() || $type->isFloat()->yes(); - } else { - $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType; - } + if ($node instanceof Node\Expr\BitwiseNot) { + $callback = static fn (Type $type): bool => $type->isString()->yes() || $type->isInteger()->yes() || $type->isFloat()->yes(); + } else { + $callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType; + } - $exprType = $this->ruleLevelHelper->findTypeToCheck( - $scope, - $node->expr, - '', - $callback, - )->getType(); - if ($exprType instanceof ErrorType) { - return []; - } + $exprType = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->expr, + '', + $callback, + )->getType(); + if ($exprType instanceof ErrorType) { + return []; + } - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } + if (!$scope instanceof MutatingScope) { + throw new ShouldNotHappenException(); + } - $scope = $scope->assignVariable($varName, $exprType, $exprType, TrinaryLogic::createYes()); - if (!$scope->getType($newNode) instanceof ErrorType) { - return []; - } - } elseif (!$scope->getType($node) instanceof ErrorType) { + $scope = $scope->assignVariable($varName, $exprType, $exprType, TrinaryLogic::createYes()); + if (!$scope->getType($newNode) instanceof ErrorType) { return []; } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 625e2d615ef..24ae397dbb7 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule return new InvalidBinaryOperationRule( new ExprPrinter(new Printer()), new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), - true, ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index 909472e0916..afd0611e89d 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -21,7 +21,6 @@ protected function getRule(): Rule { return new InvalidUnaryOperationRule( new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), - true, ); } From b7d10c89f40a652a351d5154ea361d76b79ab56f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:07:01 +0200 Subject: [PATCH 0347/3097] [BCB] Removed `checkMissingIterableValueType` config option --- UPGRADING.md | 13 +++++++++++ changelog-2.0.md | 1 - conf/bleedingEdge.neon | 1 - conf/config.level6.neon | 1 - conf/config.neon | 4 ---- conf/config.stubValidator.neon | 1 - conf/parametersSchema.neon | 2 -- src/Command/CommandHelper.php | 22 ------------------- src/Rules/MissingTypehintCheck.php | 8 ------- .../Classes/LocalTypeAliasesRuleTest.php | 2 +- .../Classes/LocalTypeTraitAliasesRuleTest.php | 2 +- .../LocalTypeTraitUseAliasesRuleTest.php | 2 +- .../Rules/Classes/MethodTagRuleTest.php | 2 +- .../Rules/Classes/MethodTagTraitRuleTest.php | 2 +- .../Classes/MethodTagTraitUseRuleTest.php | 2 +- tests/PHPStan/Rules/Classes/MixinRuleTest.php | 2 +- .../Rules/Classes/MixinTraitRuleTest.php | 2 +- .../Rules/Classes/MixinTraitUseRuleTest.php | 2 +- .../Rules/Classes/PropertyTagRuleTest.php | 2 +- .../Classes/PropertyTagTraitRuleTest.php | 2 +- .../Classes/PropertyTagTraitUseRuleTest.php | 2 +- .../MissingClassConstantTypehintRuleTest.php | 2 +- ...ssingFunctionParameterTypehintRuleTest.php | 2 +- .../MissingFunctionReturnTypehintRuleTest.php | 2 +- ...MissingMethodParameterTypehintRuleTest.php | 2 +- .../MissingMethodReturnTypehintRuleTest.php | 2 +- .../MissingMethodSelfOutTypeRuleTest.php | 2 +- .../InvalidPhpDocVarTagTypeRuleTest.php | 2 +- .../MissingPropertyTypehintRuleTest.php | 2 +- 29 files changed, 33 insertions(+), 60 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index f31af1f3d3a..07238f106c5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -32,6 +32,19 @@ Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-g After changing your `composer.json`, run `composer update 'phpstan/*' -W`. +### Removed option `checkMissingIterableValueType` + +It's strongly recommended to add the missing array typehints. + +If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.iterableValue +``` + ### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: diff --git a/changelog-2.0.md b/changelog-2.0.md index 64b626d8af3..bcda7deb8c1 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -38,7 +38,6 @@ Bleeding edge (TODO move to other sections) * In testing the memory consumption was reduced by 50–70 %. * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! -* `checkMissingIterableValueType: false` no longer does anything (https://github.com/phpstan/phpstan-src/commit/50d0c8e23ea85da508ab8481f1ff2c89148cc80b) * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index d0b7eba94be..b910eaa88e0 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,7 +8,6 @@ parameters: arrayValues: true nodeConnectingVisitorCompatibility: false nodeConnectingVisitorRule: true - disableCheckMissingIterableValueType: true strictUnnecessaryNullsafePropertyFetch: true looseComparison: true consistentConstructor: true diff --git a/conf/config.level6.neon b/conf/config.level6.neon index e49ad444b90..ec0c815769e 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -3,7 +3,6 @@ includes: parameters: checkGenericClassInNonGenericObjectType: true - checkMissingIterableValueType: true checkMissingVarTagTypehint: true checkMissingTypehints: true diff --git a/conf/config.neon b/conf/config.neon index cb61959d662..a5fdd2eb725 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,7 +43,6 @@ parameters: nodeConnectingVisitorCompatibility: true nodeConnectingVisitorRule: false illegalConstructorMethodCall: false - disableCheckMissingIterableValueType: false strictUnnecessaryNullsafePropertyFetch: false looseComparison: false consistentConstructor: false @@ -96,7 +95,6 @@ parameters: checkFunctionNameCase: false checkGenericClassInNonGenericObjectType: false checkInternalClassCaseSensitivity: false - checkMissingIterableValueType: false checkMissingCallableSignature: false checkMissingVarTagTypehint: false checkArgumentsPassedByReference: false @@ -1044,8 +1042,6 @@ services: - class: PHPStan\Rules\MissingTypehintCheck arguments: - checkMissingIterableValueType: %checkMissingIterableValueType% - disableCheckMissingIterableValueType: %featureToggles.disableCheckMissingIterableValueType% checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% checkMissingCallableSignature: %checkMissingCallableSignature% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index ae22e5ccdc3..c33dc1edf2f 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -2,7 +2,6 @@ parameters: checkThisOnly: false checkClassCaseSensitivity: true checkGenericClassInNonGenericObjectType: true - checkMissingIterableValueType: true checkMissingTypehints: true checkMissingCallableSignature: false __validate: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f797b4608be..3c8b147a880 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -38,7 +38,6 @@ parametersSchema: nodeConnectingVisitorCompatibility: bool(), nodeConnectingVisitorRule: bool(), illegalConstructorMethodCall: bool(), - disableCheckMissingIterableValueType: bool(), strictUnnecessaryNullsafePropertyFetch: bool(), looseComparison: bool(), consistentConstructor: bool() @@ -90,7 +89,6 @@ parametersSchema: checkFunctionNameCase: bool() checkGenericClassInNonGenericObjectType: bool() checkInternalClassCaseSensitivity: bool() - checkMissingIterableValueType: bool() checkMissingCallableSignature: bool() checkMissingVarTagTypehint: bool() checkArgumentsPassedByReference: bool() diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c899ccc8ccd..bd25174977b 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -547,28 +547,6 @@ public static function begin( if ($projectConfig !== null) { $parameters = $projectConfig['parameters'] ?? []; - /** @var bool $checkMissingIterableValueType */ - $checkMissingIterableValueType = $parameters['checkMissingIterableValueType'] ?? true; - if (!$checkMissingIterableValueType) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option checkMissingIterableValueType ⚠️️'); - $errorOutput->writeLineFormatted(''); - - $featureToggles = $container->getParameter('featureToggles'); - if (!((bool) $featureToggles['bleedingEdge'])) { - $errorOutput->writeLineFormatted('It\'s strongly recommended to remove it from your configuration file'); - $errorOutput->writeLineFormatted('and add the missing array typehints.'); - $errorOutput->writeLineFormatted(''); - } - - $errorOutput->writeLineFormatted('If you want to continue ignoring missing typehints from arrays,'); - $errorOutput->writeLineFormatted('add missingType.iterableValue error identifier to your ignoreErrors:'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('parameters:'); - $errorOutput->writeLineFormatted("\tignoreErrors:"); - $errorOutput->writeLineFormatted("\t\t-"); - $errorOutput->writeLineFormatted("\t\t\tidentifier: missingType.iterableValue"); - $errorOutput->writeLineFormatted(''); - } /** @var bool $checkGenericClassInNonGenericObjectType */ $checkGenericClassInNonGenericObjectType = $parameters['checkGenericClassInNonGenericObjectType'] ?? true; diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index d6c55e62ec0..50e081b0e5d 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -43,8 +43,6 @@ final class MissingTypehintCheck * @param string[] $skipCheckGenericClasses */ public function __construct( - private bool $disableCheckMissingIterableValueType, - private bool $checkMissingIterableValueType, private bool $checkGenericClassInNonGenericObjectType, private bool $checkMissingCallableSignature, private array $skipCheckGenericClasses, @@ -57,12 +55,6 @@ public function __construct( */ public function getIterableTypesWithMissingValueTypehint(Type $type): array { - if (!$this->checkMissingIterableValueType) { - if (!$this->disableCheckMissingIterableValueType) { - return []; - } - } - $iterablesWithMissingValueTypehint = []; TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$iterablesWithMissingValueTypehint): Type { if ($type instanceof TemplateType) { diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 7455afc8cc0..9bf55165536 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index a598bbd9df9..1dec5922bff 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php index 05f8fe03ff8..d78a5c795e9 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php index 7766e03bd84..8a8489dc5ef 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index 74f70ca72d1..6c048e06744 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index 7839c99123a..3a2e328b99a 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index acaf1974b05..101500b1836 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index f23e120458d..87dbaaf1f9b 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index dbc7906da5a..2fc73704ee3 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php index 7b36d89e903..43dc3e29f9a 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index 3e6ff6c9535..e4b92673e5c 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php index 76b4342abec..50212df3dbe 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php index 89b6302e8a5..e3cccffa5ff 100644 --- a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php @@ -15,7 +15,7 @@ class MissingClassConstantTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingClassConstantTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingClassConstantTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index 73a197f3a59..6eb78628d23 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingFunctionParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php index 37cd3ffb817..0a93340e1f0 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingFunctionReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index 48c0c6acde0..a7ce5312ca7 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 07629009018..742d3f02875 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingMethodReturnTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingMethodReturnTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php index 373e46494a5..9f3af68ae60 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodSelfOutTypeRuleTest extends RuleTestCase protected function getRule(): TRule { - return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 9d4dc0bb3e3..e432a8b7a9a 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index bd92fe752eb..a3343338513 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingPropertyTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingPropertyTypehintRule(new MissingTypehintCheck(true, true, true, true, [])); + return new MissingPropertyTypehintRule(new MissingTypehintCheck(true, true, [])); } public function testRule(): void From 47dc2e65fd3019a56e737fb8e540c02cb64e69a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:13:44 +0200 Subject: [PATCH 0348/3097] [BCB] Removed `checkGenericClassInNonGenericObjectType` config option --- UPGRADING.md | 13 +++++++ conf/config.level6.neon | 1 - conf/config.neon | 3 -- conf/config.stubValidator.neon | 1 - conf/parametersSchema.neon | 1 - src/Command/CommandHelper.php | 22 ----------- src/Rules/Generics/GenericAncestorsCheck.php | 39 +++++++++---------- src/Rules/MissingTypehintCheck.php | 5 --- .../Classes/LocalTypeAliasesRuleTest.php | 2 +- .../Classes/LocalTypeTraitAliasesRuleTest.php | 2 +- .../LocalTypeTraitUseAliasesRuleTest.php | 2 +- .../Rules/Classes/MethodTagRuleTest.php | 2 +- .../Rules/Classes/MethodTagTraitRuleTest.php | 2 +- .../Classes/MethodTagTraitUseRuleTest.php | 2 +- tests/PHPStan/Rules/Classes/MixinRuleTest.php | 2 +- .../Rules/Classes/MixinTraitRuleTest.php | 2 +- .../Rules/Classes/MixinTraitUseRuleTest.php | 2 +- .../Rules/Classes/PropertyTagRuleTest.php | 2 +- .../Classes/PropertyTagTraitRuleTest.php | 2 +- .../Classes/PropertyTagTraitUseRuleTest.php | 2 +- .../MissingClassConstantTypehintRuleTest.php | 2 +- ...ssingFunctionParameterTypehintRuleTest.php | 2 +- .../MissingFunctionReturnTypehintRuleTest.php | 2 +- .../Rules/Generics/ClassAncestorsRuleTest.php | 1 - .../Rules/Generics/EnumAncestorsRuleTest.php | 1 - .../Generics/InterfaceAncestorsRuleTest.php | 1 - .../Rules/Generics/UsedTraitsRuleTest.php | 1 - ...MissingMethodParameterTypehintRuleTest.php | 2 +- .../MissingMethodReturnTypehintRuleTest.php | 2 +- .../MissingMethodSelfOutTypeRuleTest.php | 2 +- .../InvalidPhpDocVarTagTypeRuleTest.php | 2 +- .../MissingPropertyTypehintRuleTest.php | 2 +- 32 files changed, 51 insertions(+), 78 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 07238f106c5..752970842b8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -45,6 +45,19 @@ parameters: identifier: missingType.iterableValue ``` +### Removed option `checkGenericClassInNonGenericObjectType` + +It's strongly recommended to add the missing generic typehints. + +If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.generics +``` + ### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: diff --git a/conf/config.level6.neon b/conf/config.level6.neon index ec0c815769e..25214e97dd1 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -2,7 +2,6 @@ includes: - config.level5.neon parameters: - checkGenericClassInNonGenericObjectType: true checkMissingVarTagTypehint: true checkMissingTypehints: true diff --git a/conf/config.neon b/conf/config.neon index a5fdd2eb725..801e3fe8b62 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -93,7 +93,6 @@ parameters: checkImplicitMixed: false checkFunctionArgumentTypes: false checkFunctionNameCase: false - checkGenericClassInNonGenericObjectType: false checkInternalClassCaseSensitivity: false checkMissingCallableSignature: false checkMissingVarTagTypehint: false @@ -990,7 +989,6 @@ services: - class: PHPStan\Rules\Generics\GenericAncestorsCheck arguments: - checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% - @@ -1042,7 +1040,6 @@ services: - class: PHPStan\Rules\MissingTypehintCheck arguments: - checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% checkMissingCallableSignature: %checkMissingCallableSignature% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index c33dc1edf2f..07390c7b8fd 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -1,7 +1,6 @@ parameters: checkThisOnly: false checkClassCaseSensitivity: true - checkGenericClassInNonGenericObjectType: true checkMissingTypehints: true checkMissingCallableSignature: false __validate: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3c8b147a880..09190209392 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -87,7 +87,6 @@ parametersSchema: checkImplicitMixed: bool() checkFunctionArgumentTypes: bool() checkFunctionNameCase: bool() - checkGenericClassInNonGenericObjectType: bool() checkInternalClassCaseSensitivity: bool() checkMissingCallableSignature: bool() checkMissingVarTagTypehint: bool() diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index bd25174977b..f380cfdbd0f 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -545,28 +545,6 @@ public static function begin( $errorOutput->writeLineFormatted(sprintf('Please implement PHPStan\Type\ExpressionTypeResolverExtension interface instead and register it as a service.')); } - if ($projectConfig !== null) { - $parameters = $projectConfig['parameters'] ?? []; - - /** @var bool $checkGenericClassInNonGenericObjectType */ - $checkGenericClassInNonGenericObjectType = $parameters['checkGenericClassInNonGenericObjectType'] ?? true; - if (!$checkGenericClassInNonGenericObjectType) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option checkGenericClassInNonGenericObjectType ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('It\'s strongly recommended to remove it from your configuration file'); - $errorOutput->writeLineFormatted('and add the missing generic typehints.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('If you want to continue ignoring missing typehints from generics,'); - $errorOutput->writeLineFormatted('add missingType.generics error identifier to your ignoreErrors:'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('parameters:'); - $errorOutput->writeLineFormatted("\tignoreErrors:"); - $errorOutput->writeLineFormatted("\t\t-"); - $errorOutput->writeLineFormatted("\t\t\tidentifier: missingType.generics"); - $errorOutput->writeLineFormatted(''); - } - } - $tempResultCachePath = $container->getParameter('tempResultCachePath'); $createDir($tempResultCachePath); diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index 201b5d3dc26..8dc3830cadb 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -33,7 +33,6 @@ public function __construct( private GenericObjectTypeCheck $genericObjectTypeCheck, private VarianceCheck $varianceCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, - private bool $checkGenericClassInNonGenericObjectType, private array $skipCheckGenericClasses, ) { @@ -152,28 +151,26 @@ public function check( } } - if ($this->checkGenericClassInNonGenericObjectType) { - foreach (array_keys($unusedNames) as $unusedName) { - if (!$this->reflectionProvider->hasClass($unusedName)) { - continue; - } - - $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); - if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { - continue; - } - if (!$unusedNameClassReflection->isGeneric()) { - continue; - } + foreach (array_keys($unusedNames) as $unusedName) { + if (!$this->reflectionProvider->hasClass($unusedName)) { + continue; + } - $messages[] = RuleErrorBuilder::message(sprintf( - $genericClassInNonGenericObjectType, - $unusedName, - implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())), - )) - ->identifier('missingType.generics') - ->build(); + $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); + if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { + continue; } + if (!$unusedNameClassReflection->isGeneric()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + $genericClassInNonGenericObjectType, + $unusedName, + implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())), + )) + ->identifier('missingType.generics') + ->build(); } return $messages; diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 50e081b0e5d..dd0d451478c 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -43,7 +43,6 @@ final class MissingTypehintCheck * @param string[] $skipCheckGenericClasses */ public function __construct( - private bool $checkGenericClassInNonGenericObjectType, private bool $checkMissingCallableSignature, private array $skipCheckGenericClasses, ) @@ -92,10 +91,6 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array */ public function getNonGenericObjectTypesWithGenericClass(Type $type): array { - if (!$this->checkGenericClassInNonGenericObjectType) { - return []; - } - $objectTypes = []; TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$objectTypes): Type { if ($type instanceof GenericObjectType) { diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 9bf55165536..47b81f1ea18 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 1dec5922bff..76dc96f81c9 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php index d78a5c795e9..a84377316b5 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php index 8a8489dc5ef..ae7ff38ec53 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index 6c048e06744..fce38f5d984 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index 3a2e328b99a..53179386201 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index 101500b1836..e05283e71b4 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -30,7 +30,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index 87dbaaf1f9b..94657239639 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index 2fc73704ee3..c94636b5e1d 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php index 43dc3e29f9a..4ec02315e10 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index e4b92673e5c..1266a5a5531 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php index 50212df3dbe..f45a1b894db 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): TRule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php index e3cccffa5ff..13e745a3d10 100644 --- a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php @@ -15,7 +15,7 @@ class MissingClassConstantTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingClassConstantTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingClassConstantTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index 6eb78628d23..c88fb550da1 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingFunctionParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php index 0a93340e1f0..2b64aba5baa 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingFunctionReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 4fc2eb30e81..867cff2e50f 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -20,7 +20,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(true, true), new UnresolvableTypeHelper(), - true, [], ), new CrossCheckInterfacesHelper(), diff --git a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php index 034d8044b87..0978f6c7c5c 100644 --- a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php @@ -21,7 +21,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(true, true), new UnresolvableTypeHelper(), - true, [], ), new CrossCheckInterfacesHelper(), diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 1c46e94d0c0..8d7a275278a 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -20,7 +20,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(true, true), new UnresolvableTypeHelper(), - true, [], ), new CrossCheckInterfacesHelper(), diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 4656dd37c62..c62ce6fc246 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(true, true), new UnresolvableTypeHelper(), - true, [], ), ); diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index a7ce5312ca7..f19534b281d 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodParameterTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingMethodParameterTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 742d3f02875..87a2f4c46b9 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodReturnTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingMethodReturnTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingMethodReturnTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php index 9f3af68ae60..cc74d519e9e 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodSelfOutTypeRuleTest.php @@ -14,7 +14,7 @@ class MissingMethodSelfOutTypeRuleTest extends RuleTestCase protected function getRule(): TRule { - return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, true, [])); + return new MissingMethodSelfOutTypeRule(new MissingTypehintCheck(true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index e432a8b7a9a..e5152569d1d 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -29,7 +29,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), new GenericObjectTypeCheck(), - new MissingTypehintCheck(true, true, []), + new MissingTypehintCheck(true, []), new UnresolvableTypeHelper(), true, true, diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index a3343338513..2338b63afb5 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingPropertyTypehintRuleTest extends RuleTestCase protected function getRule(): Rule { - return new MissingPropertyTypehintRule(new MissingTypehintCheck(true, true, [])); + return new MissingPropertyTypehintRule(new MissingTypehintCheck(true, [])); } public function testRule(): void From 222abe52c685b3e12916813a4bff0b8a882efaf9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:23:12 +0200 Subject: [PATCH 0349/3097] [BCB] Removed `excludes_analyse` config option --- UPGRADING.md | 4 ++++ conf/config.neon | 4 +--- conf/parametersSchema.neon | 5 ++--- src/Command/CommandHelper.php | 15 --------------- src/DependencyInjection/NeonAdapter.php | 3 +-- src/File/FileExcluderFactory.php | 14 ++------------ src/Testing/TestCase.neon | 1 - src/Testing/TestCaseSourceLocatorFactory.php | 3 --- 8 files changed, 10 insertions(+), 39 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 752970842b8..263be4f14e8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -58,6 +58,10 @@ parameters: identifier: missingType.generics ``` +### Removed option `excludes_analyse` + +It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). + ### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: diff --git a/conf/config.neon b/conf/config.neon index 801e3fe8b62..d9d8d98dc51 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -7,8 +7,7 @@ parameters: - ../stubs/runtime/ReflectionAttribute.php - ../stubs/runtime/Attribute.php - ../stubs/runtime/ReflectionIntersectionType.php - excludes_analyse: [] - excludePaths: null + excludePaths: [] level: null paths: [] exceptions: @@ -653,7 +652,6 @@ services: - class: PHPStan\File\FileExcluderFactory arguments: - obsoleteExcludesAnalyse: %excludes_analyse% excludePaths: %excludePaths% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 09190209392..f91e092b722 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -1,7 +1,6 @@ parametersSchema: bootstrapFiles: listOf(string()) - excludes_analyse: listOf(string()) - excludePaths: schema(anyOf( + excludePaths: anyOf( structure([ analyse: listOf(string()), ]), @@ -12,7 +11,7 @@ parametersSchema: analyse: listOf(string()), analyseAndScan: listOf(string()) ]) - ), nullable()) + ) level: schema(anyOf(int(), string()), nullable()) paths: listOf(string()) exceptions: structure([ diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index f380cfdbd0f..0fe6f1c7f26 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -524,21 +524,6 @@ public static function begin( throw new InceptionNotSuccessfulException(); } - $excludesAnalyse = $container->getParameter('excludes_analyse'); - $excludePaths = $container->getParameter('excludePaths'); - if (count($excludesAnalyse) > 0 && $excludePaths !== null) { - $errorOutput->writeLineFormatted(sprintf('Configuration parameters excludes_analyse and excludePaths cannot be used at the same time.')); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(sprintf('Parameter excludes_analyse has been deprecated so use excludePaths only from now on.')); - $errorOutput->writeLineFormatted(''); - - throw new InceptionNotSuccessfulException(); - } elseif (count($excludesAnalyse) > 0) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option excludes_analyse. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(sprintf('Parameter excludes_analyse has been deprecated so use excludePaths only from now on.')); - } - if ($container->hasParameter('scopeClass') && $container->getParameter('scopeClass') !== MutatingScope::class) { $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option scopeClass. ⚠️️'); $errorOutput->writeLineFormatted(''); diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index bfc45d6c3e4..4fb9f47156d 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -31,7 +31,7 @@ final class NeonAdapter implements Adapter { - public const CACHE_KEY = 'v28-ignore-errors'; + public const CACHE_KEY = 'v29-excludes-analyse'; private const PREVENT_MERGING_SUFFIX = '!'; @@ -121,7 +121,6 @@ public function process(array $arr, string $fileKey, string $file): array if (in_array($keyToResolve, [ '[parameters][paths][]', - '[parameters][excludes_analyse][]', '[parameters][excludePaths][]', '[parameters][excludePaths][analyse][]', '[parameters][excludePaths][analyseAndScan][]', diff --git a/src/File/FileExcluderFactory.php b/src/File/FileExcluderFactory.php index c15b426f6ee..0bdae44c209 100644 --- a/src/File/FileExcluderFactory.php +++ b/src/File/FileExcluderFactory.php @@ -11,23 +11,17 @@ final class FileExcluderFactory { /** - * @param string[] $obsoleteExcludesAnalyse - * @param array{analyse?: array, analyseAndScan?: array}|null $excludePaths + * @param array{analyse?: array, analyseAndScan?: array} $excludePaths */ public function __construct( private FileExcluderRawFactory $fileExcluderRawFactory, - private array $obsoleteExcludesAnalyse, - private ?array $excludePaths, + private array $excludePaths, ) { } public function createAnalyseFileExcluder(): FileExcluder { - if ($this->excludePaths === null) { - return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); - } - $paths = []; if (array_key_exists('analyse', $this->excludePaths)) { $paths = $this->excludePaths['analyse']; @@ -41,10 +35,6 @@ public function createAnalyseFileExcluder(): FileExcluder public function createScanFileExcluder(): FileExcluder { - if ($this->excludePaths === null) { - return $this->fileExcluderRawFactory->create($this->obsoleteExcludesAnalyse); - } - $paths = []; if (array_key_exists('analyseAndScan', $this->excludePaths)) { $paths = $this->excludePaths['analyseAndScan']; diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index cfa21959b98..a5ed7689b6b 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -10,7 +10,6 @@ services: phpParser: @phpParserDecorator php8Parser: @php8PhpParser fileExtensions: %fileExtensions% - obsoleteExcludesAnalyse: %excludes_analyse% excludePaths: %excludePaths% cacheStorage: diff --git a/src/Testing/TestCaseSourceLocatorFactory.php b/src/Testing/TestCaseSourceLocatorFactory.php index a4921c9201a..724380955e0 100644 --- a/src/Testing/TestCaseSourceLocatorFactory.php +++ b/src/Testing/TestCaseSourceLocatorFactory.php @@ -31,7 +31,6 @@ final class TestCaseSourceLocatorFactory /** * @param string[] $fileExtensions - * @param string[] $obsoleteExcludesAnalyse * @param array{analyse?: array, analyseAndScan?: array}|null $excludePaths */ public function __construct( @@ -43,7 +42,6 @@ public function __construct( private ReflectionSourceStubber $reflectionSourceStubber, private PhpVersion $phpVersion, private array $fileExtensions, - private array $obsoleteExcludesAnalyse, private ?array $excludePaths, ) { @@ -56,7 +54,6 @@ public function create(): SourceLocator $cacheKey = sha1(serialize([ $this->phpVersion->getVersionId(), $this->fileExtensions, - $this->obsoleteExcludesAnalyse, $this->excludePaths, ])); if ($classLoaderReflection->hasProperty('vendorDir') && ! isset(self::$composerSourceLocatorsCache[$cacheKey])) { From 1cdffcc033a2eb8303d43330caf100af16c9b72d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:26:18 +0200 Subject: [PATCH 0350/3097] Removed note about obsolete Docker image --- src/Command/AnalyseCommand.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 5e4aa61443f..a81115e1081 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -183,15 +183,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $errorOutput = $inceptionResult->getErrorOutput(); - $obsoleteDockerImage = $_SERVER['PHPSTAN_OBSOLETE_DOCKER_IMAGE'] ?? 'false'; - if ($obsoleteDockerImage === 'true') { - $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete PHPStan Docker image. ⚠️️'); - $errorOutput->writeLineFormatted(' You can obtain the current one from ghcr.io/phpstan/phpstan.'); - $errorOutput->writeLineFormatted(' Read more about it here:'); - $errorOutput->writeLineFormatted(' https://phpstan.org/user-guide/docker'); - $errorOutput->writeLineFormatted(''); - } - $errorFormat = $input->getOption('error-format'); if (!is_string($errorFormat) && $errorFormat !== null) { From 688e65083af831af7b87d29227c13bf3c1df1bc5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:26:52 +0200 Subject: [PATCH 0351/3097] [BCB] Remove `cache.nodesByFileCountMax` --- UPGRADING.md | 1 + conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 263be4f14e8..e4d362aa374 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -147,3 +147,4 @@ This method now longer accepts `Expr $rootExpr`. If you want to change it, call * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) +* Removed config parameter `cache.nodesByFileCountMax` diff --git a/conf/config.neon b/conf/config.neon index d9d8d98dc51..99cebcaa70b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -148,7 +148,6 @@ parameters: ignoreErrors: [] internalErrorsCountLimit: 50 cache: - nodesByFileCountMax: 1024 nodesByStringCountMax: 256 reportUnmatchedIgnoredErrors: true scopeClass: PHPStan\Analyser\MutatingScope diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f91e092b722..e0a7bb3a986 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -176,7 +176,6 @@ parametersSchema: ) internalErrorsCountLimit: int() cache: structure([ - nodesByFileCountMax: int() nodesByStringCountMax: int() ]) reportUnmatchedIgnoredErrors: bool() From cad69ee0c8d5236dfb617e6487b20cc5b3a8c174 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:27:58 +0200 Subject: [PATCH 0352/3097] [BCB] Remove `memoryLimitFile` --- UPGRADING.md | 3 ++- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php | 5 ----- tests/PHPStan/Command/CommandHelperTest.php | 1 - tests/PHPStan/Command/relative-paths/root.neon | 1 - 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index e4d362aa374..aa9b5b5bbe4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -147,4 +147,5 @@ This method now longer accepts `Expr $rootExpr`. If you want to change it, call * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) -* Removed config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `memoryLimitFile` diff --git a/conf/config.neon b/conf/config.neon index 99cebcaa70b..f354735cf7a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -182,7 +182,6 @@ parameters: - ../stubs/Countable.stub earlyTerminatingMethodCalls: [] earlyTerminatingFunctionCalls: [] - memoryLimitFile: %tmpDir%/.memory_limit tempResultCachePath: %tmpDir%/resultCaches resultCachePath: %tmpDir%/resultCache.php resultCacheChecksProjectExtensionFilesDependencies: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index e0a7bb3a986..908950b191c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -185,7 +185,6 @@ parametersSchema: stubFiles: listOf(string()) earlyTerminatingMethodCalls: arrayOf(listOf(string())) earlyTerminatingFunctionCalls: listOf(string()) - memoryLimitFile: string() tempResultCachePath: string() resultCachePath: string() resultCacheChecksProjectExtensionFilesDependencies: bool() diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index a7cb8997d84..0c4bee2f630 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -65,8 +65,6 @@ private function runPath(string $path, int $expectedStatusCode): string new \PHPStan\Command\Symfony\SymfonyStyle(new SymfonyStyle($this->createMock(InputInterface::class), $output)), ); - $memoryLimitFile = self::getContainer()->getParameter('memoryLimitFile'); - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), __DIR__, [], DIRECTORY_SEPARATOR); $errorFormatter = new TableErrorFormatter( $relativePathHelper, @@ -90,9 +88,6 @@ private function runPath(string $path, int $expectedStatusCode): string null, $this->createMock(InputInterface::class), ); - if (file_exists($memoryLimitFile)) { - unlink($memoryLimitFile); - } $statusCode = $errorFormatter->formatErrors($analysisResult, $symfonyOutput); rewind($output->getStream()); diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index 817dea23f80..ab4b4b793a4 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -186,7 +186,6 @@ public function dataParameters(): array 'paths' => [ __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', ], - 'memoryLimitFile' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . '.memory_limit', 'excludePaths' => [ 'analyseAndScan' => [ __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', diff --git a/tests/PHPStan/Command/relative-paths/root.neon b/tests/PHPStan/Command/relative-paths/root.neon index 834bcfd0014..eb1330b4fcd 100644 --- a/tests/PHPStan/Command/relative-paths/root.neon +++ b/tests/PHPStan/Command/relative-paths/root.neon @@ -11,7 +11,6 @@ parameters: - %rootDir%/conf paths: - src - memoryLimitFile: .memory_limit excludePaths: - src - src/*/data From d453064e929d15840ff94bf99e29f3321d1dddec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 11:30:45 +0200 Subject: [PATCH 0353/3097] [BCB] Removed config option `scopeClass` --- UPGRADING.md | 12 ++++++++++-- conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - phpstan-baseline.neon | 10 ---------- src/Analyser/DirectInternalScopeFactory.php | 16 +++------------- src/Analyser/InternalScopeFactory.php | 3 ++- src/Analyser/LazyInternalScopeFactory.php | 16 +++------------- src/Command/CommandHelper.php | 7 ------- src/Testing/PHPStanTestCase.php | 1 - 9 files changed, 18 insertions(+), 51 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index aa9b5b5bbe4..cb182fbd3d2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -83,6 +83,12 @@ parameters: Appending `(?)` in `ignoreErrors` is not supported. +### Minor backward compatibility breaks + +* Removed unused config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `memoryLimitFile` + + ## Upgrading guide for extension developers ### PHPStan now uses nikic/php-parser v5 @@ -142,10 +148,12 @@ If you want to change `$overwrite` or `$rootExpr` (previous parameters also used This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). +### Removed config parameter `scopeClass` + +As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. + ### Minor backward compatibility breaks * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) -* Removed unused config parameter `cache.nodesByFileCountMax` -* Removed unused config parameter `memoryLimitFile` diff --git a/conf/config.neon b/conf/config.neon index f354735cf7a..8e53e033438 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -150,7 +150,6 @@ parameters: cache: nodesByStringCountMax: 256 reportUnmatchedIgnoredErrors: true - scopeClass: PHPStan\Analyser\MutatingScope typeAliases: [] universalObjectCratesClasses: - stdClass @@ -496,8 +495,6 @@ services: - class: PHPStan\Analyser\LazyInternalScopeFactory - arguments: - scopeClass: %scopeClass% autowired: - PHPStan\Analyser\InternalScopeFactory diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 908950b191c..c5d8a0b8217 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -179,7 +179,6 @@ parametersSchema: nodesByStringCountMax: int() ]) reportUnmatchedIgnoredErrors: bool() - scopeClass: string() typeAliases: arrayOf(string()) universalObjectCratesClasses: listOf(string()) stubFiles: listOf(string()) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 69cef6f30ee..edb70aafe71 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,21 +5,11 @@ parameters: count: 1 path: src/Analyser/AnalyserResultFinalizer.php - - - message: "#^Function is_a\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" - count: 1 - path: src/Analyser/DirectInternalScopeFactory.php - - message: "#^Cannot assign offset 'realCount' to array\\|string\\.$#" count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - - - message: "#^Function is_a\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" - count: 1 - path: src/Analyser/LazyInternalScopeFactory.php - - message: """ #^Call to deprecated method getAnyArrays\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index c48074c1dd9..a696c24777d 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -7,6 +7,7 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; @@ -14,17 +15,11 @@ use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; -use PHPStan\ShouldNotHappenException; -use function is_a; final class DirectInternalScopeFactory implements InternalScopeFactory { - /** - * @param class-string $scopeClass - */ public function __construct( - private string $scopeClass, private ReflectionProvider $reflectionProvider, private InitializerExprTypeResolver $initializerExprTypeResolver, private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, @@ -51,7 +46,7 @@ public function __construct( public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|MethodReflection|null $function = null, + FunctionReflection|ExtendedMethodReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], @@ -67,12 +62,7 @@ public function create( bool $nativeTypesPromoted = false, ): MutatingScope { - $scopeClass = $this->scopeClass; - if (!is_a($scopeClass, MutatingScope::class, true)) { - throw new ShouldNotHappenException(); - } - - return new $scopeClass( + return new MutatingScope( $this, $this->reflectionProvider, $this->initializerExprTypeResolver, diff --git a/src/Analyser/InternalScopeFactory.php b/src/Analyser/InternalScopeFactory.php index fdacebc9ece..83c943f9175 100644 --- a/src/Analyser/InternalScopeFactory.php +++ b/src/Analyser/InternalScopeFactory.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; @@ -22,7 +23,7 @@ interface InternalScopeFactory public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|MethodReflection|null $function = null, + FunctionReflection|ExtendedMethodReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 0c904d0d3bd..073bc6baef6 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -7,6 +7,7 @@ use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; @@ -14,17 +15,11 @@ use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; -use PHPStan\ShouldNotHappenException; -use function is_a; final class LazyInternalScopeFactory implements InternalScopeFactory { - /** - * @param class-string $scopeClass - */ public function __construct( - private string $scopeClass, private Container $container, ) { @@ -41,7 +36,7 @@ public function __construct( public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|MethodReflection|null $function = null, + FunctionReflection|ExtendedMethodReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], @@ -57,12 +52,7 @@ public function create( bool $nativeTypesPromoted = false, ): MutatingScope { - $scopeClass = $this->scopeClass; - if (!is_a($scopeClass, MutatingScope::class, true)) { - throw new ShouldNotHappenException(); - } - - return new $scopeClass( + return new MutatingScope( $this, $this->container->getByType(ReflectionProvider::class), $this->container->getByType(InitializerExprTypeResolver::class), diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 0fe6f1c7f26..c3cfbbedfde 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -12,7 +12,6 @@ use Nette\Schema\ValidationException; use Nette\Utils\AssertionException; use Nette\Utils\Strings; -use PHPStan\Analyser\MutatingScope; use PHPStan\Command\Symfony\SymfonyOutput; use PHPStan\Command\Symfony\SymfonyStyle; use PHPStan\DependencyInjection\Container; @@ -524,12 +523,6 @@ public static function begin( throw new InceptionNotSuccessfulException(); } - if ($container->hasParameter('scopeClass') && $container->getParameter('scopeClass') !== MutatingScope::class) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option scopeClass. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(sprintf('Please implement PHPStan\Type\ExpressionTypeResolverExtension interface instead and register it as a service.')); - } - $tempResultCachePath = $container->getParameter('tempResultCachePath'); $createDir($tempResultCachePath); diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index f4ab2a470d4..b62d258607a 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -170,7 +170,6 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider return new ScopeFactory( new DirectInternalScopeFactory( - MutatingScope::class, $reflectionProvider, new InitializerExprTypeResolver( $constantResolver, From 460583333f83cbc675c9b01a54b0eae8ecf9e28c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 13:00:30 +0200 Subject: [PATCH 0354/3097] Refactor RequireFileExistsRuleTest to work like all other tests --- .../Keywords/RequireFileExistsRuleTest.php | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php index 287df686345..ee96f404662 100644 --- a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php @@ -16,18 +16,11 @@ class RequireFileExistsRuleTest extends RuleTestCase { - private RequireFileExistsRule $rule; - - public function setUp(): void - { - parent::setUp(); - - $this->rule = $this->getDefaultRule(); - } + private string $currentWorkingDirectory = __DIR__ . '/../'; protected function getRule(): Rule { - return $this->rule; + return new RequireFileExistsRule($this->currentWorkingDirectory); } public static function getAdditionalConfigFiles(): array @@ -37,11 +30,6 @@ public static function getAdditionalConfigFiles(): array ]; } - private function getDefaultRule(): RequireFileExistsRule - { - return new RequireFileExistsRule(__DIR__ . '/../'); - } - public function testBasicCase(): void { $this->analyse([__DIR__ . '/data/require-file-simple-case.php'], [ @@ -124,13 +112,8 @@ public function testRelativePathWithIncludePath(): void public function testRelativePathWithSameWorkingDirectory(): void { - $this->rule = new RequireFileExistsRule(__DIR__); - - try { - $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], []); - } finally { - $this->rule = $this->getDefaultRule(); - } + $this->currentWorkingDirectory = __DIR__; + $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], []); } } From 098fb9416779240fab9b2dea1ee730da69668014 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 13:04:28 +0200 Subject: [PATCH 0355/3097] Fix including relative path --- src/Rules/Keywords/RequireFileExistsRule.php | 3 +-- tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php | 5 +++++ tests/PHPStan/Rules/Keywords/data/bug-11738-included.php | 2 ++ tests/PHPStan/Rules/Keywords/data/bug-11738/bug-11738.php | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Keywords/data/bug-11738-included.php create mode 100644 tests/PHPStan/Rules/Keywords/data/bug-11738/bug-11738.php diff --git a/src/Rules/Keywords/RequireFileExistsRule.php b/src/Rules/Keywords/RequireFileExistsRule.php index f2a9af9989b..8ccf92e3dfc 100644 --- a/src/Rules/Keywords/RequireFileExistsRule.php +++ b/src/Rules/Keywords/RequireFileExistsRule.php @@ -77,8 +77,7 @@ private function doesFileExist(string $path, Scope $scope): bool private function doesFileExistForDirectory(string $path, string $workingDirectory): bool { $fileHelper = new FileHelper($workingDirectory); - $normalisedPath = $fileHelper->normalizePath($path); - $absolutePath = $fileHelper->absolutizePath($normalisedPath); + $absolutePath = $fileHelper->absolutizePath($path); return is_file($absolutePath); } diff --git a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php index ee96f404662..6bd3e1dfd72 100644 --- a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php @@ -116,4 +116,9 @@ public function testRelativePathWithSameWorkingDirectory(): void $this->analyse([__DIR__ . '/data/require-file-relative-path.php'], []); } + public function testBug11738(): void + { + $this->analyse([__DIR__ . '/data/bug-11738/bug-11738.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Keywords/data/bug-11738-included.php b/tests/PHPStan/Rules/Keywords/data/bug-11738-included.php new file mode 100644 index 00000000000..a4abe2dafcb --- /dev/null +++ b/tests/PHPStan/Rules/Keywords/data/bug-11738-included.php @@ -0,0 +1,2 @@ + Date: Mon, 23 Sep 2024 13:12:31 +0200 Subject: [PATCH 0356/3097] Fix CS --- src/Testing/PHPStanTestCase.php | 1 - tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index b62d258607a..c5d0a651a6b 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -5,7 +5,6 @@ use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\DirectInternalScopeFactory; use PHPStan\Analyser\Error; -use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\ScopeFactory; use PHPStan\Analyser\TypeSpecifier; diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index 0c4bee2f630..e84ac78e280 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -16,12 +16,10 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Style\SymfonyStyle; -use function file_exists; use function fopen; use function rewind; use function sprintf; use function stream_get_contents; -use function unlink; use const DIRECTORY_SEPARATOR; class AnalyseApplicationIntegrationTest extends PHPStanTestCase From 74b2185e8d23ef4e88d1464590fd3ca8072c4852 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 13:22:37 +0200 Subject: [PATCH 0357/3097] [BE] Stricter function map --- changelog-2.0.md | 29 +- conf/bleedingEdge.neon | 3 +- resources/functionMap.php | 274 +++++++++--------- resources/functionMap_bleedingEdge.php | 152 +--------- resources/functionMap_php80delta.php | 12 +- .../functionMap_php80delta_bleedingEdge.php | 11 +- 6 files changed, 166 insertions(+), 315 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index bcda7deb8c1..d84b7cf2e8d 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -62,23 +62,13 @@ Bleeding edge (TODO move to other sections) * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) * Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) -* Stricter function signature map (https://github.com/phpstan/phpstan-src/commit/06b746d8e72cc0843707896ec161559bb6a81137, [#2163](https://github.com/phpstan/phpstan-src/pull/2163)), #7239, thanks @staabm! -* Specify `Imagick` parameter types ([#2334](https://github.com/phpstan/phpstan-src/pull/2334)), thanks @zonuexe! * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! -* More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! -* `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! -* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! -* More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! -* More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! -* More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! -* More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * NoopRule - report top-level `xor` because that's probably not what the user intended to do (https://github.com/phpstan/phpstan-src/commit/a1fffb3346e09f1e8e8d987d4282263295a55142), #10267 @@ -97,7 +87,6 @@ Bleeding edge (TODO move to other sections) * BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! -* More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 @@ -105,7 +94,6 @@ Bleeding edge (TODO move to other sections) * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! -* Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3! * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! Improvements 🔧 @@ -150,6 +138,23 @@ Function signature fixes 🤖 ======================= * Countable stub with `0|positive-int` ([#1027](https://github.com/phpstan/phpstan-src/pull/1027)), thanks @staabm! +* More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! +* Specify `Imagick` parameter types ([#2334](https://github.com/phpstan/phpstan-src/pull/2334)), thanks @zonuexe! +* `max()`/`min()` should expect non-empty-array ([#2163](https://github.com/phpstan/phpstan-src/pull/2163)), thanks @staabm! +* Narrow `Closure::bind` `$newScope` param ([#2817](https://github.com/phpstan/phpstan-src/pull/2817)), thanks @mvorisek! +* `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! +* Update functionMap ([#2699](https://github.com/phpstan/phpstan-src/pull/2699), [#2783](https://github.com/phpstan/phpstan-src/pull/2783)), thanks @zonuexe! +* Improve image related functions signature ([#3127](https://github.com/phpstan/phpstan-src/pull/3127)), thanks @thg2k! +* Support `FILE_NO_DEFAULT_CONTEXT` in `file()` ([#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! +* Fix ftp related function signatures ([#2551](https://github.com/phpstan/phpstan-src/pull/2551)), thanks @thg2k! +* More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! +* More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! +* More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! +* More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! +* More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! +* More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! +* Update `Locale` signatures ([#2880](https://github.com/phpstan/phpstan-src/pull/2880)), thanks @devnix! +* Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3!* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! Internals 🔍 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index b910eaa88e0..6c988343b3b 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,6 +2,8 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses!: [] + stricterFunctionMap: true + explicitMixedViaIsArray: true arrayFilter: true arrayUnpacking: true @@ -31,7 +33,6 @@ parameters: paramOutVariance: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true - stricterFunctionMap: true zeroFiles: true projectServicesNotInAnalysedPaths: true callUserFunc: true diff --git a/resources/functionMap.php b/resources/functionMap.php index 2b9537b98e2..c9f98dbe47a 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -417,11 +417,11 @@ 'bbcode_parse' => ['string', 'bbcode_container'=>'resource', 'to_parse'=>'string'], 'bbcode_set_arg_parser' => ['bool', 'bbcode_container'=>'resource', 'bbcode_arg_parser'=>'resource'], 'bbcode_set_flags' => ['bool', 'bbcode_container'=>'resource', 'flags'=>'int', 'mode='=>'int'], -'bcadd' => ['numeric-string', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], -'bccomp' => ['int', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], -'bcdiv' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], -'bcmod' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], -'bcmul' => ['numeric-string', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], +'bcadd' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bccomp' => ['int', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcdiv' => ['numeric-string|null', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcmod' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bcmul' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'bcompiler_load' => ['bool', 'filename'=>'string'], 'bcompiler_load_exe' => ['bool', 'filename'=>'string'], 'bcompiler_parse_class' => ['bool', 'class'=>'string', 'callback'=>'string'], @@ -435,11 +435,11 @@ 'bcompiler_write_functions_from_file' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'], 'bcompiler_write_header' => ['bool', 'filehandle'=>'resource', 'write_ver='=>'string'], 'bcompiler_write_included_filename' => ['bool', 'filehandle'=>'resource', 'filename'=>'string'], -'bcpow' => ['numeric-string', 'base'=>'string', 'exponent'=>'string', 'scale='=>'int'], -'bcpowmod' => ['numeric-string|null', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], +'bcpow' => ['numeric-string', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'scale='=>'int'], +'bcpowmod' => ['numeric-string|null', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'modulus'=>'string', 'scale='=>'int'], 'bcscale' => ['int', 'scale='=>'int'], -'bcsqrt' => ['numeric-string', 'operand'=>'string', 'scale='=>'int'], -'bcsub' => ['numeric-string', 'left_operand'=>'string', 'right_operand'=>'string', 'scale='=>'int'], +'bcsqrt' => ['numeric-string', 'operand'=>'numeric-string', 'scale='=>'int'], +'bcsub' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'bin2hex' => ['string', 'data'=>'string'], 'bind_textdomain_codeset' => ['string|false', 'domain'=>'string', 'codeset'=>'string'], 'bindec' => ['float|int', 'binary_number'=>'string'], @@ -994,8 +994,8 @@ 'closelog' => ['bool'], 'Closure::__construct' => ['void'], 'Closure::__invoke' => ['', '...args='=>''], -'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|string|null'], -'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|string|null'], +'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|class-string|\'static\'|null'], +'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|class-string|\'static\'|null'], 'Closure::call' => ['', 'to'=>'object', '...parameters='=>''], 'Closure::fromCallable' => ['Closure', 'callable'=>'callable'], 'clusterObj::convertToString' => ['string'], @@ -1363,7 +1363,7 @@ 'Couchbase\WildcardSearchQuery::jsonSerialize' => ['array'], 'Couchbase\zlibCompress' => ['string', 'data'=>'string'], 'Couchbase\zlibDecompress' => ['string', 'data'=>'string'], -'count' => ['0|positive-int', 'var'=>'Countable|array', 'mode='=>'int'], +'count' => ['0|positive-int', 'var'=>'Countable|array', 'mode='=>'0|1'], 'count_chars' => ['mixed', 'input'=>'string', 'mode='=>'0|1|2|3|4'], 'Countable::count' => ['0|positive-int'], 'crack_check' => ['bool', 'dictionary'=>'', 'password'=>'string'], @@ -2334,7 +2334,7 @@ 'Error::getTraceAsString' => ['string'], 'error_clear_last' => ['void'], 'error_get_last' => ['?array{type:int,message:string,file:string,line:int}'], -'error_log' => ['bool', 'message'=>'string', 'message_type='=>'int', 'destination='=>'string', 'extra_headers='=>'string'], +'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|2|3|4', 'destination='=>'string', 'extra_headers='=>'string'], 'error_reporting' => ['int', 'new_error_level='=>'int'], 'ErrorException::__clone' => ['void'], 'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'lineno='=>'int', 'previous='=>'(?Throwable)|(?ErrorException)'], @@ -2637,7 +2637,7 @@ 'explode' => ['list|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], 'expm1' => ['float', 'number'=>'float'], 'extension_loaded' => ['bool', 'extension_name'=>'string'], -'extract' => ['0|positive-int', '&rw_var_array'=>'array', 'extract_type='=>'int', 'prefix='=>'string|null'], +'extract' => ['0|positive-int', '&rw_var_array'=>'array', 'extract_type='=>'EXTR_OVERWRITE|EXTR_SKIP|EXTR_PREFIX_SAME|EXTR_PREFIX_ALL|EXTR_PREFIX_INVALID|EXTR_IF_EXISTS|EXTR_PREFIX_IF_EXISTS|EXTR_REFS', 'prefix='=>'string|null'], 'ezmlm_hash' => ['int', 'addr'=>'string'], 'fam_cancel_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'], 'fam_close' => ['void', 'fam'=>'resource'], @@ -2935,7 +2935,7 @@ 'fgetcsv' => ['list|array{0: null}|false|null', 'fp'=>'resource', 'length='=>'0|positive-int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int'], 'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int', 'allowable_tags='=>'string'], -'file' => ['list|false', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'], +'file' => ['list|false', 'filename'=>'string', 'flags='=>'int-mask', 'context='=>'resource'], 'file_exists' => ['bool', 'filename'=>'string'], 'file_get_contents' => ['string|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'?resource', 'offset='=>'int', 'maxlen='=>'0|positive-int'], 'file_put_contents' => ['0|positive-int|false', 'file'=>'string', 'data'=>'mixed', 'flags='=>'int', 'context='=>'?resource'], @@ -2988,7 +2988,7 @@ 'finfo_open' => ['resource|false', 'options='=>'int', 'arg='=>'string'], 'finfo_set_flags' => ['bool', 'finfo'=>'resource', 'options'=>'int'], 'floatval' => ['float', 'var'=>'scalar|array|resource|null'], -'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int', '&w_wouldblock='=>'int'], +'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], 'floor' => ['__benevolent', 'number'=>'float'], 'flush' => ['void'], 'fmod' => ['float', 'x'=>'float', 'y'=>'float'], @@ -3011,7 +3011,7 @@ 'ftell' => ['int|false', 'fp'=>'resource'], 'ftok' => ['int', 'pathname'=>'string', 'proj'=>'string'], 'ftp_alloc' => ['bool', 'stream'=>'resource', 'size'=>'int', '&w_response='=>'string'], -'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int'], +'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY'], 'ftp_cdup' => ['bool', 'stream'=>'resource'], 'ftp_chdir' => ['bool', 'stream'=>'resource', 'directory'=>'string'], 'ftp_chmod' => ['int|false', 'stream'=>'resource', 'mode'=>'int', 'filename'=>'string'], @@ -3019,22 +3019,22 @@ 'ftp_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], 'ftp_delete' => ['bool', 'stream'=>'resource', 'file'=>'string'], 'ftp_exec' => ['bool', 'stream'=>'resource', 'command'=>'string'], -'ftp_fget' => ['bool', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'int', 'resumepos='=>'int'], -'ftp_fput' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'int', 'startpos='=>'int'], -'ftp_get' => ['bool', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'int', 'resume_pos='=>'int'], +'ftp_fget' => ['bool', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resumepos='=>'int'], +'ftp_fput' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], +'ftp_get' => ['bool', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resume_pos='=>'int'], 'ftp_get_option' => ['mixed', 'stream'=>'resource', 'option'=>'int'], 'ftp_login' => ['bool', 'stream'=>'resource', 'username'=>'string', 'password'=>'string'], 'ftp_mdtm' => ['int', 'stream'=>'resource', 'filename'=>'string'], 'ftp_mkdir' => ['string|false', 'stream'=>'resource', 'directory'=>'string'], 'ftp_mlsd' => ['array|false', 'ftp_stream'=>'resource', 'directory'=>'string'], 'ftp_nb_continue' => ['int', 'stream'=>'resource'], -'ftp_nb_fget' => ['int', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'int', 'resumepos='=>'int'], -'ftp_nb_fput' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'int', 'startpos='=>'int'], -'ftp_nb_get' => ['int|false', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'int', 'resume_pos='=>'int'], -'ftp_nb_put' => ['int|false', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int', 'startpos='=>'int'], +'ftp_nb_fget' => ['int', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resumepos='=>'int'], +'ftp_nb_fput' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], +'ftp_nb_get' => ['int|false', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resume_pos='=>'int'], +'ftp_nb_put' => ['int|false', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], 'ftp_nlist' => ['array|false', 'stream'=>'resource', 'directory'=>'string'], 'ftp_pasv' => ['bool', 'stream'=>'resource', 'pasv'=>'bool'], -'ftp_put' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'int', 'startpos='=>'int'], +'ftp_put' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], 'ftp_pwd' => ['string|false', 'stream'=>'resource'], 'ftp_raw' => ['array', 'stream'=>'resource', 'command'=>'string'], 'ftp_rawlist' => ['array|false', 'stream'=>'resource', 'directory'=>'string', 'recursive='=>'bool'], @@ -4542,19 +4542,19 @@ 'imagebmp' => ['bool', 'image'=>'resource', 'to='=>'string|resource|null', 'compressed='=>'bool'], 'imagechar' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'], 'imagecharup' => ['bool', 'im'=>'resource', 'font'=>'int', 'x'=>'int', 'y'=>'int', 'c'=>'string', 'col'=>'int'], -'imagecolorallocate' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], -'imagecolorallocatealpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolorallocate' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], +'imagecolorallocatealpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], 'imagecolorat' => ['int<0, max>|false', 'im'=>'resource', 'x'=>'int', 'y'=>'int'], -'imagecolorclosest' => ['int<0, max>', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], -'imagecolorclosestalpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], -'imagecolorclosesthwb' => ['int<0, max>', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], +'imagecolorclosest' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], +'imagecolorclosestalpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], +'imagecolorclosesthwb' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], 'imagecolordeallocate' => ['bool', 'im'=>'resource', 'index'=>'int'], -'imagecolorexact' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], -'imagecolorexactalpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], +'imagecolorexact' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], +'imagecolorexactalpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], 'imagecolormatch' => ['bool', 'im1'=>'resource', 'im2'=>'resource'], -'imagecolorresolve' => ['int<0, max>', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], -'imagecolorresolvealpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha'=>'int'], -'imagecolorset' => ['void', 'im'=>'resource', 'col'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'alpha='=>'int'], +'imagecolorresolve' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], +'imagecolorresolvealpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], +'imagecolorset' => ['void', 'im'=>'resource', 'col'=>'int', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha='=>'int<0, 127>'], 'imagecolorsforindex' => ['array{red: int<0, 255>, green: int<0, 255>, blue: int<0, 255>, alpha: int<0, 127>}', 'im'=>'resource', 'col'=>'int'], 'imagecolorstotal' => ['int<0, 256>', 'im'=>'resource'], 'imagecolortransparent' => ['int', 'im'=>'resource', 'col='=>'int'], @@ -4564,7 +4564,7 @@ 'imagecopymergegray' => ['bool', 'src_im'=>'resource', 'dst_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'src_w'=>'int', 'src_h'=>'int', 'pct'=>'int'], 'imagecopyresampled' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'], 'imagecopyresized' => ['bool', 'dst_im'=>'resource', 'src_im'=>'resource', 'dst_x'=>'int', 'dst_y'=>'int', 'src_x'=>'int', 'src_y'=>'int', 'dst_w'=>'int', 'dst_h'=>'int', 'src_w'=>'int', 'src_h'=>'int'], -'imagecreate' => ['__benevolent', 'x_size'=>'int', 'y_size'=>'int'], +'imagecreate' => ['__benevolent', 'x_size'=>'int<1, max>', 'y_size'=>'int<1, max>'], 'imagecreatefrombmp' => ['resource|false', 'filename'=>'string'], 'imagecreatefromgd' => ['resource|false', 'filename'=>'string'], 'imagecreatefromgd2' => ['resource|false', 'filename'=>'string'], @@ -4577,7 +4577,7 @@ 'imagecreatefromwebp' => ['resource|false', 'filename'=>'string'], 'imagecreatefromxbm' => ['resource|false', 'filename'=>'string'], 'imagecreatefromxpm' => ['resource|false', 'filename'=>'string'], -'imagecreatetruecolor' => ['__benevolent', 'x_size'=>'int', 'y_size'=>'int'], +'imagecreatetruecolor' => ['__benevolent', 'x_size'=>'int<1, max>', 'y_size'=>'int<1, max>'], 'imagecrop' => ['resource|false', 'im'=>'resource', 'rect'=>'array'], 'imagecropauto' => ['resource|false', 'im'=>'resource', 'mode='=>'int', 'threshold='=>'float', 'color='=>'int'], 'imagedashedline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], @@ -4648,25 +4648,25 @@ 'imagexbm' => ['bool', 'im'=>'resource', 'filename='=>'string|resource|null', 'foreground='=>'int'], 'Imagick::__construct' => ['void', 'files='=>''], 'Imagick::__toString' => ['string'], -'Imagick::adaptiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::adaptiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::adaptiveResizeImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool'], -'Imagick::adaptiveSharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::adaptiveSharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::adaptiveThresholdImage' => ['bool', 'width'=>'int', 'height'=>'int', 'offset'=>'int'], 'Imagick::addImage' => ['bool', 'source'=>'imagick'], -'Imagick::addNoiseImage' => ['bool', 'noise_type'=>'int', 'channel='=>'int'], +'Imagick::addNoiseImage' => ['bool', 'noise_type'=>'Imagick::NOISE_*', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::affineTransformImage' => ['bool', 'matrix'=>'imagickdraw'], 'Imagick::animateImages' => ['bool', 'x_server'=>'string'], 'Imagick::annotateImage' => ['bool', 'draw_settings'=>'imagickdraw', 'x'=>'float', 'y'=>'float', 'angle'=>'float', 'text'=>'string'], 'Imagick::appendImages' => ['Imagick', 'stack'=>'bool'], -'Imagick::autoGammaImage' => ['bool', 'channel='=>'int'], -'Imagick::autoLevelImage' => ['bool', 'channel='=>'int'], +'Imagick::autoGammaImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::autoLevelImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::autoOrient' => ['bool'], 'Imagick::averageImages' => ['Imagick'], 'Imagick::blackThresholdImage' => ['bool', 'threshold'=>'mixed'], 'Imagick::blueShiftImage' => ['bool', 'factor='=>'float'], -'Imagick::blurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::blurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::borderImage' => ['bool', 'bordercolor'=>'mixed', 'width'=>'int', 'height'=>'int'], -'Imagick::brightnessContrastImage' => ['bool', 'brightness'=>'float', 'contrast'=>'float', 'channel='=>'int'], +'Imagick::brightnessContrastImage' => ['bool', 'brightness'=>'float', 'contrast'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::charcoalImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'], 'Imagick::chopImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], 'Imagick::clampImage' => ['bool', 'channel='=>'int'], @@ -4680,16 +4680,16 @@ 'Imagick::colorFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'], 'Imagick::colorizeImage' => ['bool', 'colorize'=>'mixed', 'opacity'=>'mixed'], 'Imagick::colorMatrixImage' => ['bool', 'color_matrix'=>'array'], -'Imagick::combineImages' => ['Imagick', 'channeltype'=>'int'], +'Imagick::combineImages' => ['Imagick', 'channeltype'=>'Imagick::CHANNEL_*'], 'Imagick::commentImage' => ['bool', 'comment'=>'string'], -'Imagick::compareImageChannels' => ['array{Imagick,float}', 'image'=>'imagick', 'channeltype'=>'int', 'metrictype'=>'int'], -'Imagick::compareImageLayers' => ['Imagick', 'method'=>'int'], -'Imagick::compareImages' => ['array{Imagick,float}', 'compare'=>'imagick', 'metric'=>'int'], -'Imagick::compositeImage' => ['bool', 'composite_object'=>'imagick', 'composite'=>'int', 'x'=>'int', 'y'=>'int', 'channel='=>'int'], +'Imagick::compareImageChannels' => ['array{Imagick,float}', 'image'=>'imagick', 'channeltype'=>'Imagick::CHANNEL_*', 'metrictype'=>'Imagick::METRIC_*'], +'Imagick::compareImageLayers' => ['Imagick', 'method'=>'Imagick::LAYERMETHOD_*'], +'Imagick::compareImages' => ['array{Imagick,float}', 'compare'=>'imagick', 'metric'=>'Imagick::METRIC_*'], +'Imagick::compositeImage' => ['bool', 'composite_object'=>'imagick', 'composite'=>'Imagick::COMPOSITE_*', 'x'=>'int', 'y'=>'int', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::compositeImageGravity' => ['bool', 'imagick'=>'Imagick', 'COMPOSITE_CONSTANT'=>'int', 'GRAVITY_CONSTANT'=>'int'], 'Imagick::contrastImage' => ['bool', 'sharpen'=>'bool'], -'Imagick::contrastStretchImage' => ['bool', 'black_point'=>'float', 'white_point'=>'float', 'channel='=>'int'], -'Imagick::convolveImage' => ['bool', 'kernel'=>'array', 'channel='=>'int'], +'Imagick::contrastStretchImage' => ['bool', 'black_point'=>'float', 'white_point'=>'float', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::convolveImage' => ['bool', 'kernel'=>'array', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::count' => ['0|positive-int', 'mode='=>'int'], 'Imagick::cropImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], 'Imagick::cropThumbnailImage' => ['bool', 'width'=>'int', 'height'=>'int', 'legacy='=>'bool'], @@ -4704,28 +4704,28 @@ 'Imagick::destroy' => ['bool'], 'Imagick::displayImage' => ['bool', 'servername'=>'string'], 'Imagick::displayImages' => ['bool', 'servername'=>'string'], -'Imagick::distortImage' => ['bool', 'method'=>'int', 'arguments'=>'array', 'bestfit'=>'bool'], +'Imagick::distortImage' => ['bool', 'method'=>'Imagick::DISTORTION_*', 'arguments'=>'array', 'bestfit'=>'bool'], 'Imagick::drawImage' => ['bool', 'draw'=>'imagickdraw'], 'Imagick::edgeImage' => ['bool', 'radius'=>'float'], 'Imagick::embossImage' => ['bool', 'radius'=>'float', 'sigma'=>'float'], 'Imagick::encipherImage' => ['bool', 'passphrase'=>'string'], 'Imagick::enhanceImage' => ['bool'], 'Imagick::equalizeImage' => ['bool'], -'Imagick::evaluateImage' => ['bool', 'op'=>'int', 'constant'=>'float', 'channel='=>'int'], +'Imagick::evaluateImage' => ['bool', 'op'=>'Imagick::EVALUATE_*', 'constant'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::evaluateImages' => ['bool', 'EVALUATE_CONSTANT'=>'int'], -'Imagick::exportImagePixels' => ['list', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int'], +'Imagick::exportImagePixels' => ['list', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'Imagick::PIXEL_*'], 'Imagick::extentImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], 'Imagick::filter' => ['bool', 'ImagickKernel'=>'ImagickKernel', 'CHANNEL='=>'int'], 'Imagick::flattenImages' => ['Imagick'], 'Imagick::flipImage' => ['bool'], -'Imagick::floodFillPaintImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'target'=>'mixed', 'x'=>'int', 'y'=>'int', 'invert'=>'bool', 'channel='=>'int'], +'Imagick::floodFillPaintImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'target'=>'mixed', 'x'=>'int', 'y'=>'int', 'invert'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::flopImage' => ['bool'], 'Imagick::forwardFourierTransformimage' => ['bool', 'magnitude'=>'bool'], 'Imagick::frameImage' => ['bool', 'matte_color'=>'mixed', 'width'=>'int', 'height'=>'int', 'inner_bevel'=>'int', 'outer_bevel'=>'int'], -'Imagick::functionImage' => ['bool', 'function'=>'int', 'arguments'=>'array', 'channel='=>'int'], -'Imagick::fxImage' => ['Imagick', 'expression'=>'string', 'channel='=>'int'], -'Imagick::gammaImage' => ['bool', 'gamma'=>'float', 'channel='=>'int'], -'Imagick::gaussianBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::functionImage' => ['bool', 'function'=>'Imagick::FUNCTION_*', 'arguments'=>'array', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::fxImage' => ['Imagick', 'expression'=>'string', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::gammaImage' => ['bool', 'gamma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::gaussianBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::getColorspace' => ['Imagick::COLORSPACE_*'], 'Imagick::getCompression' => ['Imagick::COMPRESSION_*'], 'Imagick::getCompressionQuality' => ['int'], @@ -4746,13 +4746,13 @@ 'Imagick::getImageBlob' => ['string'], 'Imagick::getImageBluePrimary' => ['array{x:float,y:float}'], 'Imagick::getImageBorderColor' => ['ImagickPixel'], -'Imagick::getImageChannelDepth' => ['int', 'channel'=>'int'], -'Imagick::getImageChannelDistortion' => ['float', 'reference'=>'imagick', 'channel'=>'int', 'metric'=>'int'], -'Imagick::getImageChannelDistortions' => ['float', 'reference'=>'imagick', 'metric'=>'int', 'channel='=>'int'], -'Imagick::getImageChannelExtrema' => ['array{minima:0|positive-int,maxima:0|positive-int}', 'channel'=>'int'], -'Imagick::getImageChannelKurtosis' => ['array{kurtosis:float,skewness:float}', 'channel='=>'int'], -'Imagick::getImageChannelMean' => ['array{mean:float,standardDeviation:float}', 'channel'=>'int'], -'Imagick::getImageChannelRange' => ['array{minima:float,maxima:float}', 'channel'=>'int'], +'Imagick::getImageChannelDepth' => ['int', 'channel'=>'Imagick::CHANNEL_*'], +'Imagick::getImageChannelDistortion' => ['float', 'reference'=>'imagick', 'channel'=>'Imagick::CHANNEL_*', 'metric'=>'Imagick::METRIC_*'], +'Imagick::getImageChannelDistortions' => ['float', 'reference'=>'imagick', 'metric'=>'Imagick::METRIC_*', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::getImageChannelExtrema' => ['array{minima:0|positive-int,maxima:0|positive-int}', 'channel'=>'Imagick::CHANNEL_*'], +'Imagick::getImageChannelKurtosis' => ['array{kurtosis:float,skewness:float}', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::getImageChannelMean' => ['array{mean:float,standardDeviation:float}', 'channel'=>'Imagick::CHANNEL_*'], +'Imagick::getImageChannelRange' => ['array{minima:float,maxima:float}', 'channel'=>'Imagick::CHANNEL_*'], 'Imagick::getImageChannelStatistics' => ['array{mean:float,minima:float,maxima:float,standardDeviation:float,depth:int}'], 'Imagick::getImageClipMask' => ['Imagick'], 'Imagick::getImageColormapColor' => ['ImagickPixel', 'index'=>'int'], @@ -4764,7 +4764,7 @@ 'Imagick::getImageDelay' => ['int'], 'Imagick::getImageDepth' => ['int'], 'Imagick::getImageDispose' => ['Imagick::DISPOSE_*'], -'Imagick::getImageDistortion' => ['float', 'reference'=>'magickwand', 'metric'=>'int'], +'Imagick::getImageDistortion' => ['float', 'reference'=>'magickwand', 'metric'=>'Imagick::METRIC_*'], 'Imagick::getImageExtrema' => ['array{min:0|positive-int,max:0|positive-int}'], 'Imagick::getImageFilename' => ['string'], 'Imagick::getImageFormat' => ['string'], @@ -4819,8 +4819,8 @@ 'Imagick::getQuantumRange' => ['array{quantumRangeLong:0|positive-int,quantumRangeString:numeric-string}'], 'Imagick::getRegistry' => ['string', 'key'=>'string'], 'Imagick::getReleaseDate' => ['string'], -'Imagick::getResource' => ['int', 'type'=>'int'], -'Imagick::getResourceLimit' => ['int', 'type'=>'int'], +'Imagick::getResource' => ['int', 'type'=>'Imagick::RESOURCETYPE_*'], +'Imagick::getResourceLimit' => ['int', 'type'=>'Imagick::RESOURCETYPE_*'], 'Imagick::getSamplingFactors' => ['list'], 'Imagick::getSize' => ['array{columns:0|positive-int,rows:0|positive-int}'], 'Imagick::getSizeOffset' => ['int'], @@ -4832,11 +4832,11 @@ 'Imagick::identifyImage' => ['array{imageName:string,mimetype:string,format:string,units:string,colorSpace:string,type:string,compression:string,fileSize:string,geometry:array{width:0|positive-int,height:0|positive-int},resolution:array{x:float,y:float},signature:string}', 'appendrawoutput='=>'bool'], 'Imagick::identifyImageType' => ['int'], 'Imagick::implodeImage' => ['bool', 'radius'=>'float'], -'Imagick::importImagePixels' => ['bool', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'int', 'pixels'=>'array'], +'Imagick::importImagePixels' => ['bool', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'Imagick::PIXEL_*', 'pixels'=>'array'], 'Imagick::inverseFourierTransformImage' => ['bool', 'complement'=>'Imagick', 'magnitude'=>'bool'], 'Imagick::key' => ['int|string'], 'Imagick::labelImage' => ['bool', 'label'=>'string'], -'Imagick::levelImage' => ['bool', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'int'], +'Imagick::levelImage' => ['bool', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::linearStretchImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float'], 'Imagick::liquidRescaleImage' => ['bool', 'width'=>'int', 'height'=>'int', 'delta_x'=>'float', 'rigidity'=>'float'], 'Imagick::listRegistry' => ['array'], @@ -4845,26 +4845,26 @@ 'Imagick::mapImage' => ['bool', 'map'=>'imagick', 'dither'=>'bool'], 'Imagick::matteFloodfillImage' => ['bool', 'alpha'=>'float', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int'], 'Imagick::medianFilterImage' => ['bool', 'radius'=>'float'], -'Imagick::mergeImageLayers' => ['Imagick', 'layer_method'=>'int'], +'Imagick::mergeImageLayers' => ['Imagick', 'layer_method'=>'Imagick::LAYERMETHOD_*'], 'Imagick::minifyImage' => ['bool'], 'Imagick::modulateImage' => ['bool', 'brightness'=>'float', 'saturation'=>'float', 'hue'=>'float'], -'Imagick::montageImage' => ['Imagick', 'draw'=>'imagickdraw', 'tile_geometry'=>'string', 'thumbnail_geometry'=>'string', 'mode'=>'int', 'frame'=>'string'], +'Imagick::montageImage' => ['Imagick', 'draw'=>'imagickdraw', 'tile_geometry'=>'string', 'thumbnail_geometry'=>'string', 'mode'=>'Imagick::MONTAGEMODE_*', 'frame'=>'string'], 'Imagick::morphImages' => ['Imagick', 'number_frames'=>'int'], -'Imagick::morphology' => ['bool', 'morphologyMethod'=>'int', 'iterations'=>'int', 'ImagickKernel'=>'ImagickKernel', 'channel='=>'int'], +'Imagick::morphology' => ['bool', 'morphologyMethod'=>'Imagick::MORPHOLOGY_*', 'iterations'=>'int', 'ImagickKernel'=>'ImagickKernel', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::mosaicImages' => ['Imagick'], -'Imagick::motionBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float', 'channel='=>'int'], -'Imagick::negateImage' => ['bool', 'gray'=>'bool', 'channel='=>'int'], +'Imagick::motionBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::negateImage' => ['bool', 'gray'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::newImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'background'=>'mixed', 'format='=>'string'], 'Imagick::newPseudoImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'pseudostring'=>'string'], 'Imagick::next' => ['void'], 'Imagick::nextImage' => ['bool'], -'Imagick::normalizeImage' => ['bool', 'channel='=>'int'], +'Imagick::normalizeImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::oilPaintImage' => ['bool', 'radius'=>'float'], -'Imagick::opaquePaintImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'invert'=>'bool', 'channel='=>'int'], +'Imagick::opaquePaintImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'invert'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::optimizeImageLayers' => ['bool'], -'Imagick::orderedPosterizeImage' => ['bool', 'threshold_map'=>'string', 'channel='=>'int'], -'Imagick::paintFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int', 'channel='=>'int'], -'Imagick::paintOpaqueImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'channel='=>'int'], +'Imagick::orderedPosterizeImage' => ['bool', 'threshold_map'=>'string', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::paintFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::paintOpaqueImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::paintTransparentImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float'], 'Imagick::pingImage' => ['bool', 'filename'=>'string'], 'Imagick::pingImageBlob' => ['bool', 'image'=>'string'], @@ -4879,16 +4879,16 @@ 'Imagick::queryFontMetrics' => ['array{characterWidth:float,characterHeight:float,ascender:float,descender:float,textWidth:float,textHeight:float,maxHorizontalAdvance:float,boundingBox:array{x1:float,x2:float,y1:float,y2:float},originX:float,originY:float}', 'properties'=>'imagickdraw', 'text'=>'string', 'multiline='=>'bool'], 'Imagick::queryFonts' => ['list', 'pattern='=>'string'], 'Imagick::queryFormats' => ['list', 'pattern='=>'string'], -'Imagick::radialBlurImage' => ['bool', 'angle'=>'float', 'channel='=>'int'], +'Imagick::radialBlurImage' => ['bool', 'angle'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::raiseImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int', 'raise'=>'bool'], -'Imagick::randomThresholdImage' => ['bool', 'low'=>'float', 'high'=>'float', 'channel='=>'int'], +'Imagick::randomThresholdImage' => ['bool', 'low'=>'float', 'high'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::readImage' => ['bool', 'filename'=>'string'], 'Imagick::readImageBlob' => ['bool', 'image'=>'string', 'filename='=>'string'], 'Imagick::readImageFile' => ['bool', 'filehandle'=>'resource', 'filename='=>'string'], 'Imagick::readImages' => ['Imagick', 'filenames'=>'string'], 'Imagick::recolorImage' => ['bool', 'matrix'=>'array'], 'Imagick::reduceNoiseImage' => ['bool', 'radius'=>'float'], -'Imagick::remapImage' => ['bool', 'replacement'=>'imagick', 'dither'=>'int'], +'Imagick::remapImage' => ['bool', 'replacement'=>'imagick', 'dither'=>'Imagick::DITHERMETHOD_*'], 'Imagick::removeImage' => ['bool'], 'Imagick::removeImageProfile' => ['string', 'name'=>'string'], 'Imagick::render' => ['bool'], @@ -4899,28 +4899,28 @@ 'Imagick::rewind' => ['void'], 'Imagick::rollImage' => ['bool', 'x'=>'int', 'y'=>'int'], 'Imagick::rotateImage' => ['bool', 'background'=>'mixed', 'degrees'=>'float'], -'Imagick::rotationalBlurImage' => ['bool', 'float'=>'string', 'channel='=>'int'], +'Imagick::rotationalBlurImage' => ['bool', 'float'=>'string', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::roundCorners' => ['bool', 'x_rounding'=>'float', 'y_rounding'=>'float', 'stroke_width='=>'float', 'displace='=>'float', 'size_correction='=>'float'], 'Imagick::roundCornersImage' => ['bool', 'x_rounding'=>'', 'y_rounding'=>'', 'stroke_width='=>'', 'displace='=>'', 'size_correction='=>''], 'Imagick::sampleImage' => ['bool', 'columns'=>'int', 'rows'=>'int'], 'Imagick::scaleImage' => ['bool', 'cols'=>'int', 'rows'=>'int', 'bestfit='=>'bool', 'legacy='=>'bool'], -'Imagick::segmentImage' => ['bool', 'colorspace'=>'int', 'cluster_threshold'=>'float', 'smooth_threshold'=>'float', 'verbose='=>'bool'], -'Imagick::selectiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'threshold'=>'float', 'channel='=>'int'], -'Imagick::separateImageChannel' => ['bool', 'channel'=>'int'], +'Imagick::segmentImage' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*', 'cluster_threshold'=>'float', 'smooth_threshold'=>'float', 'verbose='=>'bool'], +'Imagick::selectiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::separateImageChannel' => ['bool', 'channel'=>'Imagick::CHANNEL_*'], 'Imagick::sepiaToneImage' => ['bool', 'threshold'=>'float'], 'Imagick::setAntiAlias' => ['int', 'antialias'=>'bool'], 'Imagick::setBackgroundColor' => ['bool', 'background'=>'mixed'], -'Imagick::setColorspace' => ['bool', 'colorspace'=>'int'], -'Imagick::setCompression' => ['bool', 'compression'=>'int'], +'Imagick::setColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], +'Imagick::setCompression' => ['bool', 'compression'=>'Imagick::COMPRESSION_*'], 'Imagick::setCompressionQuality' => ['bool', 'quality'=>'int'], 'Imagick::setFilename' => ['bool', 'filename'=>'string'], 'Imagick::setFirstIterator' => ['bool'], 'Imagick::setFont' => ['bool', 'font'=>'string'], 'Imagick::setFormat' => ['bool', 'format'=>'string'], -'Imagick::setGravity' => ['bool', 'gravity'=>'int'], +'Imagick::setGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], 'Imagick::setImage' => ['bool', 'replace'=>'imagick'], 'Imagick::setImageAlpha' => ['bool', 'alpha'=>'float'], -'Imagick::setImageAlphaChannel' => ['bool', 'mode'=>'int'], +'Imagick::setImageAlphaChannel' => ['bool', 'mode'=>'Imagick::ALPHACHANNEL_*'], 'Imagick::setImageArtifact' => ['bool', 'artifact'=>'string', 'value'=>'string'], 'Imagick::setImageAttribute' => ['bool', 'key'=>'string', 'value'=>'string'], 'Imagick::setImageBackgroundColor' => ['bool', 'background'=>'mixed'], @@ -4928,41 +4928,41 @@ 'Imagick::setImageBiasQuantum' => ['void', 'bias'=>'string'], 'Imagick::setImageBluePrimary' => ['bool', 'x'=>'float', 'y'=>'float'], 'Imagick::setImageBorderColor' => ['bool', 'border'=>'mixed'], -'Imagick::setImageChannelDepth' => ['bool', 'channel'=>'int', 'depth'=>'int'], -'Imagick::setImageChannelMask' => ['', 'channel'=>'int'], +'Imagick::setImageChannelDepth' => ['bool', 'channel'=>'Imagick::CHANNEL_*', 'depth'=>'int'], +'Imagick::setImageChannelMask' => ['', 'channel'=>'Imagick::CHANNEL_*'], 'Imagick::setImageClipMask' => ['bool', 'clip_mask'=>'imagick'], 'Imagick::setImageColormapColor' => ['bool', 'index'=>'int', 'color'=>'imagickpixel'], -'Imagick::setImageColorspace' => ['bool', 'colorspace'=>'int'], -'Imagick::setImageCompose' => ['bool', 'compose'=>'int'], -'Imagick::setImageCompression' => ['bool', 'compression'=>'int'], +'Imagick::setImageColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], +'Imagick::setImageCompose' => ['bool', 'compose'=>'Imagick::COMPOSITE_*'], +'Imagick::setImageCompression' => ['bool', 'compression'=>'Imagick::COMPRESSION_*'], 'Imagick::setImageCompressionQuality' => ['bool', 'quality'=>'int'], 'Imagick::setImageDelay' => ['bool', 'delay'=>'int'], 'Imagick::setImageDepth' => ['bool', 'depth'=>'int'], -'Imagick::setImageDispose' => ['bool', 'dispose'=>'int'], +'Imagick::setImageDispose' => ['bool', 'dispose'=>'Imagick::DISPOSE_*'], 'Imagick::setImageExtent' => ['bool', 'columns'=>'int', 'rows'=>'int'], 'Imagick::setImageFilename' => ['bool', 'filename'=>'string'], 'Imagick::setImageFormat' => ['bool', 'format'=>'string'], 'Imagick::setImageGamma' => ['bool', 'gamma'=>'float'], -'Imagick::setImageGravity' => ['bool', 'gravity'=>'int'], +'Imagick::setImageGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], 'Imagick::setImageGreenPrimary' => ['bool', 'x'=>'float', 'y'=>'float'], 'Imagick::setImageIndex' => ['bool', 'index'=>'int'], -'Imagick::setImageInterlaceScheme' => ['bool', 'interlace_scheme'=>'int'], -'Imagick::setImageInterpolateMethod' => ['bool', 'method'=>'int'], +'Imagick::setImageInterlaceScheme' => ['bool', 'interlace_scheme'=>'Imagick::INTERLACE_*'], +'Imagick::setImageInterpolateMethod' => ['bool', 'method'=>'Imagick::INTERPOLATE_*'], 'Imagick::setImageIterations' => ['bool', 'iterations'=>'int'], 'Imagick::setImageMatte' => ['bool', 'matte'=>'bool'], 'Imagick::setImageMatteColor' => ['bool', 'matte'=>'mixed'], 'Imagick::setImageOpacity' => ['bool', 'opacity'=>'float'], -'Imagick::setImageOrientation' => ['bool', 'orientation'=>'int'], +'Imagick::setImageOrientation' => ['bool', 'orientation'=>'Imagick::ORIENTATION_*'], 'Imagick::setImagePage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], 'Imagick::setImageProfile' => ['bool', 'name'=>'string', 'profile'=>'string'], 'Imagick::setImageProgressMonitor' => ['', 'filename'=>''], 'Imagick::setImageProperty' => ['bool', 'name'=>'string', 'value'=>'string'], 'Imagick::setImageRedPrimary' => ['bool', 'x'=>'float', 'y'=>'float'], -'Imagick::setImageRenderingIntent' => ['bool', 'rendering_intent'=>'int'], +'Imagick::setImageRenderingIntent' => ['bool', 'rendering_intent'=>'Imagick::RENDERINGINTENT_*'], 'Imagick::setImageResolution' => ['bool', 'x_resolution'=>'float', 'y_resolution'=>'float'], 'Imagick::setImageScene' => ['bool', 'scene'=>'int'], 'Imagick::setImageTicksPerSecond' => ['bool', 'ticks_per_second'=>'int'], -'Imagick::setImageType' => ['bool', 'image_type'=>'int'], +'Imagick::setImageType' => ['bool', 'image_type'=>'Imagick::IMGTYPE_*'], 'Imagick::setImageUnits' => ['bool', 'units'=>'int'], 'Imagick::setImageVirtualPixelMethod' => ['bool', 'method'=>'int'], 'Imagick::setImageWhitePoint' => ['bool', 'x'=>'float', 'y'=>'float'], @@ -4979,38 +4979,38 @@ 'Imagick::setSamplingFactors' => ['bool', 'factors'=>'array'], 'Imagick::setSize' => ['bool', 'columns'=>'int', 'rows'=>'int'], 'Imagick::setSizeOffset' => ['bool', 'columns'=>'int', 'rows'=>'int', 'offset'=>'int'], -'Imagick::setType' => ['bool', 'image_type'=>'int'], +'Imagick::setType' => ['bool', 'image_type'=>'Imagick::IMGTYPE_*'], 'Imagick::shadeImage' => ['bool', 'gray'=>'bool', 'azimuth'=>'float', 'elevation'=>'float'], 'Imagick::shadowImage' => ['bool', 'opacity'=>'float', 'sigma'=>'float', 'x'=>'int', 'y'=>'int'], -'Imagick::sharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'int'], +'Imagick::sharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::shaveImage' => ['bool', 'columns'=>'int', 'rows'=>'int'], 'Imagick::shearImage' => ['bool', 'background'=>'mixed', 'x_shear'=>'float', 'y_shear'=>'float'], -'Imagick::sigmoidalContrastImage' => ['bool', 'sharpen'=>'bool', 'alpha'=>'float', 'beta'=>'float', 'channel='=>'int'], -'Imagick::similarityImage' => ['Imagick', 'imagick'=>'Imagick', '&bestMatch'=>'array', '&similarity'=>'float', 'similarity_threshold'=>'float', 'metric'=>'int'], +'Imagick::sigmoidalContrastImage' => ['bool', 'sharpen'=>'bool', 'alpha'=>'float', 'beta'=>'float', 'channel='=>'Imagick::CHANNEL_*'], +'Imagick::similarityImage' => ['Imagick', 'imagick'=>'Imagick', '&bestMatch'=>'array', '&similarity'=>'float', 'similarity_threshold'=>'float', 'metric'=>'Imagick::METRIC_*'], 'Imagick::sketchImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float'], 'Imagick::smushImages' => ['Imagick', 'stack'=>'bool', 'offset'=>'int'], 'Imagick::solarizeImage' => ['bool', 'threshold'=>'0|positive-int'], -'Imagick::sparseColorImage' => ['bool', 'sparse_method'=>'int', 'arguments'=>'array', 'channel='=>'int'], +'Imagick::sparseColorImage' => ['bool', 'sparse_method'=>'Imagick::SPARSECOLORMETHOD_*', 'arguments'=>'array', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::spliceImage' => ['bool', 'width'=>'int', 'height'=>'int', 'x'=>'int', 'y'=>'int'], 'Imagick::spreadImage' => ['bool', 'radius'=>'float'], -'Imagick::statisticImage' => ['bool', 'type'=>'int', 'width'=>'int', 'height'=>'int', 'channel='=>'int'], +'Imagick::statisticImage' => ['bool', 'type'=>'Imagick::STATISTIC_*', 'width'=>'int', 'height'=>'int', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::steganoImage' => ['Imagick', 'watermark_wand'=>'imagick', 'offset'=>'int'], 'Imagick::stereoImage' => ['bool', 'offset_wand'=>'imagick'], 'Imagick::stripImage' => ['bool'], 'Imagick::subImageMatch' => ['Imagick', 'Imagick'=>'Imagick', '&w_offset='=>'array', '&w_similarity='=>'float'], 'Imagick::swirlImage' => ['bool', 'degrees'=>'float'], 'Imagick::textureImage' => ['Imagick', 'texture_wand'=>'imagick'], -'Imagick::thresholdImage' => ['bool', 'threshold'=>'float', 'channel='=>'int'], +'Imagick::thresholdImage' => ['bool', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::thumbnailImage' => ['bool', 'columns'=>'int', 'rows'=>'int', 'bestfit='=>'bool', 'fill='=>'bool', 'legacy='=>'bool'], 'Imagick::tintImage' => ['bool', 'tint'=>'mixed', 'opacity'=>'mixed'], 'Imagick::transformImage' => ['Imagick', 'crop'=>'string', 'geometry'=>'string'], -'Imagick::transformImageColorspace' => ['bool', 'colorspace'=>'int'], +'Imagick::transformImageColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], 'Imagick::transparentPaintImage' => ['bool', 'target'=>'mixed', 'alpha'=>'float', 'fuzz'=>'float', 'invert'=>'bool'], 'Imagick::transposeImage' => ['bool'], 'Imagick::transverseImage' => ['bool'], 'Imagick::trimImage' => ['bool', 'fuzz'=>'float'], 'Imagick::uniqueImageColors' => ['bool'], -'Imagick::unsharpMaskImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'amount'=>'float', 'threshold'=>'float', 'channel='=>'int'], +'Imagick::unsharpMaskImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'amount'=>'float', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], 'Imagick::valid' => ['bool'], 'Imagick::vignetteImage' => ['bool', 'blackpoint'=>'float', 'whitepoint'=>'float', 'x'=>'int', 'y'=>'int'], 'Imagick::waveImage' => ['bool', 'amplitude'=>'float', 'length'=>'float'], @@ -5027,9 +5027,9 @@ 'ImagickDraw::circle' => ['bool', 'ox'=>'float', 'oy'=>'float', 'px'=>'float', 'py'=>'float'], 'ImagickDraw::clear' => ['bool'], 'ImagickDraw::clone' => ['ImagickDraw'], -'ImagickDraw::color' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'], +'ImagickDraw::color' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'Imagick::PAINT_*'], 'ImagickDraw::comment' => ['bool', 'comment'=>'string'], -'ImagickDraw::composite' => ['bool', 'compose'=>'int', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float', 'compositewand'=>'imagick'], +'ImagickDraw::composite' => ['bool', 'compose'=>'Imagick::COMPOSITE_*', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float', 'compositewand'=>'imagick'], 'ImagickDraw::destroy' => ['bool'], 'ImagickDraw::ellipse' => ['bool', 'ox'=>'float', 'oy'=>'float', 'rx'=>'float', 'ry'=>'float', 'start'=>'float', 'end'=>'float'], 'ImagickDraw::getBorderColor' => ['ImagickPixel'], @@ -5069,7 +5069,7 @@ 'ImagickDraw::getTextUnderColor' => ['ImagickPixel'], 'ImagickDraw::getVectorGraphics' => ['string'], 'ImagickDraw::line' => ['bool', 'sx'=>'float', 'sy'=>'float', 'ex'=>'float', 'ey'=>'float'], -'ImagickDraw::matte' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'int'], +'ImagickDraw::matte' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'Imagick::PAINT_*'], 'ImagickDraw::pathClose' => ['bool'], 'ImagickDraw::pathCurveToAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x2'=>'float', 'y2'=>'float', 'x'=>'float', 'y'=>'float'], 'ImagickDraw::pathCurveToQuadraticBezierAbsolute' => ['bool', 'x1'=>'float', 'y1'=>'float', 'x'=>'float', 'y'=>'float'], @@ -5110,22 +5110,22 @@ 'ImagickDraw::scale' => ['bool', 'x'=>'float', 'y'=>'float'], 'ImagickDraw::setBorderColor' => ['bool', 'color'=>'ImagickPixel|string'], 'ImagickDraw::setClipPath' => ['bool', 'clip_mask'=>'string'], -'ImagickDraw::setClipRule' => ['bool', 'fill_rule'=>'int'], +'ImagickDraw::setClipRule' => ['bool', 'fill_rule'=>'Imagick::FILLRULE_*'], 'ImagickDraw::setClipUnits' => ['bool', 'clip_units'=>'int'], 'ImagickDraw::setDensity' => ['bool', 'density_string'=>'string'], 'ImagickDraw::setFillAlpha' => ['bool', 'opacity'=>'float'], 'ImagickDraw::setFillColor' => ['bool', 'fill_pixel'=>'ImagickPixel|string'], 'ImagickDraw::setFillOpacity' => ['bool', 'fillopacity'=>'float'], 'ImagickDraw::setFillPatternURL' => ['bool', 'fill_url'=>'string'], -'ImagickDraw::setFillRule' => ['bool', 'fill_rule'=>'int'], +'ImagickDraw::setFillRule' => ['bool', 'fill_rule'=>'Imagick::FILLRULE_*'], 'ImagickDraw::setFont' => ['bool', 'font_name'=>'string'], 'ImagickDraw::setFontFamily' => ['bool', 'font_family'=>'string'], 'ImagickDraw::setFontResolution' => ['bool', 'x'=>'float', 'y'=>'float'], 'ImagickDraw::setFontSize' => ['bool', 'pointsize'=>'float'], -'ImagickDraw::setFontStretch' => ['bool', 'fontstretch'=>'int'], -'ImagickDraw::setFontStyle' => ['bool', 'style'=>'int'], +'ImagickDraw::setFontStretch' => ['bool', 'fontstretch'=>'Imagick::STRETCH_*'], +'ImagickDraw::setFontStyle' => ['bool', 'style'=>'Imagick::STYLE_*'], 'ImagickDraw::setFontWeight' => ['bool', 'font_weight'=>'int'], -'ImagickDraw::setGravity' => ['bool', 'gravity'=>'int'], +'ImagickDraw::setGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], 'ImagickDraw::setOpacity' => ['void', 'opacity'=>'float'], 'ImagickDraw::setResolution' => ['void', 'x_resolution'=>'float', 'y_resolution'=>'float'], 'ImagickDraw::setStrokeAlpha' => ['bool', 'opacity'=>'float'], @@ -5133,15 +5133,15 @@ 'ImagickDraw::setStrokeColor' => ['bool', 'stroke_pixel'=>'ImagickPixel|string'], 'ImagickDraw::setStrokeDashArray' => ['bool', 'dasharray'=>'array'], 'ImagickDraw::setStrokeDashOffset' => ['bool', 'dash_offset'=>'float'], -'ImagickDraw::setStrokeLineCap' => ['bool', 'linecap'=>'int'], -'ImagickDraw::setStrokeLineJoin' => ['bool', 'linejoin'=>'int'], +'ImagickDraw::setStrokeLineCap' => ['bool', 'linecap'=>'Imagick::LINECAP_*'], +'ImagickDraw::setStrokeLineJoin' => ['bool', 'linejoin'=>'Imagick::LINEJOIN_*'], 'ImagickDraw::setStrokeMiterLimit' => ['bool', 'miterlimit'=>'int'], 'ImagickDraw::setStrokeOpacity' => ['bool', 'stroke_opacity'=>'float'], 'ImagickDraw::setStrokePatternURL' => ['bool', 'stroke_url'=>'string'], 'ImagickDraw::setStrokeWidth' => ['bool', 'stroke_width'=>'float'], -'ImagickDraw::setTextAlignment' => ['bool', 'alignment'=>'int'], +'ImagickDraw::setTextAlignment' => ['bool', 'alignment'=>'Imagick::ALIGN_*'], 'ImagickDraw::setTextAntialias' => ['bool', 'antialias'=>'bool'], -'ImagickDraw::setTextDecoration' => ['bool', 'decoration'=>'int'], +'ImagickDraw::setTextDecoration' => ['bool', 'decoration'=>'Imagick::DECORATION_*'], 'ImagickDraw::setTextDirection' => ['bool', 'direction'=>'int'], 'ImagickDraw::setTextEncoding' => ['bool', 'encoding'=>'string'], 'ImagickDraw::setTextInterlineSpacing' => ['void', 'spacing'=>'float'], @@ -5155,10 +5155,10 @@ 'ImagickDraw::translate' => ['bool', 'x'=>'float', 'y'=>'float'], 'ImagickKernel::addKernel' => ['void', 'ImagickKernel'=>'ImagickKernel'], 'ImagickKernel::addUnityKernel' => ['void'], -'ImagickKernel::fromBuiltin' => ['ImagickKernel', 'kernelType'=>'int', 'kernelString'=>'string'], +'ImagickKernel::fromBuiltin' => ['ImagickKernel', 'kernelType'=>'Imagick::KERNEL_*', 'kernelString'=>'string'], 'ImagickKernel::fromMatrix' => ['ImagickKernel', 'matrix'=>'array', 'origin='=>'array'], 'ImagickKernel::getMatrix' => ['list>'], -'ImagickKernel::scale' => ['void', 'scale'=>'float', 'normalizeFlag'=>'int'], +'ImagickKernel::scale' => ['void', 'scale'=>'float', 'normalizeFlag'=>'Imagick::NORMALIZE_KERNEL_*'], 'ImagickKernel::separate' => ['array'], 'ImagickPixel::__construct' => ['void', 'color='=>'string'], 'ImagickPixel::clear' => ['bool'], @@ -5953,7 +5953,7 @@ 'litespeed_response_headers' => ['array|false'], 'Locale::acceptFromHttp' => ['non-empty-string|false', 'header'=>'string'], 'Locale::canonicalize' => ['non-empty-string|null', 'locale'=>'string'], -'Locale::composeLocale' => ['string|false', 'subtags'=>'array'], +'Locale::composeLocale' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], 'Locale::filterMatches' => ['bool|null', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], 'Locale::getAllVariants' => ['array|null', 'locale'=>'string'], 'Locale::getDefault' => ['non-empty-string'], @@ -5971,7 +5971,7 @@ 'Locale::setDefault' => ['bool', 'locale'=>'string'], 'locale_accept_from_http' => ['non-empty-string|false', 'header'=>'string'], 'locale_canonicalize' => ['non-empty-string|null', 'locale'=>'string'], -'locale_compose' => ['string|false', 'subtags'=>'array'], +'locale_compose' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], 'locale_filter_matches' => ['bool|null', 'langtag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'], 'locale_get_all_variants' => ['array|null', 'locale'=>'string'], 'locale_get_default' => ['non-empty-string'], @@ -6142,7 +6142,7 @@ 'mapObj::zoomPoint' => ['int', 'nZoomFactor'=>'int', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'], 'mapObj::zoomRectangle' => ['int', 'oPixelExt'=>'rectObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj'], 'mapObj::zoomScale' => ['int', 'nScaleDenom'=>'float', 'oPixelPos'=>'pointObj', 'nImageWidth'=>'int', 'nImageHeight'=>'int', 'oGeorefExt'=>'rectObj', 'oMaxGeorefExt'=>'rectObj'], -'max' => ['', '...arg1'=>'array'], +'max' => ['', '...arg1'=>'non-empty-array'], 'max\'1' => ['', 'arg1'=>'', 'arg2'=>'', '...args='=>''], 'maxdb::__construct' => ['void', 'host='=>'string', 'username='=>'string', 'passwd='=>'string', 'dbname='=>'string', 'port='=>'int', 'socket='=>'string'], 'maxdb::affected_rows' => ['int', 'link'=>''], @@ -6318,7 +6318,7 @@ 'mb_decode_mimeheader' => ['string', 'string'=>'string'], 'mb_decode_numericentity' => ['string', 'string'=>'string', 'convmap'=>'array', 'encoding'=>'string'], 'mb_detect_encoding' => ['string|false', 'str'=>'string', 'encoding_list='=>'mixed', 'strict='=>'bool'], -'mb_detect_order' => ['bool|list', 'encoding_list='=>'mixed'], +'mb_detect_order' => ['bool|list', 'encoding_list='=>'non-empty-list|non-falsy-string'], 'mb_encode_mimeheader' => ['string', 'str'=>'string', 'charset='=>'string', 'transfer_encoding='=>'string', 'linefeed='=>'string', 'indent='=>'int'], 'mb_encode_numericentity' => ['string', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string', 'is_hex='=>'bool'], 'mb_encoding_aliases' => ['list|false', 'encoding'=>'string'], @@ -6514,7 +6514,7 @@ 'mhash_keygen_s2k' => ['string|false', 'hash'=>'int', 'input_password'=>'string', 'salt'=>'string', 'bytes'=>'int'], 'microtime' => ['mixed', 'get_as_float='=>'bool'], 'mime_content_type' => ['string|false', 'filename_or_stream'=>'string|resource'], -'min' => ['', '...arg1'=>'array'], +'min' => ['', '...arg1'=>'non-empty-array'], 'min\'1' => ['', 'arg1'=>'', 'arg2'=>'', '...args='=>''], 'ming_keypress' => ['int', 'char'=>'string'], 'ming_setcubicthreshold' => ['void', 'threshold'=>'int'], @@ -9438,7 +9438,7 @@ 'RecursiveFilterIterator::hasChildren' => ['bool'], 'RecursiveIterator::getChildren' => ['RecursiveIterator'], 'RecursiveIterator::hasChildren' => ['bool'], -'RecursiveIteratorIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'int', 'flags='=>'int'], +'RecursiveIteratorIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'RecursiveIteratorIterator::LEAVES_ONLY|RecursiveIteratorIterator::SELF_FIRST|RecursiveIteratorIterator::CHILD_FIRST', 'flags='=>'0|RecursiveIteratorIterator::CATCH_GET_CHILD'], 'RecursiveIteratorIterator::beginChildren' => ['void'], 'RecursiveIteratorIterator::beginIteration' => ['RecursiveIterator'], 'RecursiveIteratorIterator::callGetChildren' => ['RecursiveIterator'], @@ -10283,7 +10283,7 @@ 'scalebarObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], 'scalebarObj::setImageColor' => ['int', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], 'scalebarObj::updateFromString' => ['int', 'snippet'=>'string'], -'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'int', 'context='=>'resource'], +'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING| SCANDIR_SORT_NONE', 'context='=>'resource'], 'SDO_DAS_ChangeSummary::beginLogging' => [''], 'SDO_DAS_ChangeSummary::endLogging' => [''], 'SDO_DAS_ChangeSummary::getChangedDataObjects' => ['SDO_List'], @@ -11581,7 +11581,7 @@ 'SplFileObject::fgetcsv' => ['list|array{0: null}|false|null', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'SplFileObject::fgets' => ['string'], 'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], -'SplFileObject::flock' => ['bool', 'operation'=>'int', '&w_wouldblock='=>'int'], +'SplFileObject::flock' => ['bool', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], 'SplFileObject::fpassthru' => ['int'], 'SplFileObject::fputcsv' => ['int|false', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'SplFileObject::fread' => ['string|false', 'length'=>'int'], @@ -12026,8 +12026,8 @@ 'stream_set_timeout' => ['bool', 'stream'=>'resource', 'seconds'=>'int', 'microseconds='=>'int'], 'stream_set_write_buffer' => ['int', 'fp'=>'resource', 'buffer'=>'int'], 'stream_socket_accept' => ['resource|false', 'serverstream'=>'resource', 'timeout='=>'float', '&w_peername='=>'string'], -'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int', 'context='=>'resource'], -'stream_socket_enable_crypto' => ['0|bool', 'stream'=>'resource', 'enable'=>'bool', 'cryptokind='=>'int', 'sessionstream='=>'resource'], +'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int-mask', 'context='=>'resource'], +'stream_socket_enable_crypto' => ['0|bool', 'stream'=>'resource', 'enable'=>'bool', 'crypto_method='=>'STREAM_CRYPTO_METHOD_SSLv2_CLIENT|STREAM_CRYPTO_METHOD_SSLv3_CLIENT|STREAM_CRYPTO_METHOD_SSLv23_CLIENT|STREAM_CRYPTO_METHOD_ANY_CLIENT|STREAM_CRYPTO_METHOD_TLS_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT|STREAM_CRYPTO_METHOD_SSLv2_SERVER|STREAM_CRYPTO_METHOD_SSLv3_SERVER|STREAM_CRYPTO_METHOD_SSLv23_SERVER|STREAM_CRYPTO_METHOD_ANY_SERVER|STREAM_CRYPTO_METHOD_TLS_SERVER|STREAM_CRYPTO_METHOD_TLSv1_0_SERVER|STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER|STREAM_CRYPTO_METHOD_TLSv1_3_SERVER', 'session_stream='=>'resource'], 'stream_socket_get_name' => ['string|false', 'stream'=>'resource', 'want_peer'=>'bool'], 'stream_socket_pair' => ['resource[]|false', 'domain'=>'int', 'type'=>'int', 'protocol'=>'int'], 'stream_socket_recvfrom' => ['string|false', 'stream'=>'resource', 'amount'=>'int', 'flags='=>'int', '&w_remote_addr='=>'string'], diff --git a/resources/functionMap_bleedingEdge.php b/resources/functionMap_bleedingEdge.php index 11dd4fa7730..92f41e9db07 100644 --- a/resources/functionMap_bleedingEdge.php +++ b/resources/functionMap_bleedingEdge.php @@ -2,157 +2,7 @@ return [ 'new' => [ - 'bcadd' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'bccomp' => ['int', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'bcdiv' => ['numeric-string|null', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'bcmod' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'bcmul' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'bcpow' => ['numeric-string', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'scale='=>'int'], - 'bcpowmod' => ['numeric-string|null', 'base'=>'numeric-string', 'exponent'=>'numeric-string', 'modulus'=>'string', 'scale='=>'int'], - 'bcsqrt' => ['numeric-string', 'operand'=>'numeric-string', 'scale='=>'int'], - 'bcsub' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], - 'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|class-string|\'static\'|null'], - 'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|class-string|\'static\'|null'], - 'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|2|3|4', 'destination='=>'string', 'extra_headers='=>'string'], - 'SplFileObject::flock' => ['bool', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], - 'Imagick::adaptiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::adaptiveSharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::addNoiseImage' => ['bool', 'noise_type'=>'Imagick::NOISE_*', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::autoGammaImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::autoLevelImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::blurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::brightnessContrastImage' => ['bool', 'brightness'=>'float', 'contrast'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::clampImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::combineImages' => ['Imagick', 'channeltype'=>'Imagick::CHANNEL_*'], - 'Imagick::compareImageChannels' => ['array{Imagick,float}', 'image'=>'imagick', 'channeltype'=>'Imagick::CHANNEL_*', 'metrictype'=>'Imagick::METRIC_*'], - 'Imagick::compareImageLayers' => ['Imagick', 'method'=>'Imagick::LAYERMETHOD_*'], - 'Imagick::compareImages' => ['array{Imagick,float}', 'compare'=>'imagick', 'metric'=>'Imagick::METRIC_*'], - 'Imagick::compositeImage' => ['bool', 'composite_object'=>'imagick', 'composite'=>'Imagick::COMPOSITE_*', 'x'=>'int', 'y'=>'int', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::contrastStretchImage' => ['bool', 'black_point'=>'float', 'white_point'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::convolveImage' => ['bool', 'kernel'=>'array', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::distortImage' => ['bool', 'method'=>'Imagick::DISTORTION_*', 'arguments'=>'array', 'bestfit'=>'bool'], - 'Imagick::evaluateImage' => ['bool', 'op'=>'Imagick::EVALUATE_*', 'constant'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::exportImagePixels' => ['list', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'Imagick::PIXEL_*'], - 'Imagick::floodFillPaintImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'target'=>'mixed', 'x'=>'int', 'y'=>'int', 'invert'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::functionImage' => ['bool', 'function'=>'Imagick::FUNCTION_*', 'arguments'=>'array', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::fxImage' => ['Imagick', 'expression'=>'string', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::gammaImage' => ['bool', 'gamma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::gaussianBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelDepth' => ['int', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelDistortion' => ['float', 'reference'=>'imagick', 'channel'=>'Imagick::CHANNEL_*', 'metric'=>'Imagick::METRIC_*'], - 'Imagick::getImageChannelDistortions' => ['float', 'reference'=>'imagick', 'metric'=>'Imagick::METRIC_*', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelExtrema' => ['array{minima:0|positive-int,maxima:0|positive-int}', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelKurtosis' => ['array{kurtosis:float,skewness:float}', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelMean' => ['array{mean:float,standardDeviation:float}', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::getImageChannelRange' => ['array{minima:float,maxima:float}', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::getImageDistortion' => ['float', 'reference'=>'magickwand', 'metric'=>'Imagick::METRIC_*'], - 'Imagick::getResource' => ['int', 'type'=>'Imagick::RESOURCETYPE_*'], - 'Imagick::getResourceLimit' => ['int', 'type'=>'Imagick::RESOURCETYPE_*'], - 'Imagick::importImagePixels' => ['bool', 'x'=>'int', 'y'=>'int', 'width'=>'int', 'height'=>'int', 'map'=>'string', 'storage'=>'Imagick::PIXEL_*', 'pixels'=>'array'], - 'Imagick::levelImage' => ['bool', 'blackpoint'=>'float', 'gamma'=>'float', 'whitepoint'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::mergeImageLayers' => ['Imagick', 'layer_method'=>'Imagick::LAYERMETHOD_*'], - 'Imagick::montageImage' => ['Imagick', 'draw'=>'imagickdraw', 'tile_geometry'=>'string', 'thumbnail_geometry'=>'string', 'mode'=>'Imagick::MONTAGEMODE_*', 'frame'=>'string'], - 'Imagick::morphology' => ['bool', 'morphologyMethod'=>'Imagick::MORPHOLOGY_*', 'iterations'=>'int', 'ImagickKernel'=>'ImagickKernel', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::motionBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'angle'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::negateImage' => ['bool', 'gray'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::normalizeImage' => ['bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::opaquePaintImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'invert'=>'bool', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::orderedPosterizeImage' => ['bool', 'threshold_map'=>'string', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::paintFloodfillImage' => ['bool', 'fill'=>'mixed', 'fuzz'=>'float', 'bordercolor'=>'mixed', 'x'=>'int', 'y'=>'int', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::paintOpaqueImage' => ['bool', 'target'=>'mixed', 'fill'=>'mixed', 'fuzz'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::radialBlurImage' => ['bool', 'angle'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::randomThresholdImage' => ['bool', 'low'=>'float', 'high'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::remapImage' => ['bool', 'replacement'=>'imagick', 'dither'=>'Imagick::DITHERMETHOD_*'], - 'Imagick::rotationalBlurImage' => ['bool', 'float'=>'string', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::segmentImage' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*', 'cluster_threshold'=>'float', 'smooth_threshold'=>'float', 'verbose='=>'bool'], - 'Imagick::selectiveBlurImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::separateImageChannel' => ['bool', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::setColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], - 'Imagick::setCompression' => ['bool', 'compression'=>'Imagick::COMPRESSION_*'], - 'Imagick::setGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], - 'Imagick::setImageAlphaChannel' => ['bool', 'mode'=>'Imagick::ALPHACHANNEL_*'], - 'Imagick::setImageChannelDepth' => ['bool', 'channel'=>'Imagick::CHANNEL_*', 'depth'=>'int'], - 'Imagick::setImageChannelMask' => ['', 'channel'=>'Imagick::CHANNEL_*'], - 'Imagick::setImageClipMask' => ['bool', 'clip_mask'=>'imagick'], - 'Imagick::setImageColormapColor' => ['bool', 'index'=>'int', 'color'=>'imagickpixel'], - 'Imagick::setImageColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], - 'Imagick::setImageCompose' => ['bool', 'compose'=>'Imagick::COMPOSITE_*'], - 'Imagick::setImageCompression' => ['bool', 'compression'=>'Imagick::COMPRESSION_*'], - 'Imagick::setImageDispose' => ['bool', 'dispose'=>'Imagick::DISPOSE_*'], - 'Imagick::setImageGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], - 'Imagick::setImageInterlaceScheme' => ['bool', 'interlace_scheme'=>'Imagick::INTERLACE_*'], - 'Imagick::setImageInterpolateMethod' => ['bool', 'method'=>'Imagick::INTERPOLATE_*'], - 'Imagick::setImageOrientation' => ['bool', 'orientation'=>'Imagick::ORIENTATION_*'], - 'Imagick::setImageRenderingIntent' => ['bool', 'rendering_intent'=>'Imagick::RENDERINGINTENT_*'], - 'Imagick::setImageType' => ['bool', 'image_type'=>'Imagick::IMGTYPE_*'], - 'Imagick::setType' => ['bool', 'image_type'=>'Imagick::IMGTYPE_*'], - 'Imagick::sharpenImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::sigmoidalContrastImage' => ['bool', 'sharpen'=>'bool', 'alpha'=>'float', 'beta'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::similarityImage' => ['Imagick', 'imagick'=>'Imagick', '&bestMatch'=>'array', '&similarity'=>'float', 'similarity_threshold'=>'float', 'metric'=>'Imagick::METRIC_*'], - 'Imagick::sparseColorImage' => ['bool', 'sparse_method'=>'Imagick::SPARSECOLORMETHOD_*', 'arguments'=>'array', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::statisticImage' => ['bool', 'type'=>'Imagick::STATISTIC_*', 'width'=>'int', 'height'=>'int', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::thresholdImage' => ['bool', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'Imagick::transformImageColorspace' => ['bool', 'colorspace'=>'Imagick::COLORSPACE_*'], - 'Imagick::unsharpMaskImage' => ['bool', 'radius'=>'float', 'sigma'=>'float', 'amount'=>'float', 'threshold'=>'float', 'channel='=>'Imagick::CHANNEL_*'], - 'ImagickDraw::color' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'Imagick::PAINT_*'], - 'ImagickDraw::composite' => ['bool', 'compose'=>'Imagick::COMPOSITE_*', 'x'=>'float', 'y'=>'float', 'width'=>'float', 'height'=>'float', 'compositewand'=>'imagick'], - 'ImagickDraw::getFillRule' => ['Imagick::FILLRULE_*'], - 'ImagickDraw::getFontStretch' => ['Imagick::STRETCH_*'], - 'ImagickDraw::getFontStyle' => ['Imagick::STYLE_*'], - 'ImagickDraw::getGravity' => ['Imagick::GRAVITY_*'], - 'ImagickDraw::getStrokeLineCap' => ['Imagick::LINECAP_*'], - 'ImagickDraw::getStrokeLineJoin' => ['Imagick::LINEJOIN_*'], - 'ImagickDraw::getTextAlignment' => ['Imagick::ALIGN_*'], - 'ImagickDraw::getTextDecoration' => ['Imagick::DECORATION_*'], - 'ImagickDraw::matte' => ['bool', 'x'=>'float', 'y'=>'float', 'paintmethod'=>'Imagick::PAINT_*'], - 'ImagickDraw::setClipRule' => ['bool', 'fill_rule'=>'Imagick::FILLRULE_*'], - 'ImagickDraw::setFillRule' => ['bool', 'fill_rule'=>'Imagick::FILLRULE_*'], - 'ImagickDraw::setFontStretch' => ['bool', 'fontstretch'=>'Imagick::STRETCH_*'], - 'ImagickDraw::setFontStyle' => ['bool', 'style'=>'Imagick::STYLE_*'], - 'ImagickDraw::setGravity' => ['bool', 'gravity'=>'Imagick::GRAVITY_*'], - 'ImagickDraw::setStrokeLineCap' => ['bool', 'linecap'=>'Imagick::LINECAP_*'], - 'ImagickDraw::setStrokeLineJoin' => ['bool', 'linejoin'=>'Imagick::LINEJOIN_*'], - 'ImagickDraw::setTextAlignment' => ['bool', 'alignment'=>'Imagick::ALIGN_*'], - 'ImagickDraw::setTextAntialias' => ['bool', 'antialias'=>'bool'], - 'ImagickDraw::setTextDecoration' => ['bool', 'decoration'=>'Imagick::DECORATION_*'], - 'ImagickKernel::fromBuiltin' => ['ImagickKernel', 'kernelType'=>'Imagick::KERNEL_*', 'kernelString'=>'string'], - 'ImagickKernel::scale' => ['void', 'scale'=>'float', 'normalizeFlag'=>'Imagick::NORMALIZE_KERNEL_*'], - 'imagecolorallocate' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], - 'imagecolorallocatealpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], - 'imagecolorclosest' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], - 'imagecolorclosestalpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], - 'imagecolorclosesthwb' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], - 'imagecolorexact' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], - 'imagecolorexactalpha' => ['int<0, max>|false', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], - 'imagecolorresolve' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>'], - 'imagecolorresolvealpha' => ['int<0, max>', 'im'=>'resource', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha'=>'int<0, 127>'], - 'imagecolorset' => ['void', 'im'=>'resource', 'col'=>'int', 'red'=>'int<0, 255>', 'green'=>'int<0, 255>', 'blue'=>'int<0, 255>', 'alpha='=>'int<0, 127>'], - 'imagecreate' => ['__benevolent', 'x_size'=>'int<1, max>', 'y_size'=>'int<1, max>'], - 'imagecreatetruecolor' => ['__benevolent', 'x_size'=>'int<1, max>', 'y_size'=>'int<1, max>'], - 'max' => ['', '...arg1'=>'non-empty-array'], - 'mb_detect_order' => ['bool|list', 'encoding_list='=>'non-empty-list|non-falsy-string'], - 'min' => ['', '...arg1'=>'non-empty-array'], - 'file' => ['list|false', 'filename'=>'string', 'flags='=>'int-mask', 'context='=>'resource'], - 'flock' => ['bool', 'fp'=>'resource', 'operation'=>'int-mask', '&w_wouldblock='=>'0|1'], - 'ftp_append' => ['bool', 'ftp'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY'], - 'ftp_fget' => ['bool', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resumepos='=>'int'], - 'ftp_fput' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], - 'ftp_get' => ['bool', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resume_pos='=>'int'], - 'ftp_nb_fget' => ['int', 'stream'=>'resource', 'fp'=>'resource', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resumepos='=>'int'], - 'ftp_nb_fput' => ['int', 'stream'=>'resource', 'remote_file'=>'string', 'fp'=>'resource', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], - 'ftp_nb_get' => ['int|false', 'stream'=>'resource', 'local_file'=>'string', 'remote_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'resume_pos='=>'int'], - 'ftp_nb_put' => ['int|false', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], - 'ftp_put' => ['bool', 'stream'=>'resource', 'remote_file'=>'string', 'local_file'=>'string', 'mode='=>'FTP_ASCII|FTP_BINARY', 'startpos='=>'int'], - 'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING| SCANDIR_SORT_NONE', 'context='=>'resource'], - 'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int-mask', 'context='=>'resource'], - 'stream_socket_enable_crypto' => ['0|bool', 'stream'=>'resource', 'enable'=>'bool', 'crypto_method='=>'STREAM_CRYPTO_METHOD_SSLv2_CLIENT|STREAM_CRYPTO_METHOD_SSLv3_CLIENT|STREAM_CRYPTO_METHOD_SSLv23_CLIENT|STREAM_CRYPTO_METHOD_ANY_CLIENT|STREAM_CRYPTO_METHOD_TLS_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT|STREAM_CRYPTO_METHOD_SSLv2_SERVER|STREAM_CRYPTO_METHOD_SSLv3_SERVER|STREAM_CRYPTO_METHOD_SSLv23_SERVER|STREAM_CRYPTO_METHOD_ANY_SERVER|STREAM_CRYPTO_METHOD_TLS_SERVER|STREAM_CRYPTO_METHOD_TLSv1_0_SERVER|STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER|STREAM_CRYPTO_METHOD_TLSv1_3_SERVER', 'session_stream='=>'resource'], - 'extract' => ['0|positive-int', '&rw_var_array'=>'array', 'extract_type='=>'EXTR_OVERWRITE|EXTR_SKIP|EXTR_PREFIX_SAME|EXTR_PREFIX_ALL|EXTR_PREFIX_INVALID|EXTR_IF_EXISTS|EXTR_PREFIX_IF_EXISTS|EXTR_REFS', 'prefix='=>'string|null'], - 'RecursiveIteratorIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'RecursiveIteratorIterator::LEAVES_ONLY|RecursiveIteratorIterator::SELF_FIRST|RecursiveIteratorIterator::CHILD_FIRST', 'flags='=>'0|RecursiveIteratorIterator::CATCH_GET_CHILD'], - 'Locale::composeLocale' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], - 'locale_compose' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], - 'count' => ['0|positive-int', 'var'=>'Countable|array', 'mode='=>'0|1'], ], 'old' => [ - - ] + ], ]; diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 1b234d2280d..8b5c5b9f422 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -43,8 +43,11 @@ 'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], 'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], + 'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|3|4', 'destination='=>'string', 'extra_headers='=>'string'], 'explode' => ['list', 'separator'=>'non-empty-string', 'str'=>'string', 'limit='=>'int'], 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], + 'filter_input' => ['mixed', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'variable_name'=>'string', 'filter='=>'int', 'options='=>'array|int'], + 'filter_input_array' => ['array|false|null', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'definition='=>'int|array', 'add_empty='=>'bool'], 'floor' => ['float', 'number'=>'float'], 'forward_static_call_array' => ['mixed', 'function'=>'callable', 'parameters'=>'array'], 'get_debug_type' => ['string', 'var'=>'mixed'], @@ -52,11 +55,11 @@ 'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'], 'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'hash' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], - 'hash_hkdf' => ['non-falsy-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'hash_hkdf' => ['non-falsy-string', 'algo'=>'non-falsy-string', 'key'=>'string', 'length='=>'0|positive-int', 'info='=>'string', 'salt='=>'string'], 'hash_hmac' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], - 'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], + 'hash_pbkdf2' => ['non-empty-string', 'algo'=>'non-falsy-string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'positive-int', 'length='=>'0|positive-int', 'raw_output='=>'bool'], 'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], - 'imagecreate' => ['__benevolent', 'width'=>'int', 'height'=>'int'], + 'imagecreate' => ['__benevolent', 'width'=>'int<1, max>', 'height'=>'int<1, max>'], 'imagecreatefrombmp' => ['false|object', 'filename'=>'string'], 'imagecreatefromgd' => ['false|object', 'filename'=>'string'], 'imagecreatefromgd2' => ['false|object', 'filename'=>'string'], @@ -69,7 +72,7 @@ 'imagecreatefromwebp' => ['false|object', 'filename'=>'string'], 'imagecreatefromxbm' => ['false|object', 'filename'=>'string'], 'imagecreatefromxpm' => ['false|object', 'filename'=>'string'], - 'imagecreatetruecolor' => ['__benevolent', 'width'=>'int', 'height'=>'int'], + 'imagecreatetruecolor' => ['__benevolent', 'width'=>'int<1, max>', 'height'=>'int<1, max>'], 'imagecrop' => ['false|object', 'im'=>'resource', 'rect'=>'array'], 'imagecropauto' => ['false|object', 'im'=>'resource', 'mode'=>'int', 'threshold'=>'float', 'color'=>'int'], 'imagegetclip' => ['array', 'im'=>'resource'], @@ -80,6 +83,7 @@ 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], + 'mb_detect_order' => ['bool|list', 'encoding_list='=>'non-empty-list|non-falsy-string|null'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], 'mb_str_split' => ['list', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], 'mb_strlen' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'], diff --git a/resources/functionMap_php80delta_bleedingEdge.php b/resources/functionMap_php80delta_bleedingEdge.php index 82e9b720a82..92f41e9db07 100644 --- a/resources/functionMap_php80delta_bleedingEdge.php +++ b/resources/functionMap_php80delta_bleedingEdge.php @@ -2,16 +2,7 @@ return [ 'new' => [ - 'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|3|4', 'destination='=>'string', 'extra_headers='=>'string'], - 'filter_input' => ['mixed', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'variable_name'=>'string', 'filter='=>'int', 'options='=>'array|int'], - 'filter_input_array' => ['array|false|null', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'definition='=>'int|array', 'add_empty='=>'bool'], - 'hash_hkdf' => ['non-falsy-string', 'algo'=>'non-falsy-string', 'key'=>'string', 'length='=>'0|positive-int', 'info='=>'string', 'salt='=>'string'], - 'hash_pbkdf2' => ['non-empty-string', 'algo'=>'non-falsy-string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'positive-int', 'length='=>'0|positive-int', 'raw_output='=>'bool'], - 'imagecreate' => ['__benevolent', 'width'=>'int<1, max>', 'height'=>'int<1, max>'], - 'imagecreatetruecolor' => ['__benevolent', 'width'=>'int<1, max>', 'height'=>'int<1, max>'], - 'mb_detect_order' => ['bool|list', 'encoding_list='=>'non-empty-list|non-falsy-string|null'], ], 'old' => [ - - ] + ], ]; From fd570f1f39eea7aa550dfb6ea6204678960e3861 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 14:39:04 +0200 Subject: [PATCH 0358/3097] Empty skipCheckGenericClasses parameter array --- conf/config.neon | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 8e53e033438..a7377957003 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -23,18 +23,7 @@ parameters: featureToggles: bleedingEdge: false disableRuntimeReflectionProvider: true - skipCheckGenericClasses: - - DatePeriod - - CallbackFilterIterator - - FilterIterator - - RecursiveCallbackFilterIterator - - AppendIterator - - NoRewindIterator - - LimitIterator - - InfiniteIterator - - CachingIterator - - RegexIterator - - ReflectionEnum + skipCheckGenericClasses: [] explicitMixedViaIsArray: false arrayFilter: false arrayUnpacking: false From 5677025877c6a8aef51b500cd461e8dbda6dab8c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:18:36 +0200 Subject: [PATCH 0359/3097] Made IssetExpr part of BC promise --- src/Node/IssetExpr.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Node/IssetExpr.php b/src/Node/IssetExpr.php index fbe53cbc6a1..be1558fe7bd 100644 --- a/src/Node/IssetExpr.php +++ b/src/Node/IssetExpr.php @@ -4,9 +4,15 @@ use PhpParser\Node\Expr; +/** + * @api + */ final class IssetExpr extends Expr implements VirtualNode { + /** + * @api + */ public function __construct( private Expr $expr, ) From 99c831c160c31762686af92b683334f07c577f12 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:20:52 +0200 Subject: [PATCH 0360/3097] [BE] Detect duplicate stub classes and functions --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/PhpDoc/StubValidator.php | 14 ++++++-------- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index d84b7cf2e8d..882c1decbd7 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -51,7 +51,6 @@ Bleeding edge (TODO move to other sections) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) * Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) @@ -128,6 +127,7 @@ Improvements 🔧 * Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! +* Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6c988343b3b..bac0b4dedf6 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,7 +22,6 @@ parameters: nullContextForVoidReturningFunctions: true unescapeStrings: true alwaysCheckTooWideReturnTypeFinalMethods: true - duplicateStubs: true logicalXor: true betterNoop: true alwaysTrueAlwaysReported: true diff --git a/conf/config.neon b/conf/config.neon index a7377957003..e28fbc692a8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,7 +43,6 @@ parameters: nullContextForVoidReturningFunctions: false unescapeStrings: false alwaysCheckTooWideReturnTypeFinalMethods: false - duplicateStubs: false logicalXor: false betterNoop: false alwaysTrueAlwaysReported: false @@ -426,8 +425,6 @@ services: - class: PHPStan\PhpDoc\StubValidator - arguments: - duplicateStubs: %featureToggles.duplicateStubs% - class: PHPStan\PhpDoc\SocketSelectStubFilesExtension diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index c5d8a0b8217..90aca4d5828 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() - duplicateStubs: bool() logicalXor: bool() betterNoop: bool() alwaysTrueAlwaysReported: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index e37624c68b2..96922b4c091 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -95,7 +95,6 @@ final class StubValidator public function __construct( private DerivativeContainerFactory $derivativeContainerFactory, - private bool $duplicateStubs, ) { } @@ -188,6 +187,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $mixinCheck = $container->getByType(MixinCheck::class); $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); + $reflector = $container->getService('stubReflector'); + $relativePathHelper = $container->getService('simpleRelativePathHelper'); $rules = [ // level 0 @@ -247,14 +248,11 @@ private function getRuleRegistry(Container $container): RuleRegistry new MissingMethodReturnTypehintRule($missingTypehintCheck), new MissingPropertyTypehintRule($missingTypehintCheck), new MissingMethodSelfOutTypeRule($missingTypehintCheck), - ]; - if ($this->duplicateStubs) { - $reflector = $container->getService('stubReflector'); - $relativePathHelper = $container->getService('simpleRelativePathHelper'); - $rules[] = new DuplicateClassDeclarationRule($reflector, $relativePathHelper); - $rules[] = new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper); - } + // duplicate stubs + new DuplicateClassDeclarationRule($reflector, $relativePathHelper), + new DuplicateFunctionDeclarationRule($reflector, $relativePathHelper), + ]; return new DirectRuleRegistry($rules); } From ad150281e163a86b33aff3e674c046efd4823972 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:25:09 +0200 Subject: [PATCH 0361/3097] [BE] LogicalXorConstantConditionRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 4 ++-- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 882c1decbd7..d692b7a4c92 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -10,6 +10,7 @@ Major new features 🚀 * **Enhancements in Handling Parameters Passed by Reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! +* LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -69,7 +70,6 @@ Bleeding edge (TODO move to other sections) * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) -* LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * NoopRule - report top-level `xor` because that's probably not what the user intended to do (https://github.com/phpstan/phpstan-src/commit/a1fffb3346e09f1e8e8d987d4282263295a55142), #10267 * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index bac0b4dedf6..b872cc3b987 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,7 +22,6 @@ parameters: nullContextForVoidReturningFunctions: true unescapeStrings: true alwaysCheckTooWideReturnTypeFinalMethods: true - logicalXor: true betterNoop: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 327f98e1130..f28b2e2f9a7 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -26,8 +26,6 @@ conditionalTags: phpstan.collector: %featureToggles.notAnalysedTrait% PHPStan\Rules\Traits\NotAnalysedTraitRule: phpstan.rules.rule: %featureToggles.notAnalysedTrait% - PHPStan\Rules\Comparison\LogicalXorConstantConditionRule: - phpstan.rules.rule: %featureToggles.logicalXor% PHPStan\Rules\DeadCode\BetterNoopRule: phpstan.rules.rule: %featureToggles.betterNoop% PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: @@ -199,6 +197,8 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\DeadCode\BetterNoopRule diff --git a/conf/config.neon b/conf/config.neon index e28fbc692a8..079f1b457d0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,7 +43,6 @@ parameters: nullContextForVoidReturningFunctions: false unescapeStrings: false alwaysCheckTooWideReturnTypeFinalMethods: false - logicalXor: false betterNoop: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 90aca4d5828..21e82bdaa0f 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() - logicalXor: bool() betterNoop: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() From f2809dd5d487b33e20688e2184c06a238c019032 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:28:16 +0200 Subject: [PATCH 0362/3097] [BE] New better NoopRule --- changelog-2.0.md | 3 +-- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 6 +----- conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Rules/DeadCode/{BetterNoopRule.php => NoopRule.php} | 2 +- .../DeadCode/{BetterNoopRuleTest.php => NoopRuleTest.php} | 6 +++--- 7 files changed, 6 insertions(+), 15 deletions(-) rename src/Rules/DeadCode/{BetterNoopRule.php => NoopRule.php} (98%) rename tests/PHPStan/Rules/DeadCode/{BetterNoopRuleTest.php => NoopRuleTest.php} (95%) diff --git a/changelog-2.0.md b/changelog-2.0.md index d692b7a4c92..6941b58e5a6 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -70,7 +70,6 @@ Bleeding edge (TODO move to other sections) * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) -* NoopRule - report top-level `xor` because that's probably not what the user intended to do (https://github.com/phpstan/phpstan-src/commit/a1fffb3346e09f1e8e8d987d4282263295a55142), #10267 * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) * Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) @@ -83,7 +82,6 @@ Bleeding edge (TODO move to other sections) * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! -* BetterNoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 @@ -128,6 +126,7 @@ Improvements 🔧 * Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! * Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) +* NoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index b872cc3b987..5eb0bf1979b 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,7 +22,6 @@ parameters: nullContextForVoidReturningFunctions: true unescapeStrings: true alwaysCheckTooWideReturnTypeFinalMethods: true - betterNoop: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true varTagType: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index f28b2e2f9a7..cc3cdff418f 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -3,6 +3,7 @@ includes: rules: - PHPStan\Rules\Arrays\DeadForeachRule + - PHPStan\Rules\DeadCode\NoopRule - PHPStan\Rules\DeadCode\UnreachableStatementRule - PHPStan\Rules\DeadCode\UnusedPrivateConstantRule - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule @@ -26,8 +27,6 @@ conditionalTags: phpstan.collector: %featureToggles.notAnalysedTrait% PHPStan\Rules\Traits\NotAnalysedTraitRule: phpstan.rules.rule: %featureToggles.notAnalysedTrait% - PHPStan\Rules\DeadCode\BetterNoopRule: - phpstan.rules.rule: %featureToggles.betterNoop% PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureNewCollector: @@ -200,9 +199,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\DeadCode\BetterNoopRule - - class: PHPStan\Rules\Comparison\MatchExpressionRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 079f1b457d0..cb5855c70e9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,14 +43,12 @@ parameters: nullContextForVoidReturningFunctions: false unescapeStrings: false alwaysCheckTooWideReturnTypeFinalMethods: false - betterNoop: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false varTagType: false closureDefaultParameterTypeRule: false instanceofType: false paramOutVariance: false - strictStaticMethodTemplateTypeVariance: false propertyVariance: false stricterFunctionMap: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 21e82bdaa0f..be829608c9c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() - betterNoop: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() varTagType: bool() diff --git a/src/Rules/DeadCode/BetterNoopRule.php b/src/Rules/DeadCode/NoopRule.php similarity index 98% rename from src/Rules/DeadCode/BetterNoopRule.php rename to src/Rules/DeadCode/NoopRule.php index 2e246941b79..abc5200a719 100644 --- a/src/Rules/DeadCode/BetterNoopRule.php +++ b/src/Rules/DeadCode/NoopRule.php @@ -15,7 +15,7 @@ /** * @implements Rule */ -final class BetterNoopRule implements Rule +final class NoopRule implements Rule { public function __construct(private ExprPrinter $exprPrinter) diff --git a/tests/PHPStan/Rules/DeadCode/BetterNoopRuleTest.php b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php similarity index 95% rename from tests/PHPStan/Rules/DeadCode/BetterNoopRuleTest.php rename to tests/PHPStan/Rules/DeadCode/NoopRuleTest.php index a12768aa0f7..2e082297d1c 100644 --- a/tests/PHPStan/Rules/DeadCode/BetterNoopRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php @@ -8,14 +8,14 @@ use PHPStan\Testing\RuleTestCase; /** - * @extends RuleTestCase + * @extends RuleTestCase */ -class BetterNoopRuleTest extends RuleTestCase +class NoopRuleTest extends RuleTestCase { protected function getRule(): Rule { - return new BetterNoopRule(new ExprPrinter(new Printer())); + return new NoopRule(new ExprPrinter(new Printer())); } public function testRule(): void From 660cf2d19f032a5902badf861d051202ee36792f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:31:48 +0200 Subject: [PATCH 0363/3097] Remove unused feature toggle --- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 3 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 5eb0bf1979b..6ee6d1fe290 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -30,7 +30,6 @@ parameters: paramOutVariance: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true - zeroFiles: true projectServicesNotInAnalysedPaths: true callUserFunc: true magicConstantOutOfContext: true diff --git a/conf/config.neon b/conf/config.neon index cb5855c70e9..ae1b9546871 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -52,7 +52,6 @@ parameters: strictStaticMethodTemplateTypeVariance: false propertyVariance: false stricterFunctionMap: false - zeroFiles: false projectServicesNotInAnalysedPaths: false callUserFunc: false magicConstantOutOfContext: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index be829608c9c..efeb421ebce 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -58,7 +58,6 @@ parametersSchema: strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() stricterFunctionMap: bool() - zeroFiles: bool() projectServicesNotInAnalysedPaths: bool() callUserFunc: bool() magicConstantOutOfContext: bool() From e01399795162ec57f49adb416bbe41b592c99610 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:34:00 +0200 Subject: [PATCH 0364/3097] [BE] Check preg_quote delimiter sanity --- changelog-2.0.md | 3 +-- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 11 ++--------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 6941b58e5a6..991f721fdab 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -11,6 +11,7 @@ Major new features 🚀 * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 +* Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -87,10 +88,8 @@ Bleeding edge (TODO move to other sections) * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 -* RegularExpressionPatternRule: validate preg_quote'd patterns ([#3270](https://github.com/phpstan/phpstan-src/pull/3270)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! -* Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! Improvements 🔧 diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6ee6d1fe290..0b6d0ab3e39 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -37,6 +37,5 @@ parameters: checkParameterCastableToStringFunctions: true uselessReturnValue: true printfArrayParameters: true - validatePregQuote: true tooWidePropertyType: true requireFileExists: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1d5325aefa9..a7cd743caf0 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -28,8 +28,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.uselessReturnValue% PHPStan\Rules\Functions\PrintfArrayParametersRule: phpstan.rules.rule: %featureToggles.printfArrayParameters% - PHPStan\Rules\Regexp\RegularExpressionQuotingRule: - phpstan.rules.rule: %featureToggles.validatePregQuote% PHPStan\Rules\Keywords\RequireFileExistsRule: phpstan.rules.rule: %featureToggles.requireFileExists% @@ -114,6 +112,8 @@ rules: - PHPStan\Rules\Properties\PropertiesInInterfaceRule - PHPStan\Rules\Properties\PropertyAttributesRule - PHPStan\Rules\Properties\ReadOnlyPropertyRule + - PHPStan\Rules\Regexp\RegularExpressionPatternRule + - PHPStan\Rules\Regexp\RegularExpressionQuotingRule - PHPStan\Rules\Traits\ConflictingTraitConstantsRule - PHPStan\Rules\Traits\ConstantsInTraitsRule - PHPStan\Rules\Types\InvalidTypesInUnionRule @@ -279,11 +279,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Regexp\RegularExpressionPatternRule - tags: - - phpstan.rules.rule - - class: PHPStan\Reflection\ConstructorsHelper arguments: @@ -301,8 +296,6 @@ services: - class: PHPStan\Rules\Functions\PrintfArrayParametersRule - - - class: PHPStan\Rules\Regexp\RegularExpressionQuotingRule - class: PHPStan\Rules\Keywords\RequireFileExistsRule arguments: diff --git a/conf/config.neon b/conf/config.neon index ae1b9546871..65ba96b61c2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -59,7 +59,6 @@ parameters: checkParameterCastableToStringFunctions: false uselessReturnValue: false printfArrayParameters: false - validatePregQuote: false requireFileExists: false narrowPregMatches: true tooWidePropertyType: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index efeb421ebce..9f85497e620 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -65,7 +65,6 @@ parametersSchema: checkParameterCastableToStringFunctions: bool() uselessReturnValue: bool() printfArrayParameters: bool() - validatePregQuote: bool() narrowPregMatches: bool() tooWidePropertyType: bool() requireFileExists: bool() From 622b11210e3365b651b8af21791f216f91b221e0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:39:17 +0200 Subject: [PATCH 0365/3097] Remove unused feature toggle --- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 3 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0b6d0ab3e39..cffea53253c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -30,7 +30,6 @@ parameters: paramOutVariance: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true - projectServicesNotInAnalysedPaths: true callUserFunc: true magicConstantOutOfContext: true pure: true diff --git a/conf/config.neon b/conf/config.neon index 65ba96b61c2..967c875dc47 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -52,7 +52,6 @@ parameters: strictStaticMethodTemplateTypeVariance: false propertyVariance: false stricterFunctionMap: false - projectServicesNotInAnalysedPaths: false callUserFunc: false magicConstantOutOfContext: false pure: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 9f85497e620..b0b46521e6e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -58,7 +58,6 @@ parametersSchema: strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() stricterFunctionMap: bool() - projectServicesNotInAnalysedPaths: bool() callUserFunc: bool() magicConstantOutOfContext: bool() pure: bool() From 5cd4572ef009f726aed761b7aad65b3138517753 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:42:33 +0200 Subject: [PATCH 0366/3097] [BCB] Removed NodeConnectingVisitor --- UPGRADING.md | 4 +++ changelog-2.0.md | 6 ++-- conf/bleedingEdge.neon | 2 -- conf/config.level0.neon | 5 +-- conf/config.neon | 7 ---- conf/parametersSchema.neon | 2 -- .../NodeConnectingVisitorAttributesRule.php | 22 ------------- tests/PHPStan/Parser/CachedParserTest.php | 25 +++++++++++++-- tests/PHPStan/Parser/data/test.php | 5 +-- ...odeConnectingVisitorAttributesRuleTest.php | 2 +- ...ttributesRuleWithVisitorRegisteredTest.php | 32 ------------------- .../nodeConnectingVisitorCompatibility.neon | 3 -- 12 files changed, 32 insertions(+), 83 deletions(-) delete mode 100644 tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleWithVisitorRegisteredTest.php delete mode 100644 tests/PHPStan/Rules/Api/nodeConnectingVisitorCompatibility.neon diff --git a/UPGRADING.md b/UPGRADING.md index cb182fbd3d2..18861737b01 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -148,6 +148,10 @@ If you want to change `$overwrite` or `$rootExpr` (previous parameters also used This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). +### Node attributes `parent`, `previous`, `next` are no longer available + +Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules + ### Removed config parameter `scopeClass` As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. diff --git a/changelog-2.0.md b/changelog-2.0.md index 991f721fdab..c58e4ee853b 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -7,6 +7,9 @@ Major new features 🚀 * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 +* Lower memory consumption thanks to breaking up of reference cycles + * [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) + * In testing the memory consumption was reduced by 50–70 %. * **Enhancements in Handling Parameters Passed by Reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! @@ -35,9 +38,6 @@ Bleeding edge (TODO move to other sections) * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! -* Lower memory consumption thanks to breaking up of reference cycles - * This is a BC break for rules that use `'parent'`, `'next'`, and `'previous'` node attributes. [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) - * In testing the memory consumption was reduced by 50–70 %. * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index cffea53253c..fcc1ec9d55d 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,8 +8,6 @@ parameters: arrayFilter: true arrayUnpacking: true arrayValues: true - nodeConnectingVisitorCompatibility: false - nodeConnectingVisitorRule: true strictUnnecessaryNullsafePropertyFetch: true looseComparison: true consistentConstructor: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index a7cd743caf0..1f1aa09e626 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -2,8 +2,6 @@ parameters: customRulesetUsed: false conditionalTags: - PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule: - phpstan.rules.rule: %featureToggles.nodeConnectingVisitorRule% PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule: @@ -40,6 +38,7 @@ rules: - PHPStan\Rules\Api\ApiStaticCallRule - PHPStan\Rules\Api\ApiTraitUseRule - PHPStan\Rules\Api\GetTemplateTypeRule + - PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule - PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule - PHPStan\Rules\Arrays\EmptyArrayItemRule @@ -132,8 +131,6 @@ services: deprecationRulesInstalled: %deprecationRulesInstalled% tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule - class: PHPStan\Rules\Api\RuntimeReflectionFunctionRule - diff --git a/conf/config.neon b/conf/config.neon index 967c875dc47..8b60279ccc1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -28,8 +28,6 @@ parameters: arrayFilter: false arrayUnpacking: false arrayValues: false - nodeConnectingVisitorCompatibility: true - nodeConnectingVisitorRule: false illegalConstructorMethodCall: false strictUnnecessaryNullsafePropertyFetch: false looseComparison: false @@ -253,8 +251,6 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.tooWideThrowType% PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule: phpstan.rules.rule: %exceptions.check.tooWideThrowType% - PhpParser\NodeVisitor\NodeConnectingVisitor: - phpstan.parser.richParserNodeVisitor: %featureToggles.nodeConnectingVisitorCompatibility% PHPStan\Parser\CurlSetOptArgVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% PHPStan\Parser\TypeTraverserInstanceofVisitor: @@ -349,9 +345,6 @@ services: tags: - phpstan.parser.richParserNodeVisitor - - - class: PhpParser\NodeVisitor\NodeConnectingVisitor - - class: PHPStan\Node\Printer\ExprPrinter diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index b0b46521e6e..fc1d1ea2254 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -34,8 +34,6 @@ parametersSchema: arrayFilter: bool(), arrayUnpacking: bool(), arrayValues: bool(), - nodeConnectingVisitorCompatibility: bool(), - nodeConnectingVisitorRule: bool(), illegalConstructorMethodCall: bool(), strictUnnecessaryNullsafePropertyFetch: bool(), looseComparison: bool(), diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index a1f27a33e2d..7ad74631b92 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -4,16 +4,12 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; -use PhpParser\NodeVisitor\NodeConnectingVisitor; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\Container; -use PHPStan\Parser\RichParser; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ObjectType; use function array_keys; -use function get_class; use function in_array; use function sprintf; use function str_starts_with; @@ -24,10 +20,6 @@ final class NodeConnectingVisitorAttributesRule implements Rule { - public function __construct(private Container $container) - { - } - public function getNodeType(): string { return MethodCall::class; @@ -74,20 +66,6 @@ public function processNode(Node $node, Scope $scope): array return []; } - $isVisitorRegistered = false; - foreach ($this->container->getServicesByTag(RichParser::VISITOR_SERVICE_TAG) as $service) { - if (get_class($service) !== NodeConnectingVisitor::class) { - continue; - } - - $isVisitorRegistered = true; - break; - } - - if ($isVisitorRegistered) { - return []; - } - return [ RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argType->getValue())) ->identifier('phpParser.nodeConnectingAttribute') diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 13505dbce79..3b97a1b99a1 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -90,15 +90,34 @@ public function testParseTheSameFileWithDifferentMethod(): void $contents = FileReader::read($path); $stmts = $parser->parseString($contents); $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertNull($stmts[0]->stmts[0]->getAttribute('parent')); + $this->assertInstanceOf(Node\Stmt\Expression::class, $stmts[0]->stmts[0]); + $this->assertInstanceOf(Node\Expr\Assign::class, $stmts[0]->stmts[0]->expr); + $this->assertInstanceOf(Node\Expr\New_::class, $stmts[0]->stmts[0]->expr->expr); + $this->assertNull($stmts[0]->stmts[0]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); $stmts = $parser->parseFile($path); $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); + $this->assertInstanceOf(Node\Stmt\Expression::class, $stmts[0]->stmts[0]); + $this->assertInstanceOf(Node\Expr\Assign::class, $stmts[0]->stmts[0]->expr); + $this->assertInstanceOf(Node\Expr\New_::class, $stmts[0]->stmts[0]->expr->expr); + $this->assertSame(1, $stmts[0]->stmts[0]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); + + $this->assertInstanceOf(Node\Stmt\Expression::class, $stmts[0]->stmts[1]); + $this->assertInstanceOf(Node\Expr\Assign::class, $stmts[0]->stmts[1]->expr); + $this->assertInstanceOf(Node\Expr\New_::class, $stmts[0]->stmts[1]->expr->expr); + $this->assertSame(2, $stmts[0]->stmts[1]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); $stmts = $parser->parseString($contents); $this->assertInstanceOf(Namespace_::class, $stmts[0]); - $this->assertInstanceOf(Namespace_::class, $stmts[0]->stmts[0]->getAttribute('parent')); + $this->assertInstanceOf(Node\Stmt\Expression::class, $stmts[0]->stmts[0]); + $this->assertInstanceOf(Node\Expr\Assign::class, $stmts[0]->stmts[0]->expr); + $this->assertInstanceOf(Node\Expr\New_::class, $stmts[0]->stmts[0]->expr->expr); + $this->assertSame(1, $stmts[0]->stmts[0]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); + + $this->assertInstanceOf(Node\Stmt\Expression::class, $stmts[0]->stmts[1]); + $this->assertInstanceOf(Node\Expr\Assign::class, $stmts[0]->stmts[1]->expr); + $this->assertInstanceOf(Node\Expr\New_::class, $stmts[0]->stmts[1]->expr->expr); + $this->assertSame(2, $stmts[0]->stmts[1]->expr->expr->class->getAttribute(AnonymousClassVisitor::ATTRIBUTE_LINE_INDEX)); } } diff --git a/tests/PHPStan/Parser/data/test.php b/tests/PHPStan/Parser/data/test.php index a6bee51214b..b7ee628d37b 100644 --- a/tests/PHPStan/Parser/data/test.php +++ b/tests/PHPStan/Parser/data/test.php @@ -2,7 +2,4 @@ namespace CachedParserBug; -class Foo -{ - -} +$a = new class () {}; $b = new class () {}; diff --git a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php index 2e5388d2b18..c7fe99bc18f 100644 --- a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php @@ -13,7 +13,7 @@ class NodeConnectingVisitorAttributesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new NodeConnectingVisitorAttributesRule(self::getContainer()); + return new NodeConnectingVisitorAttributesRule(); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleWithVisitorRegisteredTest.php b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleWithVisitorRegisteredTest.php deleted file mode 100644 index 569d7747eb1..00000000000 --- a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleWithVisitorRegisteredTest.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ -class NodeConnectingVisitorAttributesRuleWithVisitorRegisteredTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new NodeConnectingVisitorAttributesRule(self::getContainer()); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/node-connecting-visitor.php'], []); - } - - public static function getAdditionalConfigFiles(): array - { - return array_merge(parent::getAdditionalConfigFiles(), [ - __DIR__ . '/nodeConnectingVisitorCompatibility.neon', - ]); - } - -} diff --git a/tests/PHPStan/Rules/Api/nodeConnectingVisitorCompatibility.neon b/tests/PHPStan/Rules/Api/nodeConnectingVisitorCompatibility.neon deleted file mode 100644 index efb020baeaa..00000000000 --- a/tests/PHPStan/Rules/Api/nodeConnectingVisitorCompatibility.neon +++ /dev/null @@ -1,3 +0,0 @@ -conditionalTags: - PhpParser\NodeVisitor\NodeConnectingVisitor: - phpstan.parser.richParserNodeVisitor: true From 4def38de833fa776d97dadc7c98af3c08b4e8b50 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:54:20 +0200 Subject: [PATCH 0367/3097] [BE] Check that each trait is used and analysed at least once --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 14 +++++--------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index c58e4ee853b..594e85adb07 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -14,6 +14,7 @@ Major new features 🚀 * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 +* Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) @@ -44,7 +45,6 @@ Bleeding edge (TODO move to other sections) * Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) -* Check that each trait is used and analysed at least once - level 4 (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check that PHPStan class in class constant fetch is covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) * Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) * Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index fcc1ec9d55d..82bb13d05fa 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -14,7 +14,6 @@ parameters: checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true runtimeReflectionRules: true - notAnalysedTrait: true curlSetOptTypes: true missingMagicSerializationRule: true nullContextForVoidReturningFunctions: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index cc3cdff418f..27c09f7ab7f 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -17,16 +17,11 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule - PHPStan\Rules\TooWideTypehints\TooWideFunctionParameterOutTypeRule - PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule + - PHPStan\Rules\Traits\NotAnalysedTraitRule conditionalTags: PHPStan\Rules\Comparison\ConstantLooseComparisonRule: phpstan.rules.rule: %featureToggles.looseComparison% - PHPStan\Rules\Traits\TraitDeclarationCollector: - phpstan.collector: %featureToggles.notAnalysedTrait% - PHPStan\Rules\Traits\TraitUseCollector: - phpstan.collector: %featureToggles.notAnalysedTrait% - PHPStan\Rules\Traits\NotAnalysedTraitRule: - phpstan.rules.rule: %featureToggles.notAnalysedTrait% PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureNewCollector: @@ -299,12 +294,13 @@ services: - class: PHPStan\Rules\Traits\TraitDeclarationCollector + tags: + - phpstan.collector - class: PHPStan\Rules\Traits\TraitUseCollector - - - - class: PHPStan\Rules\Traits\NotAnalysedTraitRule + tags: + - phpstan.collector - class: PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule diff --git a/conf/config.neon b/conf/config.neon index 8b60279ccc1..966b903b3c3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false runtimeReflectionRules: false - notAnalysedTrait: false curlSetOptTypes: false missingMagicSerializationRule: false nullContextForVoidReturningFunctions: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index fc1d1ea2254..55d94d52ff3 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,7 +41,6 @@ parametersSchema: checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() runtimeReflectionRules: bool() - notAnalysedTrait: bool() curlSetOptTypes: bool() missingMagicSerializationRule: bool() nullContextForVoidReturningFunctions: bool() From 6f6e25d2646dca2f93a44f87b314d37be902ccb5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:55:13 +0200 Subject: [PATCH 0368/3097] Remove unused feature toggle --- conf/bleedingEdge.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 3 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 82bb13d05fa..0e5c07251ea 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -17,7 +17,6 @@ parameters: curlSetOptTypes: true missingMagicSerializationRule: true nullContextForVoidReturningFunctions: true - unescapeStrings: true alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true diff --git a/conf/config.neon b/conf/config.neon index 966b903b3c3..3c26e4825f8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -38,7 +38,6 @@ parameters: curlSetOptTypes: false missingMagicSerializationRule: false nullContextForVoidReturningFunctions: false - unescapeStrings: false alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 55d94d52ff3..2a7bae6797c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -44,7 +44,6 @@ parametersSchema: curlSetOptTypes: bool() missingMagicSerializationRule: bool() nullContextForVoidReturningFunctions: bool() - unescapeStrings: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() From bc86dcc1f83d1967639c8b1f402d3fd55d9a0cbf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 15:57:49 +0200 Subject: [PATCH 0369/3097] [BE] Improve impossible type checker for void-returning functions --- changelog-2.0.md | 1 + conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/Rules/Comparison/ImpossibleCheckTypeHelper.php | 6 ------ .../TypeSpecifyingFunctionsDynamicReturnTypeExtension.php | 4 ++-- .../Comparison/BooleanAndConstantConditionRuleTest.php | 1 - .../Comparison/BooleanNotConstantConditionRuleTest.php | 1 - .../Rules/Comparison/BooleanOrConstantConditionRuleTest.php | 1 - .../Comparison/DoWhileLoopConstantConditionRuleTest.php | 1 - .../Rules/Comparison/ElseIfConstantConditionRuleTest.php | 1 - .../Rules/Comparison/IfConstantConditionRuleTest.php | 1 - .../Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php | 1 - .../ImpossibleCheckTypeGenericOverwriteRuleTest.php | 1 - .../ImpossibleCheckTypeMethodCallRuleEqualsTest.php | 1 - .../Comparison/ImpossibleCheckTypeMethodCallRuleTest.php | 1 - .../ImpossibleCheckTypeStaticMethodCallRuleTest.php | 1 - .../Comparison/LogicalXorConstantConditionRuleTest.php | 1 - tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php | 1 - .../Comparison/TernaryOperatorConstantConditionRuleTest.php | 1 - .../Rules/Comparison/UnreachableIfBranchesRuleTest.php | 1 - .../Comparison/UnreachableTernaryElseBranchRuleTest.php | 1 - .../Comparison/WhileLoopAlwaysFalseConditionRuleTest.php | 1 - .../Comparison/WhileLoopAlwaysTrueConditionRuleTest.php | 1 - 24 files changed, 3 insertions(+), 31 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 594e85adb07..51c219357f2 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -126,6 +126,7 @@ Improvements 🔧 * Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * NoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 +* Improve impossible type checker for void-returning functions ([#1857](https://github.com/phpstan/phpstan-src/pull/1857)), #8169, thanks @rvanvelzen! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0e5c07251ea..c695c525d17 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -16,7 +16,6 @@ parameters: runtimeReflectionRules: true curlSetOptTypes: true missingMagicSerializationRule: true - nullContextForVoidReturningFunctions: true alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true diff --git a/conf/config.neon b/conf/config.neon index 3c26e4825f8..c4892a4de70 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -37,7 +37,6 @@ parameters: runtimeReflectionRules: false curlSetOptTypes: false missingMagicSerializationRule: false - nullContextForVoidReturningFunctions: false alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false @@ -895,7 +894,6 @@ services: arguments: universalObjectCratesClasses: %universalObjectCratesClasses% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - nullContextForVoidReturningFunctions: %featureToggles.nullContextForVoidReturningFunctions% - class: PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver @@ -1792,7 +1790,6 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% universalObjectCratesClasses: %universalObjectCratesClasses% - nullContextForVoidReturningFunctions: %featureToggles.nullContextForVoidReturningFunctions% tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 2a7bae6797c..ce348929689 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -43,7 +43,6 @@ parametersSchema: runtimeReflectionRules: bool() curlSetOptTypes: bool() missingMagicSerializationRule: bool() - nullContextForVoidReturningFunctions: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 39a4da06bde..45574a089ae 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -48,7 +48,6 @@ public function __construct( private TypeSpecifier $typeSpecifier, private array $universalObjectCratesClasses, private bool $treatPhpDocTypesAsCertain, - private bool $nullContextForVoidReturningFunctions, ) { } @@ -369,16 +368,11 @@ public function doNotTreatPhpDocTypesAsCertain(): self $this->typeSpecifier, $this->universalObjectCratesClasses, false, - $this->nullContextForVoidReturningFunctions, ); } private function determineContext(Scope $scope, Expr $node): TypeSpecifierContext { - if (!$this->nullContextForVoidReturningFunctions) { - return TypeSpecifierContext::createTruthy(); - } - if ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) { return TypeSpecifierContext::createTruthy(); } diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index 1f715dad2cf..a6ab212328d 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -25,7 +25,7 @@ final class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements Dynamic /** * @param string[] $universalObjectCratesClasses */ - public function __construct(private ReflectionProvider $reflectionProvider, private bool $treatPhpDocTypesAsCertain, private array $universalObjectCratesClasses, private bool $nullContextForVoidReturningFunctions) + public function __construct(private ReflectionProvider $reflectionProvider, private bool $treatPhpDocTypesAsCertain, private array $universalObjectCratesClasses) { } @@ -68,7 +68,7 @@ public function getTypeFromFunctionCall( private function getHelper(): ImpossibleCheckTypeHelper { if ($this->helper === null) { - $this->helper = new ImpossibleCheckTypeHelper($this->reflectionProvider, $this->typeSpecifier, $this->universalObjectCratesClasses, $this->treatPhpDocTypesAsCertain, $this->nullContextForVoidReturningFunctions); + $this->helper = new ImpossibleCheckTypeHelper($this->reflectionProvider, $this->typeSpecifier, $this->universalObjectCratesClasses, $this->treatPhpDocTypesAsCertain); } return $this->helper; diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index e84e4956c70..1b66a5898e2 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php index 980be0e2071..5fb982f548f 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index b5d52ba9a0c..4db5d7167af 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -25,7 +25,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php index 7d05804a468..4ddf9941e89 100644 --- a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index bf671466842..4bac3a314fe 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -25,7 +25,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 840839ac9b3..80ee3c0763d 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -23,7 +23,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 7a9e4e59af7..bee96695101 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -31,7 +31,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [stdClass::class], $this->treatPhpDocTypesAsCertain, - true, ), $this->checkAlwaysTrueCheckTypeFunctionCall, $this->treatPhpDocTypesAsCertain, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php index 152cbaa4a49..47fb5d60f69 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php @@ -19,7 +19,6 @@ public function getRule(): Rule $this->getTypeSpecifier(), [], true, - true, ), true, true, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 54564bf9ea6..4d7eedcf84d 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -19,7 +19,6 @@ public function getRule(): Rule $this->getTypeSpecifier(), [], true, - true, ), true, true, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 5fb28b96ba9..cfaf5fce01d 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -24,7 +24,6 @@ public function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), true, $this->treatPhpDocTypesAsCertain, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index b173c04eea5..b3c7227ee13 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -23,7 +23,6 @@ public function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), true, $this->treatPhpDocTypesAsCertain, diff --git a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php index 7e6c6015ca3..4eab90f0170 100644 --- a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): TRule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index b727ba76e29..4674b642a71 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -27,7 +27,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index fa4a13a729b..2169597e681 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php index f31bbe5023c..4459045cc03 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php index 00175cc6fcf..bd3cc00a4f3 100644 --- a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php index 349eb489ed0..df5ce1d3c8f 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php index 2be9972f15b..83f53c071a2 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php @@ -22,7 +22,6 @@ protected function getRule(): Rule $this->getTypeSpecifier(), [], $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, From 30e8e8bca215a67b4d6a75d532b1ec3bb2cdb6b6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 23 Sep 2024 15:53:44 +0200 Subject: [PATCH 0370/3097] Added regression test --- .../Operators/InvalidBinaryOperationRuleTest.php | 12 ++++++++++++ tests/PHPStan/Rules/Operators/data/bug-7863.php | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/PHPStan/Rules/Operators/data/bug-7863.php diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 41c947e9379..a656898b2a8 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -798,4 +798,16 @@ public function testBenevolentUnion(): void ]); } + public function testBug7863(): void + { + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-7863.php'], [ + [ + 'Binary operation "+" between mixed and array results in an error.', + 10, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-7863.php b/tests/PHPStan/Rules/Operators/data/bug-7863.php new file mode 100644 index 00000000000..3a848d67695 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-7863.php @@ -0,0 +1,11 @@ + Date: Mon, 23 Sep 2024 16:03:47 +0200 Subject: [PATCH 0371/3097] [BE] Check template type variance in `@param-out` --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Rules/Generics/VarianceCheck.php | 5 ----- tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php | 2 +- tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php | 2 +- tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php | 2 +- tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php | 2 +- 9 files changed, 5 insertions(+), 14 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 51c219357f2..80784d20423 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -61,7 +61,6 @@ Bleeding edge (TODO move to other sections) * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) -* Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! @@ -127,6 +126,7 @@ Improvements 🔧 * Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) * NoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * Improve impossible type checker for void-returning functions ([#1857](https://github.com/phpstan/phpstan-src/pull/1857)), #8169, thanks @rvanvelzen! +* Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index c695c525d17..4d893731f55 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,7 +22,6 @@ parameters: varTagType: true closureDefaultParameterTypeRule: true instanceofType: true - paramOutVariance: true strictStaticMethodTemplateTypeVariance: true propertyVariance: true callUserFunc: true diff --git a/conf/config.neon b/conf/config.neon index c4892a4de70..e6ed12ba2f4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,7 +43,6 @@ parameters: varTagType: false closureDefaultParameterTypeRule: false instanceofType: false - paramOutVariance: false strictStaticMethodTemplateTypeVariance: false propertyVariance: false stricterFunctionMap: false @@ -967,7 +966,6 @@ services: - class: PHPStan\Rules\Generics\VarianceCheck arguments: - checkParamOutVariance: %featureToggles.paramOutVariance% strictStaticVariance: %featureToggles.strictStaticMethodTemplateTypeVariance% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index ce348929689..68102c39abe 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: varTagType: bool() closureDefaultParameterTypeRule: bool() instanceofType: bool() - paramOutVariance: bool() strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() stricterFunctionMap: bool() diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index ca13ca07a6d..376a7109e60 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -14,7 +14,6 @@ final class VarianceCheck { public function __construct( - private bool $checkParamOutVariance, private bool $strictStaticVariance, ) { @@ -68,10 +67,6 @@ public function checkParametersAcceptor( $errors[] = $error; } - if (!$this->checkParamOutVariance) { - continue; - } - $paramOutType = $parameterReflection->getOutType(); if ($paramOutType === null) { continue; diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 867cff2e50f..a7bd6c6c7a2 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true, true), + new VarianceCheck(true), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php index 0978f6c7c5c..af69ef5ea90 100644 --- a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php @@ -19,7 +19,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true, true), + new VarianceCheck(true), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 8d7a275278a..c5c92bb097e 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true, true), + new VarianceCheck(true), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index c62ce6fc246..7e1ee17d340 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true, true), + new VarianceCheck(true), new UnresolvableTypeHelper(), [], ), From 047c2d362e4f80be48747e4afe6cb84f7fdf7ae1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:05:20 +0200 Subject: [PATCH 0372/3097] [BE] Fix position variance of static method parameters --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/Rules/Generics/VarianceCheck.php | 10 +--------- .../PHPStan/Rules/Generics/ClassAncestorsRuleTest.php | 2 +- tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php | 2 +- .../Rules/Generics/InterfaceAncestorsRuleTest.php | 2 +- tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php | 2 +- 9 files changed, 6 insertions(+), 19 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 80784d20423..58a26b37cf3 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -62,7 +62,6 @@ Bleeding edge (TODO move to other sections) * Because "always true" is always reported, these are no longer needed * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) -* Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) @@ -127,6 +126,7 @@ Improvements 🔧 * NoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 * Improve impossible type checker for void-returning functions ([#1857](https://github.com/phpstan/phpstan-src/pull/1857)), #8169, thanks @rvanvelzen! * Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 +* Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 4d893731f55..8ec2f5e19eb 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,7 +22,6 @@ parameters: varTagType: true closureDefaultParameterTypeRule: true instanceofType: true - strictStaticMethodTemplateTypeVariance: true propertyVariance: true callUserFunc: true magicConstantOutOfContext: true diff --git a/conf/config.neon b/conf/config.neon index e6ed12ba2f4..dcc8b5c61af 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,7 +43,6 @@ parameters: varTagType: false closureDefaultParameterTypeRule: false instanceofType: false - strictStaticMethodTemplateTypeVariance: false propertyVariance: false stricterFunctionMap: false callUserFunc: false @@ -965,8 +964,6 @@ services: - class: PHPStan\Rules\Generics\VarianceCheck - arguments: - strictStaticVariance: %featureToggles.strictStaticMethodTemplateTypeVariance% - class: PHPStan\Rules\IssetCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 68102c39abe..7b991aedb9c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -49,7 +49,6 @@ parametersSchema: varTagType: bool() closureDefaultParameterTypeRule: bool() instanceofType: bool() - strictStaticMethodTemplateTypeVariance: bool() propertyVariance: bool() stricterFunctionMap: bool() callUserFunc: bool() diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index 376a7109e60..95170eecb49 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -13,12 +13,6 @@ final class VarianceCheck { - public function __construct( - private bool $strictStaticVariance, - ) - { - } - /** * @param 'function'|'method' $identifier * @return list @@ -56,9 +50,7 @@ public function checkParametersAcceptor( } $covariant = TemplateTypeVariance::createCovariant(); - $parameterVariance = $isStatic && !$this->strictStaticVariance - ? TemplateTypeVariance::createStatic() - : TemplateTypeVariance::createContravariant(); + $parameterVariance = TemplateTypeVariance::createContravariant(); foreach ($parametersAcceptor->getParameters() as $parameterReflection) { $type = $parameterReflection->getType(); diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index a7bd6c6c7a2..b36d8728514 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true), + new VarianceCheck(), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php index af69ef5ea90..3bc3ac59e63 100644 --- a/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/EnumAncestorsRuleTest.php @@ -19,7 +19,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true), + new VarianceCheck(), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index c5c92bb097e..ee7fd32fd98 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true), + new VarianceCheck(), new UnresolvableTypeHelper(), [], ), diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 7e1ee17d340..9eab4b213e2 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule new GenericAncestorsCheck( $this->createReflectionProvider(), new GenericObjectTypeCheck(), - new VarianceCheck(true), + new VarianceCheck(), new UnresolvableTypeHelper(), [], ), From 80ad11ea4d97d939629b4aab7526652c9abe261f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:09:11 +0200 Subject: [PATCH 0373/3097] Upgrading note --- UPGRADING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 18861737b01..e6ccef2f834 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -32,6 +32,8 @@ Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-g After changing your `composer.json`, run `composer update 'phpstan/*' -W`. +It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. + ### Removed option `checkMissingIterableValueType` It's strongly recommended to add the missing array typehints. From 27ab11b3320b8b75bfa73f0938b456fbec9263ff Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:10:07 +0200 Subject: [PATCH 0374/3097] [BE] Check code in custom PHPStan extensions for runtime reflection concepts --- changelog-2.0.md | 12 ++++++------ conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 20 ++++---------------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 10 insertions(+), 25 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 58a26b37cf3..7bd3f556523 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -33,6 +33,12 @@ Major new features 🚀 * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) +* Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (level 0) (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) +* Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (level 0) (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) +* ApiInstanceofRule (level 0) + * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) + * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) +* Check that PHPStan class in class constant fetch is covered by backward compatibility promise (level 0) (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) Bleeding edge (TODO move to other sections) ===================== @@ -45,12 +51,6 @@ Bleeding edge (TODO move to other sections) * Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) -* Check that PHPStan class in class constant fetch is covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) -* Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) -* Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) -* ApiInstanceofRule - * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) - * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8ec2f5e19eb..3356f15719a 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,7 +13,6 @@ parameters: consistentConstructor: true checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true - runtimeReflectionRules: true curlSetOptTypes: true missingMagicSerializationRule: true alwaysCheckTooWideReturnTypeFinalMethods: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1f1aa09e626..be485cdbc3f 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -10,14 +10,6 @@ conditionalTags: phpstan.rules.rule: %checkUninitializedProperties% PHPStan\Rules\Methods\ConsistentConstructorRule: phpstan.rules.rule: %featureToggles.consistentConstructor% - PHPStan\Rules\Api\ApiClassConstFetchRule: - phpstan.rules.rule: %featureToggles.runtimeReflectionRules% - PHPStan\Rules\Api\ApiInstanceofRule: - phpstan.rules.rule: %featureToggles.runtimeReflectionRules% - PHPStan\Rules\Api\RuntimeReflectionFunctionRule: - phpstan.rules.rule: %featureToggles.runtimeReflectionRules% - PHPStan\Rules\Api\RuntimeReflectionInstantiationRule: - phpstan.rules.rule: %featureToggles.runtimeReflectionRules% PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule: phpstan.rules.rule: %featureToggles.missingMagicSerializationRule% PHPStan\Rules\Constants\MagicConstantContextRule: @@ -30,7 +22,9 @@ conditionalTags: phpstan.rules.rule: %featureToggles.requireFileExists% rules: + - PHPStan\Rules\Api\ApiInstanceofRule - PHPStan\Rules\Api\ApiInstantiationRule + - PHPStan\Rules\Api\ApiClassConstFetchRule - PHPStan\Rules\Api\ApiClassExtendsRule - PHPStan\Rules\Api\ApiClassImplementsRule - PHPStan\Rules\Api\ApiInterfaceExtendsRule @@ -40,6 +34,8 @@ rules: - PHPStan\Rules\Api\GetTemplateTypeRule - PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule - PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule + - PHPStan\Rules\Api\RuntimeReflectionInstantiationRule + - PHPStan\Rules\Api\RuntimeReflectionFunctionRule - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule - PHPStan\Rules\Arrays\EmptyArrayItemRule - PHPStan\Rules\Arrays\OffsetAccessWithoutDimForReadingRule @@ -120,10 +116,6 @@ rules: - PHPStan\Rules\Whitespace\FileWhitespaceRule services: - - - class: PHPStan\Rules\Api\ApiClassConstFetchRule - - - class: PHPStan\Rules\Api\ApiInstanceofRule - class: PHPStan\Rules\Api\ApiInstanceofTypeRule arguments: @@ -131,10 +123,6 @@ services: deprecationRulesInstalled: %deprecationRulesInstalled% tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Api\RuntimeReflectionFunctionRule - - - class: PHPStan\Rules\Api\RuntimeReflectionInstantiationRule - class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule tags: diff --git a/conf/config.neon b/conf/config.neon index dcc8b5c61af..3de3cefcab4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -34,7 +34,6 @@ parameters: consistentConstructor: false checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false - runtimeReflectionRules: false curlSetOptTypes: false missingMagicSerializationRule: false alwaysCheckTooWideReturnTypeFinalMethods: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 7b991aedb9c..07fc7a40849 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -40,7 +40,6 @@ parametersSchema: consistentConstructor: bool() checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() - runtimeReflectionRules: bool() curlSetOptTypes: bool() missingMagicSerializationRule: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() From 8bf527b3b374dd694ee7f07e6deb80da54b69368 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:16:30 +0200 Subject: [PATCH 0375/3097] [BE] Deprecate various `instanceof *Type` in favour of new methods on `Type` interface --- UPGRADING.md | 4 ++++ changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 8 +------- conf/config.neon | 8 +++++--- conf/parametersSchema.neon | 1 - src/Rules/Api/ApiInstanceofTypeRule.php | 6 ------ tests/PHPStan/Rules/Api/ApiInstanceofTypeRuleTest.php | 2 +- 8 files changed, 12 insertions(+), 20 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index e6ccef2f834..9a0e7709529 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -128,6 +128,10 @@ return [ ]; ``` +### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface + +Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) + ### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters [`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): diff --git a/changelog-2.0.md b/changelog-2.0.md index 7bd3f556523..f6897df85e3 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -39,6 +39,7 @@ Major new features 🚀 * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Check that PHPStan class in class constant fetch is covered by backward compatibility promise (level 0) (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) +* Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) Bleeding edge (TODO move to other sections) ===================== @@ -61,7 +62,6 @@ Bleeding edge (TODO move to other sections) * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) -* Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 3356f15719a..7fbd8c23b32 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -20,7 +20,6 @@ parameters: disableUnreachableBranchesRules: true varTagType: true closureDefaultParameterTypeRule: true - instanceofType: true propertyVariance: true callUserFunc: true magicConstantOutOfContext: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index be485cdbc3f..0e00bc5e92a 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -23,6 +23,7 @@ conditionalTags: rules: - PHPStan\Rules\Api\ApiInstanceofRule + - PHPStan\Rules\Api\ApiInstanceofTypeRule - PHPStan\Rules\Api\ApiInstantiationRule - PHPStan\Rules\Api\ApiClassConstFetchRule - PHPStan\Rules\Api\ApiClassExtendsRule @@ -116,13 +117,6 @@ rules: - PHPStan\Rules\Whitespace\FileWhitespaceRule services: - - - class: PHPStan\Rules\Api\ApiInstanceofTypeRule - arguments: - enabled: %featureToggles.instanceofType% - deprecationRulesInstalled: %deprecationRulesInstalled% - tags: - - phpstan.rules.rule - class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule tags: diff --git a/conf/config.neon b/conf/config.neon index 3de3cefcab4..66ea8a4a679 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -41,7 +41,6 @@ parameters: disableUnreachableBranchesRules: false varTagType: false closureDefaultParameterTypeRule: false - instanceofType: false propertyVariance: false stricterFunctionMap: false callUserFunc: false @@ -247,8 +246,6 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.tooWideThrowType% PHPStan\Parser\CurlSetOptArgVisitor: phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% - PHPStan\Parser\TypeTraverserInstanceofVisitor: - phpstan.parser.richParserNodeVisitor: %featureToggles.instanceofType% services: - @@ -339,6 +336,11 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\TypeTraverserInstanceofVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Node\Printer\ExprPrinter diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 07fc7a40849..a4373609b7e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -47,7 +47,6 @@ parametersSchema: disableUnreachableBranchesRules: bool() varTagType: bool() closureDefaultParameterTypeRule: bool() - instanceofType: bool() propertyVariance: bool() stricterFunctionMap: bool() callUserFunc: bool() diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index 1ae24aa0cae..c78d682e11f 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -94,8 +94,6 @@ final class ApiInstanceofTypeRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, - private bool $enabled, - private bool $deprecationRulesInstalled, ) { } @@ -107,10 +105,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$this->enabled && !$this->deprecationRulesInstalled) { - return []; - } - if (!$node->class instanceof Node\Name) { return []; } diff --git a/tests/PHPStan/Rules/Api/ApiInstanceofTypeRuleTest.php b/tests/PHPStan/Rules/Api/ApiInstanceofTypeRuleTest.php index 6be9f18d4b3..e12f8aaf143 100644 --- a/tests/PHPStan/Rules/Api/ApiInstanceofTypeRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiInstanceofTypeRuleTest.php @@ -13,7 +13,7 @@ class ApiInstanceofTypeRuleTest extends RuleTestCase public function getRule(): Rule { - return new ApiInstanceofTypeRule($this->createReflectionProvider(), true, true); + return new ApiInstanceofTypeRule($this->createReflectionProvider()); } public function testRule(): void From c764f78868c630ebe3fd7ae3890a6acbbd7d9b20 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:22:26 +0200 Subject: [PATCH 0376/3097] [BE] Change `curl_setopt` function signature based on 2nd arg --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 5 ++--- conf/parametersSchema.neon | 1 - 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index f6897df85e3..7fee5b125ca 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -54,7 +54,6 @@ Bleeding edge (TODO move to other sections) * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) * Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones @@ -154,6 +153,7 @@ Function signature fixes 🤖 * More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! * Update `Locale` signatures ([#2880](https://github.com/phpstan/phpstan-src/pull/2880)), thanks @devnix! * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3!* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! +* Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! Internals 🔍 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 7fbd8c23b32..5a4efe46783 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,7 +13,6 @@ parameters: consistentConstructor: true checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true - curlSetOptTypes: true missingMagicSerializationRule: true alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true diff --git a/conf/config.neon b/conf/config.neon index 66ea8a4a679..637fc052493 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -34,7 +34,6 @@ parameters: consistentConstructor: false checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false - curlSetOptTypes: false missingMagicSerializationRule: false alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false @@ -244,8 +243,6 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.tooWideThrowType% PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule: phpstan.rules.rule: %exceptions.check.tooWideThrowType% - PHPStan\Parser\CurlSetOptArgVisitor: - phpstan.parser.richParserNodeVisitor: %featureToggles.curlSetOptTypes% services: - @@ -297,6 +294,8 @@ services: - class: PHPStan\Parser\CurlSetOptArgVisitor + tags: + - phpstan.parser.richParserNodeVisitor - class: PHPStan\Parser\TypeTraverserInstanceofVisitor diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index a4373609b7e..074b8bd0801 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -40,7 +40,6 @@ parametersSchema: consistentConstructor: bool() checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() - curlSetOptTypes: bool() missingMagicSerializationRule: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() From 676cbaebe057db61be6656a7af1884cb80cf09a5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:23:50 +0200 Subject: [PATCH 0377/3097] [BE] Rule for `call_user_func()` --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 5 +---- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 7fee5b125ca..24792df71a2 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -16,6 +16,7 @@ Major new features 🚀 * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! +* Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -65,7 +66,6 @@ Bleeding edge (TODO move to other sections) * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 5a4efe46783..3889cfac9ec 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -20,7 +20,6 @@ parameters: varTagType: true closureDefaultParameterTypeRule: true propertyVariance: true - callUserFunc: true magicConstantOutOfContext: true pure: true checkParameterCastableToStringFunctions: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index cdc7638cdeb..2f2e4feaca0 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -10,8 +10,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.arrayFilter% PHPStan\Rules\Functions\ArrayValuesRule: phpstan.rules.rule: %featureToggles.arrayValues% - PHPStan\Rules\Functions\CallUserFuncRule: - phpstan.rules.rule: %featureToggles.callUserFunc% PHPStan\Rules\Functions\ParameterCastableToStringRule: phpstan.rules.rule: %featureToggles.checkParameterCastableToStringFunctions% PHPStan\Rules\Functions\ImplodeParameterCastableToStringRule: @@ -21,6 +19,7 @@ conditionalTags: rules: - PHPStan\Rules\DateTimeInstantiationRule + - PHPStan\Rules\Functions\CallUserFuncRule services: - @@ -42,8 +41,6 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - - - class: PHPStan\Rules\Functions\CallUserFuncRule - class: PHPStan\Rules\Functions\ParameterCastableToStringRule - diff --git a/conf/config.neon b/conf/config.neon index 637fc052493..1d3f9c31aa1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -42,7 +42,6 @@ parameters: closureDefaultParameterTypeRule: false propertyVariance: false stricterFunctionMap: false - callUserFunc: false magicConstantOutOfContext: false pure: false checkParameterCastableToStringFunctions: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 074b8bd0801..5a53e3e544c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -48,7 +48,6 @@ parametersSchema: closureDefaultParameterTypeRule: bool() propertyVariance: bool() stricterFunctionMap: bool() - callUserFunc: bool() magicConstantOutOfContext: bool() pure: bool() checkParameterCastableToStringFunctions: bool() From 0dc119c7bb5bacc944c2f242fea9696a91f761ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:24:11 +0200 Subject: [PATCH 0378/3097] Remove unused feature toggle --- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 2 files changed, 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 1d3f9c31aa1..5a0f69d1e77 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -48,7 +48,6 @@ parameters: uselessReturnValue: false printfArrayParameters: false requireFileExists: false - narrowPregMatches: true tooWidePropertyType: false fileExtensions: - php diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 5a53e3e544c..ade1f92f343 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -53,7 +53,6 @@ parametersSchema: checkParameterCastableToStringFunctions: bool() uselessReturnValue: bool() printfArrayParameters: bool() - narrowPregMatches: bool() tooWidePropertyType: bool() requireFileExists: bool() ]) From 8270b37864cec90a00a3754a66429314bcedbf78 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:25:26 +0200 Subject: [PATCH 0379/3097] [BE] ArrayUnpackingRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level3.neon | 6 +----- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 9 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 24792df71a2..2796a14ccf5 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -17,6 +17,7 @@ Major new features 🚀 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -47,7 +48,6 @@ Bleeding edge (TODO move to other sections) * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! -* ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 3889cfac9ec..82b2a09848b 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -6,7 +6,6 @@ parameters: explicitMixedViaIsArray: true arrayFilter: true - arrayUnpacking: true arrayValues: true strictUnnecessaryNullsafePropertyFetch: true looseComparison: true diff --git a/conf/config.level3.neon b/conf/config.level3.neon index dfbfc00454d..089569684cc 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -2,8 +2,6 @@ includes: - config.level2.neon conditionalTags: - PHPStan\Rules\Arrays\ArrayUnpackingRule: - phpstan.rules.rule: %featureToggles.arrayUnpacking% PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule: @@ -11,6 +9,7 @@ conditionalTags: rules: - PHPStan\Rules\Arrays\ArrayDestructuringRule + - PHPStan\Rules\Arrays\ArrayUnpackingRule - PHPStan\Rules\Arrays\IterableInForeachRule - PHPStan\Rules\Arrays\OffsetAccessAssignmentRule - PHPStan\Rules\Arrays\OffsetAccessAssignOpRule @@ -85,9 +84,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Arrays\ArrayUnpackingRule - - class: PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule diff --git a/conf/config.neon b/conf/config.neon index 5a0f69d1e77..b5f6c75be72 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -26,7 +26,6 @@ parameters: skipCheckGenericClasses: [] explicitMixedViaIsArray: false arrayFilter: false - arrayUnpacking: false arrayValues: false illegalConstructorMethodCall: false strictUnnecessaryNullsafePropertyFetch: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index ade1f92f343..f403ca849c0 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -32,7 +32,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), explicitMixedViaIsArray: bool(), arrayFilter: bool(), - arrayUnpacking: bool(), arrayValues: bool(), illegalConstructorMethodCall: bool(), strictUnnecessaryNullsafePropertyFetch: bool(), From 32564ee9c7c78cf6ce2788671a9b643baf6080cf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:31:04 +0200 Subject: [PATCH 0380/3097] [BE] Check unresolvable parameters --- changelog-2.0.md | 4 ++-- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Rules/FunctionCallParametersCheck.php | 12 ++++-------- src/Testing/TestCase.neon | 2 -- .../PHPStan/Analyser/Bug9307CallMethodsRuleTest.php | 2 +- .../Rules/Classes/ClassAttributesRuleTest.php | 1 - .../Classes/ClassConstantAttributesRuleTest.php | 1 - .../Classes/ForbiddenNameCheckExtensionRuleTest.php | 2 +- .../PHPStan/Rules/Classes/InstantiationRuleTest.php | 2 +- .../Rules/EnumCases/EnumCaseAttributesRuleTest.php | 1 - .../Functions/ArrowFunctionAttributesRuleTest.php | 1 - .../Rules/Functions/CallCallablesRuleTest.php | 1 - .../Functions/CallToFunctionParametersRuleTest.php | 2 +- .../PHPStan/Rules/Functions/CallUserFuncRuleTest.php | 2 +- .../Rules/Functions/ClosureAttributesRuleTest.php | 1 - .../Rules/Functions/FunctionAttributesRuleTest.php | 1 - .../Rules/Functions/ParamAttributesRuleTest.php | 1 - tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 2 +- .../Rules/Methods/CallStaticMethodsRuleTest.php | 1 - .../Rules/Methods/MethodAttributesRuleTest.php | 1 - .../Rules/Properties/PropertyAttributesRuleTest.php | 1 - 23 files changed, 12 insertions(+), 33 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 2796a14ccf5..fe5a7ae0586 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -18,6 +18,8 @@ Major new features 🚀 * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! +* Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! +* Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -50,7 +52,6 @@ Bleeding edge (TODO move to other sections) * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) -* Unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! @@ -82,7 +83,6 @@ Bleeding edge (TODO move to other sections) * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! -* Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 82b2a09848b..5b911974f4f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -10,7 +10,6 @@ parameters: strictUnnecessaryNullsafePropertyFetch: true looseComparison: true consistentConstructor: true - checkUnresolvableParameterTypes: true readOnlyByPhpDoc: true missingMagicSerializationRule: true alwaysCheckTooWideReturnTypeFinalMethods: true diff --git a/conf/config.neon b/conf/config.neon index b5f6c75be72..5d60538a427 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -31,7 +31,6 @@ parameters: strictUnnecessaryNullsafePropertyFetch: false looseComparison: false consistentConstructor: false - checkUnresolvableParameterTypes: false readOnlyByPhpDoc: false missingMagicSerializationRule: false alwaysCheckTooWideReturnTypeFinalMethods: false @@ -927,7 +926,6 @@ services: checkArgumentsPassedByReference: %checkArgumentsPassedByReference% checkExtraArguments: %checkExtraArguments% checkMissingTypehints: %checkMissingTypehints% - checkUnresolvableParameterTypes: %featureToggles.checkUnresolvableParameterTypes% - class: PHPStan\Rules\FunctionDefinitionCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f403ca849c0..96adcd6578d 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -37,7 +37,6 @@ parametersSchema: strictUnnecessaryNullsafePropertyFetch: bool(), looseComparison: bool(), consistentConstructor: bool() - checkUnresolvableParameterTypes: bool() readOnlyByPhpDoc: bool() missingMagicSerializationRule: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 412dd928ba5..9f427732372 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -46,7 +46,6 @@ public function __construct( private bool $checkArgumentsPassedByReference, private bool $checkExtraArguments, private bool $checkMissingTypehints, - private bool $checkUnresolvableParameterTypes, ) { } @@ -290,7 +289,7 @@ public function check( } } - if (!$acceptsNamedArguments && $this->checkUnresolvableParameterTypes && isset($messages[14])) { + if (!$acceptsNamedArguments && isset($messages[14])) { if ($argumentName !== null) { $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('named argument $%s', $argumentName))) ->identifier('argument.named') @@ -311,10 +310,7 @@ public function check( if ($this->checkArgumentTypes) { $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); - if ( - !$parameter->passedByReference()->createsNewVariable() - || (!$isBuiltin && $this->checkUnresolvableParameterTypes) // bleeding edge only - ) { + if (!$parameter->passedByReference()->createsNewVariable() || !$isBuiltin) { $accepts = $this->ruleLevelHelper->acceptsWithReason($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()); if (!$accepts->result) { @@ -332,8 +328,8 @@ public function check( } } - if ($this->checkUnresolvableParameterTypes - && $originalParameter !== null + if ( + $originalParameter !== null && isset($messages[13]) && !$this->unresolvableTypeHelper->containsUnresolvableType($originalParameter->getType()) && $this->unresolvableTypeHelper->containsUnresolvableType($parameterType) diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index a5ed7689b6b..c5ef275d1f0 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -1,7 +1,5 @@ parameters: inferPrivatePropertyTypeFromConstructor: true - featureToggles: - checkUnresolvableParameterTypes: true services: - diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index c7b4f688a35..a49a6502863 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index e261724d7dd..aac5cbda3c5 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -38,7 +38,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 69fd7a2c2b4..d5787f8344f 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index fa8b767c4bf..4907d1d7ec8 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 4271153e362..657c47bd754 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index bb81a1157e8..52428ced2b7 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 44d564b8ff9..af9266b7e55 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 97260fb244e..31a92a4f92c 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -34,7 +34,6 @@ protected function getRule(): Rule true, true, true, - true, ), $ruleLevelHelper, true, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 22dfd46eb8c..c150f1890e4 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index 4744b8f1ce9..f4eb4c2c6eb 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -21,7 +21,7 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true)); + return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index 5827cc181c6..dbab699a2be 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 5fe8fe58f80..6d028b1b96a 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 3298bd5bb0c..842d0513a1a 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -37,7 +37,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 85f5aa6e8d5..8eeba69aabe 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -36,7 +36,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index f13767c4ed9..56e260b8d29 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -54,7 +54,6 @@ protected function getRule(): Rule true, true, true, - true, ), ); } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 03d672afd00..ef5ca25aef6 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -39,7 +39,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index 26f89696866..02b75d1007b 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -36,7 +36,6 @@ protected function getRule(): Rule true, true, true, - true, ), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), From 4e30a47a63665f35a783c623b715626eb266fc5f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Sep 2024 16:33:54 +0200 Subject: [PATCH 0381/3097] Changelog update --- changelog-2.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index fe5a7ae0586..a72024427b5 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -56,7 +56,6 @@ Bleeding edge (TODO move to other sections) * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) * Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! @@ -126,6 +125,7 @@ Improvements 🔧 * Improve impossible type checker for void-returning functions ([#1857](https://github.com/phpstan/phpstan-src/pull/1857)), #8169, thanks @rvanvelzen! * Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! +* Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) Bugfixes 🐛 ===================== From 56bfb926774041523cc4a95f13a427fc5fe1e3fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 08:45:36 +0200 Subject: [PATCH 0382/3097] [BE] Validate `@var` tags --- changelog-2.0.md | 8 +-- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 12 +---- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - .../PhpDoc/WrongVariableNameInVarTagRule.php | 44 +++++++-------- .../WrongVariableNameInVarTagRuleTest.php | 54 +++++++++---------- 7 files changed, 51 insertions(+), 70 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index a72024427b5..e40d87985af 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -7,10 +7,12 @@ Major new features 🚀 * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 -* Lower memory consumption thanks to breaking up of reference cycles +* **Validate inline PHPDoc `@var` tag** type against native type (level 2) (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) + * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones +* **Lower memory consumption** thanks to breaking up of reference cycles * [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) * In testing the memory consumption was reduced by 50–70 %. -* **Enhancements in Handling Parameters Passed by Reference** +* **Enhancements in handling parameters passed by reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 @@ -56,8 +58,6 @@ Bleeding edge (TODO move to other sections) * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Validate inline PHPDoc `@var` tag type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) - * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 5b911974f4f..1fd98bf92a7 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -15,7 +15,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true - varTagType: true closureDefaultParameterTypeRule: true propertyVariance: true magicConstantOutOfContext: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 715f1089a82..5fd37f5fd4e 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -52,6 +52,8 @@ rules: - PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule - PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule + - PHPStan\Rules\PhpDoc\VarTagChangedExpressionTypeRule + - PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule - PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule - PHPStan\Rules\Classes\RequireImplementsRule - PHPStan\Rules\Classes\RequireExtendsRule @@ -68,8 +70,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% - PHPStan\Rules\PhpDoc\VarTagChangedExpressionTypeRule: - phpstan.rules.rule: %featureToggles.varTagType% PHPStan\Rules\Generics\PropertyVarianceRule: phpstan.rules.rule: %featureToggles.propertyVariance% PHPStan\Rules\Pure\PureFunctionRule: @@ -125,14 +125,6 @@ services: class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule tags: - phpstan.rules.rule - - - class: PHPStan\Rules\PhpDoc\VarTagChangedExpressionTypeRule - - - class: PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule - arguments: - checkTypeAgainstNativeType: %featureToggles.varTagType% - tags: - - phpstan.rules.rule - class: PHPStan\Rules\Generics\PropertyVarianceRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 5d60538a427..79392f5d1ac 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -36,7 +36,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false - varTagType: false closureDefaultParameterTypeRule: false propertyVariance: false stricterFunctionMap: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 96adcd6578d..0081f682df1 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -42,7 +42,6 @@ parametersSchema: alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() - varTagType: bool() closureDefaultParameterTypeRule: bool() propertyVariance: bool() stricterFunctionMap: bool() diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 87784c71d61..72e61ffdb65 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -37,7 +37,6 @@ final class WrongVariableNameInVarTagRule implements Rule public function __construct( private FileTypeMapper $fileTypeMapper, private VarTagTypeRuleHelper $varTagTypeRuleHelper, - private bool $checkTypeAgainstNativeType, ) { } @@ -138,11 +137,6 @@ private function processAssign(Scope $scope, Node\Expr $var, Node\Expr $expr, ar $errors = []; $hasMultipleMessage = false; $assignedVariables = $this->getAssignedVariables($var); - if ($this->checkTypeAgainstNativeType) { - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $var, $expr, $varTags, $assignedVariables) as $error) { - $errors[] = $error; - } - } foreach (array_keys($varTags) as $key) { if (is_int($key)) { if (count($varTags) !== 1) { @@ -181,6 +175,12 @@ private function processAssign(Scope $scope, Node\Expr $var, Node\Expr $expr, ar } } + if (count($errors) === 0) { + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $var, $expr, $varTags, $assignedVariables) as $error) { + $errors[] = $error; + } + } + return $errors; } @@ -251,19 +251,17 @@ private function processForeach(Scope $scope, Node\Expr $iterateeExpr, ?Node\Exp ))->identifier('varTag.differentVariable')->build(); } - if ($this->checkTypeAgainstNativeType) { - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $iterateeExpr, $iterateeExpr, $varTags, $variableNames) as $error) { - $errors[] = $error; - } - if ($keyVar !== null) { - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $keyVar, new GetIterableKeyTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { - $errors[] = $error; - } - } - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $valueVar, new GetIterableValueTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $iterateeExpr, $iterateeExpr, $varTags, $variableNames) as $error) { + $errors[] = $error; + } + if ($keyVar !== null) { + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $keyVar, new GetIterableKeyTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { $errors[] = $error; } } + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $valueVar, new GetIterableValueTypeExpr($iterateeExpr), $varTags, $variableNames) as $error) { + $errors[] = $error; + } return $errors; } @@ -321,14 +319,12 @@ private function processStatic(Scope $scope, array $vars, array $varTags): array ))->identifier('varTag.differentVariable')->build(); } - if ($this->checkTypeAgainstNativeType) { - foreach ($vars as $var) { - if ($var->default === null) { - continue; - } - foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $var->var, $var->default, $varTags, $variableNames) as $error) { - $errors[] = $error; - } + foreach ($vars as $var) { + if ($var->default === null) { + continue; + } + foreach ($this->varTagTypeRuleHelper->checkVarType($scope, $var->var, $var->default, $varTags, $variableNames) as $error) { + $errors[] = $error; } } diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 6083de943d1..155c0d6ea49 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -13,8 +13,6 @@ class WrongVariableNameInVarTagRuleTest extends RuleTestCase { - private bool $checkTypeAgainstNativeType = false; - private bool $checkTypeAgainstPhpDocType = false; private bool $strictWideningCheck = false; @@ -24,13 +22,20 @@ protected function getRule(): Rule return new WrongVariableNameInVarTagRule( self::getContainer()->getByType(FileTypeMapper::class), new VarTagTypeRuleHelper($this->checkTypeAgainstPhpDocType, $this->strictWideningCheck), - $this->checkTypeAgainstNativeType, ); } public function testRule(): void { $this->analyse([__DIR__ . '/data/wrong-variable-name-var.php'], [ + [ + 'PHPDoc tag @var with type int is not subtype of native type void.', + 11, + ], + [ + 'PHPDoc tag @var with type int is not subtype of native type void.', + 14, + ], [ 'Variable $foo in PHPDoc tag @var does not match assigned variable $test.', 17, @@ -71,6 +76,10 @@ public function testRule(): void 'Variable $foo in PHPDoc tag @var does not exist.', 109, ], + [ + 'PHPDoc tag @var with type int is not subtype of native type void.', + 120, + ], [ 'Multiple PHPDoc @var tags above single variable assignment are not supported.', 126, @@ -253,12 +262,9 @@ public function dataReportWrongType(): iterable ], ]; - yield [false, false, false, []]; - yield [true, false, false, $nativeCheckOnly]; - yield [true, false, true, $nativeCheckOnly]; - yield [false, true, false, []]; - yield [false, true, true, []]; - yield [true, true, false, [ + yield [false, false, $nativeCheckOnly]; + yield [false, true, $nativeCheckOnly]; + yield [true, false, [ [ 'PHPDoc tag @var with type string|null is not subtype of native type string.', 14, @@ -349,7 +355,7 @@ public function dataReportWrongType(): iterable 204, ], ]]; - yield [true, true, true, [ + yield [true, true, [ [ 'PHPDoc tag @var with type string|null is not subtype of native type string.', 14, @@ -469,29 +475,21 @@ public function dataReportWrongType(): iterable /** * @dataProvider dataPermutateCheckTypeAgainst */ - public function testEmptyArrayInitWithWiderPhpDoc(bool $checkTypeAgainstNativeType, bool $checkTypeAgainstPhpDocType): void + public function testEmptyArrayInitWithWiderPhpDoc(bool $checkTypeAgainstPhpDocType): void { - $this->checkTypeAgainstNativeType = $checkTypeAgainstNativeType; $this->checkTypeAgainstPhpDocType = $checkTypeAgainstPhpDocType; - - $errors = !$checkTypeAgainstNativeType - ? [] - : [ - [ - 'PHPDoc tag @var with type int is not subtype of native type array{}.', - 24, - ], - ]; - - $this->analyse([__DIR__ . '/data/var-above-empty-array-widening.php'], $errors); + $this->analyse([__DIR__ . '/data/var-above-empty-array-widening.php'], [ + [ + 'PHPDoc tag @var with type int is not subtype of native type array{}.', + 24, + ], + ]); } public function dataPermutateCheckTypeAgainst(): iterable { - yield [true, true]; - yield [false, true]; - yield [true, false]; - yield [false, false]; + yield [true]; + yield [false]; } /** @@ -499,13 +497,11 @@ public function dataPermutateCheckTypeAgainst(): iterable * @param list $expectedErrors */ public function testReportWrongType( - bool $checkTypeAgainstNativeType, bool $checkTypeAgainstPhpDocType, bool $strictWideningCheck, array $expectedErrors, ): void { - $this->checkTypeAgainstNativeType = $checkTypeAgainstNativeType; $this->checkTypeAgainstPhpDocType = $checkTypeAgainstPhpDocType; $this->strictWideningCheck = $strictWideningCheck; $this->analyse([__DIR__ . '/data/wrong-var-native-type.php'], $expectedErrors); From fe4a386f173c48cc409abdf2dfac8df4ae34b1e3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 08:58:47 +0200 Subject: [PATCH 0383/3097] Always report static property fetch in `isset()`, not just on PHP 8.2+ --- src/Analyser/MutatingScope.php | 2 +- .../AccessStaticPropertiesRuleTest.php | 170 +----------------- 2 files changed, 5 insertions(+), 167 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 62786fe77a9..0e2ead8dde6 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3855,7 +3855,7 @@ public function isInExpressionAssign(Expr $expr): bool public function setAllowedUndefinedExpression(Expr $expr): self { - if ($this->phpVersion->deprecatesDynamicProperties() && $expr instanceof Expr\StaticPropertyFetch) { + if ($expr instanceof Expr\StaticPropertyFetch) { return $this; } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 822a048e1ec..87861e276ea 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -31,166 +31,6 @@ protected function getRule(): Rule public function testAccessStaticProperties(): void { - if (PHP_VERSION_ID >= 80200) { - $this->markTestSkipped('Test is not for PHP 8.2.'); - } - $this->analyse([__DIR__ . '/data/access-static-properties.php'], [ - [ - 'Access to an undefined static property FooAccessStaticProperties::$bar.', - 23, - ], - [ - 'Access to an undefined static property BarAccessStaticProperties::$bar.', - 24, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$bar.', - 25, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 26, - ], - [ - 'IpsumAccessStaticProperties::ipsum() accesses parent::$lorem but IpsumAccessStaticProperties does not extend any class.', - 42, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 44, - ], - [ - 'Access to static property $test on an unknown class UnknownStaticProperties.', - 47, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$baz.', - 53, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$nonexistent.', - 55, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyBaz.', - 63, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$emptyNonexistent.', - 65, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', - 71, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherNonexistent.', - 72, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', - 75, - ], - [ - 'Access to an undefined static property static(IpsumAccessStaticProperties)::$anotherEmptyNonexistent.', - 78, - ], - [ - 'Accessing self::$staticFooProperty outside of class scope.', - 84, - ], - [ - 'Accessing static::$staticFooProperty outside of class scope.', - 85, - ], - [ - 'Accessing parent::$staticFooProperty outside of class scope.', - 86, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 89, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 90, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$nonexistent.', - 94, - ], - [ - 'Access to static property $test on an unknown class NonexistentClass.', - 97, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Access to an undefined static property FooAccessStaticProperties&SomeInterface::$nonexistent.', - 108, - ], - [ - 'Cannot access static property $foo on int|string.', - 113, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 119, - ], - [ - 'Access to an undefined static property FooAccessStaticProperties::$unknownProperties.', - 119, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 120, - ], - [ - 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', - 120, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 121, - ], - [ - 'Access to protected property $foo of class FooAccessStaticProperties.', - 121, - ], - [ - 'Class FooAccessStaticProperties referenced with incorrect case: FOOAccessStaticPropertieS.', - 122, - ], - [ - 'Access to an undefined static property ClassOrString|string::$unknownProperty.', - 141, - ], - [ - 'Static access to instance property ClassOrString::$instanceProperty.', - 152, - ], - [ - 'Access to static property $foo on an unknown class TraitWithStaticProperty.', - 209, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], - [ - 'Static access to instance property AccessWithStatic::$bar.', - 223, - ], - [ - 'Access to an undefined static property static(AccessWithStatic)::$nonexistent.', - 224, - ], - ]); - } - - public function testAccessStaticPropertiesPhp82(): void - { - if (PHP_VERSION_ID < 80200) { - $this->markTestSkipped('Test requires PHP 8.2.'); - } - $this->analyse([__DIR__ . '/data/access-static-properties.php'], [ [ 'Access to an undefined static property FooAccessStaticProperties::$bar.', @@ -436,14 +276,12 @@ public function testBug5143(): void public function testBug6809(): void { - $errors = []; - if (PHP_VERSION_ID >= 80200) { - $errors[] = [ + $this->analyse([__DIR__ . '/data/bug-6809.php'], [ + [ 'Access to an undefined static property static(Bug6809\HelloWorld)::$coolClass.', 7, - ]; - } - $this->analyse([__DIR__ . '/data/bug-6809.php'], $errors); + ], + ]); } public function testBug8333(): void From 3e2feca2f2f1aef1a6b0070fcd16c697b49034de Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:16:24 +0200 Subject: [PATCH 0384/3097] Fix CS --- .../PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 87861e276ea..acbfca290c2 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase From 8c10b7336f31be02b1c330b880dde76986950d35 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:20:47 +0200 Subject: [PATCH 0385/3097] [BE] Report unnecessary nullsafe property fetch with different message --- changelog-2.0.md | 1 + conf/bleedingEdge.neon | 1 - conf/config.neon | 2 - conf/parametersSchema.neon | 1 - src/Rules/IssetCheck.php | 5 --- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 31 -------------- .../PHPStan/Rules/Variables/IssetRuleTest.php | 42 ++----------------- .../Rules/Variables/NullCoalesceRuleTest.php | 34 --------------- 8 files changed, 5 insertions(+), 112 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index e40d87985af..668dd218a3d 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -126,6 +126,7 @@ Improvements 🔧 * Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) +* Report unnecessary nullsafe property fetch inside `??` / `isset` / `empty` with different message ([#1253](https://github.com/phpstan/phpstan-src/pull/1253)), thanks @rajyan! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1fd98bf92a7..1154241b44a 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -7,7 +7,6 @@ parameters: explicitMixedViaIsArray: true arrayFilter: true arrayValues: true - strictUnnecessaryNullsafePropertyFetch: true looseComparison: true consistentConstructor: true readOnlyByPhpDoc: true diff --git a/conf/config.neon b/conf/config.neon index 79392f5d1ac..fef4f694c96 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -28,7 +28,6 @@ parameters: arrayFilter: false arrayValues: false illegalConstructorMethodCall: false - strictUnnecessaryNullsafePropertyFetch: false looseComparison: false consistentConstructor: false readOnlyByPhpDoc: false @@ -964,7 +963,6 @@ services: arguments: checkAdvancedIsset: %checkAdvancedIsset% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - strictUnnecessaryNullsafePropertyFetch: %featureToggles.strictUnnecessaryNullsafePropertyFetch% - class: PHPStan\Rules\Methods\MethodCallCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 0081f682df1..25cc07815bb 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -34,7 +34,6 @@ parametersSchema: arrayFilter: bool(), arrayValues: bool(), illegalConstructorMethodCall: bool(), - strictUnnecessaryNullsafePropertyFetch: bool(), looseComparison: bool(), consistentConstructor: bool() readOnlyByPhpDoc: bool() diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 1a07cb30c73..89433d32cae 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -25,7 +25,6 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, private bool $checkAdvancedIsset, private bool $treatPhpDocTypesAsCertain, - private bool $strictUnnecessaryNullsafePropertyFetch, ) { } @@ -217,10 +216,6 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str } if ($expr instanceof Expr\NullsafePropertyFetch) { - if (!$this->strictUnnecessaryNullsafePropertyFetch) { - return null; - } - if ($expr->name instanceof Node\Identifier) { return RuleErrorBuilder::message(sprintf('Using nullsafe property access "?->%s" %s is unnecessary. Use -> instead.', $expr->name->name, $operatorDescription)) ->identifier('nullsafe.neverNull') diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 6fa229669f7..150eb99d7e8 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -17,8 +17,6 @@ class EmptyRuleTest extends RuleTestCase private bool $treatPhpDocTypesAsCertain; - private bool $strictUnnecessaryNullsafePropertyFetch; - protected function getRule(): Rule { return new EmptyRule(new IssetCheck( @@ -26,7 +24,6 @@ protected function getRule(): Rule new PropertyReflectionFinder(), true, $this->treatPhpDocTypesAsCertain, - $this->strictUnnecessaryNullsafePropertyFetch, )); } @@ -38,7 +35,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/empty-rule.php'], [ [ 'Offset \'nonexistent\' on array{2: bool, 3: false, 4: true}|array{bool, false, bool, false, true} in empty() does not exist.', @@ -78,7 +74,6 @@ public function testRule(): void public function testBug970(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-970.php'], [ [ 'Variable $ar in empty() is never defined.', @@ -90,7 +85,6 @@ public function testBug970(): void public function testBug6974(): void { $this->treatPhpDocTypesAsCertain = false; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-6974.php'], [ [ 'Variable $a in empty() always exists and is always falsy.', @@ -102,7 +96,6 @@ public function testBug6974(): void public function testBug6974TreatPhpDocTypesAsCertain(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-6974.php'], [ [ 'Variable $a in empty() always exists and is always falsy.', @@ -122,24 +115,6 @@ public function testBug7109(): void } $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; - - $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ - [ - 'Expression in empty() is not falsy.', - 59, - ], - ]); - } - - public function testBug7109Strict(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ [ @@ -176,7 +151,6 @@ public function testBug7109Strict(): void public function testBug7318(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7318.php'], []); } @@ -184,7 +158,6 @@ public function testBug7318(): void public function testBug7424(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-7424.php'], []); } @@ -192,7 +165,6 @@ public function testBug7424(): void public function testBug7724(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-7724.php'], []); } @@ -200,7 +172,6 @@ public function testBug7724(): void public function testBug7199(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-7199.php'], []); } @@ -208,7 +179,6 @@ public function testBug7199(): void public function testBug9126(): void { $this->treatPhpDocTypesAsCertain = false; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-9126.php'], []); } @@ -225,7 +195,6 @@ public function dataBug9403(): iterable public function testBug9403(bool $treatPhpDocTypesAsCertain): void { $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-9403.php'], []); } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 2c22dfd35a8..a3e1ed68a2c 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -17,8 +17,6 @@ class IssetRuleTest extends RuleTestCase private bool $treatPhpDocTypesAsCertain; - private bool $strictUnnecessaryNullsafePropertyFetch; - protected function getRule(): Rule { return new IssetRule(new IssetCheck( @@ -26,7 +24,6 @@ protected function getRule(): Rule new PropertyReflectionFinder(), true, $this->treatPhpDocTypesAsCertain, - $this->strictUnnecessaryNullsafePropertyFetch, )); } @@ -38,7 +35,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/isset.php'], [ [ 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', @@ -126,7 +122,6 @@ public function testRule(): void public function testRuleWithoutTreatPhpDocTypesAsCertain(): void { $this->treatPhpDocTypesAsCertain = false; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/isset.php'], [ [ 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', @@ -206,7 +201,6 @@ public function testRuleWithoutTreatPhpDocTypesAsCertain(): void public function testNativePropertyTypes(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [ /*[ // no way to achieve this with current PHP Reflection API @@ -225,14 +219,12 @@ public function testNativePropertyTypes(): void public function testBug4290(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-4290.php'], []); } public function testBug4671(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ 'Offset numeric-string on array in isset() does not exist.', 13, @@ -242,7 +234,6 @@ public function testBug4671(): void public function testVariableCertaintyInIsset(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ [ 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', @@ -318,7 +309,6 @@ public function testVariableCertaintyInIsset(): void public function testIssetInGlobalScope(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ [ 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', @@ -330,35 +320,21 @@ public function testIssetInGlobalScope(): void public function testNullsafe(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; - $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); - } - - public function testBug7109(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; - - $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ + $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], [ [ - 'Expression in isset() is not nullable.', - 41, + 'Using nullsafe property access "?->bla" in isset() is unnecessary. Use -> instead.', + 10, ], ]); } - public function testBug7109Strict(): void + public function testBug7109(): void { if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Test requires PHP 8.0.'); } $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ [ @@ -387,7 +363,6 @@ public function testBug7109Strict(): void public function testBug7318(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7318.php'], [ [ @@ -400,7 +375,6 @@ public function testBug7318(): void public function testBug6163(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-6163.php'], []); } @@ -408,7 +382,6 @@ public function testBug6163(): void public function testBug6997(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-6997.php'], []); } @@ -420,7 +393,6 @@ public function testBug7776(): void } $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-7776.php'], []); } @@ -428,7 +400,6 @@ public function testBug7776(): void public function testBug6008(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-6008.php'], []); } @@ -436,7 +407,6 @@ public function testBug6008(): void public function testBug7292(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-7292.php'], []); } @@ -444,7 +414,6 @@ public function testBug7292(): void public function testObjectShapes(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; // could be checked but current is not $this->analyse([__DIR__ . '/data/isset-object-shapes.php'], []); @@ -453,7 +422,6 @@ public function testObjectShapes(): void public function testBug10151(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-10151.php'], []); } @@ -461,7 +429,6 @@ public function testBug10151(): void public function testBug3985(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-3985.php'], [ [ @@ -478,7 +445,6 @@ public function testBug3985(): void public function testBug10064(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-10064.php'], []); } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 302858a4e5d..2b32877d527 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -17,8 +17,6 @@ class NullCoalesceRuleTest extends RuleTestCase private bool $treatPhpDocTypesAsCertain; - private bool $strictUnnecessaryNullsafePropertyFetch; - protected function getRule(): Rule { return new NullCoalesceRule(new IssetCheck( @@ -26,7 +24,6 @@ protected function getRule(): Rule new PropertyReflectionFinder(), true, $this->treatPhpDocTypesAsCertain, - $this->strictUnnecessaryNullsafePropertyFetch, )); } @@ -38,7 +35,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testCoalesceRule(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $errors = [ [ 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', @@ -145,7 +141,6 @@ public function testCoalesceRule(): void public function testCoalesceAssignRule(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/null-coalesce-assign.php'], [ [ 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', @@ -209,14 +204,12 @@ public function testCoalesceAssignRule(): void public function testNullsafe(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/null-coalesce-nullsafe.php'], []); } public function testVariableCertaintyInNullCoalesce(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/variable-certainty-null.php'], [ [ 'Variable $scalar on left side of ?? always exists and is not nullable.', @@ -236,7 +229,6 @@ public function testVariableCertaintyInNullCoalesce(): void public function testVariableCertaintyInNullCoalesceAssign(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ [ 'Variable $scalar on left side of ??= always exists and is not nullable.', @@ -256,7 +248,6 @@ public function testVariableCertaintyInNullCoalesceAssign(): void public function testNullCoalesceInGlobalScope(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ [ 'Variable $bar on left side of ?? always exists and is not nullable.', @@ -268,7 +259,6 @@ public function testNullCoalesceInGlobalScope(): void public function testBug5933(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-5933.php'], []); } @@ -279,24 +269,6 @@ public function testBug7109(): void } $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; - - $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ - [ - 'Expression on left side of ?? is not nullable.', - 40, - ], - ]); - } - - public function testBug7109Strict(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7109.php'], [ [ @@ -325,7 +297,6 @@ public function testBug7109Strict(): void public function testBug7190(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/../Properties/data/bug-7190.php'], [ [ @@ -338,7 +309,6 @@ public function testBug7190(): void public function testBug7318(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/../Properties/data/bug-7318.php'], [ [ @@ -351,7 +321,6 @@ public function testBug7318(): void public function testBug7968(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-7968.php'], []); } @@ -359,7 +328,6 @@ public function testBug7968(): void public function testBug8084(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-8084.php'], []); } @@ -367,7 +335,6 @@ public function testBug8084(): void public function testBug10577(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-10577.php'], []); } @@ -375,7 +342,6 @@ public function testBug10577(): void public function testBug10610(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; $this->analyse([__DIR__ . '/data/bug-10610.php'], []); } From f3b719024e3eb581d96562d1d1a6fdcb95dd790d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:35:37 +0200 Subject: [PATCH 0386/3097] [BE] Check array functions which require stringish values --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 16 +++------------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 668dd218a3d..d9ad8796e9e 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -19,6 +19,7 @@ Major new features 🚀 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 @@ -86,7 +87,6 @@ Bleeding edge (TODO move to other sections) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! -* Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! Improvements 🔧 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1154241b44a..2a6accd9dcb 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -18,7 +18,6 @@ parameters: propertyVariance: true magicConstantOutOfContext: true pure: true - checkParameterCastableToStringFunctions: true uselessReturnValue: true printfArrayParameters: true tooWidePropertyType: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 2f2e4feaca0..3fa1bb8e294 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -10,16 +10,13 @@ conditionalTags: phpstan.rules.rule: %featureToggles.arrayFilter% PHPStan\Rules\Functions\ArrayValuesRule: phpstan.rules.rule: %featureToggles.arrayValues% - PHPStan\Rules\Functions\ParameterCastableToStringRule: - phpstan.rules.rule: %featureToggles.checkParameterCastableToStringFunctions% - PHPStan\Rules\Functions\ImplodeParameterCastableToStringRule: - phpstan.rules.rule: %featureToggles.checkParameterCastableToStringFunctions% - PHPStan\Rules\Functions\SortParameterCastableToStringRule: - phpstan.rules.rule: %featureToggles.checkParameterCastableToStringFunctions% rules: - PHPStan\Rules\DateTimeInstantiationRule - PHPStan\Rules\Functions\CallUserFuncRule + - PHPStan\Rules\Functions\ParameterCastableToStringRule + - PHPStan\Rules\Functions\ImplodeParameterCastableToStringRule + - PHPStan\Rules\Functions\SortParameterCastableToStringRule services: - @@ -40,10 +37,3 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - - - - class: PHPStan\Rules\Functions\ParameterCastableToStringRule - - - class: PHPStan\Rules\Functions\ImplodeParameterCastableToStringRule - - - class: PHPStan\Rules\Functions\SortParameterCastableToStringRule diff --git a/conf/config.neon b/conf/config.neon index fef4f694c96..9038685e3ad 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -40,7 +40,6 @@ parameters: stricterFunctionMap: false magicConstantOutOfContext: false pure: false - checkParameterCastableToStringFunctions: false uselessReturnValue: false printfArrayParameters: false requireFileExists: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 25cc07815bb..8a22e0fa823 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -46,7 +46,6 @@ parametersSchema: stricterFunctionMap: bool() magicConstantOutOfContext: bool() pure: bool() - checkParameterCastableToStringFunctions: bool() uselessReturnValue: bool() printfArrayParameters: bool() tooWidePropertyType: bool() From 6ce1743ae8feb1cf941f5e3ecc2d03aeede05f68 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:40:29 +0200 Subject: [PATCH 0387/3097] [BE] IncompatibleDefaultParameterTypeRule for closures --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 10 ++-------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index d9ad8796e9e..d98795d5f5b 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -23,6 +23,7 @@ Major new features 🚀 * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 +* IncompatibleDefaultParameterTypeRule for closures (level 2) (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) @@ -62,7 +63,6 @@ Bleeding edge (TODO move to other sections) * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed -* IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 2a6accd9dcb..0ad75d47188 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -14,7 +14,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true - closureDefaultParameterTypeRule: true propertyVariance: true magicConstantOutOfContext: true pure: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 5fd37f5fd4e..7863272da9b 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -23,6 +23,8 @@ rules: - PHPStan\Rules\Comparison\UsageOfVoidMatchExpressionRule - PHPStan\Rules\Constants\ValueAssignedToClassConstantRule - PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule + - PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule + - PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule - PHPStan\Rules\Generics\ClassAncestorsRule - PHPStan\Rules\Generics\ClassTemplateTypeRule - PHPStan\Rules\Generics\EnumAncestorsRule @@ -62,10 +64,6 @@ rules: - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionTraitRule conditionalTags: - PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule: - phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% - PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule: - phpstan.rules.rule: %featureToggles.closureDefaultParameterTypeRule% PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: @@ -95,10 +93,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Functions\IncompatibleArrowFunctionDefaultParameterTypeRule - - - class: PHPStan\Rules\Functions\IncompatibleClosureDefaultParameterTypeRule - class: PHPStan\Rules\Functions\CallCallablesRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 9038685e3ad..76b8e8e8146 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false - closureDefaultParameterTypeRule: false propertyVariance: false stricterFunctionMap: false magicConstantOutOfContext: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 8a22e0fa823..a9f23039eff 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,7 +41,6 @@ parametersSchema: alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() - closureDefaultParameterTypeRule: bool() propertyVariance: bool() stricterFunctionMap: bool() magicConstantOutOfContext: bool() From 8cf4281a70dbaeb048db580d8b78ba7258adbad3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:43:48 +0200 Subject: [PATCH 0388/3097] [BE] Rule about `@phpstan-consistent-constructor` --- changelog-2.0.md | 1 + conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 5 +---- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index d98795d5f5b..53269961abf 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -41,6 +41,7 @@ Major new features 🚀 * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) +* Rule about `@phpstan-consistent-constructor` (level 0) ([#1296](https://github.com/phpstan/phpstan-src/pull/1296)), thanks @canvural! * Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (level 0) (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) * Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (level 0) (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) * ApiInstanceofRule (level 0) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0ad75d47188..35f6a992265 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,7 +8,6 @@ parameters: arrayFilter: true arrayValues: true looseComparison: true - consistentConstructor: true readOnlyByPhpDoc: true missingMagicSerializationRule: true alwaysCheckTooWideReturnTypeFinalMethods: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 0e00bc5e92a..711beeff40a 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -8,8 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Methods\ConsistentConstructorRule: - phpstan.rules.rule: %featureToggles.consistentConstructor% PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule: phpstan.rules.rule: %featureToggles.missingMagicSerializationRule% PHPStan\Rules\Constants\MagicConstantContextRule: @@ -90,6 +88,7 @@ rules: - PHPStan\Rules\Methods\AbstractPrivateMethodRule - PHPStan\Rules\Methods\CallMethodsRule - PHPStan\Rules\Methods\CallStaticMethodsRule + - PHPStan\Rules\Methods\ConsistentConstructorRule - PHPStan\Rules\Methods\ConstructorReturnTypeRule - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule - PHPStan\Rules\Methods\FinalPrivateMethodRule @@ -158,8 +157,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Methods\ConsistentConstructorRule - class: PHPStan\Rules\Missing\MissingReturnRule diff --git a/conf/config.neon b/conf/config.neon index 76b8e8e8146..a6871cf0b07 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -29,7 +29,6 @@ parameters: arrayValues: false illegalConstructorMethodCall: false looseComparison: false - consistentConstructor: false readOnlyByPhpDoc: false missingMagicSerializationRule: false alwaysCheckTooWideReturnTypeFinalMethods: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index a9f23039eff..5a960314b57 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -35,7 +35,6 @@ parametersSchema: arrayValues: bool(), illegalConstructorMethodCall: bool(), looseComparison: bool(), - consistentConstructor: bool() readOnlyByPhpDoc: bool() missingMagicSerializationRule: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() From 293fbca1e3a47bd347ab7c6960335c104b7b56b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:45:38 +0200 Subject: [PATCH 0389/3097] [BE] Check too wide private property type --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 7 +------ conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 53269961abf..a1e03d841a9 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -15,6 +15,7 @@ Major new features 🚀 * **Enhancements in handling parameters passed by reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! +* Check too wide private property type (level 4) (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! @@ -84,7 +85,6 @@ Bleeding edge (TODO move to other sections) * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! -* Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 35f6a992265..ab60f6996a3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -18,5 +18,4 @@ parameters: pure: true uselessReturnValue: true printfArrayParameters: true - tooWidePropertyType: true requireFileExists: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 27c09f7ab7f..21621869e5f 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -17,6 +17,7 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule - PHPStan\Rules\TooWideTypehints\TooWideFunctionParameterOutTypeRule - PHPStan\Rules\TooWideTypehints\TooWideMethodParameterOutTypeRule + - PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule - PHPStan\Rules\Traits\NotAnalysedTraitRule conditionalTags: @@ -44,8 +45,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureStaticCallCollector: phpstan.collector: %featureToggles.pure% - PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule: - phpstan.rules.rule: %featureToggles.tooWidePropertyType% parameters: checkAdvancedIsset: true @@ -309,7 +308,3 @@ services: reportUncheckedExceptionDeadCatch: %exceptions.reportUncheckedExceptionDeadCatch% tags: - phpstan.rules.rule - - - - - class: PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule diff --git a/conf/config.neon b/conf/config.neon index a6871cf0b07..096d92c5180 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -41,7 +41,6 @@ parameters: uselessReturnValue: false printfArrayParameters: false requireFileExists: false - tooWidePropertyType: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 5a960314b57..be8d4959b99 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -46,7 +46,6 @@ parametersSchema: pure: bool() uselessReturnValue: bool() printfArrayParameters: bool() - tooWidePropertyType: bool() requireFileExists: bool() ]) fileExtensions: listOf(string()) From b593dde0278d9862447f9b254d3e3e4f679ab0e0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:47:32 +0200 Subject: [PATCH 0390/3097] [BE] Check variance of template types in properties --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 4 ++-- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index a1e03d841a9..19be2cdc9c1 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -21,6 +21,7 @@ Major new features 🚀 * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! +* Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 @@ -65,7 +66,6 @@ Bleeding edge (TODO move to other sections) * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed -* Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index ab60f6996a3..37de1e3e7ea 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,7 +13,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true - propertyVariance: true magicConstantOutOfContext: true pure: true uselessReturnValue: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 7863272da9b..f75e01dfdef 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -68,8 +68,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% - PHPStan\Rules\Generics\PropertyVarianceRule: - phpstan.rules.rule: %featureToggles.propertyVariance% PHPStan\Rules\Pure\PureFunctionRule: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\Pure\PureMethodRule: @@ -123,6 +121,8 @@ services: class: PHPStan\Rules\Generics\PropertyVarianceRule arguments: readOnlyByPhpDoc: %featureToggles.readOnlyByPhpDoc% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Pure\PureFunctionRule diff --git a/conf/config.neon b/conf/config.neon index 096d92c5180..7738f1ffa64 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -34,7 +34,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false - propertyVariance: false stricterFunctionMap: false magicConstantOutOfContext: false pure: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index be8d4959b99..e4f663d442b 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -40,7 +40,6 @@ parametersSchema: alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() - propertyVariance: bool() stricterFunctionMap: bool() magicConstantOutOfContext: bool() pure: bool() From 879590db6046d747e62692061449f81f9a13b04e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:48:59 +0200 Subject: [PATCH 0391/3097] [BE] MagicConstantContextRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 6 +----- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 9 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 19be2cdc9c1..76dd7658785 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -19,6 +19,7 @@ Major new features 🚀 * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! +* MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! @@ -69,7 +70,6 @@ Bleeding edge (TODO move to other sections) * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 37de1e3e7ea..32fb1509216 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,7 +13,6 @@ parameters: alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true - magicConstantOutOfContext: true pure: true uselessReturnValue: true printfArrayParameters: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 711beeff40a..d9fdeea93c1 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -10,8 +10,6 @@ conditionalTags: phpstan.rules.rule: %checkUninitializedProperties% PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule: phpstan.rules.rule: %featureToggles.missingMagicSerializationRule% - PHPStan\Rules\Constants\MagicConstantContextRule: - phpstan.rules.rule: %featureToggles.magicConstantOutOfContext% PHPStan\Rules\Functions\UselessFunctionReturnValueRule: phpstan.rules.rule: %featureToggles.uselessReturnValue% PHPStan\Rules\Functions\PrintfArrayParametersRule: @@ -61,6 +59,7 @@ rules: - PHPStan\Rules\Classes\TraitAttributeClassRule - PHPStan\Rules\Constants\DynamicClassConstantFetchRule - PHPStan\Rules\Constants\FinalConstantRule + - PHPStan\Rules\Constants\MagicConstantContextRule - PHPStan\Rules\Constants\NativeTypedClassConstantRule - PHPStan\Rules\EnumCases\EnumCaseAttributesRule - PHPStan\Rules\Exceptions\NoncapturingCatchRule @@ -263,9 +262,6 @@ services: - class: PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule - - - class: PHPStan\Rules\Constants\MagicConstantContextRule - - class: PHPStan\Rules\Functions\UselessFunctionReturnValueRule diff --git a/conf/config.neon b/conf/config.neon index 7738f1ffa64..bc7e8a3aa0d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false stricterFunctionMap: false - magicConstantOutOfContext: false pure: false uselessReturnValue: false printfArrayParameters: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index e4f663d442b..05826528923 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,7 +41,6 @@ parametersSchema: alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() stricterFunctionMap: bool() - magicConstantOutOfContext: bool() pure: bool() uselessReturnValue: bool() printfArrayParameters: bool() From d419f43b38f9ab045e0bf5ca5cc4967be4b28259 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:51:29 +0200 Subject: [PATCH 0392/3097] [BE] MissingMagicSerializationMethodsRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 6 +----- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 9 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 76dd7658785..49420a67539 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -20,6 +20,7 @@ Major new features 🚀 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! +* MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! @@ -63,7 +64,6 @@ Bleeding edge (TODO move to other sections) * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! -* MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! * Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 32fb1509216..ae30dc43d3c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -9,7 +9,6 @@ parameters: arrayValues: true looseComparison: true readOnlyByPhpDoc: true - missingMagicSerializationRule: true alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d9fdeea93c1..731f86cb11d 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -8,8 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule: - phpstan.rules.rule: %featureToggles.missingMagicSerializationRule% PHPStan\Rules\Functions\UselessFunctionReturnValueRule: phpstan.rules.rule: %featureToggles.uselessReturnValue% PHPStan\Rules\Functions\PrintfArrayParametersRule: @@ -93,6 +91,7 @@ rules: - PHPStan\Rules\Methods\FinalPrivateMethodRule - PHPStan\Rules\Methods\MethodCallableRule - PHPStan\Rules\Methods\MethodVisibilityInInterfaceRule + - PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule - PHPStan\Rules\Methods\MissingMethodImplementationRule - PHPStan\Rules\Methods\MethodAttributesRule - PHPStan\Rules\Methods\StaticMethodCallableRule @@ -259,9 +258,6 @@ services: arguments: additionalConstructors: %additionalConstructors% - - - class: PHPStan\Rules\Methods\MissingMagicSerializationMethodsRule - - class: PHPStan\Rules\Functions\UselessFunctionReturnValueRule diff --git a/conf/config.neon b/conf/config.neon index bc7e8a3aa0d..02795237fcd 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -30,7 +30,6 @@ parameters: illegalConstructorMethodCall: false looseComparison: false readOnlyByPhpDoc: false - missingMagicSerializationRule: false alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 05826528923..1d2af1cb847 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -36,7 +36,6 @@ parametersSchema: illegalConstructorMethodCall: bool(), looseComparison: bool(), readOnlyByPhpDoc: bool() - missingMagicSerializationRule: bool() alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() From 6703fce6c32d4e8f6917819d1eaa4066ae41999c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 09:53:59 +0200 Subject: [PATCH 0393/3097] [BE] Report useless return values of function calls like `var_export` without `$return=true` --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 5 +---- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 49420a67539..6eab3458889 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -21,6 +21,7 @@ Major new features 🚀 * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! +* Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! @@ -86,7 +87,6 @@ Bleeding edge (TODO move to other sections) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 -* Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! Improvements 🔧 diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index ae30dc43d3c..1c68c2c5abd 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,6 +13,5 @@ parameters: alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true pure: true - uselessReturnValue: true printfArrayParameters: true requireFileExists: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 731f86cb11d..691f4688096 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -8,8 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Functions\UselessFunctionReturnValueRule: - phpstan.rules.rule: %featureToggles.uselessReturnValue% PHPStan\Rules\Functions\PrintfArrayParametersRule: phpstan.rules.rule: %featureToggles.printfArrayParameters% PHPStan\Rules\Keywords\RequireFileExistsRule: @@ -77,6 +75,7 @@ rules: - PHPStan\Rules\Functions\PrintfParametersRule - PHPStan\Rules\Functions\RedefinedParametersRule - PHPStan\Rules\Functions\ReturnNullsafeByRefRule + - PHPStan\Rules\Functions\UselessFunctionReturnValueRule - PHPStan\Rules\Ignore\IgnoreParseErrorRule - PHPStan\Rules\Functions\VariadicParametersDeclarationRule - PHPStan\Rules\Keywords\ContinueBreakInLoopRule @@ -258,8 +257,6 @@ services: arguments: additionalConstructors: %additionalConstructors% - - - class: PHPStan\Rules\Functions\UselessFunctionReturnValueRule - class: PHPStan\Rules\Functions\PrintfArrayParametersRule diff --git a/conf/config.neon b/conf/config.neon index 02795237fcd..6466d2dfd22 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: disableUnreachableBranchesRules: false stricterFunctionMap: false pure: false - uselessReturnValue: false printfArrayParameters: false requireFileExists: false fileExtensions: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 1d2af1cb847..3135bb23532 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,7 +41,6 @@ parametersSchema: disableUnreachableBranchesRules: bool() stricterFunctionMap: bool() pure: bool() - uselessReturnValue: bool() printfArrayParameters: bool() requireFileExists: bool() ]) From 36dde49f709688359d6f9a77be25a6ec76c4fce4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 13:39:33 +0200 Subject: [PATCH 0394/3097] [BE] Check vprintf/vsprintf arguments against placeholder count --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 7 +------ conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 6eab3458889..100086e4497 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -21,6 +21,7 @@ Major new features 🚀 * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! +* Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! @@ -87,7 +88,6 @@ Bleeding edge (TODO move to other sections) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 -* Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! Improvements 🔧 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1c68c2c5abd..eb63f79d85d 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -13,5 +13,4 @@ parameters: alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true pure: true - printfArrayParameters: true requireFileExists: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 691f4688096..d62a5e4ce94 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -8,8 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Functions\PrintfArrayParametersRule: - phpstan.rules.rule: %featureToggles.printfArrayParameters% PHPStan\Rules\Keywords\RequireFileExistsRule: phpstan.rules.rule: %featureToggles.requireFileExists% @@ -72,6 +70,7 @@ rules: - PHPStan\Rules\Functions\InnerFunctionRule - PHPStan\Rules\Functions\InvalidLexicalVariablesInClosureUseRule - PHPStan\Rules\Functions\ParamAttributesRule + - PHPStan\Rules\Functions\PrintfArrayParametersRule - PHPStan\Rules\Functions\PrintfParametersRule - PHPStan\Rules\Functions\RedefinedParametersRule - PHPStan\Rules\Functions\ReturnNullsafeByRefRule @@ -257,10 +256,6 @@ services: arguments: additionalConstructors: %additionalConstructors% - - - - class: PHPStan\Rules\Functions\PrintfArrayParametersRule - - class: PHPStan\Rules\Keywords\RequireFileExistsRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 6466d2dfd22..48adf0b8745 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: disableUnreachableBranchesRules: false stricterFunctionMap: false pure: false - printfArrayParameters: false requireFileExists: false fileExtensions: - php diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3135bb23532..12d68d952ea 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,7 +41,6 @@ parametersSchema: disableUnreachableBranchesRules: bool() stricterFunctionMap: bool() pure: bool() - printfArrayParameters: bool() requireFileExists: bool() ]) fileExtensions: listOf(string()) From 81aaff9a79a43d44438014e427c0e9af84b964c1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:02:59 +0200 Subject: [PATCH 0395/3097] [BE] Specify explicit mixed array type via `is_array` --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- conf/parametersSchema.neon | 1 - src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php | 6 +----- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 100086e4497..c269678b381 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -60,7 +60,6 @@ Bleeding edge (TODO move to other sections) ===================== * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! -* Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! @@ -128,6 +127,7 @@ Improvements 🔧 * Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) * Report unnecessary nullsafe property fetch inside `??` / `isset` / `empty` with different message ([#1253](https://github.com/phpstan/phpstan-src/pull/1253)), thanks @rajyan! +* Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index eb63f79d85d..90ed6da046a 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,7 +4,6 @@ parameters: skipCheckGenericClasses!: [] stricterFunctionMap: true - explicitMixedViaIsArray: true arrayFilter: true arrayValues: true looseComparison: true diff --git a/conf/config.neon b/conf/config.neon index 48adf0b8745..b50daa78795 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,7 +24,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: true skipCheckGenericClasses: [] - explicitMixedViaIsArray: false arrayFilter: false arrayValues: false illegalConstructorMethodCall: false @@ -1722,8 +1721,6 @@ services: class: PHPStan\Type\Php\IsArrayFunctionTypeSpecifyingExtension tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - arguments: - explicitMixed: %featureToggles.explicitMixedViaIsArray% - class: PHPStan\Type\Php\IsCallableFunctionTypeSpecifyingExtension diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 12d68d952ea..a6278e72e26 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -30,7 +30,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), - explicitMixedViaIsArray: bool(), arrayFilter: bool(), arrayValues: bool(), illegalConstructorMethodCall: bool(), diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index 5f6c0d710e5..e31033185ed 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -20,10 +20,6 @@ final class IsArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecif private TypeSpecifier $typeSpecifier; - public function __construct(private bool $explicitMixed) - { - } - public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { return strtolower($functionReflection->getName()) === 'is_array' @@ -39,7 +35,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->getArgs()[0]->value, new ArrayType(new MixedType($this->explicitMixed), new MixedType($this->explicitMixed)), $context, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new ArrayType(new MixedType(true), new MixedType(true)), $context, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void From 7d74a638011774d9e59aa1f9472c39c00182365c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:07:00 +0200 Subject: [PATCH 0396/3097] [BE] TooWideMethodReturnTypehintRule - always report for final methods --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 1 - conf/config.neon | 1 - conf/parametersSchema.neon | 1 - .../TooWideMethodReturnTypehintRule.php | 20 +++--- .../TooWideMethodReturnTypehintRuleTest.php | 63 +------------------ 7 files changed, 10 insertions(+), 79 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index c269678b381..d5b46b03d51 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -71,7 +71,6 @@ Bleeding edge (TODO move to other sections) * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) * Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) @@ -128,6 +127,7 @@ Improvements 🔧 * Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) * Report unnecessary nullsafe property fetch inside `??` / `isset` / `empty` with different message ([#1253](https://github.com/phpstan/phpstan-src/pull/1253)), thanks @rajyan! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! +* TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) Bugfixes 🐛 ===================== diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 90ed6da046a..18fc70d92dc 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,7 +8,6 @@ parameters: arrayValues: true looseComparison: true readOnlyByPhpDoc: true - alwaysCheckTooWideReturnTypeFinalMethods: true alwaysTrueAlwaysReported: true disableUnreachableBranchesRules: true pure: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 21621869e5f..36cc5448cbc 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -282,7 +282,6 @@ services: class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: checkProtectedAndPublicMethods: %checkTooWideReturnTypesInProtectedAndPublicMethods% - alwaysCheckFinal: %featureToggles.alwaysCheckTooWideReturnTypeFinalMethods% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index b50daa78795..339b64e24af 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -29,7 +29,6 @@ parameters: illegalConstructorMethodCall: false looseComparison: false readOnlyByPhpDoc: false - alwaysCheckTooWideReturnTypeFinalMethods: false alwaysTrueAlwaysReported: false disableUnreachableBranchesRules: false stricterFunctionMap: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index a6278e72e26..c9027f11fd9 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -35,7 +35,6 @@ parametersSchema: illegalConstructorMethodCall: bool(), looseComparison: bool(), readOnlyByPhpDoc: bool() - alwaysCheckTooWideReturnTypeFinalMethods: bool() alwaysTrueAlwaysReported: bool() disableUnreachableBranchesRules: bool() stricterFunctionMap: bool() diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 0c74e65a023..58cf807050a 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -22,7 +22,7 @@ final class TooWideMethodReturnTypehintRule implements Rule { - public function __construct(private bool $checkProtectedAndPublicMethods, private bool $alwaysCheckFinal) + public function __construct(private bool $checkProtectedAndPublicMethods) { } @@ -39,20 +39,14 @@ public function processNode(Node $node, Scope $scope): array $method = $node->getMethodReflection(); $isFirstDeclaration = $method->getPrototype()->getDeclaringClass() === $method->getDeclaringClass(); if (!$method->isPrivate()) { - if ($this->alwaysCheckFinal) { - if (!$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) { - if (!$this->checkProtectedAndPublicMethods) { - return []; - } + if (!$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) { + if (!$this->checkProtectedAndPublicMethods) { + return []; + } - if ($isFirstDeclaration) { - return []; - } + if ($isFirstDeclaration) { + return []; } - } elseif (!$this->checkProtectedAndPublicMethods) { - return []; - } elseif ($isFirstDeclaration && !$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) { - return []; } } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 9e9588d219e..019e7bf59fd 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -14,11 +14,9 @@ class TooWideMethodReturnTypehintRuleTest extends RuleTestCase private bool $checkProtectedAndPublicMethods = true; - private bool $alwaysCheckFinal = false; - protected function getRule(): Rule { - return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, $this->alwaysCheckFinal); + return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods); } public function testPrivate(): void @@ -113,26 +111,6 @@ public function testBug6175(): void public function dataAlwaysCheckFinal(): iterable { yield [ - false, - false, - [ - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.', - 8, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.', - 28, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.', - 48, - ], - ], - ]; - - yield [ - true, false, [ [ @@ -167,42 +145,6 @@ public function dataAlwaysCheckFinal(): iterable ]; yield [ - false, - true, - [ - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.', - 8, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.', - 28, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test2() never returns null so it can be removed from the return type.', - 33, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test3() never returns null so it can be removed from the return type.', - 38, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.', - 48, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test2() never returns null so it can be removed from the return type.', - 53, - ], - [ - 'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test3() never returns null so it can be removed from the return type.', - 58, - ], - ], - ]; - - yield [ - true, true, [ [ @@ -241,10 +183,9 @@ public function dataAlwaysCheckFinal(): iterable * @dataProvider dataAlwaysCheckFinal * @param list $expectedErrors */ - public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, bool $alwaysCheckFinal, array $expectedErrors): void + public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, array $expectedErrors): void { $this->checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods; - $this->alwaysCheckFinal = $alwaysCheckFinal; $this->analyse([__DIR__ . '/data/method-too-wide-return-always-check-final.php'], $expectedErrors); } From cc7ff8b217021402dcbe817f7002b29a48722811 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:13:36 +0200 Subject: [PATCH 0397/3097] [BE] Remove rules about unreachable branches --- changelog-2.0.md | 4 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 19 --- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - phpstan-baseline.neon | 10 -- src/Rules/Comparison/MatchExpressionRule.php | 7 - .../Comparison/UnreachableIfBranchesRule.php | 84 ----------- .../UnreachableTernaryElseBranchRule.php | 70 --------- .../Comparison/MatchExpressionRuleTest.php | 108 +------------- .../UnreachableIfBranchesRuleTest.php | 139 ------------------ .../UnreachableTernaryElseBranchRuleTest.php | 111 -------------- .../Rules/Comparison/data/bug-7686.php | 25 ---- 13 files changed, 5 insertions(+), 575 deletions(-) delete mode 100644 src/Rules/Comparison/UnreachableIfBranchesRule.php delete mode 100644 src/Rules/Comparison/UnreachableTernaryElseBranchRule.php delete mode 100644 tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php delete mode 100644 tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-7686.php diff --git a/changelog-2.0.md b/changelog-2.0.md index d5b46b03d51..8f14a5e937d 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -16,6 +16,8 @@ Major new features 🚀 * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * Check too wide private property type (level 4) (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) +* Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule + * Because "always true" is always reported, these are no longer needed * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! @@ -66,8 +68,6 @@ Bleeding edge (TODO move to other sections) * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! -* Disable "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule - * Because "always true" is always reported, these are no longer needed * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 18fc70d92dc..7e102ef694d 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -9,6 +9,5 @@ parameters: looseComparison: true readOnlyByPhpDoc: true alwaysTrueAlwaysReported: true - disableUnreachableBranchesRules: true pure: true requireFileExists: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 36cc5448cbc..52ff2043525 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -197,7 +197,6 @@ services: class: PHPStan\Rules\Comparison\MatchExpressionRule arguments: checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% - disableUnreachable: %featureToggles.disableUnreachableBranchesRules% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% tags: @@ -237,24 +236,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Comparison\UnreachableIfBranchesRule - arguments: - treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - disable: %featureToggles.disableUnreachableBranchesRules% - treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - tags: - - phpstan.rules.rule - - - - class: PHPStan\Rules\Comparison\UnreachableTernaryElseBranchRule - arguments: - treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - disable: %featureToggles.disableUnreachableBranchesRules% - treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\Comparison\WhileLoopAlwaysFalseConditionRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 339b64e24af..01a3bf12064 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -30,7 +30,6 @@ parameters: looseComparison: false readOnlyByPhpDoc: false alwaysTrueAlwaysReported: false - disableUnreachableBranchesRules: false stricterFunctionMap: false pure: false requireFileExists: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index c9027f11fd9..dcf4230ff9a 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -36,7 +36,6 @@ parametersSchema: looseComparison: bool(), readOnlyByPhpDoc: bool() alwaysTrueAlwaysReported: bool() - disableUnreachableBranchesRules: bool() stricterFunctionMap: bool() pure: bool() requireFileExists: bool() diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index edb70aafe71..287fdbae03d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -491,16 +491,6 @@ parameters: count: 2 path: src/Rules/Comparison/TernaryOperatorConstantConditionRule.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 1 - path: src/Rules/Comparison/UnreachableIfBranchesRule.php - - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 1 - path: src/Rules/Comparison/UnreachableTernaryElseBranchRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 diff --git a/src/Rules/Comparison/MatchExpressionRule.php b/src/Rules/Comparison/MatchExpressionRule.php index 81cfb1f4714..d204ca9c64e 100644 --- a/src/Rules/Comparison/MatchExpressionRule.php +++ b/src/Rules/Comparison/MatchExpressionRule.php @@ -28,7 +28,6 @@ final class MatchExpressionRule implements Rule public function __construct( private ConstantConditionRuleHelper $constantConditionRuleHelper, private bool $checkAlwaysTrueStrictComparison, - private bool $disableUnreachable, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertain, ) @@ -54,12 +53,6 @@ public function processNode(Node $node, Scope $scope): array $nextArmIsDeadForNativeType || ($nextArmIsDeadForType && $this->treatPhpDocTypesAsCertain) ) { - if (!$this->disableUnreachable) { - $errors[] = RuleErrorBuilder::message('Match arm is unreachable because previous comparison is always true.') - ->identifier('match.unreachable') - ->line($arm->getLine()) - ->build(); - } continue; } $armConditions = $arm->getConditions(); diff --git a/src/Rules/Comparison/UnreachableIfBranchesRule.php b/src/Rules/Comparison/UnreachableIfBranchesRule.php deleted file mode 100644 index 5d6b001e88a..00000000000 --- a/src/Rules/Comparison/UnreachableIfBranchesRule.php +++ /dev/null @@ -1,84 +0,0 @@ - - */ -final class UnreachableIfBranchesRule implements Rule -{ - - public function __construct( - private ConstantConditionRuleHelper $helper, - private bool $treatPhpDocTypesAsCertain, - private bool $disable, - private bool $treatPhpDocTypesAsCertainTip, - ) - { - } - - public function getNodeType(): string - { - return Node\Stmt\If_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->disable) { - return []; - } - - $errors = []; - $condition = $node->cond; - $conditionType = $this->treatPhpDocTypesAsCertain ? $scope->getType($condition) : $scope->getNativeType($condition); - $conditionBooleanType = $conditionType->toBoolean(); - $nextBranchIsDead = $conditionBooleanType->isTrue()->yes() && $this->helper->shouldSkip($scope, $node->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond); - - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, &$condition): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $scope->getNativeType($condition)->toBoolean(); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - if (!$this->treatPhpDocTypesAsCertainTip) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); - }; - - foreach ($node->elseifs as $elseif) { - if ($nextBranchIsDead) { - $errors[] = $addTip(RuleErrorBuilder::message('Elseif branch is unreachable because previous condition is always true.')) - ->identifier('elseif.unreachable') - ->line($elseif->getStartLine()) - ->build(); - continue; - } - - $condition = $elseif->cond; - $conditionType = $this->treatPhpDocTypesAsCertain ? $scope->getType($condition) : $scope->getNativeType($condition); - $conditionBooleanType = $conditionType->toBoolean(); - $nextBranchIsDead = $conditionBooleanType->isTrue()->yes() && $this->helper->shouldSkip($scope, $elseif->cond) && !$this->helper->shouldReportAlwaysTrueByDefault($elseif->cond); - } - - if ($node->else !== null && $nextBranchIsDead) { - $errors[] = $addTip(RuleErrorBuilder::message('Else branch is unreachable because previous condition is always true.')) - ->identifier('else.unreachable') - ->line($node->else->getStartLine()) - ->build(); - } - - return $errors; - } - -} diff --git a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php b/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php deleted file mode 100644 index 63ea467ce4c..00000000000 --- a/src/Rules/Comparison/UnreachableTernaryElseBranchRule.php +++ /dev/null @@ -1,70 +0,0 @@ - - */ -final class UnreachableTernaryElseBranchRule implements Rule -{ - - public function __construct( - private ConstantConditionRuleHelper $helper, - private bool $treatPhpDocTypesAsCertain, - private bool $disable, - private bool $treatPhpDocTypesAsCertainTip, - ) - { - } - - public function getNodeType(): string - { - return Node\Expr\Ternary::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->disable) { - return []; - } - - $conditionType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->cond) : $scope->getNativeType($node->cond); - $conditionBooleanType = $conditionType->toBoolean(); - if ( - $conditionBooleanType->isTrue()->yes() - && $this->helper->shouldSkip($scope, $node->cond) - && !$this->helper->shouldReportAlwaysTrueByDefault($node->cond) - ) { - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { - if (!$this->treatPhpDocTypesAsCertain) { - return $ruleErrorBuilder; - } - - $booleanNativeType = $scope->getNativeType($node->cond); - if ($booleanNativeType instanceof ConstantBooleanType) { - return $ruleErrorBuilder; - } - if (!$this->treatPhpDocTypesAsCertainTip) { - return $ruleErrorBuilder; - } - - return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); - }; - return [ - $addTip(RuleErrorBuilder::message('Else branch is unreachable because ternary operator condition is always true.')) - ->identifier('ternary.elseUnreachable') - ->line($node->else->getStartLine()) - ->build(), - ]; - } - - return []; - } - -} diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 4674b642a71..8d9cf3443e0 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -16,8 +16,6 @@ class MatchExpressionRuleTest extends RuleTestCase private bool $reportAlwaysTrueInLastCondition = false; - private bool $disableUnreachable = false; - protected function getRule(): Rule { return new MatchExpressionRule( @@ -32,7 +30,6 @@ protected function getRule(): Rule true, ), true, - $this->disableUnreachable, $this->reportAlwaysTrueInLastCondition, $this->treatPhpDocTypesAsCertain, ); @@ -60,41 +57,21 @@ public function testRule(): void 28, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 29, - ], [ 'Match arm comparison between 3 and 3 is always true.', 35, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 36, - ], [ 'Match arm comparison between 1 and 1 is always true.', 40, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 41, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 42, - ], [ 'Match arm comparison between 1 and 1 is always true.', 46, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 47, - ], [ 'Match expression does not handle remaining value: 3', 50, @@ -169,10 +146,6 @@ public function testEnums(): void 76, 'Remove remaining cases below this one and this error will disappear too.', ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 77, - ], [ 'Match arm comparison between MatchEnums\Foo and MatchEnums\Foo::ONE is always false.', 85, @@ -291,19 +264,11 @@ public function testBug8240(): void 13, 'Remove remaining cases below this one and this error will disappear too.', ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 14, - ], [ 'Match arm comparison between Bug8240\Foo2::BAZ and Bug8240\Foo2::BAZ is always true.', 28, 'Remove remaining cases below this one and this error will disappear too.', ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 29, - ], ]); } @@ -320,41 +285,21 @@ public function testLastArmAlwaysTrue(): void 22, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 23, - ], [ 'Match arm comparison between $this(LastMatchArmAlwaysTrue\Foo)&LastMatchArmAlwaysTrue\Foo::TWO and LastMatchArmAlwaysTrue\Foo::TWO is always true.', 31, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 32, - ], [ 'Match arm comparison between $this(LastMatchArmAlwaysTrue\Foo)&LastMatchArmAlwaysTrue\Foo::TWO and LastMatchArmAlwaysTrue\Foo::TWO is always true.', 40, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 41, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 42, - ], [ 'Match arm comparison between $this(LastMatchArmAlwaysTrue\Bar)&LastMatchArmAlwaysTrue\Bar::ONE and LastMatchArmAlwaysTrue\Bar::ONE is always true.', 62, $tipText, ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 63, - ], [ 'Match arm comparison between 1 and 0 is always false.', 70, @@ -368,53 +313,7 @@ public function testLastArmAlwaysTrue(): void public function dataReportAlwaysTrueInLastCondition(): iterable { - yield [false, false, [ - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 23, - 'Remove remaining cases below this one and this error will disappear too.', - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 24, - ], - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 49, - 'Remove remaining cases below this one and this error will disappear too.', - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 50, - ], - ]]; - yield [true, false, [ - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 15, - ], - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 23, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 24, - ], - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 45, - ], - [ - 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', - 49, - ], - [ - 'Match arm is unreachable because previous comparison is always true.', - 50, - ], - ]]; - yield [false, true, [ + yield [false, [ [ 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', 23, @@ -426,7 +325,7 @@ public function dataReportAlwaysTrueInLastCondition(): iterable 'Remove remaining cases below this one and this error will disappear too.', ], ]]; - yield [true, true, [ + yield [true, [ [ 'Match arm comparison between $this(MatchAlwaysTrueLastArm\Foo)&MatchAlwaysTrueLastArm\Foo::BAR and MatchAlwaysTrueLastArm\Foo::BAR is always true.', 15, @@ -450,14 +349,13 @@ public function dataReportAlwaysTrueInLastCondition(): iterable * @dataProvider dataReportAlwaysTrueInLastCondition * @param list $expectedErrors */ - public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLastCondition, bool $disableUnreachable, array $expectedErrors): void + public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLastCondition, array $expectedErrors): void { if (PHP_VERSION_ID < 80100) { $this->markTestSkipped('Test requires PHP 8.1.'); } $this->treatPhpDocTypesAsCertain = true; $this->reportAlwaysTrueInLastCondition = $reportAlwaysTrueInLastCondition; - $this->disableUnreachable = $disableUnreachable; $this->analyse([__DIR__ . '/data/match-always-true-last-arm.php'], $expectedErrors); } diff --git a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php deleted file mode 100644 index 4459045cc03..00000000000 --- a/tests/PHPStan/Rules/Comparison/UnreachableIfBranchesRuleTest.php +++ /dev/null @@ -1,139 +0,0 @@ - - */ -class UnreachableIfBranchesRuleTest extends RuleTestCase -{ - - private bool $treatPhpDocTypesAsCertain; - - protected function getRule(): Rule - { - return new UnreachableIfBranchesRule( - new ConstantConditionRuleHelper( - new ImpossibleCheckTypeHelper( - $this->createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain, - ), - $this->treatPhpDocTypesAsCertain, - true, - ), - $this->treatPhpDocTypesAsCertain, - false, - true, - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } - - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable-if-branches.php'], [ - [ - 'Else branch is unreachable because previous condition is always true.', - 15, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 25, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 27, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 39, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 41, - ], - ]); - } - - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ - [ - 'Elseif branch is unreachable because previous condition is always true.', - 18, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 28, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 38, - ], - ]); - } - - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/unreachable-if-branches-not-phpdoc.php'], [ - [ - 'Elseif branch is unreachable because previous condition is always true.', - 18, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 28, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 38, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 44, - $tipText, - ], - [ - 'Else branch is unreachable because previous condition is always true.', - 54, - //$tipText, - ], - [ - 'Elseif branch is unreachable because previous condition is always true.', - 64, - //$tipText, - ], - ]); - } - - public function testBug8076(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-8076.php'], []); - } - - public function testBug8562(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-8562.php'], []); - } - - public function testBug7491(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-7491.php'], []); - } - -} diff --git a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php b/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php deleted file mode 100644 index bd3cc00a4f3..00000000000 --- a/tests/PHPStan/Rules/Comparison/UnreachableTernaryElseBranchRuleTest.php +++ /dev/null @@ -1,111 +0,0 @@ - - */ -class UnreachableTernaryElseBranchRuleTest extends RuleTestCase -{ - - private bool $treatPhpDocTypesAsCertain; - - protected function getRule(): Rule - { - return new UnreachableTernaryElseBranchRule( - new ConstantConditionRuleHelper( - new ImpossibleCheckTypeHelper( - $this->createReflectionProvider(), - $this->getTypeSpecifier(), - [], - $this->treatPhpDocTypesAsCertain, - ), - $this->treatPhpDocTypesAsCertain, - true, - ), - $this->treatPhpDocTypesAsCertain, - false, - true, - ); - } - - protected function shouldTreatPhpDocTypesAsCertain(): bool - { - return $this->treatPhpDocTypesAsCertain; - } - - public function testRule(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 6, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 9, - ], - ]); - } - - public function testDoNotReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 16, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 17, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 20, - ], - ]); - } - - public function testReportPhpDoc(): void - { - $this->treatPhpDocTypesAsCertain = true; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 16, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 17, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 19, - $tipText, - ], - [ - 'Else branch is unreachable because ternary operator condition is always true.', - 20, - ], - ]); - } - - public function testBug3019(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-3019.php'], []); - } - - public function testBug7686(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-7686.php'], []); - } - -} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-7686.php b/tests/PHPStan/Rules/Comparison/data/bug-7686.php deleted file mode 100644 index be053257799..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-7686.php +++ /dev/null @@ -1,25 +0,0 @@ - $input - * @return array<'return'|int, string> - */ - public static function test(array $input): array - { - $output = []; - foreach($input as $match) { - if (array_key_exists($match['name'], $output) == false) { - $output[$match['name']] = ''; - } - if (($match['type'] === '') || (in_array($match['type'], explode('|', $output[$match['name']]), true) === true)) { - continue; - } - $output[$match['name']] = ($output[$match['name']] === '' ? $match['type'] : $output[$match['name']] . '|' . $match['type']); - } - return $output; - } -} From 335c16f1abb5d26d81f2e24ab078807bbe37f2b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:19:26 +0200 Subject: [PATCH 0398/3097] [BE] Always report always true conditions, except for last elseif and match arm --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.neon | 9 ++++----- conf/parametersSchema.neon | 1 - 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 8f14a5e937d..cc93b6af1ba 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -16,6 +16,7 @@ Major new features 🚀 * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! * Check too wide private property type (level 4) (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) +* Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) * Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 @@ -67,7 +68,6 @@ Bleeding edge (TODO move to other sections) * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! -* Always report always true conditions, except for last elseif and match arm ([#2105](https://github.com/phpstan/phpstan-src/pull/2105)), thanks @staabm! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 7e102ef694d..578124828bc 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,6 +8,5 @@ parameters: arrayValues: true looseComparison: true readOnlyByPhpDoc: true - alwaysTrueAlwaysReported: true pure: true requireFileExists: true diff --git a/conf/config.neon b/conf/config.neon index 01a3bf12064..ac3f1d096c5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -29,17 +29,16 @@ parameters: illegalConstructorMethodCall: false looseComparison: false readOnlyByPhpDoc: false - alwaysTrueAlwaysReported: false stricterFunctionMap: false pure: false requireFileExists: false fileExtensions: - php checkAdvancedIsset: false - checkAlwaysTrueCheckTypeFunctionCall: %featureToggles.alwaysTrueAlwaysReported% - checkAlwaysTrueInstanceof: %featureToggles.alwaysTrueAlwaysReported% - checkAlwaysTrueStrictComparison: %featureToggles.alwaysTrueAlwaysReported% - checkAlwaysTrueLooseComparison: %featureToggles.alwaysTrueAlwaysReported% + checkAlwaysTrueCheckTypeFunctionCall: true + checkAlwaysTrueInstanceof: true + checkAlwaysTrueStrictComparison: true + checkAlwaysTrueLooseComparison: true reportAlwaysTrueInLastCondition: false checkClassCaseSensitivity: false checkExplicitMixed: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index dcf4230ff9a..97ac234f8de 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -35,7 +35,6 @@ parametersSchema: illegalConstructorMethodCall: bool(), looseComparison: bool(), readOnlyByPhpDoc: bool() - alwaysTrueAlwaysReported: bool() stricterFunctionMap: bool() pure: bool() requireFileExists: bool() From 59fd06a03d2104d577c845c73ecb91cc3202aa04 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:34:02 +0200 Subject: [PATCH 0399/3097] Update phpstan-strict-rules --- composer.lock | 8 ++++---- issue-bot/composer.lock | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 4acec2e4a1c..997e53f86f5 100644 --- a/composer.lock +++ b/composer.lock @@ -4831,12 +4831,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "1062d489f1d10e79df42d73fa5352a27741d65f1" + "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1062d489f1d10e79df42d73fa5352a27741d65f1", - "reference": "1062d489f1d10e79df42d73fa5352a27741d65f1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ad53bd9f911e7831e8e02cd3e54faf1d44910c33", + "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33", "shasum": "" }, "require": { @@ -4872,7 +4872,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-13T12:49:46+00:00" + "time": "2024-09-24T12:25:28+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index 8dca547099e..7479e1b1b15 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -1407,12 +1407,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "946cf18b3e9f64c41d2f62903f8148b3f0b3be41" + "reference": "b1165b76fe8d451783d63ac99e3e31377353a90a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/946cf18b3e9f64c41d2f62903f8148b3f0b3be41", - "reference": "946cf18b3e9f64c41d2f62903f8148b3f0b3be41", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b1165b76fe8d451783d63ac99e3e31377353a90a", + "reference": "b1165b76fe8d451783d63ac99e3e31377353a90a", "shasum": "" }, "require": { @@ -1458,7 +1458,7 @@ "type": "github" } ], - "time": "2024-09-06T18:47:21+00:00" + "time": "2024-09-24T12:23:49+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1466,12 +1466,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3" + "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/5eec39fc6ef36015e6de08949c8e9ae9d64560a3", - "reference": "5eec39fc6ef36015e6de08949c8e9ae9d64560a3", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ad53bd9f911e7831e8e02cd3e54faf1d44910c33", + "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33", "shasum": "" }, "require": { @@ -1507,7 +1507,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-06T18:44:39+00:00" + "time": "2024-09-24T12:25:28+00:00" }, { "name": "psr/cache", From d22c481ef224d5c11e8d23c4d5f84ed9ac8aac9c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:26:46 +0200 Subject: [PATCH 0400/3097] Remove `checkAlwaysTrue*` options --- UPGRADING.md | 9 ++++ conf/config.level4.neon | 7 --- conf/config.neon | 4 -- conf/parametersSchema.neon | 4 -- .../Classes/ImpossibleInstanceOfRule.php | 35 ++++++------ .../ConstantLooseComparisonRule.php | 35 ++++++------ .../ImpossibleCheckTypeFunctionCallRule.php | 33 ++++++------ .../ImpossibleCheckTypeMethodCallRule.php | 37 ++++++------- ...mpossibleCheckTypeStaticMethodCallRule.php | 37 ++++++------- src/Rules/Comparison/MatchExpressionRule.php | 36 ++++++------- .../StrictComparisonOfDifferentTypesRule.php | 53 +++++++++---------- .../Classes/ImpossibleInstanceOfRuleTest.php | 1 - .../ConstantLooseComparisonRuleTest.php | 1 - ...mpossibleCheckTypeFunctionCallRuleTest.php | 1 - ...sibleCheckTypeGenericOverwriteRuleTest.php | 1 - ...sibleCheckTypeMethodCallRuleEqualsTest.php | 1 - .../ImpossibleCheckTypeMethodCallRuleTest.php | 1 - ...sibleCheckTypeStaticMethodCallRuleTest.php | 1 - .../Comparison/MatchExpressionRuleTest.php | 1 - ...rictComparisonOfDifferentTypesRuleTest.php | 1 - 20 files changed, 132 insertions(+), 167 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 9a0e7709529..658f366c004 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -60,6 +60,15 @@ parameters: identifier: missingType.generics ``` +### Removed `checkAlwaysTrue*` options + +These options have been removed because PHPStan now always behaves as if these were set to `true`: + +* `checkAlwaysTrueCheckTypeFunctionCall` +* `checkAlwaysTrueInstanceof` +* `checkAlwaysTrueStrictComparison` +* `checkAlwaysTrueLooseComparison` + ### Removed option `excludes_analyse` It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 52ff2043525..ed64b65aec6 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -53,7 +53,6 @@ services: - class: PHPStan\Rules\Classes\ImpossibleInstanceOfRule arguments: - checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% @@ -157,7 +156,6 @@ services: - class: PHPStan\Rules\Comparison\ImpossibleCheckTypeFunctionCallRule arguments: - checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% @@ -167,7 +165,6 @@ services: - class: PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule arguments: - checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% @@ -177,7 +174,6 @@ services: - class: PHPStan\Rules\Comparison\ImpossibleCheckTypeStaticMethodCallRule arguments: - checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% @@ -196,7 +192,6 @@ services: - class: PHPStan\Rules\Comparison\MatchExpressionRule arguments: - checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% tags: @@ -213,7 +208,6 @@ services: - class: PHPStan\Rules\Comparison\StrictComparisonOfDifferentTypesRule arguments: - checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% @@ -223,7 +217,6 @@ services: - class: PHPStan\Rules\Comparison\ConstantLooseComparisonRule arguments: - checkAlwaysTrueLooseComparison: %checkAlwaysTrueLooseComparison% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% diff --git a/conf/config.neon b/conf/config.neon index ac3f1d096c5..a6be092dc44 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,10 +35,6 @@ parameters: fileExtensions: - php checkAdvancedIsset: false - checkAlwaysTrueCheckTypeFunctionCall: true - checkAlwaysTrueInstanceof: true - checkAlwaysTrueStrictComparison: true - checkAlwaysTrueLooseComparison: true reportAlwaysTrueInLastCondition: false checkClassCaseSensitivity: false checkExplicitMixed: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 97ac234f8de..d5a31f5b0e0 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -41,10 +41,6 @@ parametersSchema: ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() - checkAlwaysTrueCheckTypeFunctionCall: bool() - checkAlwaysTrueInstanceof: bool() - checkAlwaysTrueStrictComparison: bool() - checkAlwaysTrueLooseComparison: bool() reportAlwaysTrueInLastCondition: bool() checkClassCaseSensitivity: bool() checkExplicitMixed: bool() diff --git a/src/Rules/Classes/ImpossibleInstanceOfRule.php b/src/Rules/Classes/ImpossibleInstanceOfRule.php index b6d9c84ee35..a4b3cfe70c2 100644 --- a/src/Rules/Classes/ImpossibleInstanceOfRule.php +++ b/src/Rules/Classes/ImpossibleInstanceOfRule.php @@ -22,7 +22,6 @@ final class ImpossibleInstanceOfRule implements Rule { public function __construct( - private bool $checkAlwaysTrueInstanceof, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -89,28 +88,26 @@ public function processNode(Node $node, Scope $scope): array $classType->describe(VerbosityLevel::getRecommendedLevelByType($classType)), )))->identifier('instanceof.alwaysFalse')->build(), ]; - } elseif ($this->checkAlwaysTrueInstanceof) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $exprType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->expr) : $scope->getNativeType($node->expr); - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Instanceof between %s and %s will always evaluate to true.', - $exprType->describe(VerbosityLevel::typeOnly()), - $classType->describe(VerbosityLevel::getRecommendedLevelByType($classType)), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } + } - $errorBuilder->identifier('instanceof.alwaysTrue'); + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - return [$errorBuilder->build()]; + $exprType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->expr) : $scope->getNativeType($node->expr); + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Instanceof between %s and %s will always evaluate to true.', + $exprType->describe(VerbosityLevel::typeOnly()), + $classType->describe(VerbosityLevel::getRecommendedLevelByType($classType)), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } - return []; + $errorBuilder->identifier('instanceof.alwaysTrue'); + + return [$errorBuilder->build()]; } } diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index d6fa6c6aac0..09961335e7b 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -18,7 +18,6 @@ final class ConstantLooseComparisonRule implements Rule { public function __construct( - private bool $checkAlwaysTrueLooseComparison, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -67,28 +66,26 @@ public function processNode(Node $node, Scope $scope): array $scope->getType($node->right)->describe(VerbosityLevel::value()), )))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Equal ? 'equal' : 'notEqual'))->build(), ]; - } elseif ($this->checkAlwaysTrueLooseComparison) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Loose comparison using %s between %s and %s will always evaluate to true.', - $node->getOperatorSigil(), - $scope->getType($node->left)->describe(VerbosityLevel::value()), - $scope->getType($node->right)->describe(VerbosityLevel::value()), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } + } - $errorBuilder->identifier(sprintf('%s.alwaysTrue', $node instanceof Node\Expr\BinaryOp\Equal ? 'equal' : 'notEqual')); + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - return [$errorBuilder->build()]; + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Loose comparison using %s between %s and %s will always evaluate to true.', + $node->getOperatorSigil(), + $scope->getType($node->left)->describe(VerbosityLevel::value()), + $scope->getType($node->right)->describe(VerbosityLevel::value()), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } - return []; + $errorBuilder->identifier(sprintf('%s.alwaysTrue', $node instanceof Node\Expr\BinaryOp\Equal ? 'equal' : 'notEqual')); + + return [$errorBuilder->build()]; } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 9033aa38658..690307e6fcb 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -18,7 +18,6 @@ final class ImpossibleCheckTypeFunctionCallRule implements Rule public function __construct( private ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, - private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -70,27 +69,25 @@ public function processNode(Node $node, Scope $scope): array $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), )))->identifier('function.impossibleType')->build(), ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Call to function %s()%s will always evaluate to true.', - $functionName, - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } + } - $errorBuilder->identifier('function.alreadyNarrowedType'); + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - return [$errorBuilder->build()]; + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Call to function %s()%s will always evaluate to true.', + $functionName, + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } - return []; + $errorBuilder->identifier('function.alreadyNarrowedType'); + + return [$errorBuilder->build()]; } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index 7bbcecefd7a..4b79d98acb1 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -20,7 +20,6 @@ final class ImpossibleCheckTypeMethodCallRule implements Rule public function __construct( private ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, - private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -70,29 +69,27 @@ public function processNode(Node $node, Scope $scope): array $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), )))->identifier('method.impossibleType')->build(), ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $method = $this->getMethod($node->var, $node->name->name, $scope); - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Call to method %s::%s()%s will always evaluate to true.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } + } - $errorBuilder->identifier('method.alreadyNarrowedType'); + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - return [$errorBuilder->build()]; + $method = $this->getMethod($node->var, $node->name->name, $scope); + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Call to method %s::%s()%s will always evaluate to true.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } - return []; + $errorBuilder->identifier('method.alreadyNarrowedType'); + + return [$errorBuilder->build()]; } private function getMethod( diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index df4504cb0e2..e4b37215380 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -20,7 +20,6 @@ final class ImpossibleCheckTypeStaticMethodCallRule implements Rule public function __construct( private ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, - private bool $checkAlwaysTrueCheckTypeFunctionCall, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -71,29 +70,27 @@ public function processNode(Node $node, Scope $scope): array $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), )))->identifier('staticMethod.impossibleType')->build(), ]; - } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $method = $this->getMethod($node->class, $node->name->name, $scope); - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Call to static method %s::%s()%s will always evaluate to true.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } + } - $errorBuilder->identifier('staticMethod.alreadyNarrowedType'); + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - return [$errorBuilder->build()]; + $method = $this->getMethod($node->class, $node->name->name, $scope); + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Call to static method %s::%s()%s will always evaluate to true.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } - return []; + $errorBuilder->identifier('staticMethod.alreadyNarrowedType'); + + return [$errorBuilder->build()]; } /** diff --git a/src/Rules/Comparison/MatchExpressionRule.php b/src/Rules/Comparison/MatchExpressionRule.php index d204ca9c64e..6edef9747be 100644 --- a/src/Rules/Comparison/MatchExpressionRule.php +++ b/src/Rules/Comparison/MatchExpressionRule.php @@ -27,7 +27,6 @@ final class MatchExpressionRule implements Rule public function __construct( private ConstantConditionRuleHelper $constantConditionRuleHelper, - private bool $checkAlwaysTrueStrictComparison, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertain, ) @@ -98,25 +97,24 @@ public function processNode(Node $node, Scope $scope): array $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()), ))->line($armLine)->identifier('match.alwaysFalse')->build(); - } else { - if ($this->checkAlwaysTrueStrictComparison) { - if ($i === $armsCount - 1 && !$this->reportAlwaysTrueInLastCondition) { - continue; - } - $errorBuilder = RuleErrorBuilder::message(sprintf( - 'Match arm comparison between %s and %s is always true.', - $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), - $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()), - ))->line($armLine); - if ($i !== $armsCount - 1 && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); - } - - $errorBuilder->identifier('match.alwaysTrue'); - - $errors[] = $errorBuilder->build(); - } + continue; + } + + if ($i === $armsCount - 1 && !$this->reportAlwaysTrueInLastCondition) { + continue; + } + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'Match arm comparison between %s and %s is always true.', + $armConditionScope->getType($matchCondition)->describe(VerbosityLevel::value()), + $armConditionScope->getType($armCondition->getCondition())->describe(VerbosityLevel::value()), + ))->line($armLine); + if ($i !== $armsCount - 1 && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->tip('Remove remaining cases below this one and this error will disappear too.'); } + + $errorBuilder->identifier('match.alwaysTrue'); + + $errors[] = $errorBuilder->build(); } } diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index f4ea23258b3..637ba4c50b9 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -18,7 +18,6 @@ final class StrictComparisonOfDifferentTypesRule implements Rule { public function __construct( - private bool $checkAlwaysTrueStrictComparison, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, private bool $treatPhpDocTypesAsCertainTip, @@ -70,38 +69,36 @@ public function processNode(Node $node, Scope $scope): array $rightType->describe(VerbosityLevel::value()), )))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(), ]; - } elseif ($this->checkAlwaysTrueStrictComparison) { - $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); - if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { - return []; - } - - $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( - 'Strict comparison using %s between %s and %s will always evaluate to true.', - $node->getOperatorSigil(), - $leftType->describe(VerbosityLevel::value()), - $rightType->describe(VerbosityLevel::value()), - ))); - if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { - $errorBuilder->addTip('Remove remaining cases below this one and this error will disappear too.'); - } + } - if ( - $leftType->isEnum()->yes() - && $rightType->isEnum()->yes() - && $node->getAttribute(LastConditionVisitor::ATTRIBUTE_IS_MATCH_NAME, false) !== true - ) { - $errorBuilder->addTip('Use match expression instead. PHPStan will report unhandled enum cases.'); - } + $isLast = $node->getAttribute(LastConditionVisitor::ATTRIBUTE_NAME); + if ($isLast === true && !$this->reportAlwaysTrueInLastCondition) { + return []; + } - $errorBuilder->identifier(sprintf('%s.alwaysTrue', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical')); + $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( + 'Strict comparison using %s between %s and %s will always evaluate to true.', + $node->getOperatorSigil(), + $leftType->describe(VerbosityLevel::value()), + $rightType->describe(VerbosityLevel::value()), + ))); + if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { + $errorBuilder->addTip('Remove remaining cases below this one and this error will disappear too.'); + } - return [ - $errorBuilder->build(), - ]; + if ( + $leftType->isEnum()->yes() + && $rightType->isEnum()->yes() + && $node->getAttribute(LastConditionVisitor::ATTRIBUTE_IS_MATCH_NAME, false) !== true + ) { + $errorBuilder->addTip('Use match expression instead. PHPStan will report unhandled enum cases.'); } - return []; + $errorBuilder->identifier(sprintf('%s.alwaysTrue', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical')); + + return [ + $errorBuilder->build(), + ]; } } diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index fde28cab1a6..f8228f04f68 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->checkAlwaysTrueInstanceOf, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index 213b9df52de..cbd60c68ed8 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index bee96695101..95839c42cc9 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -35,7 +35,6 @@ protected function getRule(): Rule $this->checkAlwaysTrueCheckTypeFunctionCall, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php index 47fb5d60f69..03de668ca68 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php @@ -23,7 +23,6 @@ public function getRule(): Rule true, true, false, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 4d7eedcf84d..736e912ab76 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -23,7 +23,6 @@ public function getRule(): Rule true, true, false, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index cfaf5fce01d..2741c678641 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -28,7 +28,6 @@ public function getRule(): Rule true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index b3c7227ee13..3133e3aa966 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -27,7 +27,6 @@ public function getRule(): Rule true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 8d9cf3443e0..072475e3e7a 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -29,7 +29,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, true, ), - true, $this->reportAlwaysTrueInLastCondition, $this->treatPhpDocTypesAsCertain, ); diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4d184029d75..f30f842ca28 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -25,7 +25,6 @@ protected function getRule(): Rule $this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, - true, ); } From db61867f112a07feedd72687d7f4684303b3a8c3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:42:03 +0200 Subject: [PATCH 0401/3097] [BE] ConstantLooseComparisonRule --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 4 ++-- conf/config.neon | 2 -- conf/parametersSchema.neon | 1 - src/Rules/Comparison/ConstantConditionRuleHelper.php | 7 ++----- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index cc93b6af1ba..274dae47b38 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -27,6 +27,7 @@ Major new features 🚀 * Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! @@ -64,7 +65,6 @@ Bleeding edge (TODO move to other sections) * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! -* ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 578124828bc..54bad4da407 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -6,7 +6,6 @@ parameters: arrayFilter: true arrayValues: true - looseComparison: true readOnlyByPhpDoc: true pure: true requireFileExists: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index ed64b65aec6..714843e781c 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -21,8 +21,6 @@ rules: - PHPStan\Rules\Traits\NotAnalysedTraitRule conditionalTags: - PHPStan\Rules\Comparison\ConstantLooseComparisonRule: - phpstan.rules.rule: %featureToggles.looseComparison% PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: phpstan.rules.rule: %featureToggles.pure% PHPStan\Rules\DeadCode\PossiblyPureNewCollector: @@ -220,6 +218,8 @@ services: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule diff --git a/conf/config.neon b/conf/config.neon index a6be092dc44..c9fd627837f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -27,7 +27,6 @@ parameters: arrayFilter: false arrayValues: false illegalConstructorMethodCall: false - looseComparison: false readOnlyByPhpDoc: false stricterFunctionMap: false pure: false @@ -862,7 +861,6 @@ services: class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - looseComparisonRuleEnabled: %featureToggles.looseComparison% - class: PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d5a31f5b0e0..68ebb600e38 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -33,7 +33,6 @@ parametersSchema: arrayFilter: bool(), arrayValues: bool(), illegalConstructorMethodCall: bool(), - looseComparison: bool(), readOnlyByPhpDoc: bool() stricterFunctionMap: bool() pure: bool() diff --git a/src/Rules/Comparison/ConstantConditionRuleHelper.php b/src/Rules/Comparison/ConstantConditionRuleHelper.php index 9271ef2782f..dc3167a38d3 100644 --- a/src/Rules/Comparison/ConstantConditionRuleHelper.php +++ b/src/Rules/Comparison/ConstantConditionRuleHelper.php @@ -14,7 +14,6 @@ final class ConstantConditionRuleHelper public function __construct( private ImpossibleCheckTypeHelper $impossibleCheckTypeHelper, private bool $treatPhpDocTypesAsCertain, - private bool $looseComparisonRuleEnabled, ) { } @@ -32,10 +31,8 @@ public function shouldReportAlwaysTrueByDefault(Expr $expr): bool public function shouldSkip(Scope $scope, Expr $expr): bool { if ( - $this->looseComparisonRuleEnabled - && ($expr instanceof Expr\BinaryOp\Equal - || $expr instanceof Expr\BinaryOp\NotEqual - ) + $expr instanceof Expr\BinaryOp\Equal + || $expr instanceof Expr\BinaryOp\NotEqual ) { return true; } From eb0504c45191f8ffbde497394e6ceecc01686213 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:43:10 +0200 Subject: [PATCH 0402/3097] [BCB] Remove unused `disableRuntimeReflectionProvider` feature toggle --- UPGRADING.md | 2 +- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 658f366c004..2ac65c3d170 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -98,7 +98,7 @@ Appending `(?)` in `ignoreErrors` is not supported. * Removed unused config parameter `cache.nodesByFileCountMax` * Removed unused config parameter `memoryLimitFile` - +* Removed unused feature toggle `disableRuntimeReflectionProvider` ## Upgrading guide for extension developers diff --git a/conf/config.neon b/conf/config.neon index c9fd627837f..f901eb6dfea 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: tooWideThrowType: false featureToggles: bleedingEdge: false - disableRuntimeReflectionProvider: true skipCheckGenericClasses: [] arrayFilter: false arrayValues: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 68ebb600e38..98719031090 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -28,7 +28,6 @@ parametersSchema: ]) featureToggles: structure([ bleedingEdge: bool(), - disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), arrayFilter: bool(), arrayValues: bool(), From 7cfc02e0e137d633fa8e2d86f1244cddbd6bf37d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 14:56:05 +0200 Subject: [PATCH 0403/3097] Other attempt at removing `checkAlwaysTrue*` from tests --- .../Classes/ImpossibleInstanceOfRuleTest.php | 120 +--------- .../BooleanAndConstantConditionRuleTest.php | 1 - .../BooleanNotConstantConditionRuleTest.php | 1 - .../BooleanOrConstantConditionRuleTest.php | 1 - .../ConstantLooseComparisonRuleTest.php | 33 +-- .../DoWhileLoopConstantConditionRuleTest.php | 1 - .../ElseIfConstantConditionRuleTest.php | 1 - .../IfConstantConditionRuleTest.php | 1 - ...mpossibleCheckTypeFunctionCallRuleTest.php | 163 +------------ ...sibleCheckTypeGenericOverwriteRuleTest.php | 2 +- ...sibleCheckTypeMethodCallRuleEqualsTest.php | 2 +- .../ImpossibleCheckTypeMethodCallRuleTest.php | 2 +- ...sibleCheckTypeStaticMethodCallRuleTest.php | 2 +- .../LogicalXorConstantConditionRuleTest.php | 1 - .../Comparison/MatchExpressionRuleTest.php | 1 - ...rictComparisonOfDifferentTypesRuleTest.php | 214 +----------------- ...rnaryOperatorConstantConditionRuleTest.php | 1 - .../WhileLoopAlwaysFalseConditionRuleTest.php | 1 - .../WhileLoopAlwaysTrueConditionRuleTest.php | 1 - 19 files changed, 12 insertions(+), 537 deletions(-) diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index f8228f04f68..a7c21443f1c 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -12,8 +12,6 @@ class ImpossibleInstanceOfRuleTest extends RuleTestCase { - private bool $checkAlwaysTrueInstanceOf; - private bool $treatPhpDocTypesAsCertain; private bool $reportAlwaysTrueInLastCondition = false; @@ -21,9 +19,9 @@ class ImpossibleInstanceOfRuleTest extends RuleTestCase protected function getRule(): Rule { return new ImpossibleInstanceOfRule( - $this->checkAlwaysTrueInstanceOf, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } @@ -34,7 +32,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testInstanceof(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse( @@ -195,107 +192,8 @@ public function testInstanceof(): void ); } - public function testInstanceofWithoutAlwaysTrue(): void - { - $this->checkAlwaysTrueInstanceOf = false; - $this->treatPhpDocTypesAsCertain = true; - - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse( - [__DIR__ . '/data/impossible-instanceof.php'], - [ - [ - 'Instanceof between ImpossibleInstanceOf\Dolor and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 71, - ], - [ - 'Instanceof between string and ImpossibleInstanceOf\Foo will always evaluate to false.', - 94, - ], - [ - 'Instanceof between string and \'str\' will always evaluate to false.', - 98, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 119, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 137, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Test|null and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 155, - ], - [ - 'Instanceof between callable and ImpossibleInstanceOf\FinalClassWithoutInvoke will always evaluate to false.', - 204, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Lorem will always evaluate to false.', - 228, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Foo will always evaluate to false.', - 234, - ], - [ - 'Instanceof between *NEVER* and ImpossibleInstanceOf\Bar will always evaluate to false.', - 240, - //$tipText, - ], - [ - 'Instanceof between object and Exception will always evaluate to false.', - 303, - ], - [ - 'Instanceof between object and InvalidArgumentException will always evaluate to false.', - 307, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarChild will always evaluate to false.', - 318, - ], - [ - 'Instanceof between ImpossibleInstanceOf\Bar and ImpossibleInstanceOf\BarGrandChild will always evaluate to false.', - 322, - ], - /*[ - 'Instanceof between mixed and int results in an error.', - 353, - ], - [ - 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', - 362, - ],*/ - [ - 'Instanceof between T of Exception and Error will always evaluate to false.', - 404, - $tipText, - ], - [ - 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', - 418, - $tipText, - ], - [ - 'Instanceof between class-string and class-string will always evaluate to false.', - 419, - $tipText, - ], - [ - 'Instanceof between class-string and \'DateTimeInterface\' will always evaluate to false.', - 432, - $tipText, - ], - ], - ); - } - public function testDoNotReportTypesFromPhpDocs(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ [ @@ -319,7 +217,6 @@ public function testDoNotReportTypesFromPhpDocs(): void public function testReportTypesFromPhpDocs(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/impossible-instanceof-not-phpdoc.php'], [ [ @@ -353,21 +250,18 @@ public function testReportTypesFromPhpDocs(): void public function testBug3096(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3096.php'], []); } public function testBug6213(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6213.php'], []); } public function testBug5333(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-5333.php'], []); } @@ -378,7 +272,6 @@ public function testBug8042(): void $this->markTestSkipped('This test needs PHP 8.0'); } - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8042.php'], [ [ @@ -400,14 +293,12 @@ public function testBug7721(): void $this->markTestSkipped('This test needs PHP 8.1'); } - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-7721.php'], []); } public function testUnreachableIfBranches(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-if-branches.php'], [ [ @@ -432,7 +323,6 @@ public function testUnreachableIfBranches(): void public function testIfBranchesDoNotReportPhpDoc(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-if-branches-not-phpdoc.php'], [ [ @@ -454,7 +344,6 @@ public function testIfBranchesDoNotReportPhpDoc(): void public function testIfBranchesReportPhpDoc(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-if-branches-not-phpdoc.php'], [ @@ -492,7 +381,6 @@ public function testIfBranchesReportPhpDoc(): void public function testUnreachableTernaryElse(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-ternary-else-branch.php'], [ [ @@ -508,7 +396,6 @@ public function testUnreachableTernaryElse(): void public function testTernaryElseDoNotReportPhpDoc(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ [ @@ -528,7 +415,6 @@ public function testTernaryElseDoNotReportPhpDoc(): void public function testTernaryElseReportPhpDoc(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/../Comparison/data/unreachable-ternary-else-branch-not-phpdoc.php'], [ @@ -554,7 +440,6 @@ public function testTernaryElseReportPhpDoc(): void public function testBug4689(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-4689.php'], []); } @@ -590,7 +475,6 @@ public function dataReportAlwaysTrueInLastCondition(): iterable */ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLastCondition, array $expectedErrors): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->reportAlwaysTrueInLastCondition = $reportAlwaysTrueInLastCondition; $this->analyse([__DIR__ . '/data/impossible-instanceof-report-always-true-last-condition.php'], $expectedErrors); @@ -602,7 +486,6 @@ public function testBug10201(): void $this->markTestSkipped('This test needs PHP 8.1'); } - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10201.php'], [ [ @@ -614,7 +497,6 @@ public function testBug10201(): void public function testBug3632(): void { - $this->checkAlwaysTrueInstanceOf = true; $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index 1b66a5898e2..566c651fd48 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -26,7 +26,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, diff --git a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php index 5fb982f548f..ce5d1615f4b 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php @@ -26,7 +26,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index 4db5d7167af..ca462333494 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -27,7 +27,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index cbd60c68ed8..e0bbf466abd 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -12,8 +12,6 @@ class ConstantLooseComparisonRuleTest extends RuleTestCase { - private bool $checkAlwaysTrueStrictComparison; - private bool $treatPhpDocTypesAsCertain = true; private bool $reportAlwaysTrueInLastCondition = false; @@ -21,39 +19,14 @@ class ConstantLooseComparisonRuleTest extends RuleTestCase protected function getRule(): Rule { return new ConstantLooseComparisonRule( - $this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } public function testRule(): void { - $this->checkAlwaysTrueStrictComparison = false; - $this->analyse([__DIR__ . '/data/loose-comparison.php'], [ - [ - "Loose comparison using == between 0 and '1' will always evaluate to false.", - 20, - ], - [ - "Loose comparison using == between 0 and '1' will always evaluate to false.", - 27, - ], - [ - "Loose comparison using == between 0 and '1' will always evaluate to false.", - 33, - ], - [ - 'Loose comparison using != between 3 and 3 will always evaluate to false.', - 48, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ]); - } - - public function testRuleAlwaysTrue(): void - { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/loose-comparison.php'], [ [ "Loose comparison using == between 0 and '0' will always evaluate to true.", @@ -90,7 +63,6 @@ public function testBug8485(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8485.php'], [ [ 'Loose comparison using == between Bug8485\E::c and Bug8485\E::c will always evaluate to true.', @@ -142,7 +114,6 @@ public function dataReportAlwaysTrueInLastCondition(): iterable */ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLastCondition, array $expectedErrors): void { - $this->checkAlwaysTrueStrictComparison = true; $this->reportAlwaysTrueInLastCondition = $reportAlwaysTrueInLastCondition; $this->analyse([__DIR__ . '/data/loose-comparison-report-always-true-last-condition.php'], $expectedErrors); } @@ -165,14 +136,12 @@ public function dataTreatPhpDocTypesAsCertain(): iterable */ public function testTreatPhpDocTypesAsCertain(bool $treatPhpDocTypesAsCertain, array $expectedErrors): void { - $this->checkAlwaysTrueStrictComparison = true; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; $this->analyse([__DIR__ . '/data/loose-comparison-treat-phpdoc-types.php'], $expectedErrors); } public function testBug11694(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-11694.php'], [ [ 'Loose comparison using == between 3 and int<10, 20> will always evaluate to false.', diff --git a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php index 4ddf9941e89..38f3237a45e 100644 --- a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index 4bac3a314fe..e734b8ea1fb 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -27,7 +27,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 80ee3c0763d..1f03a122bdb 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -25,7 +25,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 95839c42cc9..5480eb394c8 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -17,8 +17,6 @@ class ImpossibleCheckTypeFunctionCallRuleTest extends RuleTestCase { - private bool $checkAlwaysTrueCheckTypeFunctionCall; - private bool $treatPhpDocTypesAsCertain; private bool $reportAlwaysTrueInLastCondition = false; @@ -32,9 +30,9 @@ protected function getRule(): Rule [stdClass::class], $this->treatPhpDocTypesAsCertain, ), - $this->checkAlwaysTrueCheckTypeFunctionCall, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } @@ -49,7 +47,6 @@ public function testImpossibleCheckTypeFunctionCall(): void self::markTestSkipped('Test requires PHP 8.0.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse( [__DIR__ . '/data/check-type-function-call.php'], @@ -270,121 +267,12 @@ public function testImpossibleCheckTypeFunctionCall(): void public function testBug7898(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-7898.php'], []); } - public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void - { - if (PHP_VERSION_ID < 80000) { - self::markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkAlwaysTrueCheckTypeFunctionCall = false; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse( - [__DIR__ . '/data/check-type-function-call.php'], - [ - [ - 'Call to function is_int() with string will always evaluate to false.', - 31, - ], - [ - 'Call to function is_callable() with array will always evaluate to false.', - 44, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 48, - ], - [ - 'Call to function is_callable() with \'nonexistentFunction\' will always evaluate to false.', - 87, - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 105, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', - 194, - ], - [ - 'Call to function in_array() with arguments int, array{\'foo\', \'bar\'} and true will always evaluate to false.', - 236, - ], - [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array{\'baz\', \'lorem\'} and true will always evaluate to false.', - 245, - ], - [ - 'Call to function in_array() with arguments \'bar\', array{}|array{\'foo\'} and true will always evaluate to false.', - 321, - ], - [ - 'Call to function in_array() with arguments \'baz\', array{0: \'bar\', 1?: \'foo\'} and true will always evaluate to false.', - 337, - ], - [ - 'Call to function in_array() with arguments \'foo\', array{} and true will always evaluate to false.', - 344, - ], - [ - 'Call to function array_key_exists() with \'c\' and array{a: 1, b?: 2} will always evaluate to false.', - 367, - ], - [ - 'Call to function is_string() with mixed will always evaluate to false.', - 561, - ], - [ - 'Call to function is_callable() with mixed will always evaluate to false.', - 572, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', - 595, - ], - [ - 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', - 598, - ], - [ - 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', - 631, - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 640, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 649, - ], - [ - 'Call to function assert() with false will always evaluate to false.', - 694, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 694, - ], - [ - 'Call to function in_array() with arguments 1, array and true will always evaluate to false.', - 927, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - ], - ); - } - public function testDoNotReportTypesFromPhpDocs(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ [ @@ -396,7 +284,6 @@ public function testDoNotReportTypesFromPhpDocs(): void public function testReportTypesFromPhpDocs(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/check-type-function-call-not-phpdoc.php'], [ [ @@ -423,42 +310,36 @@ public function testReportTypesFromPhpDocs(): void public function testBug2550(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-2550.php'], []); } public function testBug3994(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3994.php'], []); } public function testBug1613(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-1613.php'], []); } public function testBug2714(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-2714.php'], []); } public function testBug4657(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-4657.php'], []); } public function testBug4999(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-4999.php'], []); } @@ -469,7 +350,6 @@ public function testArrayIsList(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/array-is-list.php'], [ [ @@ -490,14 +370,12 @@ public function testArrayIsList(): void public function testBug3766(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3766.php'], []); } public function testBug6305(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6305.php'], [ [ @@ -513,21 +391,18 @@ public function testBug6305(): void public function testBug6698(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6698.php'], []); } public function testBug5369(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-5369.php'], []); } public function testBugInArrayDateFormat(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/in-array-date-format.php'], [ [ @@ -554,63 +429,54 @@ public function testBugInArrayDateFormat(): void public function testBug5496(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-5496.php'], []); } public function testBug3892(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3892.php'], []); } public function testBug3314(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3314.php'], []); } public function testBug2870(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-2870.php'], []); } public function testBug5354(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-5354.php'], []); } public function testSlevomatCsInArrayBug(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/slevomat-cs-in-array.php'], []); } public function testNonEmptySpecifiedString(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/non-empty-string-impossible-type.php'], []); } public function testBug2755(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-2755.php'], []); } public function testBug7079(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-7079.php'], []); } @@ -621,7 +487,6 @@ public function testConditionalTypesInference(): void self::markTestSkipped('Test requires PHP 8.0.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/conditional-types-inference.php'], [ [ @@ -649,7 +514,6 @@ public function testConditionalTypesInference(): void public function testBug6697(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6697.php'], []); } @@ -660,56 +524,48 @@ public function testBug6443(): void self::markTestSkipped('Test requires PHP 8.0.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6443.php'], []); } public function testBug7684(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = false; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-7684.php'], []); } public function testBug7224(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-7224.php'], []); } public function testBug4708(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-4708.php'], []); } public function testBug3821(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3821.php'], []); } public function testBug6599(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-6599.php'], []); } public function testBug7914(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-7914.php'], []); } public function testDocblockAssertEquality(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/docblock-assert-equality.php'], [ [ @@ -721,63 +577,54 @@ public function testDocblockAssertEquality(): void public function testBug8076(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8076.php'], []); } public function testBug8562(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8562.php'], []); } public function testBug6938(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-6938.php'], []); } public function testBug8727(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8727.php'], []); } public function testBug8474(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8474.php'], []); } public function testBug5695(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-5695.php'], []); } public function testBug8752(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8752.php'], []); } public function testDiscussion9134(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/discussion-9134.php'], []); } public function testImpossibleMethodExistOnGenericClassString(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; @@ -838,7 +685,6 @@ public function dataReportAlwaysTrueInLastCondition(): iterable */ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLastCondition, array $expectedErrors): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->reportAlwaysTrueInLastCondition = $reportAlwaysTrueInLastCondition; $this->analyse([__DIR__ . '/data/impossible-function-report-always-true-last-condition.php'], $expectedErrors); @@ -846,7 +692,6 @@ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLast public function testObjectShapes(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/property-exists-object-shapes.php'], [ [ @@ -1023,7 +868,6 @@ public function testLooseComparisonAgainstEnums(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $issues = array_map( static function (array $i): array { @@ -1040,7 +884,6 @@ static function (array $i): array { public function testNonStrictInArray(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-9662.php'], []); } @@ -1053,7 +896,6 @@ public function testNonStrictInArrayEnums(): void $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-9662-enums.php'], [ [ @@ -1083,7 +925,6 @@ public function testLooseComparisonAgainstEnumsNoPhpdoc(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = false; $issues = self::getLooseComparisonAgainsEnumsIssues(); $issues = array_values(array_filter($issues, static fn (array $i) => count($i) === 2)); @@ -1094,7 +935,6 @@ public function testBug10502(): void { $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-10502.php'], [ [ @@ -1111,7 +951,6 @@ public function testBug10502(): void public function testAlwaysTruePregMatch(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php index 03de668ca68..67245ebaba3 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeGenericOverwriteRuleTest.php @@ -21,8 +21,8 @@ public function getRule(): Rule true, ), true, - true, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php index 736e912ab76..b81646c0231 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleEqualsTest.php @@ -21,8 +21,8 @@ public function getRule(): Rule true, ), true, - true, false, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 2741c678641..eceaff15f9b 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -25,9 +25,9 @@ public function getRule(): Rule [], $this->treatPhpDocTypesAsCertain, ), - true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 3133e3aa966..1cdbc1a7ad7 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -24,9 +24,9 @@ public function getRule(): Rule [], $this->treatPhpDocTypesAsCertain, ), - true, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } diff --git a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php index 4eab90f0170..c1918560473 100644 --- a/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/LogicalXorConstantConditionRuleTest.php @@ -26,7 +26,6 @@ protected function getRule(): TRule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 072475e3e7a..365b9dff9c6 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -27,7 +27,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->reportAlwaysTrueInLastCondition, $this->treatPhpDocTypesAsCertain, diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index f30f842ca28..0d9fb746aad 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -13,8 +13,6 @@ class StrictComparisonOfDifferentTypesRuleTest extends RuleTestCase { - private bool $checkAlwaysTrueStrictComparison; - private bool $reportAlwaysTrueInLastCondition = false; private bool $treatPhpDocTypesAsCertain = true; @@ -22,9 +20,9 @@ class StrictComparisonOfDifferentTypesRuleTest extends RuleTestCase protected function getRule(): Rule { return new StrictComparisonOfDifferentTypesRule( - $this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, + true, ); } @@ -35,7 +33,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool public function testStrictComparison(): void { - $this->checkAlwaysTrueStrictComparison = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse( [__DIR__ . '/data/strict-comparison.php'], @@ -273,162 +270,8 @@ public function testStrictComparison(): void ); } - public function testStrictComparisonWithoutAlwaysTrue(): void - { - $this->checkAlwaysTrueStrictComparison = false; - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse( - [__DIR__ . '/data/strict-comparison.php'], - [ - [ - 'Strict comparison using === between 1 and \'1\' will always evaluate to false.', - 11, - ], - [ - 'Strict comparison using === between 1 and null will always evaluate to false.', - 14, - ], - [ - 'Strict comparison using === between StrictComparison\Bar and 1 will always evaluate to false.', - 15, - ], - [ - 'Strict comparison using === between 1 and array|bool|StrictComparison\Collection will always evaluate to false.', - 19, - $tipText, - ], - [ - 'Strict comparison using === between true and false will always evaluate to false.', - 30, - ], - [ - 'Strict comparison using === between false and true will always evaluate to false.', - 31, - ], - [ - 'Strict comparison using === between 1.0 and 1 will always evaluate to false.', - 46, - ], - [ - 'Strict comparison using === between 1 and 1.0 will always evaluate to false.', - 47, - ], - [ - 'Strict comparison using === between string and null will always evaluate to false.', - 69, - ], - [ - 'Strict comparison using === between 1|2|3 and null will always evaluate to false.', - 98, - ], - [ - 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 140, - ], - [ - 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 164, - ], - [ - 'Strict comparison using === between 1 and 2 will always evaluate to false.', - 284, - ], - [ - 'Strict comparison using === between array{X: 1} and array{X: 2} will always evaluate to false.', - 292, - ], - [ - 'Strict comparison using === between array{X: 1, Y: 2} and array{X: 2, Y: 1} will always evaluate to false.', - 300, - ], - [ - 'Strict comparison using === between array{X: 1, Y: 2} and array{Y: 2, X: 1} will always evaluate to false.', - 308, - ], - [ - 'Strict comparison using === between \'/\'|\'\\\\\' and \'//\' will always evaluate to false.', - 320, - ], - [ - 'Strict comparison using === between int<1, max> and \'string\' will always evaluate to false.', - 335, - ], - [ - 'Strict comparison using === between int<0, max> and \'string\' will always evaluate to false.', - 343, - ], - [ - 'Strict comparison using === between int<0, max> and \'string\' will always evaluate to false.', - 360, - ], - [ - 'Strict comparison using === between int<1, max> and \'string\' will always evaluate to false.', - 368, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 386, - ], - [ - 'Strict comparison using === between float and \'string\' will always evaluate to false.', - 394, - ], - [ - 'Strict comparison using !== between null and null will always evaluate to false.', - 408, - ], - [ - 'Strict comparison using === between (int|int<2, max>|string) and 1.0 will always evaluate to false.', - 464, - ], - [ - 'Strict comparison using === between (int|int<2, max>|string) and stdClass will always evaluate to false.', - 466, - ], - [ - 'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.', - 622, - $tipText, - ], - [ - 'Strict comparison using === between 100 and \'foo\' will always evaluate to false.', - 624, - ], - [ - 'Strict comparison using === between int<10, max> and \'foo\' will always evaluate to false.', - 635, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 685, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 695, - ], - [ - 'Strict comparison using === between string|null and 1 will always evaluate to false.', - 705, - ], - [ - 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', - 808, - ], - [ - 'Strict comparison using === between NAN and NAN will always evaluate to false.', - 980, - ], - [ - 'Strict comparison using !== between INF and INF will always evaluate to false.', - 982, - ], - ], - ); - } - public function testStrictComparisonPhp71(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/strict-comparison-71.php'], [ [ 'Strict comparison using === between null and null will always evaluate to true.', @@ -439,7 +282,6 @@ public function testStrictComparisonPhp71(): void public function testStrictComparisonPropertyNativeTypesPhp74(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/strict-comparison-property-native-types.php'], [ [ 'Strict comparison using === between string and null will always evaluate to false.', @@ -462,13 +304,11 @@ public function testStrictComparisonPropertyNativeTypesPhp74(): void public function testBug2835(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-2835.php'], []); } public function testBug1860(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-1860.php'], [ [ 'Strict comparison using === between string and null will always evaluate to false.', @@ -483,31 +323,26 @@ public function testBug1860(): void public function testBug3544(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-3544.php'], []); } public function testBug2675(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-2675.php'], []); } public function testBug2220(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-2220.php'], []); } public function testBug1707(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-1707.php'], []); } public function testBug3357(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-3357.php'], []); } @@ -516,7 +351,6 @@ public function testBug4848(): void if (PHP_INT_SIZE !== 8) { $this->markTestSkipped('Test requires 64-bit platform.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-4848.php'], [ [ 'Strict comparison using === between \'18446744073709551615\' and \'9223372036854775807\' will always evaluate to false.', @@ -527,25 +361,21 @@ public function testBug4848(): void public function testBug4793(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-4793.php'], []); } public function testBug5062(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-5062.php'], []); } public function testBug3366(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-3366.php'], []); } public function testBug5362(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-5362.php'], [ [ 'Strict comparison using === between 0 and 1|2 will always evaluate to false.', @@ -556,8 +386,6 @@ public function testBug5362(): void public function testBug6939(): void { - $this->checkAlwaysTrueStrictComparison = true; - if (PHP_VERSION_ID < 80000) { $this->analyse([__DIR__ . '/data/bug-6939.php'], []); return; @@ -573,13 +401,11 @@ public function testBug6939(): void public function testBug7166(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-7166.php'], []); } public function testBug7555(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-7555.php'], [ [ 'Strict comparison using === between 2 and 2 will always evaluate to true.', @@ -591,30 +417,30 @@ public function testBug7555(): void public function testBug7257(): void { - $this->checkAlwaysTrueStrictComparison = false; $this->analyse([__DIR__ . '/data/bug-7257.php'], []); } public function testBug5474(): void { - $this->checkAlwaysTrueStrictComparison = false; $this->analyse([__DIR__ . '/data/bug-5474.php'], [ [ 'Strict comparison using !== between array{test: 1} and array{test: 1} will always evaluate to false.', 25, ], + [ + 'Strict comparison using !== between array{test: 1} and array{test: 5} will always evaluate to true.', + 29, + ], ]); } public function testBug7684(): void { - $this->checkAlwaysTrueStrictComparison = false; $this->analyse([__DIR__ . '/data/bug-7684.php'], []); } public function testBug6181(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-6181.php'], []); } @@ -622,7 +448,6 @@ public function testBug2851b(): void { $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-2851b.php'], [ [ 'Strict comparison using === between 0 and 0 will always evaluate to true.', @@ -634,7 +459,6 @@ public function testBug2851b(): void public function testBug8158(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8158.php'], []); } @@ -644,7 +468,6 @@ public function testBug8485(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8485.php'], [ [ 'Strict comparison using === between Bug8485\E::c and Bug8485\E::c will always evaluate to true.', @@ -682,19 +505,16 @@ public function testBug8485(): void public function testBug8516(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8516.php'], []); } public function testPhpUnitIntegration(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/phpunit-integration.php'], []); } public function testBug8586(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8586.php'], []); } @@ -704,13 +524,11 @@ public function testBug4242(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-4242.php'], []); } public function testBug3633(): void { - $this->checkAlwaysTrueStrictComparison = true; $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/data/bug-3633.php'], [ [ @@ -781,7 +599,6 @@ public function testBug3633(): void public function testLastConditionAlwaysTrue(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/strict-comparison-last-condition-always-true.php'], [ [ 'Strict comparison using === between \'bar\' and \'bar\' will always evaluate to true.', @@ -793,13 +610,11 @@ public function testLastConditionAlwaysTrue(): void public function testBug3019(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-3019.php'], []); } public function testBug7578(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-7578.php'], []); } @@ -810,14 +625,12 @@ public function testBug6260(): void $this->markTestSkipped('Test requires PHP 8.0.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/bug-6260.php'], []); } public function testBug8736(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8736.php'], []); } @@ -895,20 +708,17 @@ public function testLastMatchArm(bool $reportAlwaysTrueInLastCondition, array $e $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->reportAlwaysTrueInLastCondition = $reportAlwaysTrueInLastCondition; $this->analyse([__DIR__ . '/data/strict-comparison-last-match-arm.php'], $expectedErrors); } public function testBug8776Part1(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8776-1.php'], []); } public function testBug8776Part2(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8776-2.php'], []); } @@ -929,13 +739,11 @@ public function testBug5978(): void $expectedErrors = []; } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-5978.php'], $expectedErrors); } public function testBug9104(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-9104.php'], [ [ 'Strict comparison using === between int<1, max> and 0 will always evaluate to false.', @@ -951,7 +759,6 @@ public function testEnumTips(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/strict-comparison-enum-tips.php'], [ [ 'Strict comparison using === between StrictComparisonEnumTips\SomeEnum::Two and StrictComparisonEnumTips\SomeEnum::Two will always evaluate to true.', @@ -967,7 +774,6 @@ public function testBug9142(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-9142.php'], [ [ 'Strict comparison using === between $this(Bug9142\MyEnum) and Bug9142\MyEnum::Three will always evaluate to false.', @@ -986,7 +792,6 @@ public function testBug4061(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-4061.php'], []); } @@ -996,7 +801,6 @@ public function testBug9723(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-9723.php'], []); } @@ -1006,25 +810,21 @@ public function testBug9723b(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-9723b.php'], []); } public function testBug8366(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8366.php'], []); } public function testBug3300(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/../../Analyser/data/bug-3300.php'], []); } public function testBug11035(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-11035.php'], [ [ "Strict comparison using === between '0' and non-falsy-string will always evaluate to false.", @@ -1036,25 +836,21 @@ public function testBug11035(): void public function testBug9804(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-9804.php'], []); } public function testBug11161(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-11161.php'], []); } public function testBug10697(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-10697.php'], []); } public function testBug10493(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-10493.php'], []); } diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index 2169597e681..e1e7474e17f 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php index df5ce1d3c8f..4d65f02d2b9 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php index 83f53c071a2..4a377f18554 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php @@ -24,7 +24,6 @@ protected function getRule(): Rule $this->treatPhpDocTypesAsCertain, ), $this->treatPhpDocTypesAsCertain, - true, ), $this->treatPhpDocTypesAsCertain, true, From a2a2905d76bde5a12cad9f69297bc2670b71ecfa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0404/3097] Issue bot - let all comments about PHPStan 2.0 through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From b02b79fa02c634a8c34e5a14f7686ed3b74fb74b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 15:06:23 +0200 Subject: [PATCH 0405/3097] Revert "Issue bot - let all comments about PHPStan 2.0 through" This reverts commit a2a2905d76bde5a12cad9f69297bc2670b71ecfa. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 29ddf4248932f88a7a97aa05213125b0e23e1db1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 15:08:07 +0200 Subject: [PATCH 0406/3097] [BE] Check if required file exists --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 4 ++-- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 274dae47b38..bd23ea35b31 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -27,6 +27,7 @@ Major new features 🚀 * Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* Check if required file exists (level 0) ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! * Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! @@ -84,7 +85,6 @@ Bleeding edge (TODO move to other sections) * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) -* Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 Improvements 🔧 diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 54bad4da407..fa60d6e22a5 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,4 +8,3 @@ parameters: arrayValues: true readOnlyByPhpDoc: true pure: true - requireFileExists: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d62a5e4ce94..13147b6c450 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -8,8 +8,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Keywords\RequireFileExistsRule: - phpstan.rules.rule: %featureToggles.requireFileExists% rules: - PHPStan\Rules\Api\ApiInstanceofRule @@ -260,3 +258,5 @@ services: class: PHPStan\Rules\Keywords\RequireFileExistsRule arguments: currentWorkingDirectory: %currentWorkingDirectory% + tags: + - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index f901eb6dfea..e8bad648c4d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -29,7 +29,6 @@ parameters: readOnlyByPhpDoc: false stricterFunctionMap: false pure: false - requireFileExists: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 98719031090..31e5cad0e01 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -35,7 +35,6 @@ parametersSchema: readOnlyByPhpDoc: bool() stricterFunctionMap: bool() pure: bool() - requireFileExists: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From e47eee311dbff0f4f8931b13f7d14f5822835602 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 15:10:22 +0200 Subject: [PATCH 0407/3097] [BE] Report useless `array_filter()` calls --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 4 ++-- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index bd23ea35b31..6da15f72597 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -27,6 +27,7 @@ Major new features 🚀 * Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! * Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! +* Report useless `array_filter()` calls (level 5) ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Check if required file exists (level 0) ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! @@ -64,7 +65,6 @@ Major new features 🚀 Bleeding edge (TODO move to other sections) ===================== -* Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index fa60d6e22a5..555389c6e6d 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,7 +4,6 @@ parameters: skipCheckGenericClasses!: [] stricterFunctionMap: true - arrayFilter: true arrayValues: true readOnlyByPhpDoc: true pure: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 3fa1bb8e294..a8aa14b9861 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -6,8 +6,6 @@ parameters: checkArgumentsPassedByReference: true conditionalTags: - PHPStan\Rules\Functions\ArrayFilterRule: - phpstan.rules.rule: %featureToggles.arrayFilter% PHPStan\Rules\Functions\ArrayValuesRule: phpstan.rules.rule: %featureToggles.arrayValues% @@ -31,6 +29,8 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Functions\ArrayValuesRule diff --git a/conf/config.neon b/conf/config.neon index e8bad648c4d..5104a7e0403 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -23,7 +23,6 @@ parameters: featureToggles: bleedingEdge: false skipCheckGenericClasses: [] - arrayFilter: false arrayValues: false illegalConstructorMethodCall: false readOnlyByPhpDoc: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 31e5cad0e01..351862a0cd4 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -29,7 +29,6 @@ parametersSchema: featureToggles: structure([ bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), - arrayFilter: bool(), arrayValues: bool(), illegalConstructorMethodCall: bool(), readOnlyByPhpDoc: bool() From 439950412f287cf78270bd32cf34912712d4b2e5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 15:11:56 +0200 Subject: [PATCH 0408/3097] [BE] Report useless `array_values()` calls --- changelog-2.0.md | 2 +- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 6 ++---- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 6da15f72597..803b7d8f429 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -28,6 +28,7 @@ Major new features 🚀 * Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Report useless `array_filter()` calls (level 5) ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! +* Report useless `array_values()` calls (level 5) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! * Check if required file exists (level 0) ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! * ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) * Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! @@ -76,7 +77,6 @@ Bleeding edge (TODO move to other sections) * Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) * Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) * Add option `reportAnyTypeWideningInVarTag` ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! -* `array_values` rule (report when a `list` type is always passed in) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! * Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! * Checking truthiness of `@phpstan-pure` above functions and methods * Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 555389c6e6d..694eba9796c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,6 +4,5 @@ parameters: skipCheckGenericClasses!: [] stricterFunctionMap: true - arrayValues: true readOnlyByPhpDoc: true pure: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index a8aa14b9861..0cbccea5d6b 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -5,10 +5,6 @@ parameters: checkFunctionArgumentTypes: true checkArgumentsPassedByReference: true -conditionalTags: - PHPStan\Rules\Functions\ArrayValuesRule: - phpstan.rules.rule: %featureToggles.arrayValues% - rules: - PHPStan\Rules\DateTimeInstantiationRule - PHPStan\Rules\Functions\CallUserFuncRule @@ -37,3 +33,5 @@ services: arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 5104a7e0403..aa8fcc3a632 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -23,7 +23,6 @@ parameters: featureToggles: bleedingEdge: false skipCheckGenericClasses: [] - arrayValues: false illegalConstructorMethodCall: false readOnlyByPhpDoc: false stricterFunctionMap: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 351862a0cd4..652bda42b0e 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -29,7 +29,6 @@ parametersSchema: featureToggles: structure([ bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), - arrayValues: bool(), illegalConstructorMethodCall: bool(), readOnlyByPhpDoc: bool() stricterFunctionMap: bool() From 375879379aa709eebc4b14eb6349e97d854a3de6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:09:55 +0200 Subject: [PATCH 0409/3097] [BE] Support `@readonly` property and `@immutable` class PHPDoc --- changelog-2.0.md | 4 ++-- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 12 ++---------- conf/config.level2.neon | 7 +------ conf/config.level3.neon | 14 ++------------ conf/config.neon | 1 - conf/parametersSchema.neon | 1 - src/Rules/Generics/PropertyVarianceRule.php | 3 +-- .../Rules/Generics/PropertyVarianceRuleTest.php | 1 - 9 files changed, 8 insertions(+), 36 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 803b7d8f429..29e9d612b4e 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -36,6 +36,8 @@ Major new features 🚀 * ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! * Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! * Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 +* Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! +* Add `@readonly` rule that disallows default values (level 0) ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * IncompatibleDefaultParameterTypeRule for closures (level 2) (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) * Added previously absent type checks (level 0) * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) @@ -67,9 +69,7 @@ Bleeding edge (TODO move to other sections) ===================== * Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! -* Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) -* Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 694eba9796c..89a624594ac 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,5 +4,4 @@ parameters: skipCheckGenericClasses!: [] stricterFunctionMap: true - readOnlyByPhpDoc: true pure: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 13147b6c450..6dd5e4c7ca1 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -2,10 +2,6 @@ parameters: customRulesetUsed: false conditionalTags: - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule: - phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule: - phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% @@ -98,9 +94,11 @@ rules: - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule - PHPStan\Rules\Properties\InvalidCallablePropertyTypeRule - PHPStan\Rules\Properties\MissingReadOnlyPropertyAssignRule + - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\PropertiesInInterfaceRule - PHPStan\Rules\Properties\PropertyAttributesRule - PHPStan\Rules\Properties\ReadOnlyPropertyRule + - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule - PHPStan\Rules\Regexp\RegularExpressionPatternRule - PHPStan\Rules\Regexp\RegularExpressionQuotingRule - PHPStan\Rules\Traits\ConflictingTraitConstantsRule @@ -203,9 +201,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule - - class: PHPStan\Rules\Properties\OverridingPropertyRule arguments: @@ -214,9 +209,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule - - class: PHPStan\Rules\Properties\UninitializedPropertyRule diff --git a/conf/config.level2.neon b/conf/config.level2.neon index f75e01dfdef..b9fbf7d0232 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -37,6 +37,7 @@ rules: - PHPStan\Rules\Generics\MethodTagTemplateTypeRule - PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule - PHPStan\Rules\Generics\MethodSignatureVarianceRule + - PHPStan\Rules\Generics\PropertyVarianceRule - PHPStan\Rules\Generics\TraitTemplateTypeRule - PHPStan\Rules\Generics\UsedTraitsRule - PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule @@ -117,12 +118,6 @@ services: class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Generics\PropertyVarianceRule - arguments: - readOnlyByPhpDoc: %featureToggles.readOnlyByPhpDoc% - tags: - - phpstan.rules.rule - class: PHPStan\Rules\Pure\PureFunctionRule diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 089569684cc..250d545f153 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -1,12 +1,6 @@ includes: - config.level2.neon -conditionalTags: - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule: - phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule: - phpstan.rules.rule: %featureToggles.readOnlyByPhpDoc% - rules: - PHPStan\Rules\Arrays\ArrayDestructuringRule - PHPStan\Rules\Arrays\ArrayUnpackingRule @@ -23,7 +17,9 @@ rules: - PHPStan\Rules\Methods\ReturnTypeRule - PHPStan\Rules\Properties\DefaultValueTypesAssignedToPropertiesRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRule + - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule + - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule - PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule @@ -83,9 +79,3 @@ services: reportMaybes: %reportMaybes% tags: - phpstan.rules.rule - - - - class: PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule - - - - class: PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule diff --git a/conf/config.neon b/conf/config.neon index aa8fcc3a632..5f0d9fd1eff 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,7 +24,6 @@ parameters: bleedingEdge: false skipCheckGenericClasses: [] illegalConstructorMethodCall: false - readOnlyByPhpDoc: false stricterFunctionMap: false pure: false fileExtensions: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 652bda42b0e..51133df7072 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -30,7 +30,6 @@ parametersSchema: bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), illegalConstructorMethodCall: bool(), - readOnlyByPhpDoc: bool() stricterFunctionMap: bool() pure: bool() ]) diff --git a/src/Rules/Generics/PropertyVarianceRule.php b/src/Rules/Generics/PropertyVarianceRule.php index 4ddff55f111..f222bd9945b 100644 --- a/src/Rules/Generics/PropertyVarianceRule.php +++ b/src/Rules/Generics/PropertyVarianceRule.php @@ -18,7 +18,6 @@ final class PropertyVarianceRule implements Rule public function __construct( private VarianceCheck $varianceCheck, - private bool $readOnlyByPhpDoc, ) { } @@ -42,7 +41,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $variance = $node->isReadOnly() || ($this->readOnlyByPhpDoc && $node->isReadOnlyByPhpDoc()) + $variance = $node->isReadOnly() || $node->isReadOnlyByPhpDoc() ? TemplateTypeVariance::createCovariant() : TemplateTypeVariance::createInvariant(); diff --git a/tests/PHPStan/Rules/Generics/PropertyVarianceRuleTest.php b/tests/PHPStan/Rules/Generics/PropertyVarianceRuleTest.php index 97870f3572d..0708ec30953 100644 --- a/tests/PHPStan/Rules/Generics/PropertyVarianceRuleTest.php +++ b/tests/PHPStan/Rules/Generics/PropertyVarianceRuleTest.php @@ -16,7 +16,6 @@ protected function getRule(): Rule { return new PropertyVarianceRule( self::getContainer()->getByType(VarianceCheck::class), - true, ); } From ba93e96748062dc931e1b4aa069980a04787544b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:13:55 +0200 Subject: [PATCH 0410/3097] [BE] Check `@phpstan-pure` --- changelog-2.0.md | 12 ++-- conf/bleedingEdge.neon | 2 - conf/config.level2.neon | 11 +--- conf/config.level4.neon | 62 ++++++------------- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - ...tructorStatementWithoutSideEffectsRule.php | 17 ++--- ...torStatementWithoutSideEffectsRuleTest.php | 2 +- 8 files changed, 34 insertions(+), 74 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 29e9d612b4e..34f9d49201a 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -19,6 +19,12 @@ Major new features 🚀 * Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) * Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed +* Checking truthiness of `@phpstan-pure` above functions and methods +* Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side + * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 + * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! + * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! + * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) * Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! @@ -78,12 +84,6 @@ Bleeding edge (TODO move to other sections) * Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) * Add option `reportAnyTypeWideningInVarTag` ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! * Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! -* Checking truthiness of `@phpstan-pure` above functions and methods -* Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side - * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 - * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! - * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! - * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) * Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 89a624594ac..8e06b22fda5 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -3,5 +3,3 @@ parameters: bleedingEdge: true skipCheckGenericClasses!: [] stricterFunctionMap: true - - pure: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index b9fbf7d0232..b3507a4a46b 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -63,16 +63,14 @@ rules: - PHPStan\Rules\PhpDoc\RequireImplementsDefinitionClassRule - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionClassRule - PHPStan\Rules\PhpDoc\RequireExtendsDefinitionTraitRule + - PHPStan\Rules\Pure\PureFunctionRule + - PHPStan\Rules\Pure\PureMethodRule conditionalTags: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% - PHPStan\Rules\Pure\PureFunctionRule: - phpstan.rules.rule: %featureToggles.pure% - PHPStan\Rules\Pure\PureMethodRule: - phpstan.rules.rule: %featureToggles.pure% services: - @@ -118,8 +116,3 @@ services: class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Pure\PureFunctionRule - - - - class: PHPStan\Rules\Pure\PureMethodRule diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 714843e781c..56364170466 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -3,12 +3,17 @@ includes: rules: - PHPStan\Rules\Arrays\DeadForeachRule + - PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule + - PHPStan\Rules\DeadCode\CallToFunctionStatementWithoutImpurePointsRule + - PHPStan\Rules\DeadCode\CallToMethodStatementWithoutImpurePointsRule + - PHPStan\Rules\DeadCode\CallToStaticMethodStatementWithoutImpurePointsRule - PHPStan\Rules\DeadCode\NoopRule - PHPStan\Rules\DeadCode\UnreachableStatementRule - PHPStan\Rules\DeadCode\UnusedPrivateConstantRule - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - PHPStan\Rules\Functions\CallToFunctionStatementWithoutSideEffectsRule + - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToMethodStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToStaticMethodStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\NullsafeMethodCallRule @@ -20,30 +25,6 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule - PHPStan\Rules\Traits\NotAnalysedTraitRule -conditionalTags: - PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule: - phpstan.rules.rule: %featureToggles.pure% - PHPStan\Rules\DeadCode\PossiblyPureNewCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\ConstructorWithoutImpurePointsCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\CallToFunctionStatementWithoutImpurePointsRule: - phpstan.rules.rule: %featureToggles.pure% - PHPStan\Rules\DeadCode\PossiblyPureFuncCallCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\FunctionWithoutImpurePointsCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\CallToMethodStatementWithoutImpurePointsRule: - phpstan.rules.rule: %featureToggles.pure% - PHPStan\Rules\DeadCode\PossiblyPureMethodCallCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\MethodWithoutImpurePointsCollector: - phpstan.collector: %featureToggles.pure% - PHPStan\Rules\DeadCode\CallToStaticMethodStatementWithoutImpurePointsRule: - phpstan.rules.rule: %featureToggles.pure% - PHPStan\Rules\DeadCode\PossiblyPureStaticCallCollector: - phpstan.collector: %featureToggles.pure% - parameters: checkAdvancedIsset: true @@ -84,38 +65,40 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\DeadCode\CallToConstructorStatementWithoutImpurePointsRule - - class: PHPStan\Rules\DeadCode\ConstructorWithoutImpurePointsCollector + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\PossiblyPureNewCollector - - - - class: PHPStan\Rules\DeadCode\CallToFunctionStatementWithoutImpurePointsRule + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\FunctionWithoutImpurePointsCollector + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\PossiblyPureFuncCallCollector - - - - class: PHPStan\Rules\DeadCode\CallToMethodStatementWithoutImpurePointsRule + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\MethodWithoutImpurePointsCollector + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\PossiblyPureMethodCallCollector - - - - class: PHPStan\Rules\DeadCode\CallToStaticMethodStatementWithoutImpurePointsRule + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\PossiblyPureStaticCallCollector + tags: + - phpstan.collector - class: PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule @@ -245,13 +228,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - arguments: - reportNoConstructor: %featureToggles.pure% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 5f0d9fd1eff..9deb65b8924 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -25,7 +25,6 @@ parameters: skipCheckGenericClasses: [] illegalConstructorMethodCall: false stricterFunctionMap: false - pure: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 51133df7072..cbdba229a41 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -31,7 +31,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), illegalConstructorMethodCall: bool(), stricterFunctionMap: bool() - pure: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php index c50d21d8780..f214edf9602 100644 --- a/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRule.php @@ -19,7 +19,6 @@ final class CallToConstructorStatementWithoutSideEffectsRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, - private bool $reportNoConstructor, ) { } @@ -47,16 +46,12 @@ public function processNode(Node $node, Scope $scope): array $classReflection = $this->reflectionProvider->getClass($className); if (!$classReflection->hasConstructor()) { - if ($this->reportNoConstructor) { - return [ - RuleErrorBuilder::message(sprintf( - 'Call to new %s() on a separate line has no effect.', - $classReflection->getDisplayName(), - ))->identifier('new.resultUnused')->build(), - ]; - } - - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'Call to new %s() on a separate line has no effect.', + $classReflection->getDisplayName(), + ))->identifier('new.resultUnused')->build(), + ]; } $constructor = $classReflection->getConstructor(); diff --git a/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php index 29e99526e20..c7c0e8f89ab 100644 --- a/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToConstructorStatementWithoutSideEffectsRuleTest.php @@ -13,7 +13,7 @@ class CallToConstructorStatementWithoutSideEffectsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new CallToConstructorStatementWithoutSideEffectsRule($this->createReflectionProvider(), true); + return new CallToConstructorStatementWithoutSideEffectsRule($this->createReflectionProvider()); } public function testRule(): void From 2d623e494593277098db40a7a279e56f37bfdab4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0411/3097] Issue bot - let all comments about `@phpstan-pure` through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From a09cae7abbc25ec283f0279b1ab6484758f3ca15 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:23:00 +0200 Subject: [PATCH 0412/3097] Upgrading note --- UPGRADING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 2ac65c3d170..640d57af1fb 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,6 +12,8 @@ PHPStan now requires PHP 7.4 or newer to run. The best way do get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. +Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). + Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: ```json From 4393881c4a28f9d7118fdf2dfd6ab02533f71959 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:34:00 +0200 Subject: [PATCH 0413/3097] Update phpstan-strict-rules --- composer.lock | 8 ++++---- issue-bot/composer.lock | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 997e53f86f5..96460dab5ae 100644 --- a/composer.lock +++ b/composer.lock @@ -4831,12 +4831,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33" + "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ad53bd9f911e7831e8e02cd3e54faf1d44910c33", - "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/63956f7896780551ed1ab29e75a6a645d8a0919c", + "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c", "shasum": "" }, "require": { @@ -4872,7 +4872,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-24T12:25:28+00:00" + "time": "2024-09-24T15:32:27+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index 7479e1b1b15..bea3c5e3560 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -1466,12 +1466,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33" + "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ad53bd9f911e7831e8e02cd3e54faf1d44910c33", - "reference": "ad53bd9f911e7831e8e02cd3e54faf1d44910c33", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/63956f7896780551ed1ab29e75a6a645d8a0919c", + "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c", "shasum": "" }, "require": { @@ -1507,7 +1507,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-24T12:25:28+00:00" + "time": "2024-09-24T15:32:27+00:00" }, { "name": "psr/cache", From 8db7e92521bc944f75a9e850708066c2d425e729 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:33:29 +0200 Subject: [PATCH 0414/3097] Moved illegalConstructorMethodCall rules from phpstan to phpstan-strict-rules --- conf/config.level2.neon | 10 -- conf/config.neon | 1 - conf/parametersSchema.neon | 1 - .../IllegalConstructorMethodCallRule.php | 34 ------ .../IllegalConstructorStaticCallRule.php | 92 ---------------- .../IllegalConstructorMethodCallRuleTest.php | 37 ------- .../IllegalConstructorStaticCallRuleTest.php | 59 ---------- tests/PHPStan/Rules/Methods/data/bug-9577.php | 40 ------- .../illegal-constructor-call-rule-test.php | 103 ------------------ 9 files changed, 377 deletions(-) delete mode 100644 src/Rules/Methods/IllegalConstructorMethodCallRule.php delete mode 100644 src/Rules/Methods/IllegalConstructorStaticCallRule.php delete mode 100644 tests/PHPStan/Rules/Methods/IllegalConstructorMethodCallRuleTest.php delete mode 100644 tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php delete mode 100644 tests/PHPStan/Rules/Methods/data/bug-9577.php delete mode 100644 tests/PHPStan/Rules/Methods/data/illegal-constructor-call-rule-test.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index b3507a4a46b..2d547cb94e8 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -66,12 +66,6 @@ rules: - PHPStan\Rules\Pure\PureFunctionRule - PHPStan\Rules\Pure\PureMethodRule -conditionalTags: - PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: - phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% - PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: - phpstan.rules.rule: %featureToggles.illegalConstructorMethodCall% - services: - class: PHPStan\Rules\Classes\MixinRule @@ -97,10 +91,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule - - - class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule - class: PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule tags: diff --git a/conf/config.neon b/conf/config.neon index 9deb65b8924..46424fd4147 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -23,7 +23,6 @@ parameters: featureToggles: bleedingEdge: false skipCheckGenericClasses: [] - illegalConstructorMethodCall: false stricterFunctionMap: false fileExtensions: - php diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index cbdba229a41..7307fc85719 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -29,7 +29,6 @@ parametersSchema: featureToggles: structure([ bleedingEdge: bool(), skipCheckGenericClasses: listOf(string()), - illegalConstructorMethodCall: bool(), stricterFunctionMap: bool() ]) fileExtensions: listOf(string()) diff --git a/src/Rules/Methods/IllegalConstructorMethodCallRule.php b/src/Rules/Methods/IllegalConstructorMethodCallRule.php deleted file mode 100644 index 1dba6ed6d24..00000000000 --- a/src/Rules/Methods/IllegalConstructorMethodCallRule.php +++ /dev/null @@ -1,34 +0,0 @@ - - */ -final class IllegalConstructorMethodCallRule implements Rule -{ - - public function getNodeType(): string - { - return Node\Expr\MethodCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { - return []; - } - - return [ - RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') - ->identifier('constructor.call') - ->build(), - ]; - } - -} diff --git a/src/Rules/Methods/IllegalConstructorStaticCallRule.php b/src/Rules/Methods/IllegalConstructorStaticCallRule.php deleted file mode 100644 index fa747d6a2b5..00000000000 --- a/src/Rules/Methods/IllegalConstructorStaticCallRule.php +++ /dev/null @@ -1,92 +0,0 @@ - - */ -final class IllegalConstructorStaticCallRule implements Rule -{ - - public function getNodeType(): string - { - return Node\Expr\StaticCall::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { - return []; - } - - if ($this->isCollectCallingConstructor($node, $scope)) { - return []; - } - - return [ - RuleErrorBuilder::message('Static call to __construct() is only allowed on a parent class in the constructor.') - ->identifier('constructor.call') - ->build(), - ]; - } - - private function isCollectCallingConstructor(Node\Expr\StaticCall $node, Scope $scope): bool - { - // __construct should be called from inside constructor - if ($scope->getFunction() === null) { - return false; - } - - if ($scope->getFunction()->getName() !== '__construct') { - if (!$this->isInRenamedTraitConstructor($scope)) { - return false; - } - } - - if (!$scope->isInClass()) { - return false; - } - - if (!$node->class instanceof Node\Name) { - return false; - } - - $parentClasses = array_map(static fn (string $name) => strtolower($name), $scope->getClassReflection()->getParentClassesNames()); - - return in_array(strtolower($scope->resolveName($node->class)), $parentClasses, true); - } - - private function isInRenamedTraitConstructor(Scope $scope): bool - { - if (!$scope->isInClass()) { - return false; - } - - if (!$scope->isInTrait()) { - return false; - } - - if ($scope->getFunction() === null) { - return false; - } - - $traitAliases = $scope->getClassReflection()->getNativeReflection()->getTraitAliases(); - $functionName = $scope->getFunction()->getName(); - if (!array_key_exists($functionName, $traitAliases)) { - return false; - } - - return $traitAliases[$functionName] === sprintf('%s::%s', $scope->getTraitReflection()->getName(), '__construct'); - } - -} diff --git a/tests/PHPStan/Rules/Methods/IllegalConstructorMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/IllegalConstructorMethodCallRuleTest.php deleted file mode 100644 index 8b0f957e857..00000000000 --- a/tests/PHPStan/Rules/Methods/IllegalConstructorMethodCallRuleTest.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class IllegalConstructorMethodCallRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new IllegalConstructorMethodCallRule(); - } - - public function testMethods(): void - { - $this->analyse([__DIR__ . '/data/illegal-constructor-call-rule-test.php'], [ - [ - 'Call to __construct() on an existing object is not allowed.', - 13, - ], - [ - 'Call to __construct() on an existing object is not allowed.', - 18, - ], - [ - 'Call to __construct() on an existing object is not allowed.', - 60, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php b/tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php deleted file mode 100644 index 065a5785bf2..00000000000 --- a/tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - */ -class IllegalConstructorStaticCallRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new IllegalConstructorStaticCallRule(); - } - - public function testMethods(): void - { - $this->analyse([__DIR__ . '/data/illegal-constructor-call-rule-test.php'], [ - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 31, - ], - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 43, - ], - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 44, - ], - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 49, - ], - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 50, - ], - [ - 'Static call to __construct() is only allowed on a parent class in the constructor.', - 100, - ], - ]); - } - - public function testBug9577(): void - { - if (PHP_VERSION_ID < 80100) { - $this->markTestSkipped('Test requires PHP 8.1.'); - } - - $this->analyse([__DIR__ . '/data/bug-9577.php'], []); - } - -} diff --git a/tests/PHPStan/Rules/Methods/data/bug-9577.php b/tests/PHPStan/Rules/Methods/data/bug-9577.php deleted file mode 100644 index 2214d45b330..00000000000 --- a/tests/PHPStan/Rules/Methods/data/bug-9577.php +++ /dev/null @@ -1,40 +0,0 @@ -= 8.1 - -namespace Bug9577IllegalConstructorStaticCall; - -trait StringableMessageTrait -{ - public function __construct( - private readonly \Stringable $StringableMessage, - int $code = 0, - ?\Throwable $previous = null, - ) { - parent::__construct((string) $StringableMessage, $code, $previous); - } - - public function getStringableMessage(): \Stringable - { - return $this->StringableMessage; - } -} - -class SpecializedException extends \RuntimeException -{ - use StringableMessageTrait { - StringableMessageTrait::__construct as __traitConstruct; - } - - public function __construct( - private readonly object $aService, - \Stringable $StringableMessage, - int $code = 0, - ?\Throwable $previous = null, - ) { - $this->__traitConstruct($StringableMessage, $code, $previous); - } - - public function getService(): object - { - return $this->aService; - } -} diff --git a/tests/PHPStan/Rules/Methods/data/illegal-constructor-call-rule-test.php b/tests/PHPStan/Rules/Methods/data/illegal-constructor-call-rule-test.php deleted file mode 100644 index f5198a50897..00000000000 --- a/tests/PHPStan/Rules/Methods/data/illegal-constructor-call-rule-test.php +++ /dev/null @@ -1,103 +0,0 @@ - 1) { - return; - } - $this->__construct($datetime, $timezone); - } - - public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void - { - $this->__construct($datetime, $timezone); - } -} - -class ExtendedDateTimeWithParentCall extends \DateTimeImmutable -{ - public function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null) - { - parent::__construct($datetime, $timezone); - } - - public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void - { - parent::__construct($datetime, $timezone); - } -} - -class ExtendedDateTimeWithSelfCall extends \DateTimeImmutable -{ - public function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null) - { - // Avoid infinite loop - if (count(debug_backtrace()) > 1) { - return; - } - self::__construct($datetime, $timezone); - ExtendedDateTimeWithSelfCall::__construct($datetime, $timezone); - } - - public function mutate(string $datetime = "now", ?\DateTimeZone $timezone = null): void - { - self::__construct($datetime, $timezone); - ExtendedDateTimeWithSelfCall::__construct($datetime, $timezone); - } -} - -class Foo -{ - - public function doFoo() - { - $extendedDateTime = new ExtendedDateTimeWithMethodCall('2022/04/12'); - $extendedDateTime->__construct('2022/04/13'); - } - -} - -abstract class Presenter -{ - - public function __construct() - { - - } - -} - -abstract class BasePresenter extends Presenter -{ - - public function __construct() - { - Presenter::__construct(); - } - -} - -class CatPresenter extends BasePresenter -{ - - public function __construct() - { - Presenter::__construct(); - } - -} - -class DogPresenter extends BasePresenter -{ - - public function __construct() - { - CatPresenter::__construct(); - } - -} From 694bc09b94914887de6ad29f2be5269edffc788d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:44:05 +0200 Subject: [PATCH 0415/3097] Revert "Issue bot - let all comments about `@phpstan-pure` through" This reverts commit 2d623e494593277098db40a7a279e56f37bfdab4. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 51de9032c6e98bff2d6eb0e5b7295720ec0276b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 17:58:40 +0200 Subject: [PATCH 0416/3097] Cover AccessoryArrayListType constructor with BC promise --- src/Type/Accessory/AccessoryArrayListType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index a413c9bae1d..64131446d9a 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -39,6 +39,7 @@ class AccessoryArrayListType implements CompoundType, AccessoryType use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; + /** @api */ public function __construct() { } From f046ebcbc643bef7ede64b0134478b63229d405c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 18:10:49 +0200 Subject: [PATCH 0417/3097] Update PHPStan extensions --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 96460dab5ae..c17a33a1e1d 100644 --- a/composer.lock +++ b/composer.lock @@ -4718,12 +4718,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "93a4f025a4d11ffcf9523617cb3c620c5373fe56" + "reference": "c903386c4e3d1d25a57f66458476bfb6347f1c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/93a4f025a4d11ffcf9523617cb3c620c5373fe56", - "reference": "93a4f025a4d11ffcf9523617cb3c620c5373fe56", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/c903386c4e3d1d25a57f66458476bfb6347f1c66", + "reference": "c903386c4e3d1d25a57f66458476bfb6347f1c66", "shasum": "" }, "require": { @@ -4771,7 +4771,7 @@ "issues": "https://github.com/phpstan/phpstan-nette/issues", "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.x" }, - "time": "2024-09-04T21:08:28+00:00" + "time": "2024-09-24T16:09:34+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -4779,12 +4779,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "4d861e0843cd1f8eccacfac14e24a8629280a887" + "reference": "09e2d3b470bdda02824c626735153dfd962e3f29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/4d861e0843cd1f8eccacfac14e24a8629280a887", - "reference": "4d861e0843cd1f8eccacfac14e24a8629280a887", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/09e2d3b470bdda02824c626735153dfd962e3f29", + "reference": "09e2d3b470bdda02824c626735153dfd962e3f29", "shasum": "" }, "require": { @@ -4823,7 +4823,7 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.x" }, - "time": "2024-09-13T12:47:01+00:00" + "time": "2024-09-24T16:07:03+00:00" }, { "name": "phpstan/phpstan-strict-rules", From 74b8e9c963398ae5ced9c26ac5702bd7f84ca92a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 18:13:34 +0200 Subject: [PATCH 0418/3097] Update changelog --- changelog-2.0.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 34f9d49201a..7469ed558c0 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -74,8 +74,6 @@ Major new features 🚀 Bleeding edge (TODO move to other sections) ===================== -* Rules for checking direct calls to `__construct()` (level 2) ([#1208](https://github.com/phpstan/phpstan-src/pull/1208)), #7022, thanks @muno92! -* Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) @@ -128,6 +126,7 @@ Improvements 🔧 * Report unnecessary nullsafe property fetch inside `??` / `isset` / `empty` with different message ([#1253](https://github.com/phpstan/phpstan-src/pull/1253)), thanks @rajyan! * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) +* Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) Bugfixes 🐛 ===================== From ae6403f117d7318721fe6a89eb8cec24994e9ed9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 20:18:45 +0200 Subject: [PATCH 0419/3097] Fix build --- tests/PHPStan/Levels/data/stubs-functions-4.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/PHPStan/Levels/data/stubs-functions-4.json diff --git a/tests/PHPStan/Levels/data/stubs-functions-4.json b/tests/PHPStan/Levels/data/stubs-functions-4.json new file mode 100644 index 00000000000..dd57bdf13f6 --- /dev/null +++ b/tests/PHPStan/Levels/data/stubs-functions-4.json @@ -0,0 +1,12 @@ +[ + { + "message": "Call to function StubsIntegrationTest\\foo() on a separate line has no effect.", + "line": 11, + "ignorable": true + }, + { + "message": "Call to function StubsIntegrationTest\\foo() on a separate line has no effect.", + "line": 13, + "ignorable": true + } +] \ No newline at end of file From 20baec2efeffff1c1c65c60f8c62315ba3712967 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 20:19:06 +0200 Subject: [PATCH 0420/3097] Missing types should always be reported on level 6, not sooner --- conf/config.neon | 1 + src/Rules/Generics/GenericAncestorsCheck.php | 39 ++++++++++--------- .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 22 +++++------ .../PHPStan/Levels/LevelsIntegrationTest.php | 1 + tests/PHPStan/Levels/data/missingTypes-6.json | 7 ++++ tests/PHPStan/Levels/data/missingTypes.php | 16 ++++++++ .../Rules/Generics/ClassAncestorsRuleTest.php | 1 + .../Rules/Generics/EnumAncestorsRuleTest.php | 1 + .../Generics/InterfaceAncestorsRuleTest.php | 1 + .../Rules/Generics/UsedTraitsRuleTest.php | 1 + 10 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 tests/PHPStan/Levels/data/missingTypes-6.json create mode 100644 tests/PHPStan/Levels/data/missingTypes.php diff --git a/conf/config.neon b/conf/config.neon index 46424fd4147..05aab74b882 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -917,6 +917,7 @@ services: class: PHPStan\Rules\Generics\GenericAncestorsCheck arguments: skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% + checkMissingTypehints: %checkMissingTypehints% - class: PHPStan\Rules\Generics\GenericObjectTypeCheck diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index 8dc3830cadb..1cedcda8be3 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -34,6 +34,7 @@ public function __construct( private VarianceCheck $varianceCheck, private UnresolvableTypeHelper $unresolvableTypeHelper, private array $skipCheckGenericClasses, + private bool $checkMissingTypehints, ) { } @@ -151,26 +152,28 @@ public function check( } } - foreach (array_keys($unusedNames) as $unusedName) { - if (!$this->reflectionProvider->hasClass($unusedName)) { - continue; - } + if ($this->checkMissingTypehints) { + foreach (array_keys($unusedNames) as $unusedName) { + if (!$this->reflectionProvider->hasClass($unusedName)) { + continue; + } - $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); - if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { - continue; - } - if (!$unusedNameClassReflection->isGeneric()) { - continue; - } + $unusedNameClassReflection = $this->reflectionProvider->getClass($unusedName); + if (in_array($unusedNameClassReflection->getName(), $this->skipCheckGenericClasses, true)) { + continue; + } + if (!$unusedNameClassReflection->isGeneric()) { + continue; + } - $messages[] = RuleErrorBuilder::message(sprintf( - $genericClassInNonGenericObjectType, - $unusedName, - implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())), - )) - ->identifier('missingType.generics') - ->build(); + $messages[] = RuleErrorBuilder::message(sprintf( + $genericClassInNonGenericObjectType, + $unusedName, + implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())), + )) + ->identifier('missingType.generics') + ->build(); + } } return $messages; diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 81b4296100d..204da1481a0 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -97,6 +97,17 @@ public function processNode(Node $node, Scope $scope): array ->identifier('missingType.iterableValue') ->build(); } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($varTagType) as [$innerName, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s contains generic %s but does not specify its types: %s', + $identifier, + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } } $escapedIdentifier = SprintfHelper::escapeFormatString($identifier); @@ -110,17 +121,6 @@ public function processNode(Node $node, Scope $scope): array sprintf('Call-site variance of %%s in generic type %%s in %s is redundant, template type %%s of %%s %%s has the same variance.', $escapedIdentifier), )); - foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($varTagType) as [$innerName, $genericTypeNames]) { - $errors[] = RuleErrorBuilder::message(sprintf( - '%s contains generic %s but does not specify its types: %s', - $identifier, - $innerName, - implode(', ', $genericTypeNames), - )) - ->identifier('missingType.generics') - ->build(); - } - $referencedClasses = $varTagType->getReferencedClasses(); foreach ($referencedClasses as $referencedClass) { if ($this->reflectionProvider->hasClass($referencedClass)) { diff --git a/tests/PHPStan/Levels/LevelsIntegrationTest.php b/tests/PHPStan/Levels/LevelsIntegrationTest.php index 45f5d7634e8..ee86187c8ce 100644 --- a/tests/PHPStan/Levels/LevelsIntegrationTest.php +++ b/tests/PHPStan/Levels/LevelsIntegrationTest.php @@ -41,6 +41,7 @@ public function dataTopics(): array ['coalesce'], ['arrayDestructuring'], ['listType'], + ['missingTypes'], ]; if (PHP_VERSION_ID >= 80300) { $topics[] = ['constantAccesses83']; diff --git a/tests/PHPStan/Levels/data/missingTypes-6.json b/tests/PHPStan/Levels/data/missingTypes-6.json new file mode 100644 index 00000000000..66b17f11112 --- /dev/null +++ b/tests/PHPStan/Levels/data/missingTypes-6.json @@ -0,0 +1,7 @@ +[ + { + "message": "Class MissingTypesLevels\\Foo extends generic class MissingTypesLevels\\Generic but does not specify its types: T", + "line": 13, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/missingTypes.php b/tests/PHPStan/Levels/data/missingTypes.php new file mode 100644 index 00000000000..ed3d9bd3bf9 --- /dev/null +++ b/tests/PHPStan/Levels/data/missingTypes.php @@ -0,0 +1,16 @@ + Date: Tue, 24 Sep 2024 20:33:40 +0200 Subject: [PATCH 0421/3097] Remove deprecated EmptyArrayItemRule --- conf/config.level0.neon | 1 - src/Rules/Arrays/EmptyArrayItemRule.php | 42 ------------------- .../Rules/Arrays/EmptyArrayItemRuleTest.php | 29 ------------- .../Rules/Arrays/data/empty-array-item.php | 7 ---- 4 files changed, 79 deletions(-) delete mode 100644 src/Rules/Arrays/EmptyArrayItemRule.php delete mode 100644 tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php delete mode 100644 tests/PHPStan/Rules/Arrays/data/empty-array-item.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 6dd5e4c7ca1..d8b1b775cc5 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -22,7 +22,6 @@ rules: - PHPStan\Rules\Api\RuntimeReflectionInstantiationRule - PHPStan\Rules\Api\RuntimeReflectionFunctionRule - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule - - PHPStan\Rules\Arrays\EmptyArrayItemRule - PHPStan\Rules\Arrays\OffsetAccessWithoutDimForReadingRule - PHPStan\Rules\Cast\UnsetCastRule - PHPStan\Rules\Classes\AllowedSubTypesRule diff --git a/src/Rules/Arrays/EmptyArrayItemRule.php b/src/Rules/Arrays/EmptyArrayItemRule.php deleted file mode 100644 index dfe2a48d4ba..00000000000 --- a/src/Rules/Arrays/EmptyArrayItemRule.php +++ /dev/null @@ -1,42 +0,0 @@ - - */ -final class EmptyArrayItemRule implements Rule -{ - - public function getNodeType(): string - { - return LiteralArrayNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - foreach ($node->getItemNodes() as $itemNode) { - $item = $itemNode->getArrayItem(); - if ($item !== null) { - continue; - } - - return [ - RuleErrorBuilder::message('Literal array contains empty item.') - ->nonIgnorable() - ->identifier('array.emptyItem') - ->build(), - ]; - } - - return []; - } - -} diff --git a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php b/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php deleted file mode 100644 index df14e334934..00000000000 --- a/tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ -class EmptyArrayItemRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new EmptyArrayItemRule(); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/empty-array-item.php'], [ - [ - 'Cannot use empty array elements in arrays on line 5', - 5, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Arrays/data/empty-array-item.php b/tests/PHPStan/Rules/Arrays/data/empty-array-item.php deleted file mode 100644 index 4a08a799a8d..00000000000 --- a/tests/PHPStan/Rules/Arrays/data/empty-array-item.php +++ /dev/null @@ -1,7 +0,0 @@ - Date: Tue, 24 Sep 2024 20:38:10 +0200 Subject: [PATCH 0422/3097] Update changelog --- changelog-2.0.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 7469ed558c0..d9fe72d6c90 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -9,6 +9,7 @@ Major new features 🚀 * Lists are arrays with sequential integer keys starting at 0 * **Validate inline PHPDoc `@var` tag** type against native type (level 2) (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones + * Use config option `reportAnyTypeWideningInVarTag: true` for stricter behaviour ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! * **Lower memory consumption** thanks to breaking up of reference cycles * [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) * In testing the memory consumption was reduced by 50–70 %. @@ -70,20 +71,8 @@ Major new features 🚀 * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) * Check that PHPStan class in class constant fetch is covered by backward compatibility promise (level 0) (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) - -Bleeding edge (TODO move to other sections) -===================== - * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 -* Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) -* InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* Report unused results of `and` and `or` (https://github.com/phpstan/phpstan-src/commit/1d8fff637d70a9e9ed3f11dee5d61b9f796cbf1a) -* Report unused result of ternary (https://github.com/phpstan/phpstan-src/commit/9664f7a9d2223c07e750f0dfc949c3accfa6b65e) -* Report unused results of `&&` and `||` (https://github.com/phpstan/phpstan-src/commit/cf2c8bbd9ebd2ebe300dbd310e136ad603d7def3) -* Add option `reportAnyTypeWideningInVarTag` ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! -* Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! -* CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) -* Check `@param-immediately-invoked-callable` and `@param-later-invoked-callable` (https://github.com/phpstan/phpstan-src/commit/580a6add422f4e34191df9e7a77ba1655e914bda), #10932 + Improvements 🔧 ===================== @@ -127,11 +116,17 @@ Improvements 🔧 * Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! * TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) * Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) +* Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) +* InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) +* CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) + Bugfixes 🐛 ===================== * Fix invariance composition ([#2054](https://github.com/phpstan/phpstan-src/pull/2054)), thanks @jiripudil! +* Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! + Function signature fixes 🤖 ======================= From 673a090f145784c08ca6136b46f78c0872d9ddf6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 20:39:25 +0200 Subject: [PATCH 0423/3097] Fix typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 640d57af1fb..a6b527bc544 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -118,7 +118,7 @@ Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Th See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. ### Returning plain strings as errors no longer supported, use RuleErrorBuilder - * + Identifiers are also required in custom rules. Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) From 6cefca5ca5f88bc02f82845de9b700ad52ec37f5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Sep 2024 20:51:05 +0200 Subject: [PATCH 0424/3097] Fix build --- phpstan-baseline.neon | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 287fdbae03d..dbc99984e0d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1749,22 +1749,6 @@ parameters: count: 1 path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php - - - message: """ - #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRule\\: - Since PHP\\-Parser 5\\.0 this is a parse error\\.$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php - - - - message: """ - #^Return type of method PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Arrays\\\\EmptyArrayItemRule\\: - Since PHP\\-Parser 5\\.0 this is a parse error\\.$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/EmptyArrayItemRuleTest.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" count: 1 From 4f7801a5c498c5a7af2c1c5b6f7659682893b6d8 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:05:17 +0000 Subject: [PATCH 0425/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index deb73479df8..3b27d117510 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.108", + "phpstan/php-8-stubs": "0.3.109", "phpstan/phpdoc-parser": "1.31.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index cb42d7d1e42..12c507d0e87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5763f95f6bf96a666d820e5e34d8e56", + "content-hash": "4de0fc2f1dd1531160dc506a885ca84d", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.108", + "version": "0.3.109", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa" + "reference": "66ae33a8ed0b88cbda063d82c1c9fdffa36b687d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/59ee6d5256a0bb43debf7d131441edf598b155fa", - "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/66ae33a8ed0b88cbda063d82c1c9fdffa36b687d", + "reference": "66ae33a8ed0b88cbda063d82c1c9fdffa36b687d", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.108" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.109" }, - "time": "2024-09-23T00:19:30+00:00" + "time": "2024-09-24T19:04:44+00:00" }, { "name": "phpstan/phpdoc-parser", From a4773235aa4a6b0ed4b9c703539ff4f8af3583d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Sep 2024 10:44:19 +0200 Subject: [PATCH 0426/3097] Fix error message on level < 7 --- phpstan-baseline.neon | 5 ---- src/Rules/RuleLevelHelper.php | 27 ++++++++++++------- .../Properties/AccessPropertiesRuleTest.php | 5 ++++ .../Properties/data/access-properties.php | 17 ++++++++++++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dbc99984e0d..9131efff54b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -601,11 +601,6 @@ parameters: count: 1 path: src/Rules/RuleLevelHelper.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" - count: 1 - path: src/Rules/RuleLevelHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 9869c16c62e..1fe931aca52 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -14,7 +14,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; -use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -241,7 +240,7 @@ private function findTypeToCheckImplementation( return new FoundTypeResult(new ErrorType(), [], $errors, null); } - if (!$this->checkUnionTypes && $type instanceof ObjectWithoutClassType) { + if (!$this->checkUnionTypes && $type->isObject()->yes() && count($type->getObjectClassNames()) === 0) { return new FoundTypeResult(new ErrorType(), [], [], null); } @@ -286,17 +285,25 @@ private function findTypeToCheckImplementation( if ($type instanceof IntersectionType) { $newTypes = []; + $changed = false; foreach ($type->getTypes() as $innerType) { - $newTypes[] = $this->findTypeToCheckImplementation( - $scope, - $var, - $innerType, - $unknownClassErrorPattern, - $unionTypeCriteriaCallback, - )->getType(); + if ($innerType instanceof TemplateMixedType) { + $changed = true; + $newTypes[] = $this->findTypeToCheckImplementation( + $scope, + $var, + $innerType->toStrictMixedType(), + $unknownClassErrorPattern, + $unionTypeCriteriaCallback, + )->getType(); + continue; + } + $newTypes[] = $innerType; } - return new FoundTypeResult(TypeCombinator::intersect(...$newTypes), $directClassNames, [], null); + if ($changed) { + return new FoundTypeResult(TypeCombinator::intersect(...$newTypes), $directClassNames, [], null); + } } $tip = null; diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 7e5d97a44b0..164aefaafed 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -169,6 +169,11 @@ public function testAccessProperties(): void 'Cannot access property $selfOrNull on TestAccessProperties\RevertNonNullabilityForIsset|null.', 407, ], + [ + 'Access to an undefined property object::$baz.', + 438, + $tipText, + ], ], ); } diff --git a/tests/PHPStan/Rules/Properties/data/access-properties.php b/tests/PHPStan/Rules/Properties/data/access-properties.php index f83bc6ef855..f755c42eac1 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties.php @@ -423,3 +423,20 @@ function mustNotReport(?\stdClass $nullable): bool } } + +class OnObjectAfterIsset +{ + + /** + * @param mixed $m + */ + public function doFoo($m): void + { + if (isset($m->foo) && isset($m->bar)) { + echo $m->foo; + echo $m->bar; + echo $m->baz; + } + } + +} From de7481355c675d9685e57907c413d357acd0cfa8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 25 Sep 2024 10:10:32 +0200 Subject: [PATCH 0427/3097] Refactor RegexGroupParser for more immutability and less pass-by-ref --- src/Type/Regex/RegexGroupParser.php | 89 ++++++++--------- src/Type/Regex/RegexGroupWalkResult.php | 121 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 52 deletions(-) create mode 100644 src/Type/Regex/RegexGroupWalkResult.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index b4000d6ada0..9780b2c69a9 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -304,35 +304,25 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p return TypeCombinator::union(...$types); } - $isNonEmpty = TrinaryLogic::createMaybe(); - $isNonFalsy = TrinaryLogic::createMaybe(); - $isNumeric = TrinaryLogic::createMaybe(); - $inOptionalQuantification = false; - $onlyLiterals = []; - - $this->walkGroupAst( + $walkResult = $this->walkGroupAst( $group, false, - $isNonEmpty, - $isNonFalsy, - $isNumeric, - $inOptionalQuantification, - $onlyLiterals, false, $patternModifiers, + RegexGroupWalkResult::createEmpty(), ); - if ($maybeConstant && $onlyLiterals !== null && $onlyLiterals !== []) { + if ($maybeConstant && $walkResult->getOnlyLiterals() !== null && $walkResult->getOnlyLiterals() !== []) { $result = []; - foreach ($onlyLiterals as $literal) { + foreach ($walkResult->getOnlyLiterals() as $literal) { $result[] = new ConstantStringType($literal); } return TypeCombinator::union(...$result); } - if ($isNumeric->yes()) { - if ($isNonFalsy->yes()) { + if ($walkResult->isNumeric()->yes()) { + if ($walkResult->isNonFalsy()->yes()) { return new IntersectionType([ new StringType(), new AccessoryNumericStringType(), @@ -341,13 +331,13 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p } $result = new IntersectionType([new StringType(), new AccessoryNumericStringType()]); - if (!$isNonEmpty->yes()) { + if (!$walkResult->isNonEmpty()->yes()) { return TypeCombinator::union(new ConstantStringType(''), $result); } return $result; - } elseif ($isNonFalsy->yes()) { + } elseif ($walkResult->isNonFalsy()->yes()) { return new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]); - } elseif ($isNonEmpty->yes()) { + } elseif ($walkResult->isNonEmpty()->yes()) { return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]); } @@ -376,20 +366,13 @@ private function getRootAlternation(TreeNode $group): ?TreeNode return null; } - /** - * @param array|null $onlyLiterals - */ private function walkGroupAst( TreeNode $ast, bool $inAlternation, - TrinaryLogic &$isNonEmpty, - TrinaryLogic &$isNonFalsy, - TrinaryLogic &$isNumeric, - bool &$inOptionalQuantification, - ?array &$onlyLiterals, bool $inClass, string $patternModifiers, - ): void + RegexGroupWalkResult $walkResult, + ): RegexGroupWalkResult { $children = $ast->getChildren(); @@ -411,61 +394,65 @@ private function walkGroupAst( } // a single token non-falsy on its own - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); break; } if ($meaningfulTokens > 0) { - $isNonEmpty = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes()); // two non-empty tokens concatenated results in a non-falsy string if ($meaningfulTokens > 1 && !$inAlternation) { - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); } } } elseif ($ast->getId() === '#quantification') { [$min] = $this->getQuantificationRange($ast); if ($min === 0) { - $inOptionalQuantification = true; + $walkResult = $walkResult->inOptionalQuantification(true); } if ($min >= 1) { - $isNonEmpty = TrinaryLogic::createYes(); - $inOptionalQuantification = false; + $walkResult = $walkResult + ->nonEmpty(TrinaryLogic::createYes()) + ->inOptionalQuantification(false); } if ($min >= 2 && !$inAlternation) { - $isNonFalsy = TrinaryLogic::createYes(); + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); } - $onlyLiterals = null; - } elseif ($ast->getId() === '#class' && $onlyLiterals !== null) { + $walkResult = $walkResult->onlyLiterals(null); + } elseif ($ast->getId() === '#class' && $walkResult->getOnlyLiterals() !== null) { $inClass = true; $newLiterals = []; foreach ($children as $child) { - $oldLiterals = $onlyLiterals; + $oldLiterals = $walkResult->getOnlyLiterals(); $this->getLiteralValue($child, $oldLiterals, true, $patternModifiers, true); foreach ($oldLiterals ?? [] as $oldLiteral) { $newLiterals[] = $oldLiteral; } } - $onlyLiterals = $newLiterals; + $walkResult = $walkResult->onlyLiterals($newLiterals); } elseif ($ast->getId() === 'token') { + $onlyLiterals = $walkResult->getOnlyLiterals(); $literalValue = $this->getLiteralValue($ast, $onlyLiterals, !$inClass, $patternModifiers, false); + $walkResult = $walkResult->onlyLiterals($onlyLiterals); + if ($literalValue !== null) { if (Strings::match($literalValue, '/^\d+$/') === null) { - $isNumeric = TrinaryLogic::createNo(); - } elseif ($isNumeric->maybe()) { - $isNumeric = TrinaryLogic::createYes(); + $walkResult = $walkResult->numeric(TrinaryLogic::createNo()); + } elseif ($walkResult->isNumeric()->maybe()) { + $walkResult = $walkResult->numeric(TrinaryLogic::createYes()); } - if (!$inOptionalQuantification && $literalValue !== '') { - $isNonEmpty = TrinaryLogic::createYes(); + if (!$walkResult->isInOptionalQuantification() && $literalValue !== '') { + $walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes()); } } } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing', '#alternation'], true)) { - $onlyLiterals = null; + $walkResult = $walkResult->onlyLiterals(null); } if ($ast->getId() === '#alternation') { @@ -476,22 +463,20 @@ private function walkGroupAst( // doable but really silly compared to just \d so we can safely assume the string is not numeric // for negative classes if ($ast->getId() === '#negativeclass') { - $isNumeric = TrinaryLogic::createNo(); + $walkResult = $walkResult->numeric(TrinaryLogic::createNo()); } foreach ($children as $child) { - $this->walkGroupAst( + $walkResult = $this->walkGroupAst( $child, $inAlternation, - $isNonEmpty, - $isNonFalsy, - $isNumeric, - $inOptionalQuantification, - $onlyLiterals, $inClass, $patternModifiers, + $walkResult, ); } + + return $walkResult; } private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool diff --git a/src/Type/Regex/RegexGroupWalkResult.php b/src/Type/Regex/RegexGroupWalkResult.php new file mode 100644 index 00000000000..65e7fd16916 --- /dev/null +++ b/src/Type/Regex/RegexGroupWalkResult.php @@ -0,0 +1,121 @@ +|null $onlyLiterals + */ + public function __construct( + private bool $inOptionalQuantification, + private ?array $onlyLiterals, + private TrinaryLogic $isNonEmpty, + private TrinaryLogic $isNonFalsy, + private TrinaryLogic $isNumeric, + ) + { + } + + public static function createEmpty(): self + { + return new self( + false, + [], + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + TrinaryLogic::createMaybe(), + ); + } + + public function inOptionalQuantification(bool $inOptionalQuantification): self + { + return new self( + $inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + /** + * @param array|null $onlyLiterals + */ + public function onlyLiterals(?array $onlyLiterals): self + { + return new self( + $this->inOptionalQuantification, + $onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + public function nonEmpty(TrinaryLogic $nonEmpty): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $nonEmpty, + $this->isNonFalsy, + $this->isNumeric, + ); + } + + public function nonFalsy(TrinaryLogic $nonFalsy): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $nonFalsy, + $this->isNumeric, + ); + } + + public function numeric(TrinaryLogic $numeric): self + { + return new self( + $this->inOptionalQuantification, + $this->onlyLiterals, + $this->isNonEmpty, + $this->isNonFalsy, + $numeric, + ); + } + + public function isInOptionalQuantification(): bool + { + return $this->inOptionalQuantification; + } + + /** + * @return array|null + */ + public function getOnlyLiterals(): ?array + { + return $this->onlyLiterals; + } + + public function isNonEmpty(): TrinaryLogic + { + return $this->isNonEmpty; + } + + public function isNonFalsy(): TrinaryLogic + { + return $this->isNonFalsy; + } + + public function isNumeric(): TrinaryLogic + { + return $this->isNumeric; + } + +} From c3cad7d4814ed4fe3bd35e521988ee4ab6b782a7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Sep 2024 12:55:31 +0200 Subject: [PATCH 0428/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/5297 --- .../Levels/data/propertyAccesses-7.json | 5 ++++ .../Levels/data/propertyAccesses-9.json | 7 +++++ .../PHPStan/Levels/data/propertyAccesses.php | 28 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/PHPStan/Levels/data/propertyAccesses-9.json diff --git a/tests/PHPStan/Levels/data/propertyAccesses-7.json b/tests/PHPStan/Levels/data/propertyAccesses-7.json index aa6291fdfe6..b6df87becb3 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-7.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-7.json @@ -38,5 +38,10 @@ "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$bar.", "line": 170, "ignorable": true + }, + { + "message": "Access to an undefined property object::$baz.", + "line": 200, + "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses-9.json b/tests/PHPStan/Levels/data/propertyAccesses-9.json new file mode 100644 index 00000000000..c7c6ae5a96a --- /dev/null +++ b/tests/PHPStan/Levels/data/propertyAccesses-9.json @@ -0,0 +1,7 @@ +[ + { + "message": "Cannot access property $foo on mixed.", + "line": 197, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses.php b/tests/PHPStan/Levels/data/propertyAccesses.php index 72c9d4bcda5..1c006ab6dce 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses.php +++ b/tests/PHPStan/Levels/data/propertyAccesses.php @@ -174,3 +174,31 @@ public function doBaz() } } + +class ObjectWithIsset +{ + + public function doFoo(): void + { + $test = new \stdClass; + + if (isset($test->foo)) { + echo $test->foo; + echo $test->bar; + echo $test->baz; + } + } + + /** + * @param mixed $test + */ + public function doBar($test): void + { + if (isset($test->foo) && isset($test->bar)) { + echo $test->foo; + echo $test->bar; + echo $test->baz; + } + } + +} From ac91552a25ce83ef6de63b9f59296d4c5ee568a9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 25 Sep 2024 17:50:35 +0200 Subject: [PATCH 0429/3097] Add PhpVersion parameter to various Type methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- UPGRADING.md | 2 ++ src/Analyser/MutatingScope.php | 8 ++--- src/Analyser/TypeSpecifier.php | 10 +++--- src/Analyser/TypeSpecifierFactory.php | 2 ++ .../InitializerExprTypeResolver.php | 8 ++--- .../Functions/RandomIntParametersRule.php | 9 +++-- src/Type/CompoundType.php | 5 +-- src/Type/Constant/ConstantBooleanType.php | 8 ++--- src/Type/Constant/ConstantStringType.php | 9 ++--- src/Type/Enum/EnumCaseObjectType.php | 5 +-- src/Type/IntegerRangeType.php | 34 +++++++++--------- src/Type/IntersectionType.php | 32 ++++++++--------- src/Type/NullType.php | 16 ++++----- .../ConstantNumericComparisonTypeTrait.php | 9 ++--- src/Type/Traits/ConstantScalarTypeTrait.php | 8 ++--- src/Type/Traits/LateResolvableTypeTrait.php | 36 +++++++++---------- .../UndecidedComparisonCompoundTypeTrait.php | 5 +-- .../Traits/UndecidedComparisonTypeTrait.php | 13 +++---- src/Type/Type.php | 12 +++---- src/Type/UnionType.php | 32 ++++++++--------- .../Rules/Api/ApiClassImplementsRuleTest.php | 12 +++---- .../data/class-implements-out-of-phpstan.php | 13 +++---- .../Functions/RandomIntParametersRuleTest.php | 3 +- 23 files changed, 155 insertions(+), 136 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index a6b527bc544..14b029acc9f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -178,3 +178,5 @@ As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtensio * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) +* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0e2ead8dde6..2c9ef02f75e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -747,19 +747,19 @@ private function resolveType(string $exprString, Expr $node): Type } if ($node instanceof Expr\BinaryOp\Smaller) { - return $this->getType($node->left)->isSmallerThan($this->getType($node->right))->toBooleanType(); + return $this->getType($node->left)->isSmallerThan($this->getType($node->right), $this->phpVersion)->toBooleanType(); } if ($node instanceof Expr\BinaryOp\SmallerOrEqual) { - return $this->getType($node->left)->isSmallerThanOrEqual($this->getType($node->right))->toBooleanType(); + return $this->getType($node->left)->isSmallerThanOrEqual($this->getType($node->right), $this->phpVersion)->toBooleanType(); } if ($node instanceof Expr\BinaryOp\Greater) { - return $this->getType($node->right)->isSmallerThan($this->getType($node->left))->toBooleanType(); + return $this->getType($node->right)->isSmallerThan($this->getType($node->left), $this->phpVersion)->toBooleanType(); } if ($node instanceof Expr\BinaryOp\GreaterOrEqual) { - return $this->getType($node->right)->isSmallerThanOrEqual($this->getType($node->left))->toBooleanType(); + return $this->getType($node->right)->isSmallerThanOrEqual($this->getType($node->left), $this->phpVersion)->toBooleanType(); } if ($node instanceof Expr\BinaryOp\Equal) { diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 8c40e83826c..9484fc52a70 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -22,6 +22,7 @@ use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Node\IssetExpr; use PHPStan\Node\Printer\ExprPrinter; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -100,6 +101,7 @@ final class TypeSpecifier public function __construct( private ExprPrinter $exprPrinter, private ReflectionProvider $reflectionProvider, + private PhpVersion $phpVersion, private array $functionTypeSpecifyingExtensions, private array $methodTypeSpecifyingExtensions, private array $staticMethodTypeSpecifyingExtensions, @@ -406,7 +408,7 @@ public function specifyTypesInCondition( $result = $result->unionWith( $this->create( $expr->left, - $orEqual ? $rightType->getSmallerOrEqualType() : $rightType->getSmallerType(), + $orEqual ? $rightType->getSmallerOrEqualType($this->phpVersion) : $rightType->getSmallerType($this->phpVersion), TypeSpecifierContext::createTruthy(), $scope, )->setRootExpr($expr), @@ -416,7 +418,7 @@ public function specifyTypesInCondition( $result = $result->unionWith( $this->create( $expr->right, - $orEqual ? $leftType->getGreaterOrEqualType() : $leftType->getGreaterType(), + $orEqual ? $leftType->getGreaterOrEqualType($this->phpVersion) : $leftType->getGreaterType($this->phpVersion), TypeSpecifierContext::createTruthy(), $scope, )->setRootExpr($expr), @@ -427,7 +429,7 @@ public function specifyTypesInCondition( $result = $result->unionWith( $this->create( $expr->left, - $orEqual ? $rightType->getGreaterType() : $rightType->getGreaterOrEqualType(), + $orEqual ? $rightType->getGreaterType($this->phpVersion) : $rightType->getGreaterOrEqualType($this->phpVersion), TypeSpecifierContext::createTruthy(), $scope, )->setRootExpr($expr), @@ -437,7 +439,7 @@ public function specifyTypesInCondition( $result = $result->unionWith( $this->create( $expr->right, - $orEqual ? $leftType->getSmallerType() : $leftType->getSmallerOrEqualType(), + $orEqual ? $leftType->getSmallerType($this->phpVersion) : $leftType->getSmallerOrEqualType($this->phpVersion), TypeSpecifierContext::createTruthy(), $scope, )->setRootExpr($expr), diff --git a/src/Analyser/TypeSpecifierFactory.php b/src/Analyser/TypeSpecifierFactory.php index 60219c3b6dc..83315b6e0ce 100644 --- a/src/Analyser/TypeSpecifierFactory.php +++ b/src/Analyser/TypeSpecifierFactory.php @@ -5,6 +5,7 @@ use PHPStan\Broker\BrokerFactory; use PHPStan\DependencyInjection\Container; use PHPStan\Node\Printer\ExprPrinter; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use function array_merge; @@ -24,6 +25,7 @@ public function create(): TypeSpecifier $typeSpecifier = new TypeSpecifier( $this->container->getByType(ExprPrinter::class), $this->container->getByType(ReflectionProvider::class), + $this->container->getByType(PhpVersion::class), $this->container->getServicesByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), $this->container->getServicesByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG), $this->container->getServicesByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG), diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index b4beb587bfa..fdb3344d693 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -316,19 +316,19 @@ public function getType(Expr $expr, InitializerExprContext $context): Type } if ($expr instanceof Expr\BinaryOp\Smaller) { - return $this->getType($expr->left, $context)->isSmallerThan($this->getType($expr->right, $context))->toBooleanType(); + return $this->getType($expr->left, $context)->isSmallerThan($this->getType($expr->right, $context), $this->phpVersion)->toBooleanType(); } if ($expr instanceof Expr\BinaryOp\SmallerOrEqual) { - return $this->getType($expr->left, $context)->isSmallerThanOrEqual($this->getType($expr->right, $context))->toBooleanType(); + return $this->getType($expr->left, $context)->isSmallerThanOrEqual($this->getType($expr->right, $context), $this->phpVersion)->toBooleanType(); } if ($expr instanceof Expr\BinaryOp\Greater) { - return $this->getType($expr->right, $context)->isSmallerThan($this->getType($expr->left, $context))->toBooleanType(); + return $this->getType($expr->right, $context)->isSmallerThan($this->getType($expr->left, $context), $this->phpVersion)->toBooleanType(); } if ($expr instanceof Expr\BinaryOp\GreaterOrEqual) { - return $this->getType($expr->right, $context)->isSmallerThanOrEqual($this->getType($expr->left, $context))->toBooleanType(); + return $this->getType($expr->right, $context)->isSmallerThanOrEqual($this->getType($expr->left, $context), $this->phpVersion)->toBooleanType(); } if ($expr instanceof Expr\BinaryOp\LogicalXor) { diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php index ecca6e7d091..e35c21a3edc 100644 --- a/src/Rules/Functions/RandomIntParametersRule.php +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -21,7 +22,11 @@ final class RandomIntParametersRule implements Rule { - public function __construct(private ReflectionProvider $reflectionProvider, private bool $reportMaybes) + public function __construct( + private ReflectionProvider $reflectionProvider, + private PhpVersion $phpVersion, + private bool $reportMaybes, + ) { } @@ -55,7 +60,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $isSmaller = $maxType->isSmallerThan($minType); + $isSmaller = $maxType->isSmallerThan($minType, $this->phpVersion); if ($isSmaller->yes() || $isSmaller->maybe() && $this->reportMaybes) { $message = 'Parameter #1 $min (%s) of function random_int expects lower number than parameter #2 $max (%s).'; diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index ea9b2f4660b..775a4eb50f8 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; /** @api */ @@ -14,8 +15,8 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult; - public function isGreaterThan(Type $otherType): TrinaryLogic; + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic; + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; } diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index db5328d0d26..cbebe5b48ba 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -40,7 +40,7 @@ public function describe(VerbosityLevel $level): string return $this->value ? 'true' : 'false'; } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { if ($this->value) { return StaticTypeFactory::falsey(); @@ -48,7 +48,7 @@ public function getSmallerType(): Type return new NeverType(); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { if ($this->value) { return new MixedType(); @@ -56,7 +56,7 @@ public function getSmallerOrEqualType(): Type return StaticTypeFactory::falsey(); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { if ($this->value) { return new NeverType(); @@ -64,7 +64,7 @@ public function getGreaterType(): Type return StaticTypeFactory::truthy(); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { if ($this->value) { return StaticTypeFactory::truthy(); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 39b259ee372..09ddd2741ec 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PhpParser\Node\Name; use PHPStan\Analyser\OutOfClassScope; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; @@ -461,7 +462,7 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new ConstantBooleanType(true), @@ -480,7 +481,7 @@ public function getSmallerType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = [ IntegerRangeType::createAllGreaterThan((float) $this->value), @@ -493,7 +494,7 @@ public function getSmallerOrEqualType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new ConstantBooleanType(false), @@ -507,7 +508,7 @@ public function getGreaterType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = [ IntegerRangeType::createAllSmallerThan((float) $this->value), diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 42d8f4afc22..e0bc8c33404 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Enum; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; @@ -179,12 +180,12 @@ public function generalize(GeneralizePrecision $precision): Type return new parent($this->getClassName(), null, $this->getClassReflection()); } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createNo(); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 534be0ebbbf..d5680a59ac4 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -308,75 +308,75 @@ public function generalize(GeneralizePrecision $precision): Type return new IntegerType(); } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($this->min === null) { $minIsSmaller = TrinaryLogic::createYes(); } else { - $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType); + $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThan($otherType, $phpVersion); } if ($this->max === null) { $maxIsSmaller = TrinaryLogic::createNo(); } else { - $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType); + $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType, $phpVersion); } return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($this->min === null) { $minIsSmaller = TrinaryLogic::createYes(); } else { - $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType); + $minIsSmaller = (new ConstantIntegerType($this->min))->isSmallerThanOrEqual($otherType, $phpVersion); } if ($this->max === null) { $maxIsSmaller = TrinaryLogic::createNo(); } else { - $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType); + $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType, $phpVersion); } return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } - public function isGreaterThan(Type $otherType): TrinaryLogic + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($this->min === null) { $minIsSmaller = TrinaryLogic::createNo(); } else { - $minIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->min))); + $minIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->min)), $phpVersion); } if ($this->max === null) { $maxIsSmaller = TrinaryLogic::createYes(); } else { - $maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max))); + $maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max)), $phpVersion); } return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($this->min === null) { $minIsSmaller = TrinaryLogic::createNo(); } else { - $minIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->min))); + $minIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->min)), $phpVersion); } if ($this->max === null) { $maxIsSmaller = TrinaryLogic::createYes(); } else { - $maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max))); + $maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max)), $phpVersion); } return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new ConstantBooleanType(true), @@ -389,7 +389,7 @@ public function getSmallerType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = []; @@ -400,7 +400,7 @@ public function getSmallerOrEqualType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new NullType(), @@ -418,7 +418,7 @@ public function getGreaterType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = []; @@ -692,7 +692,7 @@ public function toPhpDocNode(): TypeNode public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - if ($this->isSmallerThan($type)->yes() || $this->isGreaterThan($type)->yes()) { + if ($this->isSmallerThan($type, $phpVersion)->yes() || $this->isGreaterThan($type, $phpVersion)->yes()) { return new ConstantBooleanType(false); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 8bf38bdddaa..13704c9a5da 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -802,14 +802,14 @@ public function isCloneable(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isCloneable()); } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType)); + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType, $phpVersion)); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType)); + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType, $phpVersion)); } public function isNull(): TrinaryLogic @@ -876,34 +876,34 @@ public function isInteger(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isInteger()); } - public function isGreaterThan(Type $otherType): TrinaryLogic + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type)); + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type, $phpVersion)); } - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type)); + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type, $phpVersion)); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getSmallerType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getSmallerType($phpVersion)); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType($phpVersion)); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getGreaterType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getGreaterType($phpVersion)); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType($phpVersion)); } public function toBoolean(): BooleanType diff --git a/src/Type/NullType.php b/src/Type/NullType.php index a59f86e868f..01870ed2daf 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -108,27 +108,27 @@ public function equals(Type $type): bool return $type instanceof self; } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { return TrinaryLogic::createFromBoolean(null < $otherType->getValue()); } if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThan($this); + return $otherType->isGreaterThan($this, $phpVersion); } return TrinaryLogic::createMaybe(); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { return TrinaryLogic::createFromBoolean(null <= $otherType->getValue()); } if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThanOrEqual($this); + return $otherType->isGreaterThanOrEqual($this, $phpVersion); } return TrinaryLogic::createMaybe(); @@ -338,12 +338,12 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { return new NeverType(); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { // All falsey types except '0' return new UnionType([ @@ -356,7 +356,7 @@ public function getSmallerOrEqualType(): Type ]); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { // All truthy types, but also '0' return new MixedType(false, new UnionType([ @@ -369,7 +369,7 @@ public function getGreaterType(): Type ])); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { return new MixedType(); } diff --git a/src/Type/Traits/ConstantNumericComparisonTypeTrait.php b/src/Type/Traits/ConstantNumericComparisonTypeTrait.php index 2b3b4a45c5b..c6efe96950a 100644 --- a/src/Type/Traits/ConstantNumericComparisonTypeTrait.php +++ b/src/Type/Traits/ConstantNumericComparisonTypeTrait.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Traits; +use PHPStan\Php\PhpVersion; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\IntegerRangeType; @@ -13,7 +14,7 @@ trait ConstantNumericComparisonTypeTrait { - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new ConstantBooleanType(true), @@ -29,7 +30,7 @@ public function getSmallerType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = [ IntegerRangeType::createAllGreaterThan($this->value), @@ -43,7 +44,7 @@ public function getSmallerOrEqualType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { $subtractedTypes = [ new NullType(), @@ -59,7 +60,7 @@ public function getGreaterType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { $subtractedTypes = [ IntegerRangeType::createAllSmallerThan($this->value), diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 527d10b68a1..7452cd3c833 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -78,27 +78,27 @@ public function equals(Type $type): bool return $type instanceof self && $this->value === $type->value; } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { return TrinaryLogic::createFromBoolean($this->value < $otherType->getValue()); } if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThan($this); + return $otherType->isGreaterThan($this, $phpVersion); } return TrinaryLogic::createMaybe(); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { if ($otherType instanceof ConstantScalarType) { return TrinaryLogic::createFromBoolean($this->value <= $otherType->getValue()); } if ($otherType instanceof CompoundType) { - return $otherType->isGreaterThanOrEqual($this); + return $otherType->isGreaterThanOrEqual($this, $phpVersion); } return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index bff88f730eb..ff3d015f361 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -367,14 +367,14 @@ public function toArrayKey(): Type return $this->resolve()->toArrayKey(); } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->resolve()->isSmallerThan($otherType); + return $this->resolve()->isSmallerThan($otherType, $phpVersion); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->resolve()->isSmallerThanOrEqual($otherType); + return $this->resolve()->isSmallerThanOrEqual($otherType, $phpVersion); } public function isNull(): TrinaryLogic @@ -482,24 +482,24 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { - return $this->resolve()->getSmallerType(); + return $this->resolve()->getSmallerType($phpVersion); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { - return $this->resolve()->getSmallerOrEqualType(); + return $this->resolve()->getSmallerOrEqualType($phpVersion); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { - return $this->resolve()->getGreaterType(); + return $this->resolve()->getGreaterType($phpVersion); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { - return $this->resolve()->getGreaterOrEqualType(); + return $this->resolve()->getGreaterOrEqualType($phpVersion); } public function inferTemplateTypes(Type $receivedType): TemplateTypeMap @@ -539,26 +539,26 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): return $acceptingType->acceptsWithReason($result, $strictTypes); } - public function isGreaterThan(Type $otherType): TrinaryLogic + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isGreaterThan($otherType); + return $result->isGreaterThan($otherType, $phpVersion); } - return $otherType->isSmallerThan($result); + return $otherType->isSmallerThan($result, $phpVersion); } - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isGreaterThanOrEqual($otherType); + return $result->isGreaterThanOrEqual($otherType, $phpVersion); } - return $otherType->isSmallerThanOrEqual($result); + return $otherType->isSmallerThanOrEqual($result, $phpVersion); } public function exponentiate(Type $exponent): Type diff --git a/src/Type/Traits/UndecidedComparisonCompoundTypeTrait.php b/src/Type/Traits/UndecidedComparisonCompoundTypeTrait.php index e40b72fad4e..6adf571d54d 100644 --- a/src/Type/Traits/UndecidedComparisonCompoundTypeTrait.php +++ b/src/Type/Traits/UndecidedComparisonCompoundTypeTrait.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Traits; +use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -10,12 +11,12 @@ trait UndecidedComparisonCompoundTypeTrait use UndecidedComparisonTypeTrait; - public function isGreaterThan(Type $otherType): TrinaryLogic + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createMaybe(); } - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Traits/UndecidedComparisonTypeTrait.php b/src/Type/Traits/UndecidedComparisonTypeTrait.php index e5c6d2c891e..6761274cf1d 100644 --- a/src/Type/Traits/UndecidedComparisonTypeTrait.php +++ b/src/Type/Traits/UndecidedComparisonTypeTrait.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Traits; +use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -9,32 +10,32 @@ trait UndecidedComparisonTypeTrait { - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createMaybe(); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return TrinaryLogic::createMaybe(); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { return new MixedType(); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { return new MixedType(); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { return new MixedType(); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { return new MixedType(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index 727e2f8a312..47446d0dadf 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -215,9 +215,9 @@ public function toArray(): Type; public function toArrayKey(): Type; - public function isSmallerThan(Type $otherType): TrinaryLogic; + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic; + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; /** * Is Type of a known constant value? Includes literal strings, integers, floats, true, false, null, and array shapes. @@ -269,13 +269,13 @@ public function isScalar(): TrinaryLogic; public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType; - public function getSmallerType(): Type; + public function getSmallerType(PhpVersion $phpVersion): Type; - public function getSmallerOrEqualType(): Type; + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type; - public function getGreaterType(): Type; + public function getGreaterType(PhpVersion $phpVersion): Type; - public function getGreaterOrEqualType(): Type; + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type; /** * Returns actual template type for a given object. diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index af4ede1b405..839bad3817d 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -783,14 +783,14 @@ public function isCloneable(): TrinaryLogic return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCloneable()); } - public function isSmallerThan(Type $otherType): TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType)); + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType, $phpVersion)); } - public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType)); + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType, $phpVersion)); } public function isNull(): TrinaryLogic @@ -843,34 +843,34 @@ public function isInteger(): TrinaryLogic return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isInteger()); } - public function getSmallerType(): Type + public function getSmallerType(PhpVersion $phpVersion): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerType($phpVersion)); } - public function getSmallerOrEqualType(): Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getSmallerOrEqualType($phpVersion)); } - public function getGreaterType(): Type + public function getGreaterType(PhpVersion $phpVersion): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterType($phpVersion)); } - public function getGreaterOrEqualType(): Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getGreaterOrEqualType($phpVersion)); } - public function isGreaterThan(Type $otherType): TrinaryLogic + public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type)); + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type, $phpVersion)); } - public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic + public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { - return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type)); + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type, $phpVersion)); } public function toBoolean(): BooleanType diff --git a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php index a6a68a9b5ec..f2b13d25c21 100644 --- a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php @@ -32,32 +32,32 @@ public function testRuleOutOfPhpStan(): void $this->analyse([__DIR__ . '/data/class-implements-out-of-phpstan.php'], [ [ 'Implementing PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 19, + 20, $tip, ], [ 'Implementing PHPStan\Type\Type is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 53, + 54, $tip, ], [ 'Implementing PHPStan\Reflection\ReflectionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 338, + 339, $tip, ], [ 'Implementing PHPStan\Analyser\Scope is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 343, + 344, $tip, ], [ 'Implementing PHPStan\Reflection\FunctionReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 348, + 349, $tip, ], [ 'Implementing PHPStan\Reflection\ExtendedMethodReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 352, + 353, $tip, ], ]); diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index c7b42d7e6e3..c5211fd6501 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; @@ -247,12 +248,12 @@ public function toArrayKey(): \PHPStan\Type\Type // TODO: Implement toArrayKey() method. } - public function isSmallerThan(Type $otherType): \PHPStan\TrinaryLogic + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): \PHPStan\TrinaryLogic { // TODO: Implement isSmallerThan() method. } - public function isSmallerThanOrEqual(Type $otherType): \PHPStan\TrinaryLogic + public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): \PHPStan\TrinaryLogic { // TODO: Implement isSmallerThanOrEqual() method. } @@ -277,22 +278,22 @@ public function isLiteralString(): \PHPStan\TrinaryLogic // TODO: Implement isLiteralString() method. } - public function getSmallerType(): \PHPStan\Type\Type + public function getSmallerType(PhpVersion $phpVersion): \PHPStan\Type\Type { // TODO: Implement getSmallerType() method. } - public function getSmallerOrEqualType(): \PHPStan\Type\Type + public function getSmallerOrEqualType(PhpVersion $phpVersion): \PHPStan\Type\Type { // TODO: Implement getSmallerOrEqualType() method. } - public function getGreaterType(): \PHPStan\Type\Type + public function getGreaterType(PhpVersion $phpVersion): \PHPStan\Type\Type { // TODO: Implement getGreaterType() method. } - public function getGreaterOrEqualType(): \PHPStan\Type\Type + public function getGreaterOrEqualType(PhpVersion $phpVersion): \PHPStan\Type\Type { // TODO: Implement getGreaterOrEqualType() method. } diff --git a/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php index 4b9ab4e23c0..40c0526e255 100644 --- a/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_INT_SIZE; @@ -14,7 +15,7 @@ class RandomIntParametersRuleTest extends RuleTestCase protected function getRule(): Rule { - return new RandomIntParametersRule($this->createReflectionProvider(), true); + return new RandomIntParametersRule($this->createReflectionProvider(), new PhpVersion(80000), true); } public function testFile(): void From 18196e9ec47ef933cb65a7e97d2b37d7ecc4518b Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Wed, 25 Sep 2024 22:18:22 +0200 Subject: [PATCH 0430/3097] Change iptcparse return type the inner arrays are lists, see https://github.com/php/php-src/blob/e035a957237a7c17aa3dc2810d86333c93654c11/ext/standard/iptc.c#L365 --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 2b9537b98e2..b79549a45dd 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5627,7 +5627,7 @@ 'InvalidArgumentException::getTraceAsString' => ['string'], 'ip2long' => ['int|false', 'ip_address'=>'string'], 'iptcembed' => ['string|bool', 'iptcdata'=>'string', 'jpeg_file_name'=>'string', 'spool='=>'int'], -'iptcparse' => ['array>|false', 'iptcdata'=>'string'], +'iptcparse' => ['array>|false', 'iptcdata'=>'string'], 'is_a' => ['bool', 'object_or_string'=>'object|string', 'class_name'=>'string', 'allow_string='=>'bool'], 'is_array' => ['bool', 'var'=>'mixed'], 'is_bool' => ['bool', 'var'=>'mixed'], From 31362eb1971a0d408bf4565da077397adaa0733f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Sep 2024 09:02:59 +0200 Subject: [PATCH 0431/3097] Fix sprintf() inference for constant values with format-width in pattern --- .../SprintfFunctionDynamicReturnTypeExtension.php | 14 ++++++++------ tests/PHPStan/Analyser/nsrt/bug-7387.php | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 4688917e1f1..26cc34d860b 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -84,13 +84,13 @@ public function getTypeFromFunctionCall( } // The printf format is %[argnum$][flags][width][.precision]specifier. - if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) { - if ($matches[1] !== '') { + if (preg_match('/^%(?P[0-9]*\$)?(?P[0-9]*)\.?[0-9]*(?P[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) { + if ($matches['argnum'] !== '') { // invalid positional argument - if ($matches[1] === '0$') { + if ($matches['argnum'] === '0$') { return null; } - $checkArg = intval(substr($matches[1], 0, -1)); + $checkArg = intval(substr($matches['argnum'], 0, -1)); } else { $checkArg = 1; } @@ -103,11 +103,13 @@ public function getTypeFromFunctionCall( // if the format string is just a placeholder and specified an argument // of stringy type, then the return value will be of the same type $checkArgType = $scope->getType($args[$checkArg]->value); - if ($matches[2] === 's' + if ( + $matches['specifier'] === 's' + && ($checkArgType->isConstantValue()->no() || $matches['width'] === '') && ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes()) ) { $singlePlaceholderEarlyReturn = $checkArgType->toString(); - } elseif ($matches[2] !== 's') { + } elseif ($matches['specifier'] !== 's') { $singlePlaceholderEarlyReturn = new IntersectionType([ new StringType(), new AccessoryNumericStringType(), diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index 4623719b4d8..0081826133f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -59,6 +59,13 @@ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, assertType('numeric-string', sprintf('%2$14s', $mixed, $intRange)); assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $nonZeroIntRange)); + assertType("non-falsy-string", sprintf('%2$14s', $mixed, 1)); + assertType("non-falsy-string", sprintf('%2$14s', $mixed, '1')); + assertType("non-falsy-string", sprintf('%2$14s', $mixed, 'abc')); + assertType("'1'", sprintf('%2$s', $mixed, 1)); + assertType("'1'", sprintf('%2$s', $mixed, '1')); + assertType("'abc'", sprintf('%2$s', $mixed, 'abc')); + assertType('numeric-string', sprintf('%2$.14F', $mixed, $i)); assertType('numeric-string', sprintf('%2$.14F', $mixed, $f)); assertType('numeric-string', sprintf('%2$.14F', $mixed, $s)); From b76fecccf2acb7d966051dcfbf499a118d341956 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Sep 2024 09:16:57 +0200 Subject: [PATCH 0432/3097] `isset()` narrows string-key in int-keyed-array to numeric-string --- src/Analyser/TypeSpecifier.php | 44 ++-------------- src/Rules/Arrays/AllowedArrayKeysTypes.php | 59 ++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-11716.php | 32 ++++++++++-- 3 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index a5dc7bc6a58..fb8b5985e75 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -28,6 +28,7 @@ use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ResolvedFunctionVariant; +use PHPStan\Rules\Arrays\AllowedArrayKeysTypes; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -820,47 +821,8 @@ public function specifyTypesInCondition( ); } else { $varType = $scope->getType($var->var); - if ($varType->isArray()->yes() && !$varType->isIterableAtLeastOnce()->no()) { - $varIterableKeyType = $varType->getIterableKeyType(); - - if ($varIterableKeyType->isConstantScalarValue()->yes()) { - $narrowedKey = TypeCombinator::union( - $varIterableKeyType, - TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')), - ); - - if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) { - $narrowedKey = TypeCombinator::union( - $narrowedKey, - new ConstantBooleanType(false), - ); - } - - if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) { - $narrowedKey = TypeCombinator::union( - $narrowedKey, - new ConstantBooleanType(true), - ); - } - - if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) { - $narrowedKey = TypeCombinator::addNull($narrowedKey); - } - - if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) { - $narrowedKey = TypeCombinator::union($narrowedKey, new FloatType()); - } - } else { - $narrowedKey = new MixedType( - false, - new UnionType([ - new ArrayType(new MixedType(), new MixedType()), - new ObjectWithoutClassType(), - new ResourceType(), - ]), - ); - } - + $narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType, $dimType); + if ($narrowedKey !== null) { $types = $types->unionWith( $this->create( $var->dim, diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index 0bc7c1f4c42..2b15a4eb651 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -2,12 +2,20 @@ namespace PHPStan\Rules\Arrays; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\NullType; +use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\ResourceType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; final class AllowedArrayKeysTypes @@ -24,4 +32,55 @@ public static function getType(): Type ]); } + public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type + { + if (!$varType->isArray()->yes() || $varType->isIterableAtLeastOnce()->no()) { + return null; + } + + $varIterableKeyType = $varType->getIterableKeyType(); + + if ($varIterableKeyType->isConstantScalarValue()->yes()) { + $narrowedKey = TypeCombinator::union( + $varIterableKeyType, + TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')), + ); + + if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) { + $narrowedKey = TypeCombinator::union( + $narrowedKey, + new ConstantBooleanType(false), + ); + } + + if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) { + $narrowedKey = TypeCombinator::union( + $narrowedKey, + new ConstantBooleanType(true), + ); + } + + if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) { + $narrowedKey = TypeCombinator::addNull($narrowedKey); + } + + if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) { + $narrowedKey = TypeCombinator::union($narrowedKey, new FloatType()); + } + + return $narrowedKey; + } elseif ($varIterableKeyType->isInteger()->yes() && $keyType->isString()->yes()) { + return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType); + } + + return new MixedType( + false, + new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new ObjectWithoutClassType(), + new ResourceType(), + ]), + ); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index 2394e8a175b..e637669483c 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -35,9 +35,10 @@ public function parse(string $glue): string } /** - * @param array $arr + * @param array $intKeyedArr + * @param array $stringKeyedArr */ -function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void { +function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyedArr, array $stringKeyedArr): void { if (isset($generalArr[$mixed])) { assertType('mixed~(array|object|resource)', $mixed); } else { @@ -59,21 +60,42 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): vo } assertType('string', $s); - if (isset($arr[$mixed])) { + if (isset($intKeyedArr[$mixed])) { assertType('mixed~(array|object|resource)', $mixed); } else { assertType('mixed', $mixed); } assertType('mixed', $mixed); - if (isset($arr[$i])) { + if (isset($intKeyedArr[$i])) { assertType('int', $i); } else { assertType('int', $i); } assertType('int', $i); - if (isset($arr[$s])) { + if (isset($intKeyedArr[$s])) { + assertType("numeric-string", $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (isset($stringKeyedArr[$mixed])) { + assertType('mixed~(array|object|resource)', $mixed); + } else { + assertType('mixed', $mixed); + } + assertType('mixed', $mixed); + + if (isset($stringKeyedArr[$i])) { + assertType('int', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + + if (isset($stringKeyedArr[$s])) { assertType('string', $s); } else { assertType('string', $s); From 9f13233ec7c1caa6554867ca5be63552e5914ec9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 26 Sep 2024 09:18:04 +0200 Subject: [PATCH 0433/3097] More precise `MixedType::toBoolean()` with subtracted type --- src/Type/MixedType.php | 6 ++++-- tests/PHPStan/Analyser/nsrt/mixed-subtract.php | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 86c5ee66266..865611056fd 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -478,8 +478,10 @@ function () use ($level): string { public function toBoolean(): BooleanType { - if ($this->subtractedType !== null && StaticTypeFactory::falsey()->equals($this->subtractedType)) { - return new ConstantBooleanType(true); + if ($this->subtractedType !== null) { + if ($this->subtractedType->isSuperTypeOf(StaticTypeFactory::falsey())->yes()) { + return new ConstantBooleanType(true); + } } return new BooleanType(); diff --git a/tests/PHPStan/Analyser/nsrt/mixed-subtract.php b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php index 31c201d1650..59fa2afa13a 100644 --- a/tests/PHPStan/Analyser/nsrt/mixed-subtract.php +++ b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php @@ -53,4 +53,9 @@ function subtract(mixed $m, $moreThenFalsy) { assertType('mixed', $m); assertType('bool', (bool) $m); // could be true } + + if ($m != 0 && !is_array($m) && $m != null && !is_object($m)) { // subtract more types then falsy + assertType("mixed~(0|0.0|''|'0'|array|object|false|null)", $m); + assertType('true', (bool) $m); + } } From ae53f11329d9203dd4ac83df2d7338ed1a4c2d83 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Thu, 26 Sep 2024 07:25:00 +0000 Subject: [PATCH 0434/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 3b27d117510..86914b4b26d 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", "phpstan/php-8-stubs": "0.3.109", - "phpstan/phpdoc-parser": "1.31.0", + "phpstan/phpdoc-parser": "1.32.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 12c507d0e87..366cbe5c061 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4de0fc2f1dd1531160dc506a885ca84d", + "content-hash": "9302f03bb175e275adfbd782511530c4", "packages": [ { "name": "clue/ndjson-react", @@ -2280,16 +2280,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.31.0", + "version": "1.32.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "249f15fb843bf240cf058372dad29e100cee6c17" + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/249f15fb843bf240cf058372dad29e100cee6c17", - "reference": "249f15fb843bf240cf058372dad29e100cee6c17", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", "shasum": "" }, "require": { @@ -2321,9 +2321,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.31.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" }, - "time": "2024-09-22T11:32:18+00:00" + "time": "2024-09-26T07:23:32+00:00" }, { "name": "psr/container", From fd304ca33f9653bdb30b3f6a4f1cddaed34c7343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Mon, 23 Sep 2024 09:23:33 +0200 Subject: [PATCH 0435/3097] Drop wrong float comparison for filter_var() --- src/Type/Php/FilterFunctionReturnTypeHelper.php | 3 +-- tests/PHPStan/Analyser/nsrt/filter-var.php | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index 7ae801bfd92..17ba5ade7b8 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -32,7 +32,6 @@ use function octdec; use function preg_match; use function sprintf; -use const PHP_FLOAT_EPSILON; final class FilterFunctionReturnTypeHelper { @@ -293,7 +292,7 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp } if ($in instanceof ConstantFloatType) { - return $in->getValue() - (int) $in->getValue() <= PHP_FLOAT_EPSILON + return $in->getValue() - (int) $in->getValue() === 0.0 ? $in->toInteger() : $defaultType; } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index 12f97e63b5d..0d43930de33 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -106,12 +106,14 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType('bool|null', filter_var($float, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); assertType('bool|null', filter_var(17.0, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null assertType('bool|null', filter_var(17.1, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null + assertType('bool|null', filter_var(1e-50, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null assertType('bool|null', filter_var($int, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); assertType('bool|null', filter_var($intRange, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); assertType('bool|null', filter_var(17, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null assertType('bool|null', filter_var($string, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); assertType('bool|null', filter_var($nonEmptyString, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); assertType('bool|null', filter_var('17', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null + assertType('bool|null', filter_var('17.0', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null assertType('bool|null', filter_var('17.1', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); // could be null assertType('null', filter_var(null, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)); @@ -121,12 +123,14 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType('float', filter_var($float, FILTER_VALIDATE_FLOAT)); assertType('17.0', filter_var(17.0, FILTER_VALIDATE_FLOAT)); assertType('17.1', filter_var(17.1, FILTER_VALIDATE_FLOAT)); + assertType('1.0E-50', filter_var(1e-50, FILTER_VALIDATE_FLOAT)); assertType('float', filter_var($int, FILTER_VALIDATE_FLOAT)); assertType('float', filter_var($intRange, FILTER_VALIDATE_FLOAT)); assertType('17.0', filter_var(17, FILTER_VALIDATE_FLOAT)); assertType('float|false', filter_var($string, FILTER_VALIDATE_FLOAT)); assertType('float|false', filter_var($nonEmptyString, FILTER_VALIDATE_FLOAT)); assertType('float|false', filter_var('17', FILTER_VALIDATE_FLOAT)); // could be 17.0 + assertType('float|false', filter_var('17.0', FILTER_VALIDATE_FLOAT)); // could be 17.0 assertType('float|false', filter_var('17.1', FILTER_VALIDATE_FLOAT)); // could be 17.1 assertType('false', filter_var(null, FILTER_VALIDATE_FLOAT)); @@ -136,12 +140,14 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType('int|false', filter_var($float, FILTER_VALIDATE_INT)); assertType('17', filter_var(17.0, FILTER_VALIDATE_INT)); assertType('false', filter_var(17.1, FILTER_VALIDATE_INT)); + assertType('false', filter_var(1e-50, FILTER_VALIDATE_INT)); assertType('int', filter_var($int, FILTER_VALIDATE_INT)); assertType('int<0, 9>', filter_var($intRange, FILTER_VALIDATE_INT)); assertType('17', filter_var(17, FILTER_VALIDATE_INT)); assertType('int|false', filter_var($string, FILTER_VALIDATE_INT)); assertType('int|false', filter_var($nonEmptyString, FILTER_VALIDATE_INT)); assertType('17', filter_var('17', FILTER_VALIDATE_INT)); + assertType('false', filter_var('17.0', FILTER_VALIDATE_INT)); assertType('false', filter_var('17.1', FILTER_VALIDATE_INT)); assertType('false', filter_var(null, FILTER_VALIDATE_INT)); @@ -151,12 +157,14 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType('numeric-string', filter_var($float)); assertType("'17'", filter_var(17.0)); assertType("'17.1'", filter_var(17.1)); + assertType("'1.0E-50'", filter_var(1e-50)); assertType('numeric-string', filter_var($int)); assertType('numeric-string', filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); assertType('non-empty-string', filter_var($nonEmptyString)); assertType("'17'", filter_var('17')); + assertType("'17.0'", filter_var('17.0')); assertType("'17.1'", filter_var('17.1')); assertType("''", filter_var(null)); } From c83196bef4ab9f48591c25d2a216c90b51fa6740 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 13:01:05 +0200 Subject: [PATCH 0436/3097] Introduce lowercase-string Co-authored-by: Ondrej Mirtes --- phpstan-baseline.neon | 5 + resources/functionMap.php | 4 +- src/Php/PhpVersion.php | 5 + src/PhpDoc/TypeNodeResolver.php | 11 +- .../InitializerExprTypeResolver.php | 4 + src/Rules/Api/ApiInstanceofTypeRule.php | 2 + .../StrictComparisonOfDifferentTypesRule.php | 28 +- src/Type/Accessory/AccessoryArrayListType.php | 5 + .../Accessory/AccessoryLiteralStringType.php | 5 + .../AccessoryLowercaseStringType.php | 374 ++++++++++++++++++ .../Accessory/AccessoryNonEmptyStringType.php | 5 + .../Accessory/AccessoryNonFalsyStringType.php | 5 + .../Accessory/AccessoryNumericStringType.php | 5 + src/Type/Accessory/HasOffsetType.php | 5 + src/Type/Accessory/HasOffsetValueType.php | 5 + src/Type/Accessory/NonEmptyArrayType.php | 5 + src/Type/Accessory/OversizedArrayType.php | 5 + src/Type/ArrayType.php | 5 + src/Type/CallableType.php | 5 + src/Type/ClassStringType.php | 5 + src/Type/ClosureType.php | 5 + src/Type/Constant/ConstantStringType.php | 11 + src/Type/FloatType.php | 5 + src/Type/IntersectionType.php | 12 + src/Type/IterableType.php | 5 + src/Type/JustNullableTypeTrait.php | 5 + src/Type/MixedType.php | 17 + src/Type/NeverType.php | 5 + src/Type/NullType.php | 5 + src/Type/ObjectType.php | 5 + ...lodeFunctionDynamicReturnTypeExtension.php | 12 +- .../ImplodeFunctionReturnTypeExtension.php | 4 + .../StrCaseFunctionsReturnTypeExtension.php | 50 ++- .../Php/SubstrDynamicReturnTypeExtension.php | 34 +- src/Type/StaticType.php | 5 + src/Type/StrictMixedType.php | 5 + src/Type/StringType.php | 5 + src/Type/Traits/LateResolvableTypeTrait.php | 5 + src/Type/Traits/ObjectTypeTrait.php | 5 + src/Type/Type.php | 2 + src/Type/UnionType.php | 5 + src/Type/UnionTypeHelper.php | 2 +- src/Type/VerbosityLevel.php | 20 +- src/Type/VoidType.php | 5 + .../Analyser/LegacyNodeScopeResolverTest.php | 8 +- .../Analyser/NodeScopeResolverTest.php | 6 + tests/PHPStan/Analyser/ScopeTest.php | 6 +- tests/PHPStan/Analyser/data/explode-php74.php | 14 + tests/PHPStan/Analyser/data/explode-php80.php | 14 + tests/PHPStan/Analyser/nsrt/bug-2911.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4711.php | 4 +- tests/PHPStan/Analyser/nsrt/bug7856.php | 2 +- .../Analyser/nsrt/constant-string-unions.php | 6 +- .../nsrt/foreach-partially-non-iterable.php | 2 +- .../nsrt/lowercase-string-implode.php | 22 ++ .../Analyser/nsrt/lowercase-string-subtr.php | 30 ++ tests/PHPStan/Analyser/nsrt/more-types.php | 8 +- .../Analyser/nsrt/non-empty-string-substr.php | 2 +- .../Analyser/nsrt/non-empty-string.php | 10 +- .../Analyser/nsrt/non-falsy-string.php | 4 +- tests/PHPStan/Analyser/nsrt/str-casing.php | 37 +- ...rictComparisonOfDifferentTypesRuleTest.php | 48 +++ .../Comparison/data/lowercase-string.php | 31 ++ .../CallToFunctionParametersRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 23 ++ .../Rules/Methods/data/lowercase-string.php | 33 ++ .../Type/Constant/ConstantStringTypeTest.php | 16 +- .../Constant/OversizedArrayBuilderTest.php | 4 +- tests/PHPStan/Type/IntersectionTypeTest.php | 28 ++ tests/PHPStan/Type/TypeCombinatorTest.php | 98 +++++ tests/PHPStan/Type/TypeToPhpDocNodeTest.php | 8 +- 71 files changed, 1115 insertions(+), 80 deletions(-) create mode 100644 src/Type/Accessory/AccessoryLowercaseStringType.php create mode 100644 tests/PHPStan/Analyser/data/explode-php74.php create mode 100644 tests/PHPStan/Analyser/data/explode-php80.php create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-implode.php create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php create mode 100644 tests/PHPStan/Rules/Comparison/data/lowercase-string.php create mode 100644 tests/PHPStan/Rules/Methods/data/lowercase-string.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 79f1b546ea2..b7c18db8d9b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -691,6 +691,11 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryLiteralStringType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + count: 1 + path: src/Type/Accessory/AccessoryLowercaseStringType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 diff --git a/resources/functionMap.php b/resources/functionMap.php index b79549a45dd..80c57788d62 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6361,7 +6361,7 @@ 'mb_strripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], 'mb_strrpos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], 'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], -'mb_strtolower' => ['string', 'str'=>'string', 'encoding='=>'string'], +'mb_strtolower' => ['lowercase-string', 'str'=>'string', 'encoding='=>'string'], 'mb_strtoupper' => ['string', 'str'=>'string', 'encoding='=>'string'], 'mb_strwidth' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'], 'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'], @@ -12085,7 +12085,7 @@ 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'], 'strtok' => ['non-empty-string|false', 'str'=>'string', 'token'=>'string'], 'strtok\'1' => ['non-empty-string|false', 'token'=>'string'], -'strtolower' => ['string', 'str'=>'string'], +'strtolower' => ['lowercase-string', 'str'=>'string'], 'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'], 'strtoupper' => ['string', 'str'=>'string'], 'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'], diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 4eae1e59f91..b275c464eb1 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -348,4 +348,9 @@ public function deprecatesImplicitlyNullableParameterTypes(): bool return $this->versionId >= 80400; } + public function substrReturnFalseInsteadOfEmptyString(): bool + { + return $this->versionId < 80000; + } + } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index c0fa6c55857..06098d79388 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -46,6 +46,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -216,9 +217,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco ]); case 'string': - case 'lowercase-string': return new StringType(); + case 'lowercase-string': + return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); + case 'literal-string': return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]); @@ -287,10 +290,16 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco ]); case 'non-empty-string': + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + case 'non-empty-lowercase-string': return new IntersectionType([ new StringType(), new AccessoryNonEmptyStringType(), + new AccessoryLowercaseStringType(), ]); case 'truthy-string': diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 1eec9736d1e..8b015c91ab4 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -27,6 +27,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -478,6 +479,9 @@ public function resolveConcatType(Type $left, Type $right): Type if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) { $accessoryTypes[] = new AccessoryLiteralStringType(); } + if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); if ($leftNumericStringNonEmpty->isNumericString()->yes()) { diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index 1ae24aa0cae..225e65b0002 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -11,6 +11,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -84,6 +85,7 @@ final class ApiInstanceofTypeRule implements Rule AccessoryArrayListType::class => 'Type::isList()', AccessoryNumericStringType::class => 'Type::isNumericString()', AccessoryLiteralStringType::class => 'Type::isLiteralString()', + AccessoryLowercaseStringType::class => 'Type::isLowercaseString()', AccessoryNonEmptyStringType::class => 'Type::isNonEmptyString()', AccessoryNonFalsyStringType::class => 'Type::isNonFalsyString()', HasMethodType::class => 'Type::hasMethod()', diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index f4ea23258b3..c703e93cb27 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -7,6 +7,7 @@ use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -61,13 +62,32 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; + $verbosity = VerbosityLevel::value(); + if ( + ( + $leftType->isConstantScalarValue()->yes() + && $leftType->isString()->yes() + && $rightType->isConstantScalarValue()->no() + && $rightType->isString()->yes() + && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + ) || ( + $rightType->isConstantScalarValue()->yes() + && $rightType->isString()->yes() + && $leftType->isConstantScalarValue()->no() + && $leftType->isString()->yes() + && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + ) + ) { + $verbosity = VerbosityLevel::precise(); + } + if (!$nodeType->getValue()) { return [ $addTip(RuleErrorBuilder::message(sprintf( 'Strict comparison using %s between %s and %s will always evaluate to false.', $node->getOperatorSigil(), - $leftType->describe(VerbosityLevel::value()), - $rightType->describe(VerbosityLevel::value()), + $leftType->describe($verbosity), + $rightType->describe($verbosity), )))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(), ]; } elseif ($this->checkAlwaysTrueStrictComparison) { @@ -79,8 +99,8 @@ public function processNode(Node $node, Scope $scope): array $errorBuilder = $addTip(RuleErrorBuilder::message(sprintf( 'Strict comparison using %s between %s and %s will always evaluate to true.', $node->getOperatorSigil(), - $leftType->describe(VerbosityLevel::value()), - $rightType->describe(VerbosityLevel::value()), + $leftType->describe($verbosity), + $rightType->describe($verbosity), ))); if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) { $errorBuilder->addTip('Remove remaining cases below this one and this error will disappear too.'); diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 511087818d1..d32cc8882c8 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -387,6 +387,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index dd4af579acd..110fbea58c7 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -297,6 +297,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php new file mode 100644 index 00000000000..195a807dbcf --- /dev/null +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -0,0 +1,374 @@ +acceptsWithReason($type, $strictTypes)->result; + } + + public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + { + if ($type instanceof CompoundType) { + return $type->isAcceptedWithReasonBy($this, $strictTypes); + } + + return new AcceptsResult($type->isLowercaseString(), []); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + return $type->isLowercaseString(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isLowercaseString() + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; + } + + public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + { + return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(VerbosityLevel $level): string + { + return 'lowercase-string'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isOffsetAccessLegal(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return $offsetType->isInteger()->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + + if ($valueType->isLowercaseString()->yes()) { + return $this; + } + + return new StringType(); + } + + public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type + { + return $this; + } + + public function unsetOffset(Type $offsetType): Type + { + return new ErrorType(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toAbsoluteNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toBoolean(): BooleanType + { + return new BooleanType(); + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + [1], + [], + TrinaryLogic::createYes(), + ); + } + + public function toArrayKey(): Type + { + return $this; + } + + public function isNull(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isConstantValue(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isConstantScalarValue(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getConstantScalarTypes(): array + { + return []; + } + + public function getConstantScalarValues(): array + { + return []; + } + + public function isTrue(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFalse(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isBoolean(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFloat(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonFalsyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isClassStringType(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getClassStringObjectType(): Type + { + return new ObjectWithoutClassType(); + } + + public function getObjectTypeOrClassStringObjectType(): Type + { + return new ObjectWithoutClassType(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isVoid(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType + { + return new BooleanType(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function traverseSimultaneously(Type $right, callable $cb): Type + { + return $this; + } + + public function generalize(GeneralizePrecision $precision): Type + { + return new StringType(); + } + + public static function __set_state(array $properties): Type + { + return new self(); + } + + public function exponentiate(Type $exponent): Type + { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + } + + public function getFiniteTypes(): array + { + return []; + } + + public function toPhpDocNode(): TypeNode + { + return new IdentifierTypeNode('lowercase-string'); + } + +} diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 16566ee5308..b911c21fbbe 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -294,6 +294,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index dacccd3e31c..5703420e9f8 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -294,6 +294,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9d376256125..9cb274782d5 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -296,6 +296,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 5ed39d97970..2194a10cef7 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -297,6 +297,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index ae1ef84f564..853645c91a1 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -353,6 +353,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 1f2abd69b69..293e1f111f2 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -364,6 +364,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 2e9da3ae05e..fa64240e897 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -360,6 +360,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index d2eaf1bc242..4543e36b771 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -358,6 +358,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index ce30d8983c3..82b3645a273 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -591,6 +591,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 802d17e1624..eaca9d4572e 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -69,6 +69,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 967b850525c..e55847aae03 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -713,6 +713,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index fcd467fbbba..382911e69dc 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -20,6 +20,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -48,6 +49,7 @@ use function is_numeric; use function key; use function strlen; +use function strtolower; use function substr; use function substr_count; @@ -343,6 +345,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean(strtolower($this->value) === $this->value); + } + public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType->isInteger()->yes()) { @@ -450,6 +457,10 @@ public function generalize(GeneralizePrecision $precision): Type $accessories[] = new AccessoryNonEmptyStringType(); } + if (strtolower($this->getValue()) === $this->getValue()) { + $accessories[] = new AccessoryLowercaseStringType(); + } + return new IntersectionType($accessories); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 2111d4d9125..0ffa96e002c 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -229,6 +229,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 8bf38bdddaa..22a4d811e4a 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -21,6 +21,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -328,7 +329,12 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) || $type instanceof AccessoryLiteralStringType || $type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonFalsyStringType + || $type instanceof AccessoryLowercaseStringType ) { + if ($type instanceof AccessoryLowercaseStringType && !$level->isPrecise()) { + continue; + } + if ($type instanceof AccessoryNonFalsyStringType) { $nonFalsyStr = true; } @@ -637,6 +643,11 @@ public function isLiteralString(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isLiteralString()); } + public function isLowercaseString(): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); + } + public function isClassStringType(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); @@ -1114,6 +1125,7 @@ public function toPhpDocNode(): TypeNode || $type instanceof AccessoryLiteralStringType || $type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonFalsyStringType + || $type instanceof AccessoryLowercaseStringType ) { if ($type instanceof AccessoryNonFalsyStringType) { $nonFalsyStr = true; diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index f36cab2e7f6..6ef8ff5ee3e 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -372,6 +372,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 294b7d1d2b7..7f48131262a 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -147,6 +147,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 865611056fd..f72e8f3d760 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -21,6 +21,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -916,6 +917,22 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + if ($this->subtractedType !== null) { + $lowercaseString = TypeCombinator::intersect( + new StringType(), + new AccessoryLowercaseStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($lowercaseString)->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 5d488d319c5..1523562cabe 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -476,6 +476,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index a59f86e868f..ab50c2040e2 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -295,6 +295,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 1732c3c6277..b756e3f77d7 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1045,6 +1045,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index 9927cc252e5..ccad08cefb8 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\StringType; @@ -54,7 +56,15 @@ public function getTypeFromFunctionCall( return new ConstantBooleanType(false); } - $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())); + $stringType = $scope->getType($args[1]->value); + if ($stringType->isLowercaseString()->yes()) { + $returnValueType = new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); + } else { + $returnValueType = new StringType(); + } + + $returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $returnValueType)); + if ( !isset($args[2]) || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($args[2]->value))->yes() diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index bb29690bd60..d29e20a7ccb 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantArrayType; @@ -90,6 +91,9 @@ private function implode(Type $arrayType, Type $separatorType): Type if ($arrayType->getIterableValueType()->isLiteralString()->yes() && $separatorType->isLiteralString()->yes()) { $accessoryTypes[] = new AccessoryLiteralStringType(); } + if ($arrayType->getIterableValueType()->isLowercaseString()->yes() && $separatorType->isLowercaseString()->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); diff --git a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php index 39355b579ee..149caff0818 100644 --- a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php +++ b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -15,11 +16,13 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; +use function array_diff; use function array_map; use function count; use function in_array; use function is_callable; use function mb_check_encoding; +use const MB_CASE_LOWER; final class StrCaseFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -65,9 +68,24 @@ public function getTypeFromFunctionCall( } $modes = []; + $keepLowercase = false; + $forceLowercase = false; + if ($fnName === 'mb_convert_case') { $modeType = $scope->getType($args[1]->value); $modes = array_map(static fn ($mode) => $mode->getValue(), TypeUtils::getConstantIntegers($modeType)); + if (count($modes) > 0) { + $forceLowercase = count(array_diff($modes, [ + MB_CASE_LOWER, + 5, // MB_CASE_LOWER_SIMPLE + ])) === 0; + $keepLowercase = count(array_diff($modes, [ + MB_CASE_LOWER, + 5, // MB_CASE_LOWER_SIMPLE + 3, // MB_CASE_FOLD, + 7, // MB_CASE_FOLD_SIMPLE + ])) === 0; + } } elseif (in_array($fnName, ['ucwords', 'mb_convert_kana'], true)) { if (count($args) >= 2) { $modeType = $scope->getType($args[1]->value); @@ -75,6 +93,10 @@ public function getTypeFromFunctionCall( } else { $modes = $fnName === 'mb_convert_kana' ? ['KV'] : [" \t\r\n\f\v"]; } + } elseif (in_array($fnName, ['strtolower', 'mb_strtolower'], true)) { + $forceLowercase = true; + } elseif (in_array($fnName, ['lcfirst', 'mb_lcfirst'], true)) { + $keepLowercase = true; } $constantStrings = array_map(static fn ($type) => $type->getValue(), $argType->getConstantStrings()); @@ -101,25 +123,23 @@ public function getTypeFromFunctionCall( } } - if ($argType->isNumericString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]); + $accessoryTypes = []; + if ($forceLowercase || ($keepLowercase && $argType->isLowercaseString()->yes())) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); } - if ($argType->isNonFalsyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonFalsyStringType(), - ]); + if ($argType->isNumericString()->yes()) { + $accessoryTypes[] = new AccessoryNumericStringType(); + } elseif ($argType->isNonFalsyString()->yes()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($argType->isNonEmptyString()->yes()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); } - if ($argType->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + + return new IntersectionType($accessoryTypes); } return new StringType(); diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index f40ff3900d8..4364f2374f4 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -4,7 +4,9 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -25,6 +27,10 @@ final class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return in_array($functionReflection->getName(), ['substr', 'mb_substr'], true); @@ -88,18 +94,30 @@ public function getTypeFromFunctionCall( return TypeCombinator::union(...$results); } + $accessoryTypes = []; + $isNotEmpty = false; + if ($string->isLowercaseString()->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { + $isNotEmpty = true; if ($string->isNonFalsyString()->yes() && !$maybeOneLength) { - return new IntersectionType([ - new StringType(), - new AccessoryNonFalsyStringType(), - ]); + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } else { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + } + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + if (!$isNotEmpty && $this->phpVersion->substrReturnFalseInsteadOfEmptyString()) { + return TypeCombinator::union( + new ConstantBooleanType(false), + new IntersectionType($accessoryTypes), + ); } - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + + return new IntersectionType($accessoryTypes); } return null; diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index bb55a6c9fa0..e0d29249510 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -565,6 +565,11 @@ public function isLiteralString(): TrinaryLogic return $this->getStaticObjectType()->isLiteralString(); } + public function isLowercaseString(): TrinaryLogic + { + return $this->getStaticObjectType()->isLowercaseString(); + } + public function isClassStringType(): TrinaryLogic { return $this->getStaticObjectType()->isClassStringType(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index cc114d5c344..00a91412337 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -275,6 +275,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 5fe9ae444a7..9fcd1e1895a 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -240,6 +240,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index bff88f730eb..fb03a4d1702 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -452,6 +452,11 @@ public function isLiteralString(): TrinaryLogic return $this->resolve()->isLiteralString(); } + public function isLowercaseString(): TrinaryLogic + { + return $this->resolve()->isLowercaseString(); + } + public function isClassStringType(): TrinaryLogic { return $this->resolve()->isClassStringType(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 0c37e439c78..8ca9c69c293 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -198,6 +198,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 727e2f8a312..398529994e1 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -261,6 +261,8 @@ public function isNonFalsyString(): TrinaryLogic; public function isLiteralString(): TrinaryLogic; + public function isLowercaseString(): TrinaryLogic; + public function isClassStringType(): TrinaryLogic; public function isVoid(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index af4ede1b405..08b8d2c186d 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -612,6 +612,11 @@ public function isLiteralString(): TrinaryLogic return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLiteralString()); } + public function isLowercaseString(): TrinaryLogic + { + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); + } + public function isClassStringType(): TrinaryLogic { return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index 485c4826445..f91ea5cb458 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -117,7 +117,7 @@ public static function sortTypes(array $types): array } if ($a->isString()->yes() && $b->isString()->yes()) { - return self::compareStrings($a->describe(VerbosityLevel::value()), $b->describe(VerbosityLevel::value())); + return self::compareStrings($a->describe(VerbosityLevel::precise()), $b->describe(VerbosityLevel::precise())); } return self::compareStrings($a->describe(VerbosityLevel::typeOnly()), $b->describe(VerbosityLevel::typeOnly())); diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index d9724fc64d9..52b66c154fb 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -4,6 +4,7 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -86,7 +87,7 @@ public function isPrecise(): bool /** @api */ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self { - $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose): Type { + $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type { if ($type->isCallable()->yes()) { $moreVerbose = true; return $type; @@ -107,6 +108,11 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerbose = true; return $type; } + if ($type instanceof AccessoryLowercaseStringType) { + $moreVerbose = true; + $veryVerbose = true; + return $type; + } if ($type instanceof IntegerRangeType) { $moreVerbose = true; return $type; @@ -116,8 +122,14 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc /** @var bool $moreVerbose */ $moreVerbose = false; + /** @var bool $veryVerbose */ + $veryVerbose = false; TypeTraverser::map($acceptingType, $moreVerboseCallback); + if ($veryVerbose) { + return self::precise(); + } + if ($moreVerbose) { return self::value(); } @@ -156,8 +168,14 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc /** @var bool $moreVerbose */ $moreVerbose = false; + /** @var bool $veryVerbose */ + $veryVerbose = false; TypeTraverser::map($acceptedType, $moreVerboseCallback); + if ($veryVerbose) { + return self::precise(); + } + return $moreVerbose ? self::value() : self::typeOnly(); } diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 8cbd1076ba3..add4ffca45a 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -207,6 +207,11 @@ public function isLiteralString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 234bbb82d3b..0cb675d14bc 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1129,11 +1129,11 @@ public function dataArrayDestructuring(): array '$fourthStringArrayForeachList', ], [ - 'string', + 'lowercase-string', '$dateArray[\'Y\']', ], [ - 'string', + 'lowercase-string', '$dateArray[\'m\']', ], [ @@ -1141,7 +1141,7 @@ public function dataArrayDestructuring(): array '$dateArray[\'d\']', ], [ - 'string', + 'lowercase-string', '$intArrayForRewritingFirstElement[0]', ], [ @@ -8093,7 +8093,7 @@ public function dataArrayKeysInBranches(): array '$arrayAppendedInForeach', ], [ - 'non-empty-array, literal-string&non-falsy-string>', // could be 'array, \'bar\'|\'baz\'|\'foo\'>' + 'non-empty-array, literal-string&lowercase-string&non-falsy-string>', // could be 'array, \'bar\'|\'baz\'|\'foo\'>' '$anotherArrayAppendedInForeach', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a4bfe753641..2816b5ce526 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -51,6 +51,12 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Methods/data/bug-6856.php'; + if (PHP_VERSION_ID < 80000) { + yield __DIR__ . '/data/explode-php74.php'; + } else { + yield __DIR__ . '/data/explode-php80.php'; + } + if (PHP_VERSION_ID >= 80000) { yield __DIR__ . '/../Reflection/data/unionTypes.php'; yield __DIR__ . '/../Reflection/data/mixedType.php'; diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 99bba5ea9bd..fe0644cd307 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -29,7 +29,7 @@ public function dataGeneralize(): array [ new ConstantStringType('a'), new ConstantStringType('b'), - 'literal-string&non-falsy-string', + 'literal-string&lowercase-string&non-falsy-string', ], [ new ConstantIntegerType(0), @@ -139,7 +139,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(1), ]), - 'non-empty-array', + 'non-empty-array', ], [ new ConstantArrayType([ @@ -154,7 +154,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(2), ]), - 'non-empty-array>', + 'non-empty-array>', ], [ new UnionType([ diff --git a/tests/PHPStan/Analyser/data/explode-php74.php b/tests/PHPStan/Analyser/data/explode-php74.php new file mode 100644 index 00000000000..efdd4044249 --- /dev/null +++ b/tests/PHPStan/Analyser/data/explode-php74.php @@ -0,0 +1,14 @@ +|false', explode($s, 'foo')); + assertType('non-empty-list|false', explode($s, 'FOO')); + } +} diff --git a/tests/PHPStan/Analyser/data/explode-php80.php b/tests/PHPStan/Analyser/data/explode-php80.php new file mode 100644 index 00000000000..4fa45393564 --- /dev/null +++ b/tests/PHPStan/Analyser/data/explode-php80.php @@ -0,0 +1,14 @@ +', explode($s, 'foo')); + assertType('non-empty-list', explode($s, 'FOO')); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-2911.php b/tests/PHPStan/Analyser/nsrt/bug-2911.php index 1544279ea27..1d75efdcbef 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-2911.php +++ b/tests/PHPStan/Analyser/nsrt/bug-2911.php @@ -59,7 +59,7 @@ private function getResultSettings(array $settings): array $settings['remove'] = strtolower($settings['remove']); - assertType("non-empty-array&hasOffsetValue('remove', string)", $settings); + assertType("non-empty-array&hasOffsetValue('remove', lowercase-string)", $settings); if (!in_array($settings['remove'], ['first', 'last', 'all'], true)) { throw $this->configException($settings, 'remove'); diff --git a/tests/PHPStan/Analyser/nsrt/bug-4711.php b/tests/PHPStan/Analyser/nsrt/bug-4711.php index 46bc69565f9..8fbed765a95 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4711.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4711.php @@ -12,8 +12,8 @@ function x(string $string): void { return; } - assertType('non-empty-list', explode($string, '')); - assertType('non-empty-list', explode($string[0], '')); + assertType('non-empty-list', explode($string, '')); + assertType('non-empty-list', explode($string[0], '')); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug7856.php b/tests/PHPStan/Analyser/nsrt/bug7856.php index 0b48d36ebc8..fcdca30b442 100644 --- a/tests/PHPStan/Analyser/nsrt/bug7856.php +++ b/tests/PHPStan/Analyser/nsrt/bug7856.php @@ -10,7 +10,7 @@ function doFoo() { $endDate = new DateTimeImmutable('+1year'); do { - assertType("array{'+1week', '+1months', '+6months', '+17months'}|array{0: literal-string&non-falsy-string, 1?: literal-string&non-falsy-string, 2?: '+17months'}", $intervals); + assertType("array{'+1week', '+1months', '+6months', '+17months'}|array{0: literal-string&lowercase-string&non-falsy-string, 1?: literal-string&lowercase-string&non-falsy-string, 2?: '+17months'}", $intervals); $periodEnd = $periodEnd->modify(array_shift($intervals)); } while (count($intervals) > 0 && $periodEnd->format('U') < $endDate); } diff --git a/tests/PHPStan/Analyser/nsrt/constant-string-unions.php b/tests/PHPStan/Analyser/nsrt/constant-string-unions.php index 06881808e80..b97b1171792 100644 --- a/tests/PHPStan/Analyser/nsrt/constant-string-unions.php +++ b/tests/PHPStan/Analyser/nsrt/constant-string-unions.php @@ -72,7 +72,7 @@ public function testLimit(string $s15, string $s16, string $s17) { // union should contain 32 elements assertType("'1'|'10'|'10a'|'11'|'11a'|'12'|'12a'|'13'|'13a'|'14'|'14a'|'15'|'15a'|'16'|'16a'|'1a'|'2'|'2a'|'3'|'3a'|'4'|'4a'|'5'|'5a'|'6'|'6a'|'7'|'7a'|'8'|'8a'|'9'|'9a'", $s16); // fallback to the more general form - assertType("literal-string&non-falsy-string", $s17); + assertType("literal-string&lowercase-string&non-falsy-string", $s17); $left = rand() ? 'a' : 'b'; $right = rand() ? 'x' : 'y'; $left .= $right; @@ -80,7 +80,7 @@ public function testLimit(string $s15, string $s16, string $s17) { $left .= $right; assertType("'axxx'|'axxy'|'axyx'|'axyy'|'ayxx'|'ayxy'|'ayyx'|'ayyy'|'bxxx'|'bxxy'|'bxyx'|'bxyy'|'byxx'|'byxy'|'byyx'|'byyy'", $left); $left .= $right; - assertType("literal-string&non-falsy-string", $left); + assertType("literal-string&lowercase-string&non-falsy-string", $left); $left = rand() ? 'a' : 'b'; $right = rand() ? 'x' : 'y'; @@ -89,7 +89,7 @@ public function testLimit(string $s15, string $s16, string $s17) { $left = "{$left}{$right}"; assertType("'axxx'|'axxy'|'axyx'|'axyy'|'ayxx'|'ayxy'|'ayyx'|'ayyy'|'bxxx'|'bxxy'|'bxyx'|'bxyy'|'byxx'|'byxy'|'byyx'|'byyy'", $left); $left = "{$left}{$right}"; - assertType("literal-string&non-falsy-string", $left); + assertType("literal-string&lowercase-string&non-falsy-string", $left); } /** diff --git a/tests/PHPStan/Analyser/nsrt/foreach-partially-non-iterable.php b/tests/PHPStan/Analyser/nsrt/foreach-partially-non-iterable.php index ad8e375e583..a1d72792523 100644 --- a/tests/PHPStan/Analyser/nsrt/foreach-partially-non-iterable.php +++ b/tests/PHPStan/Analyser/nsrt/foreach-partially-non-iterable.php @@ -29,7 +29,7 @@ public function sayHello(\stdClass $s): void foreach ($s as $k => $v) { $a .= 'test'; } - assertType('(literal-string&non-falsy-string)|null', $a); + assertType('(literal-string&lowercase-string&non-falsy-string)|null', $a); } } diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-implode.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-implode.php new file mode 100644 index 00000000000..3aff9f3389d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-implode.php @@ -0,0 +1,22 @@ + $commonStrings + * @param array $lowercaseStrings + */ + public function doFoo(string $s, string $ls, array $commonStrings, array $lowercaseStrings): void + { + assertType('string', implode($s, $commonStrings)); + assertType('string', implode($s, $lowercaseStrings)); + assertType('string', implode($ls, $commonStrings)); + assertType('lowercase-string', implode($ls, $lowercaseStrings)); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php new file mode 100644 index 00000000000..b2a2ff43fa7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php @@ -0,0 +1,30 @@ += 8.0 + +namespace LowercaseStringSubstr; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @param lowercase-string $lowercase + */ + public function doSubstr(string $lowercase): void + { + assertType('lowercase-string', substr($lowercase, 5)); + assertType('lowercase-string', substr($lowercase, -5)); + assertType('lowercase-string', substr($lowercase, 0, 5)); + } + + /** + * @param lowercase-string $lowercase + */ + public function doMbSubstr(string $lowercase): void + { + assertType('lowercase-string', mb_substr($lowercase, 5)); + assertType('lowercase-string', mb_substr($lowercase, -5)); + assertType('lowercase-string', mb_substr($lowercase, 0, 5)); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/more-types.php b/tests/PHPStan/Analyser/nsrt/more-types.php index 70dc86d3d9e..9c3296c3d89 100644 --- a/tests/PHPStan/Analyser/nsrt/more-types.php +++ b/tests/PHPStan/Analyser/nsrt/more-types.php @@ -17,6 +17,8 @@ class Foo * @param non-empty-scalar $nonEmptyScalar * @param empty-scalar $emptyScalar * @param non-empty-mixed $nonEmptyMixed + * @param lowercase-string $lowercaseString + * @param non-empty-lowercase-string $nonEmptyLowercaseString */ public function doFoo( $pureCallable, @@ -27,7 +29,9 @@ public function doFoo( $nonEmptyLiteralString, $nonEmptyScalar, $emptyScalar, - $nonEmptyMixed + $nonEmptyMixed, + $lowercaseString, + $nonEmptyLowercaseString, ): void { assertType('pure-callable(): mixed', $pureCallable); @@ -39,6 +43,8 @@ public function doFoo( assertType('float|int|int<1, max>|non-falsy-string|true', $nonEmptyScalar); assertType("0|0.0|''|'0'|false", $emptyScalar); assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $nonEmptyMixed); + assertType('lowercase-string', $lowercaseString); + assertType('lowercase-string&non-empty-string', $nonEmptyLowercaseString); } } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-substr.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-substr.php index c1f85c4df7d..42dacb886fd 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-substr.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-substr.php @@ -54,7 +54,7 @@ public function doMbSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $n assertType('string', mb_substr($s, 0, $positiveInt)); assertType('non-empty-string', mb_substr($nonEmpty, 0, $positiveInt)); - assertType('non-empty-string', mb_substr("déjà_vu", 0, $positiveInt)); + assertType('lowercase-string&non-empty-string', mb_substr("déjà_vu", 0, $positiveInt)); assertType("'déjà_vu'", mb_substr("déjà_vu", 0)); assertType("'déj'", mb_substr("déjà_vu", 0, 3)); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 78e536a735a..19a5d6b96ab 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -127,7 +127,7 @@ public function doFoo3(string $s): void */ public function doFoo4(string $s): void { - assertType('non-empty-list', explode($s, 'foo')); + assertType('non-empty-list', explode($s, 'foo')); } /** @@ -320,12 +320,12 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo assertType('string', strtoupper($s)); assertType('non-empty-string', strtoupper($nonEmpty)); - assertType('string', strtolower($s)); - assertType('non-empty-string', strtolower($nonEmpty)); + assertType('lowercase-string', strtolower($s)); + assertType('lowercase-string&non-empty-string', strtolower($nonEmpty)); assertType('string', mb_strtoupper($s)); assertType('non-empty-string', mb_strtoupper($nonEmpty)); - assertType('string', mb_strtolower($s)); - assertType('non-empty-string', mb_strtolower($nonEmpty)); + assertType('lowercase-string', mb_strtolower($s)); + assertType('lowercase-string&non-empty-string', mb_strtolower($nonEmpty)); assertType('string', lcfirst($s)); assertType('non-empty-string', lcfirst($nonEmpty)); assertType('string', ucfirst($s)); diff --git a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php index f8c6dc40b72..4127dd58705 100644 --- a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php @@ -88,9 +88,9 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra assertType('non-falsy-string', escapeshellcmd($nonFalsey)); assertType('non-falsy-string', strtoupper($nonFalsey)); - assertType('non-falsy-string', strtolower($nonFalsey)); + assertType('lowercase-string&non-falsy-string', strtolower($nonFalsey)); assertType('non-falsy-string', mb_strtoupper($nonFalsey)); - assertType('non-falsy-string', mb_strtolower($nonFalsey)); + assertType('lowercase-string&non-falsy-string', mb_strtolower($nonFalsey)); assertType('non-falsy-string', lcfirst($nonFalsey)); assertType('non-falsy-string', ucfirst($nonFalsey)); assertType('non-falsy-string', ucwords($nonFalsey)); diff --git a/tests/PHPStan/Analyser/nsrt/str-casing.php b/tests/PHPStan/Analyser/nsrt/str-casing.php index df4883845af..3f0c76f2506 100644 --- a/tests/PHPStan/Analyser/nsrt/str-casing.php +++ b/tests/PHPStan/Analyser/nsrt/str-casing.php @@ -8,13 +8,14 @@ class Foo { /** * @param numeric-string $numericS * @param non-empty-string $nonE + * @param lowercase-string $lowercaseS * @param literal-string $literal * @param 'foo'|'Foo' $edgeUnion * @param MB_CASE_UPPER|MB_CASE_LOWER|MB_CASE_TITLE|MB_CASE_FOLD|MB_CASE_UPPER_SIMPLE|MB_CASE_LOWER_SIMPLE|MB_CASE_TITLE_SIMPLE|MB_CASE_FOLD_SIMPLE $caseMode * @param 'aKV'|'hA'|'AH'|'K'|'KV'|'RNKV' $kanaMode * @param mixed $mixed */ - public function bar($numericS, $nonE, $literal, $edgeUnion, $caseMode, $kanaMode, $mixed) { + public function bar($numericS, $nonE, $lowercaseS, $literal, $edgeUnion, $caseMode, $kanaMode, $mixed) { assertType("'abc'", strtolower('ABC')); assertType("'ABC'", strtoupper('abc')); assertType("'abc'", mb_strtolower('ABC')); @@ -37,51 +38,65 @@ public function bar($numericS, $nonE, $literal, $edgeUnion, $caseMode, $kanaMode assertType("'Abc123アガば漢'|'Abc123あか゛ば漢'|'Abc123アカ゛ば漢'|'Abc123アガば漢'|'Abc123アガバ漢'", mb_convert_kana('Abc123アガば漢', $kanaMode)); assertType("non-falsy-string", mb_convert_kana('Abc123アガば漢', $mixed)); - assertType("numeric-string", strtolower($numericS)); + assertType("lowercase-string&numeric-string", strtolower($numericS)); assertType("numeric-string", strtoupper($numericS)); - assertType("numeric-string", mb_strtolower($numericS)); + assertType("lowercase-string&numeric-string", mb_strtolower($numericS)); assertType("numeric-string", mb_strtoupper($numericS)); assertType("numeric-string", lcfirst($numericS)); assertType("numeric-string", ucfirst($numericS)); assertType("numeric-string", ucwords($numericS)); assertType("numeric-string", mb_convert_case($numericS, MB_CASE_UPPER)); - assertType("numeric-string", mb_convert_case($numericS, MB_CASE_LOWER)); + assertType("lowercase-string&numeric-string", mb_convert_case($numericS, MB_CASE_LOWER)); assertType("numeric-string", mb_convert_case($numericS, $mixed)); assertType("numeric-string", mb_convert_kana($numericS)); assertType("numeric-string", mb_convert_kana($numericS, $mixed)); - assertType("non-empty-string", strtolower($nonE)); + assertType("lowercase-string&non-empty-string", strtolower($nonE)); assertType("non-empty-string", strtoupper($nonE)); - assertType("non-empty-string", mb_strtolower($nonE)); + assertType("lowercase-string&non-empty-string", mb_strtolower($nonE)); assertType("non-empty-string", mb_strtoupper($nonE)); assertType("non-empty-string", lcfirst($nonE)); assertType("non-empty-string", ucfirst($nonE)); assertType("non-empty-string", ucwords($nonE)); assertType("non-empty-string", mb_convert_case($nonE, MB_CASE_UPPER)); - assertType("non-empty-string", mb_convert_case($nonE, MB_CASE_LOWER)); + assertType("lowercase-string&non-empty-string", mb_convert_case($nonE, MB_CASE_LOWER)); assertType("non-empty-string", mb_convert_case($nonE, $mixed)); assertType("non-empty-string", mb_convert_kana($nonE)); assertType("non-empty-string", mb_convert_kana($nonE, $mixed)); - assertType("string", strtolower($literal)); + assertType("lowercase-string", strtolower($literal)); assertType("string", strtoupper($literal)); - assertType("string", mb_strtolower($literal)); + assertType("lowercase-string", mb_strtolower($literal)); assertType("string", mb_strtoupper($literal)); assertType("string", lcfirst($literal)); assertType("string", ucfirst($literal)); assertType("string", ucwords($literal)); assertType("string", mb_convert_case($literal, MB_CASE_UPPER)); - assertType("string", mb_convert_case($literal, MB_CASE_LOWER)); + assertType("lowercase-string", mb_convert_case($literal, MB_CASE_LOWER)); assertType("string", mb_convert_case($literal, $mixed)); assertType("string", mb_convert_kana($literal)); assertType("string", mb_convert_kana($literal, $mixed)); + assertType("lowercase-string", strtolower($lowercaseS)); + assertType("string", strtoupper($lowercaseS)); + assertType("lowercase-string", mb_strtolower($lowercaseS)); + assertType("string", mb_strtoupper($lowercaseS)); + assertType("lowercase-string", lcfirst($lowercaseS)); + assertType("string", ucfirst($lowercaseS)); + assertType("string", ucwords($lowercaseS)); + assertType("string", mb_convert_case($lowercaseS, MB_CASE_UPPER)); + assertType("lowercase-string", mb_convert_case($lowercaseS, MB_CASE_LOWER)); + assertType("string", mb_convert_case($lowercaseS, $mixed)); + assertType("lowercase-string", mb_convert_case($lowercaseS, rand(0, 1) ? MB_CASE_LOWER : MB_CASE_LOWER_SIMPLE)); + assertType("string", mb_convert_kana($lowercaseS)); + assertType("string", mb_convert_kana($lowercaseS, $mixed)); + assertType("'foo'", lcfirst($edgeUnion)); } public function foo() { // invalid char conversions still lead to non-falsy-string - assertType("non-falsy-string", mb_strtolower("\xfe\xff\x65\xe5\x67\x2c\x8a\x9e", 'CP1252')); + assertType("lowercase-string&non-falsy-string", mb_strtolower("\xfe\xff\x65\xe5\x67\x2c\x8a\x9e", 'CP1252')); // valid char sequence, but not support non ASCII / UTF-8 encodings assertType("non-falsy-string", mb_convert_kana("\x95\x5c\x8c\xbb", 'SJIS-win')); // invalid UTF-8 sequence diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 2bfd60e993f..e6ff6be2d76 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1057,6 +1057,54 @@ public function testBug10697(): void $this->analyse([__DIR__ . '/data/bug-10697.php'], []); } + public function testLowercaseString(): void + { + $errors = [ + [ + "Strict comparison using === between lowercase-string and 'AB' will always evaluate to false.", + 10, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using === between 'AB' and lowercase-string will always evaluate to false.", + 11, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using !== between 'AB' and lowercase-string will always evaluate to true.", + 12, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using === between lowercase-string and 'aBc' will always evaluate to false.", + 15, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using !== between lowercase-string and 'aBc' will always evaluate to true.", + 16, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]; + + if (PHP_VERSION_ID < 80000) { + $errors[] = [ + "Strict comparison using === between lowercase-string|false and 'AB' will always evaluate to false.", + 28, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ]; + } else { + $errors[] = [ + "Strict comparison using === between lowercase-string and 'AB' will always evaluate to false.", + 28, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ]; + } + + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/lowercase-string.php'], $errors); + } + public function testBug10493(): void { $this->checkAlwaysTrueStrictComparison = true; diff --git a/tests/PHPStan/Rules/Comparison/data/lowercase-string.php b/tests/PHPStan/Rules/Comparison/data/lowercase-string.php new file mode 100644 index 00000000000..772939626ab --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/lowercase-string.php @@ -0,0 +1,31 @@ +analyse([__DIR__ . '/data/trait-mixin.php'], []); } + public function testLowercaseString(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/lowercase-string.php'], [ + [ + 'Parameter #1 $s of method LowercaseString\Bar::acceptLowercaseString() expects lowercase-string, \'NotLowerCase\' given.', + 26, + ], + [ + 'Parameter #1 $s of method LowercaseString\Bar::acceptLowercaseString() expects lowercase-string, string given.', + 28, + ], + [ + 'Parameter #1 $s of method LowercaseString\Bar::acceptLowercaseString() expects lowercase-string, numeric-string given.', + 30, + ], + ]); + } + public function testBug10159(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/lowercase-string.php b/tests/PHPStan/Rules/Methods/data/lowercase-string.php new file mode 100644 index 00000000000..40c475e9e53 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/lowercase-string.php @@ -0,0 +1,33 @@ +acceptLowercaseString('NotLowerCase'); + $this->acceptLowercaseString('lowercase'); + $this->acceptLowercaseString($string); + $this->acceptLowercaseString($lowercaseString); + $this->acceptLowercaseString($numericString); + $this->acceptLowercaseString($nonEmptyLowercaseString); + } +} diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 976917737ba..3845352fd63 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -153,14 +153,14 @@ public function testGeneralize(): void { $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-empty-string&numeric-string', (new ConstantStringType('0'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string&numeric-string', (new ConstantStringType('1.123'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType(' 1 1 '))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string&numeric-string', (new ConstantStringType('+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('+1+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string&numeric-string', (new ConstantStringType('1e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('1e91e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-empty-string&numeric-string', (new ConstantStringType('0'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('1.123'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType(' 1 1 '))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('+1+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('1e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('1e91e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType(stdClass::class))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); diff --git a/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php b/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php index e083852496f..2e2bcf976c5 100644 --- a/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php +++ b/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php @@ -28,7 +28,7 @@ public function dataBuild(): iterable yield [ '[1, 2, 3, ...[1, \'foo\' => 2, 3]]', - 'non-empty-array&oversized-array', + 'non-empty-array&oversized-array', ]; yield [ @@ -49,7 +49,7 @@ public function dataBuild(): iterable ]; yield [ '[1, \'foo\' => 2, 3]', - 'non-empty-array&oversized-array', + 'non-empty-array&oversized-array', ]; } diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index aae9faa0bd9..7c9bcc0722c 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -7,6 +7,7 @@ use ObjectTypeEnums\FooEnum; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -447,4 +448,31 @@ public function testGetEnumCases( } } + public function dataDescribe(): iterable + { + yield [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + VerbosityLevel::typeOnly(), + 'string', + ]; + yield [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + VerbosityLevel::value(), + 'string', + ]; + yield [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + VerbosityLevel::precise(), + 'lowercase-string', + ]; + } + + /** + * @dataProvider dataDescribe + */ + public function testDescribe(IntersectionType $type, VerbosityLevel $verbosityLevel, string $expected): void + { + static::assertSame($expected, $type->describe($verbosityLevel)); + } + } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 95e11da9738..55dc30542bd 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -19,6 +19,7 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -1968,6 +1969,46 @@ public function dataUnion(): iterable UnionType::class, 'literal-string|numeric-string', ], + [ + [ + new StringType(), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + StringType::class, + 'string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNumericStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + UnionType::class, + 'lowercase-string|numeric-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + UnionType::class, + 'lowercase-string|non-falsy-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + UnionType::class, + 'lowercase-string|non-empty-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLiteralStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + UnionType::class, + 'literal-string|lowercase-string', + ], [ [ TemplateTypeFactory::create( @@ -3853,6 +3894,46 @@ public function dataIntersect(): iterable NeverType::class, '*NEVER*=implicit', ], + [ + [ + new StringType(), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + IntersectionType::class, + 'lowercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNumericStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + IntersectionType::class, + 'lowercase-string&numeric-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + IntersectionType::class, + 'lowercase-string&non-falsy-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + IntersectionType::class, + 'lowercase-string&non-empty-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLiteralStringType()]), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + IntersectionType::class, + 'literal-string&lowercase-string', + ], ]; if (PHP_VERSION_ID < 80100) { @@ -4325,6 +4406,23 @@ public function dataIntersect(): iterable NeverType::class, '*NEVER*=implicit', ]; + + yield [ + [ + new ConstantStringType('FOO'), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + yield [ + [ + new ConstantStringType('foo'), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + ], + ConstantStringType::class, + '\'foo\'', + ]; } /** diff --git a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php index 592271bddac..eebdb080142 100644 --- a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php +++ b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php @@ -6,6 +6,7 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -217,6 +218,11 @@ public function dataToPhpDocNode(): iterable 'literal-string', ]; + yield [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + 'lowercase-string', + ]; + yield [ new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), 'non-empty-string', @@ -324,7 +330,7 @@ public function dataToPhpDocNodeWithoutCheckingEquals(): iterable { yield [ new ConstantStringType("foo\nbar\nbaz"), - '(literal-string & non-falsy-string)', + '(literal-string & lowercase-string & non-falsy-string)', ]; yield [ From 0e2587fae6e65be54ecbd47d33277d3f529d44d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:08:03 +0200 Subject: [PATCH 0437/3097] More specific return type for methods used to analyse currently entered function or method --- src/Analyser/MutatingScope.php | 5 ++--- src/Analyser/NodeScopeResolver.php | 2 +- src/Analyser/Scope.php | 6 ++---- src/Node/FunctionReturnStatementsNode.php | 6 +++--- src/Node/MethodReturnStatementsNode.php | 6 +++--- src/Rules/Api/ApiInstanceofRule.php | 3 +++ src/Rules/Functions/ReturnTypeRule.php | 8 ++------ 7 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7ac09d1e92e..589fd947d2c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -208,7 +208,7 @@ public function __construct( private ScopeContext $context, private PhpVersion $phpVersion, private bool $declareStrictTypes = false, - private FunctionReflection|ExtendedMethodReflection|null $function = null, + private PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, private array $expressionTypes = [], private array $nativeExpressionTypes = [], @@ -310,9 +310,8 @@ public function getTraitReflection(): ?ClassReflection /** * @api - * @return FunctionReflection|ExtendedMethodReflection|null */ - public function getFunction() + public function getFunction(): ?PhpFunctionFromParserNodeReflection { return $this->function; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6871211346e..c9ae8045466 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -728,7 +728,7 @@ private function processStmtNode( $classReflection = $scope->getClassReflection(); $methodReflection = $methodScope->getFunction(); - if (!$methodReflection instanceof ExtendedMethodReflection) { + if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { throw new ShouldNotHappenException(); } diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 53ceaa34437..0c1682209d3 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -16,6 +16,7 @@ use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; @@ -49,10 +50,7 @@ public function isInTrait(): bool; public function getTraitReflection(): ?ClassReflection; - /** - * @return FunctionReflection|ExtendedMethodReflection|null - */ - public function getFunction(); + public function getFunction(): ?PhpFunctionFromParserNodeReflection; public function getFunctionName(): ?string; diff --git a/src/Node/FunctionReturnStatementsNode.php b/src/Node/FunctionReturnStatementsNode.php index 0b065f8a25f..4ab53d25c31 100644 --- a/src/Node/FunctionReturnStatementsNode.php +++ b/src/Node/FunctionReturnStatementsNode.php @@ -9,7 +9,7 @@ use PhpParser\NodeAbstract; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\StatementResult; -use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use function count; /** @@ -32,7 +32,7 @@ public function __construct( private StatementResult $statementResult, private array $executionEnds, private array $impurePoints, - private FunctionReflection $functionReflection, + private PhpFunctionFromParserNodeReflection $functionReflection, ) { parent::__construct($function->getAttributes()); @@ -91,7 +91,7 @@ public function getSubNodeNames(): array return []; } - public function getFunctionReflection(): FunctionReflection + public function getFunctionReflection(): PhpFunctionFromParserNodeReflection { return $this->functionReflection; } diff --git a/src/Node/MethodReturnStatementsNode.php b/src/Node/MethodReturnStatementsNode.php index 0103f580597..96218713d35 100644 --- a/src/Node/MethodReturnStatementsNode.php +++ b/src/Node/MethodReturnStatementsNode.php @@ -10,7 +10,7 @@ use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\StatementResult; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use function count; /** @@ -36,7 +36,7 @@ public function __construct( private array $executionEnds, private array $impurePoints, private ClassReflection $classReflection, - private ExtendedMethodReflection $methodReflection, + private PhpMethodFromParserNodeReflection $methodReflection, ) { parent::__construct($method->getAttributes()); @@ -88,7 +88,7 @@ public function getClassReflection(): ClassReflection return $this->classReflection; } - public function getMethodReflection(): ExtendedMethodReflection + public function getMethodReflection(): PhpMethodFromParserNodeReflection { return $this->methodReflection; } diff --git a/src/Rules/Api/ApiInstanceofRule.php b/src/Rules/Api/ApiInstanceofRule.php index 7882c3473e0..db7c28afffc 100644 --- a/src/Rules/Api/ApiInstanceofRule.php +++ b/src/Rules/Api/ApiInstanceofRule.php @@ -84,6 +84,9 @@ private function processCoveredClass(Node\Expr\Instanceof_ $node, Scope $scope, if ($classReflection->getName() === Type::class || $classReflection->isSubclassOf(Type::class)) { return []; } + if ($classReflection->isInterface()) { + return []; + } $instanceofType = $scope->getType($node); if ($instanceofType->isTrue()->or($instanceofType->isFalse())->yes()) { diff --git a/src/Rules/Functions/ReturnTypeRule.php b/src/Rules/Functions/ReturnTypeRule.php index 4a02e64989d..8d4714f4239 100644 --- a/src/Rules/Functions/ReturnTypeRule.php +++ b/src/Rules/Functions/ReturnTypeRule.php @@ -5,9 +5,8 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; -use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\FunctionReturnTypeCheck; use PHPStan\Rules\Rule; use function sprintf; @@ -40,10 +39,7 @@ public function processNode(Node $node, Scope $scope): array } $function = $scope->getFunction(); - if ( - !($function instanceof PhpFunctionFromParserNodeReflection) - || $function instanceof PhpMethodFromParserNodeReflection - ) { + if ($function instanceof MethodReflection) { return []; } From 1bea5c79d53e06f6cdd481decba73b504fb4bec0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:17:13 +0200 Subject: [PATCH 0438/3097] PhpFunctionFromParserNodeReflection becomes ParametersAcceptorWithPhpDocs --- .../PhpFunctionFromParserNodeReflection.php | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 8e2429016c3..86ab3666329 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -16,6 +16,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; @@ -26,7 +27,7 @@ /** * @api */ -class PhpFunctionFromParserNodeReflection implements FunctionReflection +class PhpFunctionFromParserNodeReflection implements FunctionReflection, ParametersAcceptorWithPhpDocs { /** @var Function_|ClassMethod */ @@ -101,12 +102,12 @@ public function getVariants(): array if ($this->variants === null) { $this->variants = [ new FunctionVariantWithPhpDocs( - $this->templateTypeMap, - null, + $this->getTemplateTypeMap(), + $this->getResolvedTemplateTypeMap(), $this->getParameters(), $this->isVariadic(), $this->getReturnType(), - $this->phpDocReturnType ?? new MixedType(), + $this->getPhpDocReturnType(), $this->realReturnType, ), ]; @@ -120,10 +121,20 @@ public function getNamedArgumentsVariants(): ?array return null; } + public function getTemplateTypeMap(): TemplateTypeMap + { + return $this->templateTypeMap; + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return TemplateTypeMap::createEmpty(); + } + /** - * @return ParameterReflectionWithPhpDocs[] + * @return array */ - private function getParameters(): array + public function getParameters(): array { $parameters = []; $isOptional = true; @@ -169,7 +180,7 @@ private function getParameters(): array return array_reverse($parameters); } - private function isVariadic(): bool + public function isVariadic(): bool { foreach ($this->functionLike->getParams() as $parameter) { if ($parameter->variadic) { @@ -180,11 +191,26 @@ private function isVariadic(): bool return false; } - protected function getReturnType(): Type + public function getReturnType(): Type { return TypehintHelper::decideType($this->realReturnType, $this->phpDocReturnType); } + public function getPhpDocReturnType(): Type + { + return $this->phpDocReturnType ?? new MixedType(); + } + + public function getNativeReturnType(): Type + { + return $this->realReturnType; + } + + public function getCallSiteVarianceMap(): TemplateTypeVarianceMap + { + return TemplateTypeVarianceMap::createEmpty(); + } + public function getDeprecatedDescription(): ?string { if ($this->isDeprecated) { From 41916ba39d429d096d5234acedcfe75f22025785 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:22:31 +0200 Subject: [PATCH 0439/3097] Use methods directly on PhpFunctionFromParserNodeReflection instead of `selectSingle()` when analysing function body in rules --- .../Php/PhpFunctionFromParserNodeReflection.php | 2 +- .../ConstructorWithoutImpurePointsCollector.php | 4 +--- .../FunctionWithoutImpurePointsCollector.php | 4 +--- .../MethodWithoutImpurePointsCollector.php | 4 +--- src/Rules/FunctionDefinitionCheck.php | 14 ++++---------- .../IncompatibleDefaultParameterTypeRule.php | 5 +---- .../MissingFunctionParameterTypehintRule.php | 3 +-- .../MissingFunctionReturnTypehintRule.php | 3 +-- src/Rules/Functions/ReturnTypeRule.php | 3 +-- src/Rules/Generators/YieldFromTypeRule.php | 5 ++--- src/Rules/Generators/YieldInGeneratorRule.php | 3 +-- src/Rules/Generators/YieldTypeRule.php | 3 +-- .../Generics/FunctionSignatureVarianceRule.php | 3 +-- src/Rules/Generics/MethodSignatureVarianceRule.php | 3 +-- .../IncompatibleDefaultParameterTypeRule.php | 5 +---- .../Methods/MethodParameterComparisonHelper.php | 4 +--- src/Rules/Methods/MethodSignatureRule.php | 8 +++----- .../Methods/MissingMethodParameterTypehintRule.php | 3 +-- .../Methods/MissingMethodReturnTypehintRule.php | 3 +-- src/Rules/Methods/OverridingMethodRule.php | 6 ++---- src/Rules/Methods/ReturnTypeRule.php | 3 +-- src/Rules/Missing/MissingReturnRule.php | 3 +-- src/Rules/Playground/FunctionNeverRule.php | 3 +-- src/Rules/Playground/MethodNeverRule.php | 3 +-- src/Rules/Pure/PureFunctionRule.php | 6 ++---- src/Rules/Pure/PureMethodRule.php | 6 ++---- .../TooWideFunctionParameterOutTypeRule.php | 3 +-- .../TooWideFunctionReturnTypehintRule.php | 3 +-- .../TooWideMethodParameterOutTypeRule.php | 3 +-- .../TooWideMethodReturnTypehintRule.php | 3 +-- .../Variables/ParameterOutAssignedTypeRule.php | 4 +--- .../Variables/ParameterOutExecutionEndTypeRule.php | 4 +--- 32 files changed, 41 insertions(+), 91 deletions(-) diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 86ab3666329..538762ba0df 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -108,7 +108,7 @@ public function getVariants(): array $this->isVariadic(), $this->getReturnType(), $this->getPhpDocReturnType(), - $this->realReturnType, + $this->getNativeReturnType(), ), ]; } diff --git a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php index 40839309e5d..2dca4eb85e2 100644 --- a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Collectors\Collector; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use function count; use function strtolower; @@ -40,8 +39,7 @@ public function processNode(Node $node, Scope $scope) return null; } - $variant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - foreach ($variant->getParameters() as $parameter) { + foreach ($method->getParameters() as $parameter) { if (!$parameter->passedByReference()->createsNewVariable()) { continue; } diff --git a/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php b/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php index 6dafe5d35b2..7283f333d30 100644 --- a/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/FunctionWithoutImpurePointsCollector.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Collectors\Collector; use PHPStan\Node\FunctionReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use function count; /** @@ -38,8 +37,7 @@ public function processNode(Node $node, Scope $scope) return null; } - $variant = ParametersAcceptorSelector::selectSingle($function->getVariants()); - foreach ($variant->getParameters() as $parameter) { + foreach ($function->getParameters() as $parameter) { if (!$parameter->passedByReference()->createsNewVariable()) { continue; } diff --git a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php index f07b2aac37d..1ec55619b64 100644 --- a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Collectors\Collector; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use function count; /** @@ -38,8 +37,7 @@ public function processNode(Node $node, Scope $scope) return null; } - $variant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - foreach ($variant->getParameters() as $parameter) { + foreach ($method->getParameters() as $parameter) { if (!$parameter->passedByReference()->createsNewVariable()) { continue; } diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index e50021d7ed3..327c0a7be43 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -18,12 +18,11 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\Printer\NodeTypePrinter; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -65,7 +64,7 @@ public function __construct( */ public function checkFunction( Function_ $function, - FunctionReflection $functionReflection, + PhpFunctionFromParserNodeReflection $functionReflection, string $parameterMessage, string $returnMessage, string $unionTypesMessage, @@ -74,10 +73,8 @@ public function checkFunction( string $unresolvableReturnTypeMessage, ): array { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); - return $this->checkParametersAcceptor( - $parametersAcceptor, + $functionReflection, $function, $parameterMessage, $returnMessage, @@ -259,11 +256,8 @@ public function checkClassMethod( string $selfOutMessage, ): array { - /** @var ParametersAcceptorWithPhpDocs $parametersAcceptor */ - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - $errors = $this->checkParametersAcceptor( - $parametersAcceptor, + $methodReflection, $methodNode, $parameterMessage, $returnMessage, diff --git a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php index 7a64f9e7eb4..af9d403b348 100644 --- a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InFunctionNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -28,8 +27,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $function = $node->getFunctionReflection(); - $parameters = ParametersAcceptorSelector::selectSingle($function->getVariants()); - $errors = []; foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { if ($param->default === null) { @@ -43,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array } $defaultValueType = $scope->getType($param->default); - $parameterType = $parameters->getParameters()[$paramI]->getType(); + $parameterType = $function->getParameters()[$paramI]->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); $accepts = $parameterType->acceptsWithReason($defaultValueType, true); diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php index 73782fcf533..a7519de7a57 100644 --- a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\InFunctionNode; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; @@ -40,7 +39,7 @@ public function processNode(Node $node, Scope $scope): array $functionReflection = $node->getFunctionReflection(); $messages = []; - foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameterReflection) { + foreach ($functionReflection->getParameters() as $parameterReflection) { foreach ($this->checkFunctionParameter($functionReflection, sprintf('parameter $%s', $parameterReflection->getName()), $parameterReflection->getType()) as $parameterMessage) { $messages[] = $parameterMessage; } diff --git a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php index 1546a632190..d49e7f9aa93 100644 --- a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InFunctionNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -34,7 +33,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $functionReflection = $node->getFunctionReflection(); - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $returnType = $functionReflection->getReturnType(); if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { return [ diff --git a/src/Rules/Functions/ReturnTypeRule.php b/src/Rules/Functions/ReturnTypeRule.php index 8d4714f4239..9fdd33f14a7 100644 --- a/src/Rules/Functions/ReturnTypeRule.php +++ b/src/Rules/Functions/ReturnTypeRule.php @@ -6,7 +6,6 @@ use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionReturnTypeCheck; use PHPStan\Rules\Rule; use function sprintf; @@ -45,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array return $this->returnTypeCheck->checkReturnType( $scope, - ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(), + $function->getReturnType(), $node->expr, $node, sprintf( diff --git a/src/Rules/Generators/YieldFromTypeRule.php b/src/Rules/Generators/YieldFromTypeRule.php index 89a5d1fff1b..b73b5a35090 100644 --- a/src/Rules/Generators/YieldFromTypeRule.php +++ b/src/Rules/Generators/YieldFromTypeRule.php @@ -6,7 +6,6 @@ use PhpParser\Node; use PhpParser\Node\Expr\YieldFrom; use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -69,7 +68,7 @@ public function processNode(Node $node, Scope $scope): array if ($anonymousFunctionReturnType !== null) { $returnType = $anonymousFunctionReturnType; } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + $returnType = $scopeFunction->getReturnType(); } else { return []; // already reported by YieldInGeneratorRule } @@ -112,7 +111,7 @@ public function processNode(Node $node, Scope $scope): array return $messages; } - $currentReturnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + $currentReturnType = $scopeFunction->getReturnType(); $exprSendType = $exprType->getTemplateType(Generator::class, 'TSend'); $thisSendType = $currentReturnType->getTemplateType(Generator::class, 'TSend'); if ($exprSendType instanceof ErrorType || $thisSendType instanceof ErrorType) { diff --git a/src/Rules/Generators/YieldInGeneratorRule.php b/src/Rules/Generators/YieldInGeneratorRule.php index 25ddeef8bd9..c6283e8bb50 100644 --- a/src/Rules/Generators/YieldInGeneratorRule.php +++ b/src/Rules/Generators/YieldInGeneratorRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\TrinaryLogic; @@ -38,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array if ($anonymousFunctionReturnType !== null) { $returnType = $anonymousFunctionReturnType; } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + $returnType = $scopeFunction->getReturnType(); } else { return [ RuleErrorBuilder::message('Yield can be used only inside a function.') diff --git a/src/Rules/Generators/YieldTypeRule.php b/src/Rules/Generators/YieldTypeRule.php index d97c3eb76be..c8475e23a83 100644 --- a/src/Rules/Generators/YieldTypeRule.php +++ b/src/Rules/Generators/YieldTypeRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -38,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array if ($anonymousFunctionReturnType !== null) { $returnType = $anonymousFunctionReturnType; } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + $returnType = $scopeFunction->getReturnType(); } else { return []; // already reported by YieldInGeneratorRule } diff --git a/src/Rules/Generics/FunctionSignatureVarianceRule.php b/src/Rules/Generics/FunctionSignatureVarianceRule.php index e73c28da760..65e0f7bca32 100644 --- a/src/Rules/Generics/FunctionSignatureVarianceRule.php +++ b/src/Rules/Generics/FunctionSignatureVarianceRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InFunctionNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -31,7 +30,7 @@ public function processNode(Node $node, Scope $scope): array $functionName = $functionReflection->getName(); return $this->varianceCheck->checkParametersAcceptor( - ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()), + $functionReflection, sprintf('in parameter %%s of function %s()', SprintfHelper::escapeFormatString($functionName)), sprintf('in param-out type of parameter %%s of function %s()', SprintfHelper::escapeFormatString($functionName)), sprintf('in return type of function %s()', $functionName), diff --git a/src/Rules/Generics/MethodSignatureVarianceRule.php b/src/Rules/Generics/MethodSignatureVarianceRule.php index 4590950baae..44543983dc4 100644 --- a/src/Rules/Generics/MethodSignatureVarianceRule.php +++ b/src/Rules/Generics/MethodSignatureVarianceRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassMethodNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -30,7 +29,7 @@ public function processNode(Node $node, Scope $scope): array $method = $node->getMethodReflection(); return $this->varianceCheck->checkParametersAcceptor( - ParametersAcceptorSelector::selectSingle($method->getVariants()), + $method, sprintf('in parameter %%s of method %s::%s()', SprintfHelper::escapeFormatString($method->getDeclaringClass()->getDisplayName()), SprintfHelper::escapeFormatString($method->getName())), sprintf('in param-out type of parameter %%s of method %s::%s()', SprintfHelper::escapeFormatString($method->getDeclaringClass()->getDisplayName()), SprintfHelper::escapeFormatString($method->getName())), sprintf('in return type of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), diff --git a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php index 409b32d65f4..d8814af6012 100644 --- a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -28,8 +27,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $method = $node->getMethodReflection(); - $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $errors = []; foreach ($node->getOriginalNode()->getParams() as $paramI => $param) { if ($param->default === null) { @@ -43,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array } $defaultValueType = $scope->getType($param->default); - $parameter = $parameters->getParameters()[$paramI]; + $parameter = $method->getParameters()[$paramI]; $parameterType = $parameter->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 2f21d52123c..d8bb7f1f4fb 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -5,7 +5,6 @@ use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -37,8 +36,7 @@ public function compare(ExtendedMethodReflection $prototype, ClassReflection $pr $messages = []; $prototypeVariant = $prototype->getVariants()[0]; - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodParameters = $methodVariant->getParameters(); + $methodParameters = $method->getParameters(); $prototypeAfterVariadic = false; foreach ($prototypeVariant->getParameters() as $i => $prototypeParameter) { diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 1e9d6b1ba27..b0965e55bc8 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -67,8 +67,6 @@ public function processNode(Node $node, Scope $scope): array if ($method->isPrivate()) { return []; } - $parameters = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $errors = []; $declaringClass = $method->getDeclaringClass(); foreach ($this->collectParentMethods($methodName, $method->getDeclaringClass()) as [$parentMethod, $parentMethodDeclaringClass]) { @@ -77,7 +75,7 @@ public function processNode(Node $node, Scope $scope): array continue; } $parentParameters = ParametersAcceptorSelector::selectSingle($parentVariants); - [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $parameters, $parentParameters); + [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $method, $parentParameters); if ($returnTypeCompatibility->no() || (!$returnTypeCompatibility->yes() && $this->reportMaybes)) { $builder = RuleErrorBuilder::message(sprintf( 'Return type (%s) of method %s::%s() should be %s with return type (%s) of method %s::%s()', @@ -116,7 +114,7 @@ public function processNode(Node $node, Scope $scope): array $errors[] = $builder->build(); } - $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $parameters->getParameters(), $parentParameters->getParameters()); + $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $method->getParameters(), $parentParameters->getParameters()); foreach ($parameterResults as $parameterIndex => [$parameterResult, $parameterType, $parentParameterType]) { if ($parameterResult->yes()) { continue; @@ -124,7 +122,7 @@ public function processNode(Node $node, Scope $scope): array if (!$parameterResult->no() && !$this->reportMaybes) { continue; } - $parameter = $parameters->getParameters()[$parameterIndex]; + $parameter = $method->getParameters()[$parameterIndex]; $parentParameter = $parentParameters->getParameters()[$parameterIndex]; $errors[] = RuleErrorBuilder::message(sprintf( 'Parameter #%d $%s (%s) of method %s::%s() should be %s with parameter $%s (%s) of method %s::%s()', diff --git a/src/Rules/Methods/MissingMethodParameterTypehintRule.php b/src/Rules/Methods/MissingMethodParameterTypehintRule.php index 5ef4db93d13..32d64a68ade 100644 --- a/src/Rules/Methods/MissingMethodParameterTypehintRule.php +++ b/src/Rules/Methods/MissingMethodParameterTypehintRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; @@ -40,7 +39,7 @@ public function processNode(Node $node, Scope $scope): array $methodReflection = $node->getMethodReflection(); $messages = []; - foreach (ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getParameters() as $parameterReflection) { + foreach ($methodReflection->getParameters() as $parameterReflection) { foreach ($this->checkMethodParameter($methodReflection, sprintf('parameter $%s', $parameterReflection->getName()), $parameterReflection->getType()) as $parameterMessage) { $messages[] = $parameterMessage; } diff --git a/src/Rules/Methods/MissingMethodReturnTypehintRule.php b/src/Rules/Methods/MissingMethodReturnTypehintRule.php index e0e6685ae90..e48ed2d7850 100644 --- a/src/Rules/Methods/MissingMethodReturnTypehintRule.php +++ b/src/Rules/Methods/MissingMethodReturnTypehintRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -39,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array return []; } } - $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + $returnType = $methodReflection->getReturnType(); if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { return [ diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 9d414f6f2b0..29a936b35a7 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -11,7 +11,6 @@ use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\Native\NativeMethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\NativeBuiltinMethodReflection; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Reflection\Php\PhpMethodReflection; @@ -204,8 +203,7 @@ public function processNode(Node $node, Scope $scope): array $prototypeVariant = $prototypeVariants[0]; - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodReturnType = $methodVariant->getNativeReturnType(); + $methodReturnType = $method->getNativeReturnType(); $realPrototype = $method->getPrototype(); @@ -216,7 +214,7 @@ public function processNode(Node $node, Scope $scope): array && !$this->hasReturnTypeWillChangeAttribute($node->getOriginalNode()) && count($prototypeDeclaringClass->getNativeReflection()->getMethod($prototype->getName())->getAttributes('ReturnTypeWillChange')) === 0 ) { - if (!$this->methodParameterComparisonHelper->isReturnTypeCompatible($realPrototype->getTentativeReturnType(), $methodVariant->getNativeReturnType(), true)) { + if (!$this->methodParameterComparisonHelper->isReturnTypeCompatible($realPrototype->getTentativeReturnType(), $method->getNativeReturnType(), true)) { $messages[] = RuleErrorBuilder::message(sprintf( 'Return type %s of method %s::%s() is not covariant with tentative return type %s of method %s::%s().', $methodReturnType->describe(VerbosityLevel::typeOnly()), diff --git a/src/Rules/Methods/ReturnTypeRule.php b/src/Rules/Methods/ReturnTypeRule.php index 00753a620c3..9f851ce9c6e 100644 --- a/src/Rules/Methods/ReturnTypeRule.php +++ b/src/Rules/Methods/ReturnTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\FunctionReturnTypeCheck; use PHPStan\Rules\IdentifierRuleError; @@ -53,7 +52,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + $returnType = $method->getReturnType(); $errors = $this->returnTypeCheck->checkReturnType( $scope, $returnType, diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index a1f16bbd07d..9a3e64e3ac8 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -7,7 +7,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\ExecutionEndNode; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -55,7 +54,7 @@ public function processNode(Node $node, Scope $scope): array return []; } } elseif ($scopeFunction !== null) { - $returnType = ParametersAcceptorSelector::selectSingle($scopeFunction->getVariants())->getReturnType(); + $returnType = $scopeFunction->getReturnType(); if ($scopeFunction instanceof MethodReflection) { $description = sprintf('Method %s::%s()', $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getName()); } else { diff --git a/src/Rules/Playground/FunctionNeverRule.php b/src/Rules/Playground/FunctionNeverRule.php index 84f6db5239a..5bb47145214 100644 --- a/src/Rules/Playground/FunctionNeverRule.php +++ b/src/Rules/Playground/FunctionNeverRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\FunctionReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function count; @@ -34,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array $function = $node->getFunctionReflection(); - $returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); + $returnType = $function->getReturnType(); $helperResult = $this->helper->shouldReturnNever($node, $returnType); if ($helperResult === false) { return []; diff --git a/src/Rules/Playground/MethodNeverRule.php b/src/Rules/Playground/MethodNeverRule.php index d07066b4cc9..fdef4e9396e 100644 --- a/src/Rules/Playground/MethodNeverRule.php +++ b/src/Rules/Playground/MethodNeverRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function count; @@ -34,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array $method = $node->getMethodReflection(); - $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + $returnType = $method->getReturnType(); $helperResult = $this->helper->shouldReturnNever($node, $returnType); if ($helperResult === false) { return []; diff --git a/src/Rules/Pure/PureFunctionRule.php b/src/Rules/Pure/PureFunctionRule.php index 2355503a93d..622a093e0bf 100644 --- a/src/Rules/Pure/PureFunctionRule.php +++ b/src/Rules/Pure/PureFunctionRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\FunctionReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -27,14 +26,13 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $function = $node->getFunctionReflection(); - $variant = ParametersAcceptorSelector::selectSingle($function->getVariants()); return $this->check->check( sprintf('Function %s()', $function->getName()), 'Function', $function, - $variant->getParameters(), - $variant->getReturnType(), + $function->getParameters(), + $function->getReturnType(), $node->getImpurePoints(), $node->getStatementResult()->getThrowPoints(), $node->getStatements(), diff --git a/src/Rules/Pure/PureMethodRule.php b/src/Rules/Pure/PureMethodRule.php index 30685b4d6ea..5dd972f7096 100644 --- a/src/Rules/Pure/PureMethodRule.php +++ b/src/Rules/Pure/PureMethodRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -27,14 +26,13 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $method = $node->getMethodReflection(); - $variant = ParametersAcceptorSelector::selectSingle($method->getVariants()); return $this->check->check( sprintf('Method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), 'Method', $method, - $variant->getParameters(), - $variant->getReturnType(), + $method->getParameters(), + $method->getReturnType(), $node->getImpurePoints(), $node->getStatementResult()->getThrowPoints(), $node->getStatements(), diff --git a/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php b/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php index 6c62fea6885..8e2c1f57e8e 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionParameterOutTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\FunctionReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -33,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array return $this->check->check( $node->getExecutionEnds(), $node->getReturnStatements(), - ParametersAcceptorSelector::selectSingle($inFunction->getVariants())->getParameters(), + $inFunction->getParameters(), sprintf('Function %s()', $inFunction->getName()), ); } diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 4f4aedec6db..217d8576cdc 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\FunctionReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\TypeCombinator; @@ -30,7 +29,7 @@ public function processNode(Node $node, Scope $scope): array { $function = $node->getFunctionReflection(); - $functionReturnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); + $functionReturnType = $function->getReturnType(); $functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType); if (!$functionReturnType instanceof UnionType) { return []; diff --git a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php index b404ff34646..9c098e27b25 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodParameterOutTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use function sprintf; @@ -33,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array return $this->check->check( $node->getExecutionEnds(), $node->getReturnStatements(), - ParametersAcceptorSelector::selectSingle($inMethod->getVariants())->getParameters(), + $inMethod->getParameters(), sprintf('Method %s::%s()', $inMethod->getDeclaringClass()->getDisplayName(), $inMethod->getName()), ); } diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 0c74e65a023..ebb22df8ae5 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\MethodReturnStatementsNode; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantBooleanType; @@ -56,7 +55,7 @@ public function processNode(Node $node, Scope $scope): array } } - $methodReturnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + $methodReturnType = $method->getReturnType(); $methodReturnType = TypeUtils::resolveLateResolvableTypes($methodReturnType); if (!$methodReturnType instanceof UnionType) { return []; diff --git a/src/Rules/Variables/ParameterOutAssignedTypeRule.php b/src/Rules/Variables/ParameterOutAssignedTypeRule.php index 0d4487d2173..e24a938064f 100644 --- a/src/Rules/Variables/ParameterOutAssignedTypeRule.php +++ b/src/Rules/Variables/ParameterOutAssignedTypeRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\VariableAssignNode; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -50,8 +49,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $variant = ParametersAcceptorSelector::selectSingle($inFunction->getVariants()); - $parameters = $variant->getParameters(); + $parameters = $inFunction->getParameters(); $foundParameter = null; foreach ($parameters as $parameter) { if (!$parameter->passedByReference()->createsNewVariable()) { diff --git a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php index 177079ac6cc..b87573b8ff3 100644 --- a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php +++ b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php @@ -9,7 +9,6 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -61,8 +60,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $variant = ParametersAcceptorSelector::selectSingle($inFunction->getVariants()); - $parameters = $variant->getParameters(); + $parameters = $inFunction->getParameters(); $errors = []; foreach ($parameters as $parameter) { if (!$parameter->passedByReference()->createsNewVariable()) { From 865c618f82030cbc2e915c6da6bd424bc9b8aa41 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:31:50 +0200 Subject: [PATCH 0440/3097] Use methods directly on PhpFunctionFromParserNodeReflection instead of `selectSingle()` in MutatingScope --- src/Analyser/MutatingScope.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 589fd947d2c..135a0deae9d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1716,7 +1716,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return new MixedType(); } - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $returnType = $functionReflection->getReturnType(); $generatorSendType = $returnType->getTemplateType(Generator::class, 'TSend'); if ($generatorSendType instanceof ErrorType) { return new MixedType(); @@ -3134,17 +3134,16 @@ private function enterFunctionLike( bool $preserveThis, ): self { - $acceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); $parametersByName = []; - foreach ($acceptor->getParameters() as $parameter) { + foreach ($functionReflection->getParameters() as $parameter) { $parametersByName[$parameter->getName()] = $parameter; } $expressionTypes = []; $nativeExpressionTypes = []; $conditionalTypes = []; - foreach ($acceptor->getParameters() as $parameter) { + foreach ($functionReflection->getParameters() as $parameter) { $parameterType = $parameter->getType(); if ($parameterType instanceof ConditionalTypeForParameter) { From e283d3a6df7df502f3c3e70bc086dd2018dc965b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:37:47 +0200 Subject: [PATCH 0441/3097] Use `ParametersAcceptorSelector::selectFromArgs()` instead of `selectSingle()` wherever possible --- src/Internal/ContainerDynamicReturnTypeExtension.php | 12 ++++++++++-- src/Type/Php/HashFunctionsReturnTypeExtension.php | 6 +++++- .../JsonThrowOnErrorDynamicReturnTypeExtension.php | 6 +++++- src/Type/Php/MbFunctionsReturnTypeExtension.php | 6 +++++- src/Type/Php/MbStrlenFunctionReturnTypeExtension.php | 6 +++++- .../Php/PregFilterFunctionReturnTypeExtension.php | 6 +++++- .../Php/StrtotimeFunctionReturnTypeExtension.php | 6 +++++- ...allReturnsBoolExpressionTypeResolverExtension.php | 6 +++++- tests/PHPStan/Analyser/nsrt/preg_filter.php | 4 ++-- 9 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/Internal/ContainerDynamicReturnTypeExtension.php b/src/Internal/ContainerDynamicReturnTypeExtension.php index 7ca1a0a70b0..0fc017e67f5 100644 --- a/src/Internal/ContainerDynamicReturnTypeExtension.php +++ b/src/Internal/ContainerDynamicReturnTypeExtension.php @@ -33,11 +33,19 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->getArgs()) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } $argType = $scope->getType($methodCall->getArgs()[0]->value); if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } $type = new ObjectType($argType->getValue()); diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 72b26d3dd50..925873293da 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -88,7 +88,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); if (!isset($functionCall->getArgs()[0])) { return $defaultReturnType; diff --git a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php index eda7d4df477..65d82ccd7bc 100644 --- a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php @@ -54,7 +54,11 @@ public function getTypeFromFunctionCall( ): Type { $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); if ($functionReflection->getName() === 'json_decode') { $defaultReturnType = $this->narrowTypeForJsonDecode($functionCall, $scope, $defaultReturnType); diff --git a/src/Type/Php/MbFunctionsReturnTypeExtension.php b/src/Type/Php/MbFunctionsReturnTypeExtension.php index d6cb1445062..dc27fe349b4 100644 --- a/src/Type/Php/MbFunctionsReturnTypeExtension.php +++ b/src/Type/Php/MbFunctionsReturnTypeExtension.php @@ -47,7 +47,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $returnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); $positionEncodingParam = $this->encodingPositionMap[$functionReflection->getName()]; if (count($functionCall->getArgs()) < $positionEncodingParam) { diff --git a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php index fe28eb34ca9..73bdfa483ff 100644 --- a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php @@ -147,7 +147,11 @@ public function getTypeFromFunctionCall( $range = new ConstantIntegerType(0); } else { $range = TypeCombinator::remove( - ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(), + ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(), new ConstantBooleanType(false), ); } diff --git a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php index acd5ab309bb..c2c23e4155e 100644 --- a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php @@ -25,7 +25,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $defaultReturn = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $defaultReturn = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); $argsCount = count($functionCall->getArgs()); if ($argsCount < 3) { diff --git a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php index 479adf4c435..084ee527afa 100644 --- a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php @@ -31,7 +31,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); if (count($functionCall->getArgs()) === 0) { return $defaultReturnType; } diff --git a/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php b/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php index fa6fb0a43ae..83a8ff826db 100644 --- a/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php +++ b/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php @@ -34,7 +34,11 @@ public function getType(Expr $expr, Scope $scope): ?Type return null; } - $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + $returnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); if ($returnType instanceof StringType) { return null; diff --git a/tests/PHPStan/Analyser/nsrt/preg_filter.php b/tests/PHPStan/Analyser/nsrt/preg_filter.php index 02824d3c9c6..aedf0bca2ac 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_filter.php +++ b/tests/PHPStan/Analyser/nsrt/preg_filter.php @@ -24,10 +24,10 @@ function doFoo1() { function doFoo2() { $subject = 123; - assertType('list|string|null', preg_filter('/\d/', '$0', $subject)); + assertType('string|null', preg_filter('/\d/', '$0', $subject)); $subject = 123.123; - assertType('list|string|null', preg_filter('/\d/', '$0', $subject)); + assertType('string|null', preg_filter('/\d/', '$0', $subject)); } public function dooFoo3(string $pattern, string $replace) { From 7e216a274f00bf0a77a72628c884fdd4cb6c24c6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:39:26 +0200 Subject: [PATCH 0442/3097] Use PhpFunctionFromParserNodeReflection as ParametersAcceptor in DependencyResolver --- src/Dependency/DependencyResolver.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 57a80a5a2e9..f9bfcf314b1 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -18,7 +18,6 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ClosureType; @@ -70,9 +69,8 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies } } elseif ($node instanceof InClassMethodNode) { $nativeMethod = $node->getMethodReflection(); - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants()); $this->extractThrowType($nativeMethod->getThrowType(), $dependenciesReflections); - $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); + $this->extractFromParametersAcceptor($nativeMethod, $dependenciesReflections); foreach ($nativeMethod->getAsserts()->getAll() as $assertTag) { foreach ($assertTag->getType()->getReferencedClasses() as $referencedClass) { $this->addClassToDependencies($referencedClass, $dependenciesReflections); @@ -103,9 +101,8 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies } elseif ($node instanceof InFunctionNode) { $functionReflection = $node->getFunctionReflection(); $this->extractThrowType($functionReflection->getThrowType(), $dependenciesReflections); - $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); - $this->extractFromParametersAcceptor($parametersAcceptor, $dependenciesReflections); + $this->extractFromParametersAcceptor($functionReflection, $dependenciesReflections); foreach ($functionReflection->getAsserts()->getAll() as $assertTag) { foreach ($assertTag->getType()->getReferencedClasses() as $referencedClass) { $this->addClassToDependencies($referencedClass, $dependenciesReflections); From 1322aaf1d029c8db49c4c72742cb3d46f56be132 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:41:07 +0200 Subject: [PATCH 0443/3097] Use methods directly on PhpFunctionFromParserNodeReflection instead of `selectSingle()` in ParametersAcceptorSelector --- src/Reflection/ParametersAcceptorSelector.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 4d14c56ced8..3ec43fd80fd 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -266,9 +266,8 @@ public static function selectFromArgs( if ((new ObjectType(Closure::class))->isSuperTypeOf($varType)->yes()) { $inFunction = $scope->getFunction(); if ($inFunction !== null) { - $inFunctionVariant = self::selectSingle($inFunction->getVariants()); $closureThisParameters = []; - foreach ($inFunctionVariant->getParameters() as $parameter) { + foreach ($inFunction->getParameters() as $parameter) { if ($parameter->getClosureThisType() === null) { continue; } @@ -310,9 +309,8 @@ public static function selectFromArgs( $closureVarName = $args[0]->value->name; $inFunction = $scope->getFunction(); if ($inFunction !== null) { - $inFunctionVariant = self::selectSingle($inFunction->getVariants()); $closureThisParameters = []; - foreach ($inFunctionVariant->getParameters() as $parameter) { + foreach ($inFunction->getParameters() as $parameter) { if ($parameter->getClosureThisType() === null) { continue; } From 714877be8cafc1ba08610929e4dcb0d43273cc8d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:42:29 +0200 Subject: [PATCH 0444/3097] Introduce `@internal` `getOnlyVariant()` method on FunctionReflection/ExtendedMethodReflection to use instead of `selectSingle()` --- src/Analyser/MutatingScope.php | 2 +- .../AnnotationMethodReflection.php | 6 +++++ src/Reflection/ClassReflection.php | 2 +- .../Dummy/ChangedTypeMethodReflection.php | 12 +++++++++ .../Dummy/DummyConstructorReflection.php | 6 +++++ .../Dummy/DummyMethodReflection.php | 6 +++++ src/Reflection/ExtendedMethodReflection.php | 5 ++++ src/Reflection/FunctionReflection.php | 5 ++++ .../Native/NativeFunctionReflection.php | 15 ++++++++--- .../Native/NativeMethodReflection.php | 12 +++++++++ .../Php/ClosureCallMethodReflection.php | 6 +++++ .../Php/EnumCasesMethodReflection.php | 6 +++++ src/Reflection/Php/ExitFunctionReflection.php | 5 ++++ .../PhpFunctionFromParserNodeReflection.php | 5 ++++ src/Reflection/Php/PhpFunctionReflection.php | 5 ++++ src/Reflection/Php/PhpMethodReflection.php | 5 ++++ ...alObjectCratesClassReflectionExtension.php | 5 ++-- src/Reflection/ResolvedMethodReflection.php | 5 ++++ .../Type/IntersectionTypeMethodReflection.php | 11 ++++++++ .../Type/UnionTypeMethodReflection.php | 6 +++++ .../WrappedExtendedMethodReflection.php | 5 ++++ src/Rules/Methods/MethodSignatureRule.php | 9 +++---- src/Type/ObjectType.php | 25 ++++++------------- .../Analyser/AnalyserIntegrationTest.php | 5 ++-- .../ArgumentsNormalizerLegacyTest.php | 9 +++---- ...onsMethodsClassReflectionExtensionTest.php | 3 +-- .../Reflection/ClassReflectionTest.php | 2 +- tests/PHPStan/Reflection/MixedTypeTest.php | 4 +-- 28 files changed, 149 insertions(+), 43 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 135a0deae9d..086a09c3c2d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5514,7 +5514,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type $assignedToProperty = $node->getAttribute(NewAssignedToPropertyVisitor::ATTRIBUTE_NAME); if ($assignedToProperty !== null) { - $constructorVariant = ParametersAcceptorSelector::selectSingle($constructorMethod->getVariants()); + $constructorVariant = $constructorMethod->getOnlyVariant(); $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); $originalClassTemplateTypes = $classTemplateTypes; foreach ($constructorVariant->getParameters() as $parameter) { diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 0486d11048e..b4065332ce8 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -83,6 +84,11 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 9527e526b74..d980be78649 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1305,7 +1305,7 @@ private function findAttributeFlags(): ?int return null; } $attributeConstructor = $attributeClass->getConstructor(); - $attributeConstructorVariant = ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants()); + $attributeConstructorVariant = $attributeConstructor->getOnlyVariant(); if (count($arguments) === 0) { $flagType = $attributeConstructorVariant->getParameters()[0]->getDefaultValue(); diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 69d7a7a1a6d..b81e30e8465 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -7,8 +7,10 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; +use function count; use function is_bool; final class ChangedTypeMethodReflection implements ExtendedMethodReflection @@ -62,6 +64,16 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + $variants = $this->getVariants(); + if (count($variants) !== 1) { + throw new ShouldNotHappenException(); + } + + return $variants[0]; + } + public function getNamedArgumentsVariants(): ?array { return $this->namedArgumentsVariants; diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 7f81cd13637..ca67c3c2e27 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -66,6 +67,11 @@ public function getVariants(): array ]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index 79e85fda5bd..ba4faeded3b 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -6,6 +6,7 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; @@ -58,6 +59,11 @@ public function getVariants(): array ]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 14f7061aa46..7cb583f1dca 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -27,6 +27,11 @@ interface ExtendedMethodReflection extends MethodReflection */ public function getVariants(): array; + /** + * @internal + */ + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; + /** * @return ParametersAcceptorWithPhpDocs[]|null */ diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index bdd5ed8d63f..e09ceb27edd 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -18,6 +18,11 @@ public function getFileName(): ?string; */ public function getVariants(): array; + /** + * @internal + */ + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; + /** * @return ParametersAcceptorWithPhpDocs[]|null */ diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 2dd98f29517..c2a61a01318 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -5,8 +5,10 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; +use function count; final class NativeFunctionReflection implements FunctionReflection { @@ -46,14 +48,21 @@ public function getFileName(): ?string return null; } - /** - * @return ParametersAcceptorWithPhpDocs[] - */ public function getVariants(): array { return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + $variants = $this->getVariants(); + if (count($variants) !== 1) { + throw new ShouldNotHappenException(); + } + + return $variants[0]; + } + public function getNamedArgumentsVariants(): ?array { return $this->namedArgumentsVariants; diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 425f75edd6f..d588cea5586 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -10,10 +10,12 @@ use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\BuiltinMethodReflection; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; use ReflectionException; +use function count; use function strtolower; final class NativeMethodReflection implements ExtendedMethodReflection @@ -109,6 +111,16 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + $variants = $this->getVariants(); + if (count($variants) !== 1) { + throw new ShouldNotHappenException(); + } + + return $variants[0]; + } + public function getNamedArgumentsVariants(): ?array { return $this->namedArgumentsVariants; diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index 0f47e2c746a..be5d97660fe 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\ClosureType; @@ -105,6 +106,11 @@ public function getVariants(): array ]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index ec9f1b3b9dc..ff5fd5f9a54 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -75,6 +76,11 @@ public function getVariants(): array ]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index e343d801b1c..dc8f74eceab 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -69,6 +69,11 @@ public function getVariants(): array ]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + /** * @return ParametersAcceptorWithPhpDocs[] */ diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 538762ba0df..96ba2f6be1a 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -116,6 +116,11 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index a52f3829f8f..1f3ffee131e 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -107,6 +107,11 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 5a75f85a8ac..390ebc886a0 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -212,6 +212,11 @@ public function getVariants(): array return $this->variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index be769e05a08..a39fe6c67b6 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -4,7 +4,6 @@ use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PropertiesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProvider; @@ -66,13 +65,13 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa } if ($classReflection->hasNativeMethod('__get')) { - $readableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__get')->getVariants())->getReturnType(); + $readableType = $classReflection->getNativeMethod('__get')->getOnlyVariant()->getReturnType(); } else { $readableType = new MixedType(); } if ($classReflection->hasNativeMethod('__set')) { - $writableType = ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__set')->getVariants())->getParameters()[1]->getType(); + $writableType = $classReflection->getNativeMethod('__set')->getOnlyVariant()->getParameters()[1]->getType(); } else { $writableType = new MixedType(); } diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 69863e86665..477cbdea74c 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -52,6 +52,11 @@ public function getVariants(): array return $this->variants = $this->resolveVariants($this->reflection->getVariants()); } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { $variants = $this->namedArgumentVariants; diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index c4478755802..65c3b8b1528 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -94,6 +95,16 @@ public function getVariants(): array ), $this->methods[0]->getVariants()); } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + $variants = $this->getVariants(); + if (count($variants) !== 1) { + throw new ShouldNotHappenException(); + } + + return $variants[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 3b2598c3687..38083044444 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -82,6 +83,11 @@ public function getVariants(): array return [ParametersAcceptorSelector::combineAcceptors($variants)]; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index 78f31cdce6f..12d263b0e69 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -87,6 +87,11 @@ public function getVariants(): array return $variants; } + public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + { + return $this->getVariants()[0]; + } + public function getNamedArgumentsVariants(): ?array { return null; diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index b0965e55bc8..4cd7fd8277f 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -8,7 +8,6 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\NativeBuiltinMethodReflection; use PHPStan\Reflection\Php\PhpClassReflectionExtension; @@ -74,8 +73,8 @@ public function processNode(Node $node, Scope $scope): array if (count($parentVariants) !== 1) { continue; } - $parentParameters = ParametersAcceptorSelector::selectSingle($parentVariants); - [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $method, $parentParameters); + $parentVariant = $parentVariants[0]; + [$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($declaringClass, $method, $parentVariant); if ($returnTypeCompatibility->no() || (!$returnTypeCompatibility->yes() && $this->reportMaybes)) { $builder = RuleErrorBuilder::message(sprintf( 'Return type (%s) of method %s::%s() should be %s with return type (%s) of method %s::%s()', @@ -114,7 +113,7 @@ public function processNode(Node $node, Scope $scope): array $errors[] = $builder->build(); } - $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $method->getParameters(), $parentParameters->getParameters()); + $parameterResults = $this->checkParameterTypeCompatibility($declaringClass, $method->getParameters(), $parentVariant->getParameters()); foreach ($parameterResults as $parameterIndex => [$parameterResult, $parameterType, $parentParameterType]) { if ($parameterResult->yes()) { continue; @@ -123,7 +122,7 @@ public function processNode(Node $node, Scope $scope): array continue; } $parameter = $method->getParameters()[$parameterIndex]; - $parentParameter = $parentParameters->getParameters()[$parameterIndex]; + $parentParameter = $parentVariant->getParameters()[$parameterIndex]; $errors[] = RuleErrorBuilder::message(sprintf( 'Parameter #%d $%s (%s) of method %s::%s() should be %s with parameter $%s (%s) of method %s::%s()', $parameterIndex + 1, diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index b756e3f77d7..31a9613e918 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -24,7 +24,6 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; @@ -601,7 +600,7 @@ public function toString(): Type } if ($classReflection->hasNativeMethod('__toString')) { - return ParametersAcceptorSelector::selectSingle($this->getMethod('__toString', new OutOfClassScope())->getVariants())->getReturnType(); + return $this->getMethod('__toString', new OutOfClassScope())->getOnlyVariant()->getReturnType(); } return new ErrorType(); @@ -872,9 +871,7 @@ public function getIterableKeyType(): Type { $isTraversable = false; if ($this->isInstanceOf(IteratorAggregate::class)->yes()) { - $keyType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle( - $this->getMethod('getIterator', new OutOfClassScope())->getVariants(), - )->getReturnType()->getIterableKeyType()); + $keyType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableKeyType()); $isTraversable = true; if (!$keyType instanceof MixedType || $keyType->isExplicitMixed()) { return $keyType; @@ -893,9 +890,7 @@ public function getIterableKeyType(): Type } if ($this->isInstanceOf(Iterator::class)->yes()) { - return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle( - $this->getMethod('key', new OutOfClassScope())->getVariants(), - )->getReturnType()); + return RecursionGuard::run($this, fn (): Type => $this->getMethod('key', new OutOfClassScope())->getOnlyVariant()->getReturnType()); } if ($extraOffsetAccessible) { @@ -923,9 +918,7 @@ public function getIterableValueType(): Type { $isTraversable = false; if ($this->isInstanceOf(IteratorAggregate::class)->yes()) { - $valueType = RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle( - $this->getMethod('getIterator', new OutOfClassScope())->getVariants(), - )->getReturnType()->getIterableValueType()); + $valueType = RecursionGuard::run($this, fn (): Type => $this->getMethod('getIterator', new OutOfClassScope())->getOnlyVariant()->getReturnType()->getIterableValueType()); $isTraversable = true; if (!$valueType instanceof MixedType || $valueType->isExplicitMixed()) { return $valueType; @@ -944,9 +937,7 @@ public function getIterableValueType(): Type } if ($this->isInstanceOf(Iterator::class)->yes()) { - return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle( - $this->getMethod('current', new OutOfClassScope())->getVariants(), - )->getReturnType()); + return RecursionGuard::run($this, fn (): Type => $this->getMethod('current', new OutOfClassScope())->getOnlyVariant()->getReturnType()); } if ($extraOffsetAccessible) { @@ -1129,7 +1120,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($this->isInstanceOf(ArrayAccess::class)->yes()) { $acceptedOffsetType = RecursionGuard::run($this, function (): Type { - $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); + $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters(); if (count($parameters) < 2) { throw new ShouldNotHappenException(sprintf( 'Method %s::%s() has less than 2 parameters.', @@ -1161,7 +1152,7 @@ public function getOffsetValueType(Type $offsetType): Type } if ($this->isInstanceOf(ArrayAccess::class)->yes()) { - return RecursionGuard::run($this, fn (): Type => ParametersAcceptorSelector::selectSingle($this->getMethod('offsetGet', new OutOfClassScope())->getVariants())->getReturnType()); + return RecursionGuard::run($this, fn (): Type => $this->getMethod('offsetGet', new OutOfClassScope())->getOnlyVariant()->getReturnType()); } return new ErrorType(); @@ -1176,7 +1167,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni if ($this->isInstanceOf(ArrayAccess::class)->yes()) { $acceptedValueType = new NeverType(); $acceptedOffsetType = RecursionGuard::run($this, function () use (&$acceptedValueType): Type { - $parameters = ParametersAcceptorSelector::selectSingle($this->getMethod('offsetSet', new OutOfClassScope())->getVariants())->getParameters(); + $parameters = $this->getMethod('offsetSet', new OutOfClassScope())->getOnlyVariant()->getParameters(); if (count($parameters) < 2) { throw new ShouldNotHappenException(sprintf( 'Method %s::%s() has less than 2 parameters.', diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index c98129fc998..b237cdd33ad 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -7,7 +7,6 @@ use ExtendingKnownClassWithCheck\Foo; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Constant\ConstantIntegerType; @@ -369,7 +368,7 @@ public function testBug4713(): void $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(Service::class); - $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('createInstance')->getVariants())->getParameters()[0]; + $parameter = $class->getNativeMethod('createInstance')->getOnlyVariant()->getParameters()[0]; $defaultValue = $parameter->getDefaultValue(); $this->assertInstanceOf(ConstantStringType::class, $defaultValue); $this->assertSame(Service::class, $defaultValue->getValue()); @@ -382,7 +381,7 @@ public function testBug4288(): void $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(MyClass::class); - $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('paginate')->getVariants())->getParameters()[0]; + $parameter = $class->getNativeMethod('paginate')->getOnlyVariant()->getParameters()[0]; $defaultValue = $parameter->getDefaultValue(); $this->assertInstanceOf(ConstantIntegerType::class, $defaultValue); $this->assertSame(10, $defaultValue->getValue()); diff --git a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php index 8d4f675a667..b7f4e23bf02 100644 --- a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php +++ b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php @@ -9,7 +9,6 @@ use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; use PHPStan\Node\Expr\TypeExpr; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Testing\PHPStanTestCase; @@ -34,7 +33,7 @@ public function testArgumentReorderAllNamed(): void if ($functionReflection === null) { throw new ShouldNotHappenException(); } - $parameterAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + $parameterAcceptor = $functionReflection->getOnlyVariant(); $args = [ new Arg( @@ -84,7 +83,7 @@ public function testArgumentReorderAllNamedWithSkipped(): void if ($functionReflection === null) { throw new ShouldNotHappenException(); } - $parameterAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + $parameterAcceptor = $functionReflection->getOnlyVariant(); $args = [ new Arg( @@ -137,7 +136,7 @@ public function testMissingRequiredParameter(): void if ($functionReflection === null) { throw new ShouldNotHappenException(); } - $parameterAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + $parameterAcceptor = $functionReflection->getOnlyVariant(); $args = [ new Arg( @@ -161,7 +160,7 @@ public function testLeaveRegularCallAsIs(): void if ($functionReflection === null) { throw new ShouldNotHappenException(); } - $parameterAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); + $parameterAcceptor = $functionReflection->getOnlyVariant(); $args = [ new Arg( diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php index dfdb480fb84..36b4402d9e5 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php @@ -8,7 +8,6 @@ use AnnotationsMethods\Foo; use AnnotationsMethods\FooInterface; use PHPStan\Analyser\Scope; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Testing\PHPStanTestCase; @@ -974,7 +973,7 @@ public function testMethods(string $className, array $methods): void $this->assertTrue($class->hasMethod($methodName), sprintf('Method %s() not found in class %s.', $methodName, $className)); $method = $class->getMethod($methodName, $scope); - $selectedParametersAcceptor = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $selectedParametersAcceptor = $method->getOnlyVariant(); $this->assertSame( $expectedMethodData['class'], $method->getDeclaringClass()->getName(), diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index fa00afc63fa..d798e65b66f 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -115,7 +115,7 @@ public function testVariadicTraitMethod(): void $reflectionProvider = $this->createReflectionProvider(); $fooReflection = $reflectionProvider->getClass(Foo::class); $variadicMethod = $fooReflection->getNativeMethod('variadicMethod'); - $methodVariant = ParametersAcceptorSelector::selectSingle($variadicMethod->getVariants()); + $methodVariant = $variadicMethod->getOnlyVariant(); $this->assertTrue($methodVariant->isVariadic()); } diff --git a/tests/PHPStan/Reflection/MixedTypeTest.php b/tests/PHPStan/Reflection/MixedTypeTest.php index f6c511df33d..5c5248d38c0 100644 --- a/tests/PHPStan/Reflection/MixedTypeTest.php +++ b/tests/PHPStan/Reflection/MixedTypeTest.php @@ -19,7 +19,7 @@ public function testMixedType(): void $this->assertTrue($propertyType->isExplicitMixed()); $method = $class->getNativeMethod('doFoo'); - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodVariant = $method->getOnlyVariant(); $methodReturnType = $methodVariant->getReturnType(); $this->assertInstanceOf(MixedType::class, $methodReturnType); $this->assertTrue($methodReturnType->isExplicitMixed()); @@ -29,7 +29,7 @@ public function testMixedType(): void $this->assertTrue($methodParameterType->isExplicitMixed()); $function = $reflectionProvider->getFunction(new Name('NativeMixedType\doFoo'), null); - $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); + $functionVariant = $function->getOnlyVariant(); $functionReturnType = $functionVariant->getReturnType(); $this->assertInstanceOf(MixedType::class, $functionReturnType); $this->assertTrue($functionReturnType->isExplicitMixed()); From 1b9c1e6666caccf2bc15437fdd0c67e1b9f3fa17 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:50:07 +0200 Subject: [PATCH 0445/3097] Note about removing `ParametersAcceptorSelector::selectSingle()` --- UPGRADING.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 14b029acc9f..d28c6aed08c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -123,13 +123,13 @@ Identifiers are also required in custom rules. Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) -Before: +**Before**: ```php return ['My error']; ``` -After: +**After**: ```php return [ @@ -143,6 +143,47 @@ return [ Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) +### Removed deprecated `ParametersAcceptorSelector::selectSingle()` + +Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions. + +**Before**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); +``` + +**After**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants() +)->getReturnType(); +``` + +If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object: + +* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html) +* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html) +* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html) +* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html) +* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction) + +**Before**: + +```php +$function = $node->getFunctionReflection(); +$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); +``` + +**After**: + +``` +$returnType = $node->getFunctionReflection()->getReturnType(); +``` + ### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters [`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): From 23c53a2210b715f672ad3087dd476faf34bdec6e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:51:09 +0200 Subject: [PATCH 0446/3097] Deprecate `ParametersAcceptorSelector::selectSingle()` --- src/Reflection/ParametersAcceptorSelector.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 3ec43fd80fd..619ee2aa819 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -61,6 +61,8 @@ class ParametersAcceptorSelector { /** + * @deprecated See https://github.com/phpstan/phpstan-src/blob/2.0.x/UPGRADING.md#removed-deprecated-parametersacceptorselectorselectsingle + * * @template T of ParametersAcceptor * @param T[] $parametersAcceptors * @return T From 4cbe3f62a39df393c3618a49bc0a1347ebc0f648 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:58:48 +0200 Subject: [PATCH 0447/3097] Fix build --- src/Analyser/DirectInternalScopeFactory.php | 4 ++-- src/Analyser/InternalScopeFactory.php | 4 ++-- src/Analyser/LazyInternalScopeFactory.php | 3 ++- .../Comparison/StrictComparisonOfDifferentTypesRuleTest.php | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index a696c24777d..6b66961f4f7 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -7,12 +7,12 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; @@ -46,7 +46,7 @@ public function __construct( public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|ExtendedMethodReflection|null $function = null, + PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], diff --git a/src/Analyser/InternalScopeFactory.php b/src/Analyser/InternalScopeFactory.php index 83c943f9175..6d8608ec186 100644 --- a/src/Analyser/InternalScopeFactory.php +++ b/src/Analyser/InternalScopeFactory.php @@ -2,11 +2,11 @@ namespace PHPStan\Analyser; -use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; interface InternalScopeFactory { @@ -23,7 +23,7 @@ interface InternalScopeFactory public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|ExtendedMethodReflection|null $function = null, + PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 073bc6baef6..c2b2069ac53 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; @@ -36,7 +37,7 @@ public function __construct( public function create( ScopeContext $context, bool $declareStrictTypes = false, - FunctionReflection|ExtendedMethodReflection|null $function = null, + PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, array $expressionTypes = [], array $nativeExpressionTypes = [], diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 7012690dd94..9572fa7580c 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -893,7 +893,6 @@ public function testLowercaseString(): void ]; } - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/lowercase-string.php'], $errors); } From e880a75c0038d42ce7000ba14014c14e75b14706 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:15:12 +0200 Subject: [PATCH 0448/3097] Update phpstan-deprecation-rules --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index f131aa7efbc..b72f00167dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4670,12 +4670,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "89572d5481ec1e121ac1567f689fe49a25d6cef6" + "reference": "392bbe7be54b00fbe945fede6a8ef543216f3b9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/89572d5481ec1e121ac1567f689fe49a25d6cef6", - "reference": "89572d5481ec1e121ac1567f689fe49a25d6cef6", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/392bbe7be54b00fbe945fede6a8ef543216f3b9c", + "reference": "392bbe7be54b00fbe945fede6a8ef543216f3b9c", "shasum": "" }, "require": { @@ -4710,7 +4710,7 @@ "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" }, - "time": "2024-09-11T15:52:56+00:00" + "time": "2024-09-26T12:14:06+00:00" }, { "name": "phpstan/phpstan-nette", From 7f6913705b5b5091c59f59702b2fa1609ee60af7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 13:54:30 +0200 Subject: [PATCH 0449/3097] Remove `ParametersAcceptorSelector::selectSingle()` --- src/Reflection/ParametersAcceptorSelector.php | 24 --------- .../data/TestDynamicReturnTypeExtensions.php | 54 +++++++++++++++---- tests/PHPStan/Reflection/UnionTypesTest.php | 4 +- .../Api/data/static-call-out-of-phpstan.php | 2 +- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 619ee2aa819..f9f66c7531c 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -60,30 +60,6 @@ class ParametersAcceptorSelector { - /** - * @deprecated See https://github.com/phpstan/phpstan-src/blob/2.0.x/UPGRADING.md#removed-deprecated-parametersacceptorselectorselectsingle - * - * @template T of ParametersAcceptor - * @param T[] $parametersAcceptors - * @return T - */ - public static function selectSingle( - array $parametersAcceptors, - ): ParametersAcceptor - { - $count = count($parametersAcceptors); - if ($count === 0) { - throw new ShouldNotHappenException( - 'getVariants() must return at least one variant.', - ); - } - if ($count !== 1) { - throw new ShouldNotHappenException('Multiple variants - use selectFromArgs() instead.'); - } - - return $parametersAcceptors[0]; - } - /** * @param Node\Arg[] $args * @param ParametersAcceptor[] $parametersAcceptors diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 446e478cd03..dd72c4525ee 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -41,16 +41,28 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method { $args = $methodCall->args; if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } $arg = $args[0]->value; if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } return new ObjectType((string) $arg->class); @@ -75,12 +87,20 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method { $args = $methodCall->args; if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } $argType = $scope->getType($args[0]->value); if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } return new ObjectType($argType->getValue()); @@ -105,16 +125,28 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, { $args = $methodCall->args; if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } $arg = $args[0]->value; if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } return new ObjectType((string) $arg->class); @@ -215,7 +247,11 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); } } diff --git a/tests/PHPStan/Reflection/UnionTypesTest.php b/tests/PHPStan/Reflection/UnionTypesTest.php index d8977504d2b..79fb96b28a8 100644 --- a/tests/PHPStan/Reflection/UnionTypesTest.php +++ b/tests/PHPStan/Reflection/UnionTypesTest.php @@ -22,7 +22,7 @@ public function testUnionTypes(): void $this->assertSame('bool|int', $propertyType->describe(VerbosityLevel::precise())); $method = $class->getNativeMethod('doFoo'); - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodVariant = $method->getOnlyVariant(); $methodReturnType = $methodVariant->getReturnType(); $this->assertInstanceOf(UnionType::class, $methodReturnType); $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $methodReturnType->describe(VerbosityLevel::precise())); @@ -32,7 +32,7 @@ public function testUnionTypes(): void $this->assertSame('bool|int', $methodParameterType->describe(VerbosityLevel::precise())); $function = $reflectionProvider->getFunction(new Name('NativeUnionTypes\doFoo'), null); - $functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants()); + $functionVariant = $function->getOnlyVariant(); $functionReturnType = $functionVariant->getReturnType(); $this->assertInstanceOf(UnionType::class, $functionReturnType); $this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $functionReturnType->describe(VerbosityLevel::precise())); diff --git a/tests/PHPStan/Rules/Api/data/static-call-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/static-call-out-of-phpstan.php index ec450207c73..f0f5bdd9ab2 100644 --- a/tests/PHPStan/Rules/Api/data/static-call-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/static-call-out-of-phpstan.php @@ -19,7 +19,7 @@ public function doFoo(): void public function doBar(FunctionReflection $f): void { - ParametersAcceptorSelector::selectSingle($f->getVariants()); // @api above class + ParametersAcceptorSelector::selectFromArgs($f->getVariants()); // @api above class ScopeContext::create(__DIR__ . '/test.php'); // @api above method } From 5ff4cab1296b9a447bb1e39de601208a80733e9b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:19:44 +0200 Subject: [PATCH 0450/3097] Fix --- .../StrictComparisonOfDifferentTypesRule.php | 12 ++++++------ .../StrictComparisonOfDifferentTypesRuleTest.php | 10 ++++++++++ .../Rules/Comparison/data/strict-comparison.php | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index c703e93cb27..0db119501fd 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -66,15 +66,15 @@ public function processNode(Node $node, Scope $scope): array if ( ( $leftType->isConstantScalarValue()->yes() - && $leftType->isString()->yes() - && $rightType->isConstantScalarValue()->no() - && $rightType->isString()->yes() + && !$leftType->isString()->no() + && !$rightType->isConstantScalarValue()->yes() + && !$rightType->isString()->no() && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() ) || ( $rightType->isConstantScalarValue()->yes() - && $rightType->isString()->yes() - && $leftType->isConstantScalarValue()->no() - && $leftType->isString()->yes() + && !$rightType->isString()->no() + && !$leftType->isConstantScalarValue()->yes() + && !$leftType->isString()->no() && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() ) ) { diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index e6ff6be2d76..e50ab41805a 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -270,6 +270,11 @@ public function testStrictComparison(): void 996, 'Remove remaining cases below this one and this error will disappear too.', ], + [ + 'Strict comparison using === between lowercase-string|false and \'AB\' will always evaluate to false.', + 1014, + $tipText, + ], ], ); } @@ -423,6 +428,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 'Strict comparison using !== between INF and INF will always evaluate to false.', 982, ], + [ + 'Strict comparison using === between lowercase-string|false and \'AB\' will always evaluate to false.', + 1014, + $tipText, + ], ], ); } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 9719e9c133d..3e05dd8b831 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -1002,3 +1002,18 @@ public function doFoo() } } + +class TestLiteralStringVerbosityFix +{ + + /** + * @param lowercase-string|false $a + */ + public function doFoo($a): void + { + if ($a === 'AB') { + + } + } + +} From 6b66eb07e38e50c1a44f05c51076d46bd2d1c769 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:22:47 +0200 Subject: [PATCH 0451/3097] Fix CS --- src/Analyser/LazyInternalScopeFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index c2b2069ac53..32cfc6f8436 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -7,7 +7,6 @@ use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; From ab84e5579f4766b8582cbc0a27d395098fee1407 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:24:16 +0200 Subject: [PATCH 0452/3097] Fix --- .../MethodCallReturnsBoolExpressionTypeResolverExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php b/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php index 83a8ff826db..eb02e6e436c 100644 --- a/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php +++ b/tests/PHPStan/Analyser/data/MethodCallReturnsBoolExpressionTypeResolverExtension.php @@ -37,7 +37,7 @@ public function getType(Expr $expr, Scope $scope): ?Type $returnType = ParametersAcceptorSelector::selectFromArgs( $scope, $expr->getArgs(), - $methodReflection->getVariants(), + $methodReflection->getVariants() )->getReturnType(); if ($returnType instanceof StringType) { From f7380291c2b5ae2d78bac86e227b886c3e9e7381 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 14:35:48 +0200 Subject: [PATCH 0453/3097] Removed no longer valid test --- ...icReturnTypeExtensionTypeInferenceTest.php | 1 - ...ic-method-return-getsingle-conditional.php | 20 ------------------- 2 files changed, 21 deletions(-) delete mode 100644 tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php diff --git a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php index c76ca0ebca1..7e5fad5dda9 100644 --- a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php +++ b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php @@ -14,7 +14,6 @@ public function dataAsserts(): iterable if (PHP_VERSION_ID >= 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types-named-args.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php'); diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php b/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php deleted file mode 100644 index 270658e7ef6..00000000000 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php +++ /dev/null @@ -1,20 +0,0 @@ -get(0)); - assertType('bool', $this->get(1)); - assertType('bool', $this->get(2)); - } -} From 9fc11f78f31d8efbb02716b371602c2cc228bd2e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 15:26:17 +0200 Subject: [PATCH 0454/3097] Keep lowercase when trim --- stubs/core.stub | 15 ++++++++++ .../Analyser/nsrt/lowercase-string-trim.php | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-trim.php diff --git a/stubs/core.stub b/stubs/core.stub index 652fed707d0..bcf195b086c 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -313,3 +313,18 @@ function is_callable(mixed $value, bool $syntax_only = false, ?string &$callable * @return ($num is float ? float : $num is int ? non-negative-int : float|non-negative-int) */ function abs($num) {} + +/** + * @return ($string is lowercase-string ? lowercase-string : string) + */ +function trim(string $string, string $characters = " \n\r\t\v\x00"): string {} + +/** + * @return ($string is lowercase-string ? lowercase-string : string) + */ +function ltrim(string $string, string $characters = " \n\r\t\v\x00"): string {} + +/** + * @return ($string is lowercase-string ? lowercase-string : string) + */ +function rtrim(string $string, string $characters = " \n\r\t\v\x00"): string {} diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-trim.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-trim.php new file mode 100644 index 00000000000..e5632e293d6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-trim.php @@ -0,0 +1,29 @@ + Date: Thu, 26 Sep 2024 15:07:17 +0200 Subject: [PATCH 0455/3097] Add support for str_repeat and str_pad for lowercase string --- .../Php/StrPadFunctionReturnTypeExtension.php | 21 +++++++++-------- .../StrRepeatFunctionReturnTypeExtension.php | 5 ++++ .../PHPStan/Analyser/nsrt/literal-string.php | 16 ++++++------- .../Analyser/nsrt/lowercase-string-pad.php | 23 +++++++++++++++++++ .../Analyser/nsrt/lowercase-string-repeat.php | 19 +++++++++++++++ 5 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-pad.php create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-repeat.php diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index fd98d36ae46..92b0ec286dc 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -44,15 +45,17 @@ public function getTypeFromFunctionCall( $accessoryTypes[] = new AccessoryNonEmptyStringType(); } - if ($inputType->isLiteralString()->yes()) { - if (count($args) < 3) { - $accessoryTypes[] = new AccessoryLiteralStringType(); - } else { - $padStringType = $scope->getType($args[2]->value); - if ($padStringType->isLiteralString()->yes()) { - $accessoryTypes[] = new AccessoryLiteralStringType(); - } - } + if (count($args) < 3) { + $padStringType = null; + } else { + $padStringType = $scope->getType($args[2]->value); + } + + if ($inputType->isLiteralString()->yes() && ($padStringType === null || $padStringType->isLiteralString()->yes())) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + if ($inputType->isLowercaseString()->yes() && ($padStringType === null || $padStringType->isLowercaseString()->yes())) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); } if (count($accessoryTypes) > 0) { diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 22c9714168c..632fea85f05 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -93,6 +94,10 @@ public function getTypeFromFunctionCall( } } + if ($inputType->isLowercaseString()->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } + if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); return new IntersectionType($accessoryTypes); diff --git a/tests/PHPStan/Analyser/nsrt/literal-string.php b/tests/PHPStan/Analyser/nsrt/literal-string.php index ff63036d9b0..93bf8949d93 100644 --- a/tests/PHPStan/Analyser/nsrt/literal-string.php +++ b/tests/PHPStan/Analyser/nsrt/literal-string.php @@ -36,9 +36,9 @@ public function doFoo($literalString, string $string, $numericString) "'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'", str_repeat('a', 99) ); - assertType('literal-string&non-falsy-string', str_repeat('a', 100)); - assertType('literal-string&non-empty-string&numeric-string', str_repeat('0', 100)); // could be non-falsy-string - assertType('literal-string&non-falsy-string&numeric-string', str_repeat('1', 100)); + assertType('literal-string&lowercase-string&non-falsy-string', str_repeat('a', 100)); + assertType('literal-string&lowercase-string&non-empty-string&numeric-string', str_repeat('0', 100)); // could be non-falsy-string + assertType('literal-string&lowercase-string&non-falsy-string&numeric-string', str_repeat('1', 100)); // Repeating a numeric type multiple times can lead to a non-numeric type: 3v4l.org/aRBdZ assertType('non-empty-string', str_repeat($numericString, 100)); @@ -51,13 +51,13 @@ public function doFoo($literalString, string $string, $numericString) assertType("non-empty-string", str_repeat($numericString, 2)); assertType("literal-string", str_repeat($literalString, 1)); $x = rand(1,2); - assertType("literal-string&non-falsy-string", str_repeat(' 1 ', $x)); - assertType("literal-string&non-falsy-string", str_repeat('+1', $x)); - assertType("literal-string&non-falsy-string", str_repeat('1e9', $x)); - assertType("literal-string&non-falsy-string&numeric-string", str_repeat('19', $x)); + assertType("literal-string&lowercase-string&non-falsy-string", str_repeat(' 1 ', $x)); + assertType("literal-string&lowercase-string&non-falsy-string", str_repeat('+1', $x)); + assertType("literal-string&lowercase-string&non-falsy-string", str_repeat('1e9', $x)); + assertType("literal-string&lowercase-string&non-falsy-string&numeric-string", str_repeat('19', $x)); $x = rand(0,2); - assertType("literal-string", str_repeat('19', $x)); + assertType("literal-string&lowercase-string", str_repeat('19', $x)); $x = rand(-10,-1); assertType("*NEVER*", str_repeat('19', $x)); diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-pad.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-pad.php new file mode 100644 index 00000000000..79633d7538b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-pad.php @@ -0,0 +1,23 @@ + Date: Thu, 26 Sep 2024 15:58:01 +0200 Subject: [PATCH 0456/3097] Update ReplaceFunctionsDynamicReturnTypeExtension for lowercase-string --- ...aceFunctionsDynamicReturnTypeExtension.php | 18 ++++++-- .../Analyser/LegacyNodeScopeResolverTest.php | 4 +- .../nsrt/isset-coalesce-empty-type.php | 2 +- .../nsrt/lowercase-string-replace.php | 42 +++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-replace.php diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 18a66da75c2..c80c10d9c0e 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -82,17 +83,26 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( return TypeUtils::toBenevolentUnion($defaultReturnType); } - if ($subjectArgumentType->isNonEmptyString()->yes() && array_key_exists($functionReflection->getName(), self::FUNCTIONS_REPLACE_POSITION)) { + if (array_key_exists($functionReflection->getName(), self::FUNCTIONS_REPLACE_POSITION)) { $replaceArgumentPosition = self::FUNCTIONS_REPLACE_POSITION[$functionReflection->getName()]; if (count($functionCall->getArgs()) > $replaceArgumentPosition) { $replaceArgumentType = $scope->getType($functionCall->getArgs()[$replaceArgumentPosition]->value); + $accessories = []; if ($subjectArgumentType->isNonFalsyString()->yes() && $replaceArgumentType->isNonFalsyString()->yes()) { - return new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]); + $accessories[] = new AccessoryNonFalsyStringType(); + } elseif ($subjectArgumentType->isNonEmptyString()->yes() && $replaceArgumentType->isNonEmptyString()->yes()) { + $accessories[] = new AccessoryNonEmptyStringType(); } - if ($replaceArgumentType->isNonEmptyString()->yes()) { - return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]); + + if ($subjectArgumentType->isLowercaseString()->yes() && $replaceArgumentType->isLowercaseString()->yes()) { + $accessories[] = new AccessoryLowercaseStringType(); + } + + if (count($accessories) > 0) { + $accessories[] = new StringType(); + return new IntersectionType($accessories); } } } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 0cb675d14bc..9860a71f59f 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -7414,7 +7414,7 @@ public function dataReplaceFunctions(): array { return [ [ - 'non-falsy-string', + 'lowercase-string&non-falsy-string', '$expectedString', ], [ @@ -7422,7 +7422,7 @@ public function dataReplaceFunctions(): array '$expectedString2', ], [ - 'non-falsy-string|null', + '(lowercase-string&non-falsy-string)|null', '$anotherExpectedString', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php index ef291855ed5..a38116b682a 100644 --- a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php +++ b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php @@ -472,7 +472,7 @@ function coalesce() assertType('int<0, max>', rand() ?? false); - assertType('0|string', preg_replace('', '', '') ?? 0); + assertType('0|lowercase-string', preg_replace('', '', '') ?? 0); $foo = new FooCoalesce(); diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-replace.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-replace.php new file mode 100644 index 00000000000..c9546ba2d03 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-replace.php @@ -0,0 +1,42 @@ + Date: Thu, 26 Sep 2024 15:57:21 +0200 Subject: [PATCH 0457/3097] Update parse_str for lowercase string --- stubs/core.stub | 2 +- .../Analyser/nsrt/lowercase-string-parse.php | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-parse.php diff --git a/stubs/core.stub b/stubs/core.stub index bcf195b086c..853222642c7 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -69,7 +69,7 @@ function str_shuffle(string $string): string {} /** * @param array $result - * @param-out array|string> $result + * @param-out ($string is lowercase-string ? array|lowercase-string> : array|string>) $result */ function parse_str(string $string, array &$result): void {} diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-parse.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-parse.php new file mode 100644 index 00000000000..ba95e975dad --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-parse.php @@ -0,0 +1,36 @@ + Date: Mon, 23 Sep 2024 20:16:08 +0200 Subject: [PATCH 0458/3097] More precise `IntegerRangeType::toString()` --- src/Type/IntegerRangeType.php | 5 +++ ...intfFunctionDynamicReturnTypeExtension.php | 22 +++++++++++- tests/PHPStan/Analyser/nsrt/bug-7387.php | 35 +++++++++++++------ tests/PHPStan/Analyser/nsrt/filter-var.php | 2 +- .../PHPStan/Analyser/nsrt/range-to-string.php | 22 ++++++++++++ .../nsrt/unset-conditional-expressions.php | 2 +- 6 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/range-to-string.php diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 534be0ebbbf..8a9414fbeed 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -465,6 +465,11 @@ public function toAbsoluteNumber(): Type public function toString(): Type { + $finiteTypes = $this->getFiniteTypes(); + if ($finiteTypes !== []) { + return TypeCombinator::union(...$finiteTypes)->toString(); + } + $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this); if ($isZero->no()) { return new IntersectionType([ diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 26cc34d860b..4e00f1d8ec5 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -105,9 +105,29 @@ public function getTypeFromFunctionCall( $checkArgType = $scope->getType($args[$checkArg]->value); if ( $matches['specifier'] === 's' - && ($checkArgType->isConstantValue()->no() || $matches['width'] === '') && ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes()) ) { + if ($checkArgType instanceof IntegerRangeType) { + $constArgTypes = $checkArgType->getFiniteTypes(); + } else { + $constArgTypes = $checkArgType->getConstantScalarTypes(); + } + if ($constArgTypes !== []) { + $result = []; + $printfArgs = array_fill(0, count($args) - 1, ''); + foreach ($constArgTypes as $constArgType) { + $printfArgs[$checkArg - 1] = $constArgType->getValue(); + try { + $result[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs)); + } catch (Throwable) { + continue 2; + } + } + $singlePlaceholderEarlyReturn = TypeCombinator::union(...$result); + + continue; + } + $singlePlaceholderEarlyReturn = $checkArgType->toString(); } elseif ($matches['specifier'] !== 's') { $singlePlaceholderEarlyReturn = new IntersectionType([ diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index 0081826133f..ad53206b257 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -6,7 +6,10 @@ class HelloWorld { - public function inputTypes(int $i, float $f, string $s) { + /** + * @param int<-1, 5> $intRange + */ + public function inputTypes(int $i, float $f, string $s, int $intRange) { // https://3v4l.org/iXaDX assertType('numeric-string', sprintf('%.14F', $i)); assertType('numeric-string', sprintf('%.14F', $f)); @@ -19,6 +22,9 @@ public function inputTypes(int $i, float $f, string $s) { assertType('numeric-string', sprintf('%14F', $i)); assertType('numeric-string', sprintf('%14F', $f)); assertType('numeric-string', sprintf('%14F', $s)); + + assertType("'-1'|'0'|'1'|'2'|'3'|'4'|'5'", sprintf('%s', $intRange)); + assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|'-1'", sprintf('%2s', $intRange)); } public function specifiers(int $i) { @@ -53,18 +59,27 @@ public function specifiers(int $i) { */ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) { // https://3v4l.org/vVL0c - assertType('numeric-string', sprintf('%2$14s', $mixed, $i)); - assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $posInt)); - assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $negInt)); - assertType('numeric-string', sprintf('%2$14s', $mixed, $intRange)); - assertType('non-falsy-string&numeric-string', sprintf('%2$14s', $mixed, $nonZeroIntRange)); - - assertType("non-falsy-string", sprintf('%2$14s', $mixed, 1)); - assertType("non-falsy-string", sprintf('%2$14s', $mixed, '1')); - assertType("non-falsy-string", sprintf('%2$14s', $mixed, 'abc')); + assertType('numeric-string', sprintf('%2$6s', $mixed, $i)); + assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); + assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); + assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange)); + + // https://3v4l.org/1ECIq + assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, 1)); + assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $i)); + assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $posInt)); + assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $negInt)); + assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|' -1'", sprintf('%2$6s', $mixed, $intRange)); + assertType('non-falsy-string', sprintf("%2$'#6s", $mixed, $nonZeroIntRange)); + + assertType("' 1'", sprintf('%2$6s', $mixed, 1)); + assertType("' 1'", sprintf('%2$6s', $mixed, '1')); + assertType("' abc'", sprintf('%2$6s', $mixed, 'abc')); + assertType("' 0'|' 1'|' 2'|' 3'|' 4'|' 5'|' -1'", sprintf('%2$6s', $mixed, $intRange)); assertType("'1'", sprintf('%2$s', $mixed, 1)); assertType("'1'", sprintf('%2$s', $mixed, '1')); assertType("'abc'", sprintf('%2$s', $mixed, 'abc')); + assertType("'-1'|'0'|'1'|'2'|'3'|'4'|'5'", sprintf('%2$s', $mixed, $intRange)); assertType('numeric-string', sprintf('%2$.14F', $mixed, $i)); assertType('numeric-string', sprintf('%2$.14F', $mixed, $f)); diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index 0d43930de33..e726c77d75c 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -159,7 +159,7 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType("'17.1'", filter_var(17.1)); assertType("'1.0E-50'", filter_var(1e-50)); assertType('numeric-string', filter_var($int)); - assertType('numeric-string', filter_var($intRange)); + assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); assertType('non-empty-string', filter_var($nonEmptyString)); diff --git a/tests/PHPStan/Analyser/nsrt/range-to-string.php b/tests/PHPStan/Analyser/nsrt/range-to-string.php new file mode 100644 index 00000000000..494c135d95b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/range-to-string.php @@ -0,0 +1,22 @@ + $i + * @param int<-10, 10> $ii + * @param int<0, 128> $maxlong + * @param int<0, 129> $toolong + */ + public function sayHello($i, $ii, $maxlong, $toolong): void + { + assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i); + assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii); + assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong); + assertType("numeric-string", (string) $toolong); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/unset-conditional-expressions.php b/tests/PHPStan/Analyser/nsrt/unset-conditional-expressions.php index b310ffe1a5d..afda5d22291 100644 --- a/tests/PHPStan/Analyser/nsrt/unset-conditional-expressions.php +++ b/tests/PHPStan/Analyser/nsrt/unset-conditional-expressions.php @@ -42,7 +42,7 @@ public function doBaz(): void } } - assertType('array{a?: bool, b?: numeric-string, c?: int<-1, 1>, d?: int<0, 1>}', $breakdowns); + assertType("array{a?: bool, b?: '0'|'1', c?: int<-1, 1>, d?: int<0, 1>}", $breakdowns); } } From 5d4f259803cac9bae1baa60a47517bb3c6a4f3c2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 16:58:02 +0200 Subject: [PATCH 0459/3097] Fix test --- tests/PHPStan/Analyser/data/param-out.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index 1f4fc69bd38..05684938b8e 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -315,7 +315,7 @@ function testParseStr() { echo $output['arr'][1];//baz */ - \PHPStan\Testing\assertType('array', $output); + \PHPStan\Testing\assertType('array', $output); } function fooSimilar() { @@ -501,4 +501,3 @@ function testMatch() { preg_match('#.*#', 'foo', $matches); assertType('array{0?: string}', $matches); } - From 7080f402b007539ff359a8705ad26bf0a6c2192b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 17:38:22 +0200 Subject: [PATCH 0460/3097] Add support for lowercase-string on parse_url() --- ...eUrlFunctionDynamicReturnTypeExtension.php | 94 ++++++++++++++++--- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../nsrt/lowercase-string-parse-url.php | 26 +++++ 3 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-parse-url.php diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index 2a3d43c9c01..d5c7450d19a 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -6,12 +6,14 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -37,8 +39,16 @@ final class ParseUrlFunctionDynamicReturnTypeExtension implements DynamicFunctio /** @var array|null */ private ?array $componentTypesPairedStrings = null; + /** @var array|null */ + private ?array $componentTypesPairedConstantsForLowercaseString = null; + + /** @var array|null */ + private ?array $componentTypesPairedStringsForLowercaseString = null; + private ?Type $allComponentsTogetherType = null; + private ?Type $allComponentsTogetherTypeForLowercaseString = null; + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'parse_url'; @@ -52,23 +62,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $this->cacheReturnTypes(); + $urlType = $scope->getType($functionCall->getArgs()[0]->value); if (count($functionCall->getArgs()) > 1) { $componentType = $scope->getType($functionCall->getArgs()[1]->value); if (!$componentType->isConstantValue()->yes()) { - return $this->createAllComponentsReturnType(); + return $this->createAllComponentsReturnType($urlType->isLowercaseString()->yes()); } $componentType = $componentType->toInteger(); - if (!$componentType instanceof ConstantIntegerType) { - return $this->createAllComponentsReturnType(); + return $this->createAllComponentsReturnType($urlType->isLowercaseString()->yes()); } } else { $componentType = new ConstantIntegerType(-1); } - $urlType = $scope->getType($functionCall->getArgs()[0]->value); if (count($urlType->getConstantStrings()) > 0) { $types = []; foreach ($urlType->getConstantStrings() as $constantString) { @@ -86,21 +95,44 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } if ($componentType->getValue() === -1) { - return TypeCombinator::union($this->createComponentsArray(), new ConstantBooleanType(false)); + return TypeCombinator::union( + $this->createComponentsArray($urlType->isLowercaseString()->yes()), + new ConstantBooleanType(false) + ); + } + + if ($urlType->isLowercaseString()->yes()) { + return $this->componentTypesPairedConstantsForLowercaseString[$componentType->getValue()] ?? new ConstantBooleanType(false); } return $this->componentTypesPairedConstants[$componentType->getValue()] ?? new ConstantBooleanType(false); } - private function createAllComponentsReturnType(): Type + private function createAllComponentsReturnType(bool $urlIsLowercase): Type { + if ($urlIsLowercase) { + if ($this->allComponentsTogetherTypeForLowercaseString === null) { + $returnTypes = [ + new ConstantBooleanType(false), + new NullType(), + IntegerRangeType::fromInterval(0, 65535), + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + $this->createComponentsArray(true), + ]; + + $this->allComponentsTogetherTypeForLowercaseString = TypeCombinator::union(...$returnTypes); + } + + return $this->allComponentsTogetherTypeForLowercaseString; + } + if ($this->allComponentsTogetherType === null) { $returnTypes = [ new ConstantBooleanType(false), new NullType(), IntegerRangeType::fromInterval(0, 65535), new StringType(), - $this->createComponentsArray(), + $this->createComponentsArray(false), ]; $this->allComponentsTogetherType = TypeCombinator::union(...$returnTypes); @@ -109,19 +141,29 @@ private function createAllComponentsReturnType(): Type return $this->allComponentsTogetherType; } - private function createComponentsArray(): Type + private function createComponentsArray(bool $urlIsLowercase): Type { - $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder = ConstantArrayTypeBuilder::createEmpty(); - if ($this->componentTypesPairedStrings === null) { - throw new ShouldNotHappenException(); - } + if ($urlIsLowercase) { + if ($this->componentTypesPairedStringsForLowercaseString === null) { + throw new ShouldNotHappenException(); + } + + foreach ($this->componentTypesPairedStringsForLowercaseString as $componentName => $componentValueType) { + $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); + } + } else { + if ($this->componentTypesPairedStrings === null) { + throw new ShouldNotHappenException(); + } - foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { - $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); + foreach ($this->componentTypesPairedStrings as $componentName => $componentValueType) { + $builder->setOffsetValueType(new ConstantStringType($componentName), $componentValueType, true); + } } - return $builder->getArray(); + return $builder->getArray(); } private function cacheReturnTypes(): void @@ -131,11 +173,13 @@ private function cacheReturnTypes(): void } $string = new StringType(); + $lowercaseString = new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); $port = IntegerRangeType::fromInterval(0, 65535); $false = new ConstantBooleanType(false); $null = new NullType(); $stringOrFalseOrNull = TypeCombinator::union($string, $false, $null); + $lowercaseStringOrFalseOrNull = TypeCombinator::union($lowercaseString, $false, $null); $portOrFalseOrNull = TypeCombinator::union($port, $false, $null); $this->componentTypesPairedConstants = [ @@ -148,6 +192,16 @@ private function cacheReturnTypes(): void PHP_URL_QUERY => $stringOrFalseOrNull, PHP_URL_FRAGMENT => $stringOrFalseOrNull, ]; + $this->componentTypesPairedConstantsForLowercaseString = [ + PHP_URL_SCHEME => $lowercaseStringOrFalseOrNull, + PHP_URL_HOST => $lowercaseStringOrFalseOrNull, + PHP_URL_PORT => $portOrFalseOrNull, + PHP_URL_USER => $lowercaseStringOrFalseOrNull, + PHP_URL_PASS => $lowercaseStringOrFalseOrNull, + PHP_URL_PATH => $lowercaseStringOrFalseOrNull, + PHP_URL_QUERY => $lowercaseStringOrFalseOrNull, + PHP_URL_FRAGMENT => $lowercaseStringOrFalseOrNull, + ]; $this->componentTypesPairedStrings = [ 'scheme' => $string, @@ -159,6 +213,16 @@ private function cacheReturnTypes(): void 'query' => $string, 'fragment' => $string, ]; + $this->componentTypesPairedStringsForLowercaseString = [ + 'scheme' => $lowercaseString, + 'host' => $lowercaseString, + 'port' => $port, + 'user' => $lowercaseString, + 'pass' => $lowercaseString, + 'path' => $lowercaseString, + 'query' => $lowercaseString, + 'fragment' => $lowercaseString, + ]; } } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 9860a71f59f..430b0e02f80 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5492,7 +5492,7 @@ public function dataFunctions(): array '$parseUrlConstantUrlWithoutComponent2', ], [ - 'array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|int<0, 65535>|string|false|null', + 'array{scheme?: lowercase-string, host?: lowercase-string, port?: int<0, 65535>, user?: lowercase-string, pass?: lowercase-string, path?: lowercase-string, query?: lowercase-string, fragment?: lowercase-string}|int<0, 65535>|lowercase-string|false|null', '$parseUrlConstantUrlUnknownComponent', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-parse-url.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-parse-url.php new file mode 100644 index 00000000000..1a6f7e683ea --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-parse-url.php @@ -0,0 +1,26 @@ +, user?: lowercase-string, pass?: lowercase-string, path?: lowercase-string, query?: lowercase-string, fragment?: lowercase-string}|false', parse_url($lowercase)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_SCHEME)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_HOST)); + assertType('int<0, 65535>|false|null', parse_url($lowercase, PHP_URL_PORT)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_USER)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_PASS)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_PATH)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_QUERY)); + assertType('lowercase-string|false|null', parse_url($lowercase, PHP_URL_FRAGMENT)); + } + +} From c539491e5a4f9005d957ce53705cbb2aa6c65190 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 19:39:41 +0200 Subject: [PATCH 0461/3097] Fix cs check --- src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index d5c7450d19a..19d6c20b26f 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -97,7 +97,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if ($componentType->getValue() === -1) { return TypeCombinator::union( $this->createComponentsArray($urlType->isLowercaseString()->yes()), - new ConstantBooleanType(false) + new ConstantBooleanType(false), ); } From 5651bec661582b2d62de1b4ae9d5f27e69e3c524 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:16:29 +0200 Subject: [PATCH 0462/3097] Move ContainerDynamicReturnTypeExtension to build/PHPStan --- .../PHPStan/Build}/ContainerDynamicReturnTypeExtension.php | 2 +- build/phpstan.neon | 2 +- phpstan-baseline.neon | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename {src/Internal => build/PHPStan/Build}/ContainerDynamicReturnTypeExtension.php (98%) diff --git a/src/Internal/ContainerDynamicReturnTypeExtension.php b/build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php similarity index 98% rename from src/Internal/ContainerDynamicReturnTypeExtension.php rename to build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php index 0fc017e67f5..8e43bd2d47c 100644 --- a/src/Internal/ContainerDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php @@ -1,6 +1,6 @@ Date: Thu, 26 Sep 2024 20:18:22 +0200 Subject: [PATCH 0463/3097] Classes that were previously `@final` were made `final` --- UPGRADING.md | 1 + src/Analyser/Error.php | 3 +-- src/Analyser/ImpurePoint.php | 3 +-- src/Analyser/InternalError.php | 3 +-- src/Analyser/NameScope.php | 3 +-- src/Analyser/ScopeFactory.php | 3 +-- src/Analyser/StatementExitPoint.php | 3 +-- src/Analyser/StatementResult.php | 3 +-- src/Analyser/ThrowPoint.php | 3 +-- src/Analyser/TypeSpecifierContext.php | 3 +-- src/Broker/Broker.php | 3 +-- src/Collectors/CollectedData.php | 3 +-- src/Command/AnalysisResult.php | 3 +-- src/Command/ErrorFormatter/CiDetectedErrorFormatter.php | 3 +-- src/DependencyInjection/ContainerFactory.php | 3 +-- src/Node/BooleanAndNode.php | 3 +-- src/Node/BooleanOrNode.php | 3 +-- src/Node/BreaklessWhileLoopNode.php | 3 +-- src/Node/CatchWithUnthrownExceptionNode.php | 3 +-- src/Node/ClassConstantsNode.php | 3 +-- src/Node/ClassMethod.php | 3 +-- src/Node/ClassMethodsNode.php | 3 +-- src/Node/ClassPropertiesNode.php | 3 +-- src/Node/ClassPropertyNode.php | 3 +-- src/Node/ClosureReturnStatementsNode.php | 3 +-- src/Node/CollectedDataNode.php | 3 +-- src/Node/Constant/ClassConstantFetch.php | 3 +-- src/Node/ExecutionEndNode.php | 3 +-- src/Node/FileNode.php | 3 +-- src/Node/FinallyExitPointsNode.php | 3 +-- src/Node/FunctionCallableNode.php | 3 +-- src/Node/FunctionReturnStatementsNode.php | 3 +-- src/Node/InArrowFunctionNode.php | 3 +-- src/Node/InClassMethodNode.php | 3 +-- src/Node/InClassNode.php | 3 +-- src/Node/InClosureNode.php | 3 +-- src/Node/InFunctionNode.php | 3 +-- src/Node/InTraitNode.php | 3 +-- src/Node/InstantiationCallableNode.php | 3 +-- src/Node/InvalidateExprNode.php | 3 +-- src/Node/LiteralArrayItem.php | 3 +-- src/Node/LiteralArrayNode.php | 3 +-- src/Node/MatchExpressionArm.php | 3 +-- src/Node/MatchExpressionArmBody.php | 3 +-- src/Node/MatchExpressionArmCondition.php | 3 +-- src/Node/MatchExpressionNode.php | 3 +-- src/Node/Method/MethodCall.php | 3 +-- src/Node/MethodCallableNode.php | 3 +-- src/Node/MethodReturnStatementsNode.php | 3 +-- src/Node/Printer/ExprPrinter.php | 3 +-- src/Node/Property/PropertyRead.php | 3 +-- src/Node/Property/PropertyWrite.php | 3 +-- src/Node/ReturnStatement.php | 3 +-- src/Node/StaticMethodCallableNode.php | 3 +-- src/Node/UnreachableStatementNode.php | 3 +-- src/Php/PhpVersion.php | 3 +-- src/PhpDoc/ResolvedPhpDocBlock.php | 3 +-- src/PhpDoc/Tag/DeprecatedTag.php | 3 +-- src/PhpDoc/Tag/ExtendsTag.php | 3 +-- src/PhpDoc/Tag/ImplementsTag.php | 3 +-- src/PhpDoc/Tag/MethodTag.php | 3 +-- src/PhpDoc/Tag/MethodTagParameter.php | 3 +-- src/PhpDoc/Tag/MixinTag.php | 3 +-- src/PhpDoc/Tag/ParamClosureThisTag.php | 1 - src/PhpDoc/Tag/ParamOutTag.php | 3 +-- src/PhpDoc/Tag/ParamTag.php | 3 +-- src/PhpDoc/Tag/PropertyTag.php | 3 +-- src/PhpDoc/Tag/RequireExtendsTag.php | 3 +-- src/PhpDoc/Tag/RequireImplementsTag.php | 3 +-- src/PhpDoc/Tag/ReturnTag.php | 3 +-- src/PhpDoc/Tag/SelfOutTypeTag.php | 3 +-- src/PhpDoc/Tag/TemplateTag.php | 3 +-- src/PhpDoc/Tag/ThrowsTag.php | 3 +-- src/PhpDoc/Tag/TypeAliasTag.php | 3 +-- src/PhpDoc/Tag/UsesTag.php | 3 +-- src/PhpDoc/Tag/VarTag.php | 3 +-- src/Reflection/Assertions.php | 3 +-- src/Reflection/ClassConstantReflection.php | 3 +-- src/Reflection/ClassReflection.php | 3 +-- src/Reflection/EnumCaseReflection.php | 3 +-- src/Reflection/InitializerExprContext.php | 3 +-- src/Reflection/ParametersAcceptorSelector.php | 3 +-- src/Reflection/PassedByReference.php | 3 +-- src/Reflection/Php/PhpMethodFromParserNodeReflection.php | 3 +-- src/Reflection/Php/PhpMethodReflection.php | 3 +-- src/Reflection/Php/PhpPropertyReflection.php | 3 +-- src/Reflection/TrivialParametersAcceptor.php | 3 +-- src/Rules/DirectRegistry.php | 5 +---- src/Rules/Exceptions/DefaultExceptionTypeResolver.php | 3 +-- src/Rules/FoundTypeResult.php | 3 +-- src/Rules/RuleErrorBuilder.php | 3 +-- src/TrinaryLogic.php | 3 +-- src/Type/AcceptsResult.php | 3 +-- src/Type/ClosureTypeFactory.php | 3 +-- src/Type/Constant/ConstantArrayTypeAndMethod.php | 3 +-- src/Type/Constant/ConstantArrayTypeBuilder.php | 3 +-- src/Type/ConstantTypeHelper.php | 3 +-- src/Type/Generic/TemplateTypeMap.php | 3 +-- src/Type/Generic/TemplateTypeVariance.php | 3 +-- src/Type/Generic/TemplateTypeVarianceMap.php | 3 +-- src/Type/GenericTypeVariableResolver.php | 3 +-- src/Type/TypeCombinator.php | 3 +-- src/Type/TypeUtils.php | 3 +-- 103 files changed, 102 insertions(+), 205 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d28c6aed08c..6dc6ec47e34 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -216,6 +216,7 @@ As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtensio ### Minor backward compatibility breaks +* Classes that were previously `@final` were made `final` * Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required * Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index d92f983a3e8..1ad85c60bec 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -14,9 +14,8 @@ /** * @api - * @final */ -class Error implements JsonSerializable +final class Error implements JsonSerializable { public const PATTERN_IDENTIFIER = '[a-zA-Z0-9](?:[a-zA-Z0-9\\.]*[a-zA-Z0-9])?'; diff --git a/src/Analyser/ImpurePoint.php b/src/Analyser/ImpurePoint.php index d4dc6fe133f..20335325a74 100644 --- a/src/Analyser/ImpurePoint.php +++ b/src/Analyser/ImpurePoint.php @@ -8,9 +8,8 @@ /** * @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyAssignByRef'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags' * @api - * @final */ -class ImpurePoint +final class ImpurePoint { /** diff --git a/src/Analyser/InternalError.php b/src/Analyser/InternalError.php index d778e894623..371b64cdc34 100644 --- a/src/Analyser/InternalError.php +++ b/src/Analyser/InternalError.php @@ -10,10 +10,9 @@ /** * @api - * @final * @phpstan-type Trace = list */ -class InternalError implements JsonSerializable +final class InternalError implements JsonSerializable { public const STACK_TRACE_METADATA_KEY = 'stackTrace'; diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index fbc602329e2..f7f54f0a6a1 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -18,9 +18,8 @@ /** * @api - * @final */ -class NameScope +final class NameScope { private TemplateTypeMap $templateTypeMap; diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index ae35c9d74cb..ade6e1d8943 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -4,9 +4,8 @@ /** * @api - * @final */ -class ScopeFactory +final class ScopeFactory { public function __construct(private InternalScopeFactory $internalScopeFactory) diff --git a/src/Analyser/StatementExitPoint.php b/src/Analyser/StatementExitPoint.php index 14c8d24824c..5c4916373e9 100644 --- a/src/Analyser/StatementExitPoint.php +++ b/src/Analyser/StatementExitPoint.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class StatementExitPoint +final class StatementExitPoint { public function __construct(private Stmt $statement, private MutatingScope $scope) diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index 985777317e9..71f0ddc7406 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class StatementResult +final class StatementResult { /** diff --git a/src/Analyser/ThrowPoint.php b/src/Analyser/ThrowPoint.php index 1de4b937f9e..873c11e425f 100644 --- a/src/Analyser/ThrowPoint.php +++ b/src/Analyser/ThrowPoint.php @@ -10,9 +10,8 @@ /** * @api - * @final */ -class ThrowPoint +final class ThrowPoint { /** diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 3cd0ead0f9f..fe09aa861c9 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class TypeSpecifierContext +final class TypeSpecifierContext { public const CONTEXT_TRUE = 0b0001; diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 8db42d1f45f..080d3accc83 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -13,9 +13,8 @@ /** * @api - * @final */ -class Broker implements ReflectionProvider +final class Broker implements ReflectionProvider { private static ?Broker $instance = null; diff --git a/src/Collectors/CollectedData.php b/src/Collectors/CollectedData.php index e6817382b66..1ae0078880a 100644 --- a/src/Collectors/CollectedData.php +++ b/src/Collectors/CollectedData.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class CollectedData implements JsonSerializable +final class CollectedData implements JsonSerializable { /** diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index 6ddf536bc1b..4b090e8a359 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -11,9 +11,8 @@ /** * @api - * @final */ -class AnalysisResult +final class AnalysisResult { /** @var list sorted by their file name, line number and message */ diff --git a/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php b/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php index a6c66bbafb7..38d0a674390 100644 --- a/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php +++ b/src/Command/ErrorFormatter/CiDetectedErrorFormatter.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class CiDetectedErrorFormatter implements ErrorFormatter +final class CiDetectedErrorFormatter implements ErrorFormatter { public function __construct( diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 2a491725bb5..4ab01e56c97 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -55,9 +55,8 @@ /** * @api - * @final */ -class ContainerFactory +final class ContainerFactory { private FileHelper $fileHelper; diff --git a/src/Node/BooleanAndNode.php b/src/Node/BooleanAndNode.php index 6d508713e72..361177c705e 100644 --- a/src/Node/BooleanAndNode.php +++ b/src/Node/BooleanAndNode.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class BooleanAndNode extends Expr implements VirtualNode +final class BooleanAndNode extends Expr implements VirtualNode { public function __construct(private BooleanAnd|LogicalAnd $originalNode, private Scope $rightScope) diff --git a/src/Node/BooleanOrNode.php b/src/Node/BooleanOrNode.php index ca327164ee1..c2ca5d14be6 100644 --- a/src/Node/BooleanOrNode.php +++ b/src/Node/BooleanOrNode.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class BooleanOrNode extends Expr implements VirtualNode +final class BooleanOrNode extends Expr implements VirtualNode { public function __construct(private BooleanOr|LogicalOr $originalNode, private Scope $rightScope) diff --git a/src/Node/BreaklessWhileLoopNode.php b/src/Node/BreaklessWhileLoopNode.php index 3d3ff248a20..f7df71bf19a 100644 --- a/src/Node/BreaklessWhileLoopNode.php +++ b/src/Node/BreaklessWhileLoopNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class BreaklessWhileLoopNode extends NodeAbstract implements VirtualNode +final class BreaklessWhileLoopNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/CatchWithUnthrownExceptionNode.php b/src/Node/CatchWithUnthrownExceptionNode.php index d1872895ddb..9f06bf20096 100644 --- a/src/Node/CatchWithUnthrownExceptionNode.php +++ b/src/Node/CatchWithUnthrownExceptionNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class CatchWithUnthrownExceptionNode extends NodeAbstract implements VirtualNode +final class CatchWithUnthrownExceptionNode extends NodeAbstract implements VirtualNode { public function __construct(private Catch_ $originalNode, private Type $caughtType, private Type $originalCaughtType) diff --git a/src/Node/ClassConstantsNode.php b/src/Node/ClassConstantsNode.php index 0b12946d8cf..4da543a2d75 100644 --- a/src/Node/ClassConstantsNode.php +++ b/src/Node/ClassConstantsNode.php @@ -10,9 +10,8 @@ /** * @api - * @final */ -class ClassConstantsNode extends NodeAbstract implements VirtualNode +final class ClassConstantsNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/ClassMethod.php b/src/Node/ClassMethod.php index e3f2cef2217..3a30a402d65 100644 --- a/src/Node/ClassMethod.php +++ b/src/Node/ClassMethod.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ClassMethod extends PhpParserClassMethod +final class ClassMethod extends PhpParserClassMethod { public function __construct( diff --git a/src/Node/ClassMethodsNode.php b/src/Node/ClassMethodsNode.php index 3a8a2df77d1..4c46fd9253b 100644 --- a/src/Node/ClassMethodsNode.php +++ b/src/Node/ClassMethodsNode.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class ClassMethodsNode extends NodeAbstract implements VirtualNode +final class ClassMethodsNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index df8f0f993ac..c22ec65c296 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -30,9 +30,8 @@ /** * @api - * @final */ -class ClassPropertiesNode extends NodeAbstract implements VirtualNode +final class ClassPropertiesNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index f0ad86ff8cd..571f8b34ed0 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -13,9 +13,8 @@ /** * @api - * @final */ -class ClassPropertyNode extends NodeAbstract implements VirtualNode +final class ClassPropertyNode extends NodeAbstract implements VirtualNode { public function __construct( diff --git a/src/Node/ClosureReturnStatementsNode.php b/src/Node/ClosureReturnStatementsNode.php index 7231c02e5f1..920b61750b3 100644 --- a/src/Node/ClosureReturnStatementsNode.php +++ b/src/Node/ClosureReturnStatementsNode.php @@ -13,9 +13,8 @@ /** * @api - * @final */ -class ClosureReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode +final class ClosureReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { private Node\Expr\Closure $closureExpr; diff --git a/src/Node/CollectedDataNode.php b/src/Node/CollectedDataNode.php index 7588947625f..8c0f52dc3b8 100644 --- a/src/Node/CollectedDataNode.php +++ b/src/Node/CollectedDataNode.php @@ -10,9 +10,8 @@ /** * @api - * @final */ -class CollectedDataNode extends NodeAbstract implements VirtualNode +final class CollectedDataNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/Constant/ClassConstantFetch.php b/src/Node/Constant/ClassConstantFetch.php index 8f23935dd1c..bda533900b3 100644 --- a/src/Node/Constant/ClassConstantFetch.php +++ b/src/Node/Constant/ClassConstantFetch.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class ClassConstantFetch +final class ClassConstantFetch { public function __construct(private ClassConstFetch $node, private Scope $scope) diff --git a/src/Node/ExecutionEndNode.php b/src/Node/ExecutionEndNode.php index e9cf081b135..5e0ddf13da7 100644 --- a/src/Node/ExecutionEndNode.php +++ b/src/Node/ExecutionEndNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class ExecutionEndNode extends NodeAbstract implements VirtualNode +final class ExecutionEndNode extends NodeAbstract implements VirtualNode { public function __construct( diff --git a/src/Node/FileNode.php b/src/Node/FileNode.php index 355a4b9559d..286168fb6ad 100644 --- a/src/Node/FileNode.php +++ b/src/Node/FileNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class FileNode extends NodeAbstract implements VirtualNode +final class FileNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/FinallyExitPointsNode.php b/src/Node/FinallyExitPointsNode.php index 32c2adb50c7..fed8d4888dc 100644 --- a/src/Node/FinallyExitPointsNode.php +++ b/src/Node/FinallyExitPointsNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class FinallyExitPointsNode extends NodeAbstract implements VirtualNode +final class FinallyExitPointsNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/FunctionCallableNode.php b/src/Node/FunctionCallableNode.php index 00e9e24fba9..9cd2cfc8c87 100644 --- a/src/Node/FunctionCallableNode.php +++ b/src/Node/FunctionCallableNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class FunctionCallableNode extends Expr implements VirtualNode +final class FunctionCallableNode extends Expr implements VirtualNode { public function __construct(private Name|Expr $name, private Expr\FuncCall $originalNode) diff --git a/src/Node/FunctionReturnStatementsNode.php b/src/Node/FunctionReturnStatementsNode.php index 4ab53d25c31..14582f309b1 100644 --- a/src/Node/FunctionReturnStatementsNode.php +++ b/src/Node/FunctionReturnStatementsNode.php @@ -14,9 +14,8 @@ /** * @api - * @final */ -class FunctionReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode +final class FunctionReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { /** diff --git a/src/Node/InArrowFunctionNode.php b/src/Node/InArrowFunctionNode.php index fb60b2b4045..20acb7c36ff 100644 --- a/src/Node/InArrowFunctionNode.php +++ b/src/Node/InArrowFunctionNode.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class InArrowFunctionNode extends NodeAbstract implements VirtualNode +final class InArrowFunctionNode extends NodeAbstract implements VirtualNode { private Node\Expr\ArrowFunction $originalNode; diff --git a/src/Node/InClassMethodNode.php b/src/Node/InClassMethodNode.php index f74db8e890c..52b50c17fd6 100644 --- a/src/Node/InClassMethodNode.php +++ b/src/Node/InClassMethodNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class InClassMethodNode extends Node\Stmt implements VirtualNode +final class InClassMethodNode extends Node\Stmt implements VirtualNode { public function __construct( diff --git a/src/Node/InClassNode.php b/src/Node/InClassNode.php index 1be1b7fab28..84a4ecab832 100644 --- a/src/Node/InClassNode.php +++ b/src/Node/InClassNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class InClassNode extends Node\Stmt implements VirtualNode +final class InClassNode extends Node\Stmt implements VirtualNode { public function __construct(private ClassLike $originalNode, private ClassReflection $classReflection) diff --git a/src/Node/InClosureNode.php b/src/Node/InClosureNode.php index 21def5dbefe..3e95aea8679 100644 --- a/src/Node/InClosureNode.php +++ b/src/Node/InClosureNode.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class InClosureNode extends NodeAbstract implements VirtualNode +final class InClosureNode extends NodeAbstract implements VirtualNode { private Node\Expr\Closure $originalNode; diff --git a/src/Node/InFunctionNode.php b/src/Node/InFunctionNode.php index 550c00e41b2..ce90bb7f387 100644 --- a/src/Node/InFunctionNode.php +++ b/src/Node/InFunctionNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class InFunctionNode extends Node\Stmt implements VirtualNode +final class InFunctionNode extends Node\Stmt implements VirtualNode { public function __construct( diff --git a/src/Node/InTraitNode.php b/src/Node/InTraitNode.php index 2a3a810fb5d..b7834e713fb 100644 --- a/src/Node/InTraitNode.php +++ b/src/Node/InTraitNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class InTraitNode extends Node\Stmt implements VirtualNode +final class InTraitNode extends Node\Stmt implements VirtualNode { public function __construct(private Node\Stmt\Trait_ $originalNode, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) diff --git a/src/Node/InstantiationCallableNode.php b/src/Node/InstantiationCallableNode.php index 289fb6fe5f6..98d838b1be4 100644 --- a/src/Node/InstantiationCallableNode.php +++ b/src/Node/InstantiationCallableNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class InstantiationCallableNode extends Expr implements VirtualNode +final class InstantiationCallableNode extends Expr implements VirtualNode { public function __construct(private Name|Expr $class, private Expr\New_ $originalNode) diff --git a/src/Node/InvalidateExprNode.php b/src/Node/InvalidateExprNode.php index a59799f0aa0..5fb5ba27deb 100644 --- a/src/Node/InvalidateExprNode.php +++ b/src/Node/InvalidateExprNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class InvalidateExprNode extends NodeAbstract implements VirtualNode +final class InvalidateExprNode extends NodeAbstract implements VirtualNode { public function __construct(private Expr $expr) diff --git a/src/Node/LiteralArrayItem.php b/src/Node/LiteralArrayItem.php index 1ba0c04ef59..4d9699121ba 100644 --- a/src/Node/LiteralArrayItem.php +++ b/src/Node/LiteralArrayItem.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class LiteralArrayItem +final class LiteralArrayItem { public function __construct(private Scope $scope, private ?ArrayItem $arrayItem) diff --git a/src/Node/LiteralArrayNode.php b/src/Node/LiteralArrayNode.php index e0bd824fad1..9c8a693ff67 100644 --- a/src/Node/LiteralArrayNode.php +++ b/src/Node/LiteralArrayNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class LiteralArrayNode extends NodeAbstract implements VirtualNode +final class LiteralArrayNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/MatchExpressionArm.php b/src/Node/MatchExpressionArm.php index 427ed83cae3..bad62656989 100644 --- a/src/Node/MatchExpressionArm.php +++ b/src/Node/MatchExpressionArm.php @@ -4,9 +4,8 @@ /** * @api - * @final */ -class MatchExpressionArm +final class MatchExpressionArm { /** diff --git a/src/Node/MatchExpressionArmBody.php b/src/Node/MatchExpressionArmBody.php index 628f0f777c1..dbb6f3f9170 100644 --- a/src/Node/MatchExpressionArmBody.php +++ b/src/Node/MatchExpressionArmBody.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class MatchExpressionArmBody +final class MatchExpressionArmBody { public function __construct(private Scope $scope, private Expr $body) diff --git a/src/Node/MatchExpressionArmCondition.php b/src/Node/MatchExpressionArmCondition.php index 4b3bc547203..95a291cce3b 100644 --- a/src/Node/MatchExpressionArmCondition.php +++ b/src/Node/MatchExpressionArmCondition.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class MatchExpressionArmCondition +final class MatchExpressionArmCondition { public function __construct(private Expr $condition, private Scope $scope, private int $line) diff --git a/src/Node/MatchExpressionNode.php b/src/Node/MatchExpressionNode.php index 0dd31d9b2ba..fb7f3606424 100644 --- a/src/Node/MatchExpressionNode.php +++ b/src/Node/MatchExpressionNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class MatchExpressionNode extends NodeAbstract implements VirtualNode +final class MatchExpressionNode extends NodeAbstract implements VirtualNode { /** diff --git a/src/Node/Method/MethodCall.php b/src/Node/Method/MethodCall.php index c88997a3f9e..d3726915a93 100644 --- a/src/Node/Method/MethodCall.php +++ b/src/Node/Method/MethodCall.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class MethodCall +final class MethodCall { public function __construct( diff --git a/src/Node/MethodCallableNode.php b/src/Node/MethodCallableNode.php index 7e7f1240ddc..b1e52bf1e6b 100644 --- a/src/Node/MethodCallableNode.php +++ b/src/Node/MethodCallableNode.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class MethodCallableNode extends Expr implements VirtualNode +final class MethodCallableNode extends Expr implements VirtualNode { public function __construct( diff --git a/src/Node/MethodReturnStatementsNode.php b/src/Node/MethodReturnStatementsNode.php index 96218713d35..2444df30b59 100644 --- a/src/Node/MethodReturnStatementsNode.php +++ b/src/Node/MethodReturnStatementsNode.php @@ -15,9 +15,8 @@ /** * @api - * @final */ -class MethodReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode +final class MethodReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode { private ClassMethod $classMethod; diff --git a/src/Node/Printer/ExprPrinter.php b/src/Node/Printer/ExprPrinter.php index 6df730ddfc4..32505ef5681 100644 --- a/src/Node/Printer/ExprPrinter.php +++ b/src/Node/Printer/ExprPrinter.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ExprPrinter +final class ExprPrinter { public function __construct(private Printer $printer) diff --git a/src/Node/Property/PropertyRead.php b/src/Node/Property/PropertyRead.php index 1c24537453b..86c220b77f0 100644 --- a/src/Node/Property/PropertyRead.php +++ b/src/Node/Property/PropertyRead.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class PropertyRead +final class PropertyRead { public function __construct( diff --git a/src/Node/Property/PropertyWrite.php b/src/Node/Property/PropertyWrite.php index fec7a1c4f0f..df39b83d0b3 100644 --- a/src/Node/Property/PropertyWrite.php +++ b/src/Node/Property/PropertyWrite.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class PropertyWrite +final class PropertyWrite { public function __construct(private PropertyFetch|StaticPropertyFetch $fetch, private Scope $scope, private bool $promotedPropertyWrite) diff --git a/src/Node/ReturnStatement.php b/src/Node/ReturnStatement.php index 7a5da6f2035..153faf15345 100644 --- a/src/Node/ReturnStatement.php +++ b/src/Node/ReturnStatement.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class ReturnStatement +final class ReturnStatement { private Node\Stmt\Return_ $returnNode; diff --git a/src/Node/StaticMethodCallableNode.php b/src/Node/StaticMethodCallableNode.php index 19e823234bb..407a5cfa4ce 100644 --- a/src/Node/StaticMethodCallableNode.php +++ b/src/Node/StaticMethodCallableNode.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class StaticMethodCallableNode extends Expr implements VirtualNode +final class StaticMethodCallableNode extends Expr implements VirtualNode { public function __construct( diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index 7c3cfb163c0..e0c8cb0af9b 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class UnreachableStatementNode extends Stmt implements VirtualNode +final class UnreachableStatementNode extends Stmt implements VirtualNode { public function __construct(private Stmt $originalStatement) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index b275c464eb1..fcd6871c398 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class PhpVersion +final class PhpVersion { public const SOURCE_RUNTIME = 1; diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index ed45d60e483..6cd991341bf 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -42,9 +42,8 @@ /** * @api - * @final */ -class ResolvedPhpDocBlock +final class ResolvedPhpDocBlock { public const EMPTY_DOC_STRING = '/** */'; diff --git a/src/PhpDoc/Tag/DeprecatedTag.php b/src/PhpDoc/Tag/DeprecatedTag.php index e7bf3c553f5..9bc036e1d81 100644 --- a/src/PhpDoc/Tag/DeprecatedTag.php +++ b/src/PhpDoc/Tag/DeprecatedTag.php @@ -4,9 +4,8 @@ /** * @api - * @final */ -class DeprecatedTag +final class DeprecatedTag { public function __construct(private ?string $message) diff --git a/src/PhpDoc/Tag/ExtendsTag.php b/src/PhpDoc/Tag/ExtendsTag.php index e8a48922b44..72cb97f7cf5 100644 --- a/src/PhpDoc/Tag/ExtendsTag.php +++ b/src/PhpDoc/Tag/ExtendsTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ExtendsTag +final class ExtendsTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/ImplementsTag.php b/src/PhpDoc/Tag/ImplementsTag.php index bc82888d3fb..556959b68d9 100644 --- a/src/PhpDoc/Tag/ImplementsTag.php +++ b/src/PhpDoc/Tag/ImplementsTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ImplementsTag +final class ImplementsTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/MethodTag.php b/src/PhpDoc/Tag/MethodTag.php index 85018267b19..43bda4cf97e 100644 --- a/src/PhpDoc/Tag/MethodTag.php +++ b/src/PhpDoc/Tag/MethodTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class MethodTag +final class MethodTag { /** diff --git a/src/PhpDoc/Tag/MethodTagParameter.php b/src/PhpDoc/Tag/MethodTagParameter.php index 21f4377ce66..3e4c817bf84 100644 --- a/src/PhpDoc/Tag/MethodTagParameter.php +++ b/src/PhpDoc/Tag/MethodTagParameter.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class MethodTagParameter +final class MethodTagParameter { public function __construct( diff --git a/src/PhpDoc/Tag/MixinTag.php b/src/PhpDoc/Tag/MixinTag.php index 5df36d74bd5..c115c2cacb0 100644 --- a/src/PhpDoc/Tag/MixinTag.php +++ b/src/PhpDoc/Tag/MixinTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class MixinTag +final class MixinTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/ParamClosureThisTag.php b/src/PhpDoc/Tag/ParamClosureThisTag.php index ae601dabc21..eba2903f214 100644 --- a/src/PhpDoc/Tag/ParamClosureThisTag.php +++ b/src/PhpDoc/Tag/ParamClosureThisTag.php @@ -6,7 +6,6 @@ /** * @api - * @final */ final class ParamClosureThisTag implements TypedTag { diff --git a/src/PhpDoc/Tag/ParamOutTag.php b/src/PhpDoc/Tag/ParamOutTag.php index 8bc982f2922..50d289fc871 100644 --- a/src/PhpDoc/Tag/ParamOutTag.php +++ b/src/PhpDoc/Tag/ParamOutTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ParamOutTag implements TypedTag +final class ParamOutTag implements TypedTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/ParamTag.php b/src/PhpDoc/Tag/ParamTag.php index f21038c4e17..50a3e98cc82 100644 --- a/src/PhpDoc/Tag/ParamTag.php +++ b/src/PhpDoc/Tag/ParamTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ParamTag implements TypedTag +final class ParamTag implements TypedTag { public function __construct( diff --git a/src/PhpDoc/Tag/PropertyTag.php b/src/PhpDoc/Tag/PropertyTag.php index a46bb9cdbf3..372e8b3cb9d 100644 --- a/src/PhpDoc/Tag/PropertyTag.php +++ b/src/PhpDoc/Tag/PropertyTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class PropertyTag +final class PropertyTag { public function __construct( diff --git a/src/PhpDoc/Tag/RequireExtendsTag.php b/src/PhpDoc/Tag/RequireExtendsTag.php index 60861f76155..97bf6854685 100644 --- a/src/PhpDoc/Tag/RequireExtendsTag.php +++ b/src/PhpDoc/Tag/RequireExtendsTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class RequireExtendsTag +final class RequireExtendsTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/RequireImplementsTag.php b/src/PhpDoc/Tag/RequireImplementsTag.php index 12702bce71e..aafd5602603 100644 --- a/src/PhpDoc/Tag/RequireImplementsTag.php +++ b/src/PhpDoc/Tag/RequireImplementsTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class RequireImplementsTag +final class RequireImplementsTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/ReturnTag.php b/src/PhpDoc/Tag/ReturnTag.php index ef5b1302936..c2354fa3b1a 100644 --- a/src/PhpDoc/Tag/ReturnTag.php +++ b/src/PhpDoc/Tag/ReturnTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ReturnTag implements TypedTag +final class ReturnTag implements TypedTag { public function __construct(private Type $type, private bool $isExplicit) diff --git a/src/PhpDoc/Tag/SelfOutTypeTag.php b/src/PhpDoc/Tag/SelfOutTypeTag.php index 1e1dacc2fa5..63d275cc4c7 100644 --- a/src/PhpDoc/Tag/SelfOutTypeTag.php +++ b/src/PhpDoc/Tag/SelfOutTypeTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class SelfOutTypeTag implements TypedTag +final class SelfOutTypeTag implements TypedTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/TemplateTag.php b/src/PhpDoc/Tag/TemplateTag.php index a14fa2c6abb..a7ab4ac8b4b 100644 --- a/src/PhpDoc/Tag/TemplateTag.php +++ b/src/PhpDoc/Tag/TemplateTag.php @@ -7,9 +7,8 @@ /** * @api - * @final */ -class TemplateTag +final class TemplateTag { /** diff --git a/src/PhpDoc/Tag/ThrowsTag.php b/src/PhpDoc/Tag/ThrowsTag.php index 220eefae956..1c1e30b897d 100644 --- a/src/PhpDoc/Tag/ThrowsTag.php +++ b/src/PhpDoc/Tag/ThrowsTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class ThrowsTag +final class ThrowsTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/TypeAliasTag.php b/src/PhpDoc/Tag/TypeAliasTag.php index 5a360b86139..d5cd10e5d68 100644 --- a/src/PhpDoc/Tag/TypeAliasTag.php +++ b/src/PhpDoc/Tag/TypeAliasTag.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class TypeAliasTag +final class TypeAliasTag { public function __construct( diff --git a/src/PhpDoc/Tag/UsesTag.php b/src/PhpDoc/Tag/UsesTag.php index 63ec60d0b4c..1679997ed37 100644 --- a/src/PhpDoc/Tag/UsesTag.php +++ b/src/PhpDoc/Tag/UsesTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class UsesTag +final class UsesTag { public function __construct(private Type $type) diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index 0d93daeac81..c4d5842474c 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class VarTag implements TypedTag +final class VarTag implements TypedTag { public function __construct(private Type $type) diff --git a/src/Reflection/Assertions.php b/src/Reflection/Assertions.php index 988652d0d43..a1f7ebfa6db 100644 --- a/src/Reflection/Assertions.php +++ b/src/Reflection/Assertions.php @@ -12,9 +12,8 @@ /** * @api - * @final */ -class Assertions +final class Assertions { private static ?self $empty = null; diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index f54fc37ba42..78cec7e379d 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -12,9 +12,8 @@ /** * @api - * @final */ -class ClassConstantReflection implements ConstantReflection +final class ClassConstantReflection implements ConstantReflection { private ?Type $valueType = null; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index d980be78649..fe4204dddf8 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -71,9 +71,8 @@ /** * @api - * @final */ -class ClassReflection +final class ClassReflection { /** @var ExtendedMethodReflection[] */ diff --git a/src/Reflection/EnumCaseReflection.php b/src/Reflection/EnumCaseReflection.php index 234ccbf95bd..2ce5cc63cfa 100644 --- a/src/Reflection/EnumCaseReflection.php +++ b/src/Reflection/EnumCaseReflection.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class EnumCaseReflection +final class EnumCaseReflection { public function __construct(private ClassReflection $declaringEnum, private string $name, private ?Type $backingValueType) diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index 4289056bcf5..9d7d9aa5bf8 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -17,9 +17,8 @@ /** * @api - * @final */ -class InitializerExprContext implements NamespaceAnswerer +final class InitializerExprContext implements NamespaceAnswerer { /** diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index f9f66c7531c..d78cb09f33f 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -55,9 +55,8 @@ /** * @api - * @final */ -class ParametersAcceptorSelector +final class ParametersAcceptorSelector { /** diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index 5a6bbd4a046..9a5c95f806b 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class PassedByReference +final class PassedByReference { private const NO = 1; diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index a05f5501058..329ad1de61d 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -25,9 +25,8 @@ /** * @api - * @final */ -class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection +final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection { /** diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 390ebc886a0..8de0a7870e2 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -47,9 +47,8 @@ /** * @api - * @final */ -class PhpMethodReflection implements ExtendedMethodReflection +final class PhpMethodReflection implements ExtendedMethodReflection { /** @var PhpParameterReflection[]|null */ diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index b1f2c186562..28e559719aa 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -15,9 +15,8 @@ /** * @api - * @final */ -class PhpPropertyReflection implements ExtendedPropertyReflection +final class PhpPropertyReflection implements ExtendedPropertyReflection { private ?Type $finalNativeType = null; diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index b6e638c9792..1d9f2aa6288 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -13,9 +13,8 @@ /** * @api - * @final */ -class TrivialParametersAcceptor implements ParametersAcceptorWithPhpDocs, CallableParametersAcceptor +final class TrivialParametersAcceptor implements ParametersAcceptorWithPhpDocs, CallableParametersAcceptor { /** @api */ diff --git a/src/Rules/DirectRegistry.php b/src/Rules/DirectRegistry.php index 0dfb5d71ecc..7cc3d331ca0 100644 --- a/src/Rules/DirectRegistry.php +++ b/src/Rules/DirectRegistry.php @@ -6,10 +6,7 @@ use function class_implements; use function class_parents; -/** - * @final - */ -class DirectRegistry implements Registry +final class DirectRegistry implements Registry { /** @var Rule[][] */ diff --git a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php index fd6866bc72d..75f020fe771 100644 --- a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php +++ b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php @@ -9,9 +9,8 @@ /** * @api - * @final */ -class DefaultExceptionTypeResolver implements ExceptionTypeResolver +final class DefaultExceptionTypeResolver implements ExceptionTypeResolver { /** diff --git a/src/Rules/FoundTypeResult.php b/src/Rules/FoundTypeResult.php index 4e89ed8ec52..61702c61947 100644 --- a/src/Rules/FoundTypeResult.php +++ b/src/Rules/FoundTypeResult.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class FoundTypeResult +final class FoundTypeResult { /** diff --git a/src/Rules/RuleErrorBuilder.php b/src/Rules/RuleErrorBuilder.php index bf673c0fe83..cb714dfd2c4 100644 --- a/src/Rules/RuleErrorBuilder.php +++ b/src/Rules/RuleErrorBuilder.php @@ -13,10 +13,9 @@ /** * @api - * @final * @template-covariant T of RuleError */ -class RuleErrorBuilder +final class RuleErrorBuilder { private const TYPE_MESSAGE = 1; diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index 7e387901e04..569d5d2ec10 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -10,10 +10,9 @@ /** * @api - * @final * @see https://phpstan.org/developing-extensions/trinary-logic */ -class TrinaryLogic +final class TrinaryLogic { private const YES = 1; diff --git a/src/Type/AcceptsResult.php b/src/Type/AcceptsResult.php index 4cfecb05f60..949151542c2 100644 --- a/src/Type/AcceptsResult.php +++ b/src/Type/AcceptsResult.php @@ -11,9 +11,8 @@ /** * @api - * @final */ -class AcceptsResult +final class AcceptsResult { /** diff --git a/src/Type/ClosureTypeFactory.php b/src/Type/ClosureTypeFactory.php index a0e82946ff5..fb36d04c44c 100644 --- a/src/Type/ClosureTypeFactory.php +++ b/src/Type/ClosureTypeFactory.php @@ -25,9 +25,8 @@ /** * @api - * @final */ -class ClosureTypeFactory +final class ClosureTypeFactory { public function __construct( diff --git a/src/Type/Constant/ConstantArrayTypeAndMethod.php b/src/Type/Constant/ConstantArrayTypeAndMethod.php index 01e735d949b..07f4156550d 100644 --- a/src/Type/Constant/ConstantArrayTypeAndMethod.php +++ b/src/Type/Constant/ConstantArrayTypeAndMethod.php @@ -8,9 +8,8 @@ /** * @api - * @final */ -class ConstantArrayTypeAndMethod +final class ConstantArrayTypeAndMethod { private function __construct( diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 952254e1b9d..5b8bb62bcbf 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -24,9 +24,8 @@ /** * @api - * @final */ -class ConstantArrayTypeBuilder +final class ConstantArrayTypeBuilder { public const ARRAY_COUNT_LIMIT = 256; diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 48cc18025b9..29a36e42f14 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -22,9 +22,8 @@ /** * @api - * @final */ -class ConstantTypeHelper +final class ConstantTypeHelper { /** diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 00fbc34a1dc..e631819818e 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -11,9 +11,8 @@ /** * @api - * @final */ -class TemplateTypeMap +final class TemplateTypeMap { private static ?TemplateTypeMap $empty = null; diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index b3089b62eb7..ff7d6098812 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -14,9 +14,8 @@ /** * @api - * @final */ -class TemplateTypeVariance +final class TemplateTypeVariance { private const INVARIANT = 1; diff --git a/src/Type/Generic/TemplateTypeVarianceMap.php b/src/Type/Generic/TemplateTypeVarianceMap.php index bcbb23ea424..072c7952e5d 100644 --- a/src/Type/Generic/TemplateTypeVarianceMap.php +++ b/src/Type/Generic/TemplateTypeVarianceMap.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class TemplateTypeVarianceMap +final class TemplateTypeVarianceMap { private static ?TemplateTypeVarianceMap $empty = null; diff --git a/src/Type/GenericTypeVariableResolver.php b/src/Type/GenericTypeVariableResolver.php index 8c3f7d2da58..732e57c10ac 100644 --- a/src/Type/GenericTypeVariableResolver.php +++ b/src/Type/GenericTypeVariableResolver.php @@ -6,9 +6,8 @@ /** * @api - * @final */ -class GenericTypeVariableResolver +final class GenericTypeVariableResolver { /** diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 3d2edcc4196..7a016c7bee0 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -38,9 +38,8 @@ /** * @api - * @final */ -class TypeCombinator +final class TypeCombinator { public static function addNull(Type $type): Type diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 8ae601b8323..6987e9482b5 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -17,9 +17,8 @@ /** * @api - * @final */ -class TypeUtils +final class TypeUtils { /** From eb78fe3bd9322657ea04fa4ac69aa426f6d54b8b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 26 Sep 2024 19:23:29 +0200 Subject: [PATCH 0464/3097] Improve Vsprintf inference --- ...intfFunctionDynamicReturnTypeExtension.php | 51 ++++++++++++------- tests/PHPStan/Analyser/nsrt/bug-7387.php | 2 +- .../Analyser/nsrt/non-empty-string.php | 7 +++ 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 4e00f1d8ec5..2a9b991915a 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -155,37 +155,50 @@ public function getTypeFromFunctionCall( } $isNonEmpty = $allPatternsNonEmpty; - if ( - !$isNonEmpty - && $functionReflection->getName() === 'sprintf' - && count($args) >= 2 - && $formatType->isNonEmptyString()->yes() - ) { - $allArgsNonEmpty = true; + if (!$isNonEmpty && $formatType->isNonEmptyString()->yes()) { + $isNonEmpty = $this->allValuesSatisfies( + $functionReflection, + $scope, + $args, + static fn (Type $type): bool => $type->toString()->isNonEmptyString()->yes() + ); + } + + if ($isNonEmpty) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + + return new StringType(); + } + + /** + * @param array $args + * @param callable(Type): bool $cb + */ + private function allValuesSatisfies(FunctionReflection $functionReflection, Scope $scope, array $args, callable $cb): bool + { + if ($functionReflection->getName() === 'sprintf' && count($args) >= 2) { foreach ($args as $key => $arg) { if ($key === 0) { continue; } - if (!$scope->getType($arg->value)->toString()->isNonEmptyString()->yes()) { - $allArgsNonEmpty = false; - break; + if (!$cb($scope->getType($arg->value))) { + return false; } } - if ($allArgsNonEmpty) { - $isNonEmpty = true; - } + return true; } - if ($isNonEmpty) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + if ($functionReflection->getName() === 'vsprintf' && count($args) >= 2) { + return $cb($scope->getType($args[1]->value)->getIterableValueType()); } - return new StringType(); + return false; } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index ad53206b257..fb7468ca333 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -110,7 +110,7 @@ public function vsprintf(array $array) assertType('numeric-string', vsprintf("%4d", explode('-', '1988-8-1'))); assertType('numeric-string', vsprintf("%4d", $array)); assertType('numeric-string', vsprintf("%4d", ['123'])); - assertType('string', vsprintf("%s", ['123'])); // could be '123' + assertType('non-empty-string', vsprintf("%s", ['123'])); // could be '123' // too many arguments.. php silently allows it assertType('numeric-string', vsprintf("%4d", ['123', '456'])); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 19a5d6b96ab..da8426c9057 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -364,6 +364,13 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo assertType('non-empty-string', sprintf($nonFalsy, $nonFalsy, $nonFalsy)); assertType('string', vsprintf($s, [])); assertType('string', vsprintf($nonEmpty, [])); + assertType('non-empty-string', vsprintf($nonEmpty, [$nonEmpty])); + assertType('non-empty-string', vsprintf($nonEmpty, [$nonEmpty, $nonEmpty])); + assertType('non-empty-string', vsprintf($nonEmpty, [$nonFalsy, $nonFalsy])); + assertType('non-empty-string', vsprintf($nonFalsy, [$nonEmpty])); + assertType('non-empty-string', vsprintf($nonFalsy, [$nonEmpty, $nonEmpty])); + assertType('non-empty-string', vsprintf($nonFalsy, [$nonFalsy, $nonEmpty])); + assertType('non-empty-string', vsprintf($nonFalsy, [$nonFalsy, $nonFalsy])); assertType('non-empty-string', sprintf("%s0%s", $s, $s)); assertType('non-empty-string', sprintf("%s0%s%s%s%s", $s, $s, $s, $s, $s)); From dfc1c316dbae1d9c43043155b9fa552355818dbb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:30:15 +0200 Subject: [PATCH 0465/3097] [BCB] Removed unused config parameter `staticReflectionClassNamePatterns` --- UPGRADING.md | 1 + conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 6dc6ec47e34..1650428bab1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -101,6 +101,7 @@ Appending `(?)` in `ignoreErrors` is not supported. * Removed unused config parameter `cache.nodesByFileCountMax` * Removed unused config parameter `memoryLimitFile` * Removed unused feature toggle `disableRuntimeReflectionProvider` +* Removed unused config parameter `staticReflectionClassNamePatterns` ## Upgrading guide for extension developers diff --git a/conf/config.neon b/conf/config.neon index 05aab74b882..3a18b983b8a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -125,7 +125,6 @@ parameters: tempResultCachePath: %tmpDir%/resultCaches resultCachePath: %tmpDir%/resultCache.php resultCacheChecksProjectExtensionFilesDependencies: false - staticReflectionClassNamePatterns: [] dynamicConstantNames: - ICONV_IMPL - LIBXML_VERSION diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 7307fc85719..143187dd1d5 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -140,7 +140,6 @@ parametersSchema: tempResultCachePath: string() resultCachePath: string() resultCacheChecksProjectExtensionFilesDependencies: bool() - staticReflectionClassNamePatterns: listOf(string()) dynamicConstantNames: listOf(string()) customRulesetUsed: schema(bool(), nullable()) rootDir: string() From df278a95b2f69972a0a21398a1121ba1762975e2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:31:54 +0200 Subject: [PATCH 0466/3097] [BCB] Removed `ReflectionProvider::supportsAnonymousClasses()` --- UPGRADING.md | 1 + src/Broker/Broker.php | 8 -------- .../BetterReflection/BetterReflectionProvider.php | 5 ----- src/Reflection/ReflectionProvider.php | 2 -- .../ReflectionProvider/DummyReflectionProvider.php | 5 ----- .../ReflectionProvider/MemoizingReflectionProvider.php | 5 ----- 6 files changed, 1 insertion(+), 25 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1650428bab1..52bc5808de5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -223,3 +223,4 @@ As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtensio * ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) * `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. * `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 080d3accc83..5465a4fb457 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -69,14 +69,6 @@ public function getClassName(string $className): string return $this->reflectionProvider->getClassName($className); } - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function supportsAnonymousClasses(): bool - { - return $this->reflectionProvider->supportsAnonymousClasses(); - } - /** * @deprecated Use PHPStan\Reflection\ReflectionProvider instead */ diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index be27dd03a06..0bc27581520 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -179,11 +179,6 @@ public function getClassName(string $className): string return $reflectionClass->getName(); } - public function supportsAnonymousClasses(): bool - { - return true; - } - public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection { if (isset($classNode->namespacedName)) { diff --git a/src/Reflection/ReflectionProvider.php b/src/Reflection/ReflectionProvider.php index e268800f5a3..e0a3c6efce2 100644 --- a/src/Reflection/ReflectionProvider.php +++ b/src/Reflection/ReflectionProvider.php @@ -16,8 +16,6 @@ public function getClass(string $className): ClassReflection; public function getClassName(string $className): string; - public function supportsAnonymousClasses(): bool; - public function getAnonymousClassReflection( Node\Stmt\Class_ $classNode, Scope $scope, diff --git a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php index 7ee57294830..1fbe97ae5d9 100644 --- a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php @@ -29,11 +29,6 @@ public function getClassName(string $className): string return $className; } - public function supportsAnonymousClasses(): bool - { - return false; - } - public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection { throw new ShouldNotHappenException(); diff --git a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php index 9d6cacd951f..17dc5ad4ea8 100644 --- a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php @@ -56,11 +56,6 @@ public function getClassName(string $className): string return $this->classNames[$lowerClassName] = $this->provider->getClassName($className); } - public function supportsAnonymousClasses(): bool - { - return $this->provider->supportsAnonymousClasses(); - } - public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection { return $this->provider->getAnonymousClassReflection($classNode, $scope); From 30b9eb8b9879bb4bd92f99ee3a235ac3d81ea2fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:35:52 +0200 Subject: [PATCH 0467/3097] [BCB] Removed `Broker` --- UPGRADING.md | 8 + conf/config.neon | 9 -- phpstan-baseline.neon | 40 ----- src/Broker/Broker.php | 138 ------------------ src/Broker/BrokerFactory.php | 15 -- src/DependencyInjection/ContainerFactory.php | 3 - ...assReflectionExtensionRegistryProvider.php | 2 - ...micReturnTypeExtensionRegistryProvider.php | 2 - ...ypeSpecifyingExtensionRegistryProvider.php | 2 - .../ValidateIgnoredErrorsExtension.php | 2 +- src/PhpDoc/StubValidator.php | 3 - .../BetterReflectionProvider.php | 7 +- src/Reflection/BrokerAwareExtension.php | 16 -- src/Reflection/ClassReflection.php | 1 - .../ClassReflectionExtensionRegistry.php | 10 -- ...alObjectCratesClassReflectionExtension.php | 20 ++- src/Reflection/ReflectionProvider.php | 3 + .../DummyReflectionProvider.php | 5 + .../MemoizingReflectionProvider.php | 5 + src/Testing/PHPStanTestCase.php | 10 -- .../DynamicReturnTypeExtensionRegistry.php | 10 -- src/Type/ObjectShapeType.php | 3 - src/Type/ObjectType.php | 2 - ...peratorTypeSpecifyingExtensionRegistry.php | 14 -- tests/PHPStan/Type/FileTypeMapperTest.php | 3 +- 25 files changed, 45 insertions(+), 288 deletions(-) delete mode 100644 src/Broker/Broker.php delete mode 100644 src/Reflection/BrokerAwareExtension.php diff --git a/UPGRADING.md b/UPGRADING.md index 52bc5808de5..729d6066b5b 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -215,6 +215,14 @@ Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. +### Removed `PHPStan\Broker\Broker` + +Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead. + +`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead. + +Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. + ### Minor backward compatibility breaks * Classes that were previously `@final` were made `final` diff --git a/conf/config.neon b/conf/config.neon index 3a18b983b8a..5dc867fc1be 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1881,15 +1881,6 @@ services: parentDirectory: %currentWorkingDirectory% autowired: false - broker: - class: PHPStan\Broker\Broker - factory: @brokerFactory::create - autowired: - - PHPStan\Broker\Broker - - brokerFactory: - class: PHPStan\Broker\BrokerFactory - cacheStorage: class: PHPStan\Cache\FileCacheStorage arguments: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 81b6bc27c67..80700ff58ba 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -189,14 +189,6 @@ parameters: count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php - - - message: """ - #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# - """ - count: 1 - path: src/PhpDoc/StubValidator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 @@ -1161,22 +1153,6 @@ parameters: count: 3 path: src/Type/NullType.php - - - message: """ - #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# - """ - count: 2 - path: src/Type/ObjectShapeType.php - - - - message: """ - #^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Inject %%universalObjectCratesClasses%% parameter instead\\.$# - """ - count: 2 - path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 @@ -1192,22 +1168,6 @@ parameters: count: 1 path: src/Type/ObjectShapeType.php - - - message: """ - #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# - """ - count: 1 - path: src/Type/ObjectType.php - - - - message: """ - #^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Inject %%universalObjectCratesClasses%% parameter instead\\.$# - """ - count: 1 - path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" count: 1 diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php deleted file mode 100644 index 5465a4fb457..00000000000 --- a/src/Broker/Broker.php +++ /dev/null @@ -1,138 +0,0 @@ -reflectionProvider->hasClass($className); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function getClass(string $className): ClassReflection - { - return $this->reflectionProvider->getClass($className); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function getClassName(string $className): string - { - return $this->reflectionProvider->getClassName($className); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection - { - return $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool - { - return $this->reflectionProvider->hasFunction($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): FunctionReflection - { - return $this->reflectionProvider->getFunction($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string - { - return $this->reflectionProvider->resolveFunctionName($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool - { - return $this->reflectionProvider->hasConstant($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): GlobalConstantReflection - { - return $this->reflectionProvider->getConstant($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Use PHPStan\Reflection\ReflectionProvider instead - */ - public function resolveConstantName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string - { - return $this->reflectionProvider->resolveConstantName($nameNode, $namespaceAnswerer); - } - - /** - * @deprecated Inject %universalObjectCratesClasses% parameter instead. - * - * @return string[] - */ - public function getUniversalObjectCratesClasses(): array - { - return $this->universalObjectCratesClasses; - } - -} diff --git a/src/Broker/BrokerFactory.php b/src/Broker/BrokerFactory.php index 177b6b5843d..bbd8d97a3db 100644 --- a/src/Broker/BrokerFactory.php +++ b/src/Broker/BrokerFactory.php @@ -2,9 +2,6 @@ namespace PHPStan\Broker; -use PHPStan\DependencyInjection\Container; -use PHPStan\Reflection\ReflectionProvider; - final class BrokerFactory { @@ -17,16 +14,4 @@ final class BrokerFactory public const OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.broker.operatorTypeSpecifyingExtension'; public const EXPRESSION_TYPE_RESOLVER_EXTENSION_TAG = 'phpstan.broker.expressionTypeResolverExtension'; - public function __construct(private Container $container) - { - } - - public function create(): Broker - { - return new Broker( - $this->container->getByType(ReflectionProvider::class), - $this->container->getParameter('universalObjectCratesClasses'), - ); - } - } diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 4ab01e56c97..c997c65ec29 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -22,7 +22,6 @@ use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; -use PHPStan\Broker\Broker; use PHPStan\Command\CommandHelper; use PHPStan\File\FileHelper; use PHPStan\Node\Printer\Printer; @@ -181,8 +180,6 @@ public static function postInitializeContainer(Container $container): void $container->getByType(Printer::class), ); - $broker = $container->getByType(Broker::class); - Broker::registerInstance($broker); ReflectionProviderStaticAccessor::registerInstance($container->getByType(ReflectionProvider::class)); PhpVersionStaticAccessor::registerInstance($container->getByType(PhpVersion::class)); ObjectType::resetCaches(); diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index cb8c2d6543b..7e47b6498c3 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -2,7 +2,6 @@ namespace PHPStan\DependencyInjection\Reflection; -use PHPStan\Broker\Broker; use PHPStan\Broker\BrokerFactory; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension; @@ -35,7 +34,6 @@ public function getRegistry(): ClassReflectionExtensionRegistry $mixinPropertiesClassReflectionExtension = $this->container->getByType(MixinPropertiesClassReflectionExtension::class); $this->registry = new ClassReflectionExtensionRegistry( - $this->container->getByType(Broker::class), array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension, $mixinPropertiesClassReflectionExtension]), array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension, $mixinMethodsClassReflectionExtension]), $this->container->getServicesByTag(BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG), diff --git a/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php index 9a15af910f9..3dea1201777 100644 --- a/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyDynamicReturnTypeExtensionRegistryProvider.php @@ -2,7 +2,6 @@ namespace PHPStan\DependencyInjection\Type; -use PHPStan\Broker\Broker; use PHPStan\Broker\BrokerFactory; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; @@ -21,7 +20,6 @@ public function getRegistry(): DynamicReturnTypeExtensionRegistry { if ($this->registry === null) { $this->registry = new DynamicReturnTypeExtensionRegistry( - $this->container->getByType(Broker::class), $this->container->getByType(ReflectionProvider::class), $this->container->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $this->container->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), diff --git a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php index 5f1a719b487..2be97ee7772 100644 --- a/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php +++ b/src/DependencyInjection/Type/LazyOperatorTypeSpecifyingExtensionRegistryProvider.php @@ -2,7 +2,6 @@ namespace PHPStan\DependencyInjection\Type; -use PHPStan\Broker\Broker; use PHPStan\Broker\BrokerFactory; use PHPStan\DependencyInjection\Container; use PHPStan\Type\OperatorTypeSpecifyingExtensionRegistry; @@ -20,7 +19,6 @@ public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry { if ($this->registry === null) { $this->registry = new OperatorTypeSpecifyingExtensionRegistry( - $this->container->getByType(Broker::class), $this->container->getServicesByTag(BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG), ); } diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 0f5dc0d9e20..87d7a31a2e6 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -103,7 +103,7 @@ public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry { - return new OperatorTypeSpecifyingExtensionRegistry(null, []); + return new OperatorTypeSpecifyingExtensionRegistry([]); } }, new OversizedArrayBuilder(), true), diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 96922b4c091..a0539b7168b 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\FileAnalyser; use PHPStan\Analyser\InternalError; use PHPStan\Analyser\NodeScopeResolver; -use PHPStan\Broker\Broker; use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\DerivativeContainerFactory; @@ -109,7 +108,6 @@ public function validate(array $stubFiles, bool $debug): array return []; } - $originalBroker = Broker::getInstance(); $originalReflectionProvider = ReflectionProviderStaticAccessor::getInstance(); $originalPhpVersion = PhpVersionStaticAccessor::getInstance(); $container = $this->derivativeContainerFactory->create([ @@ -158,7 +156,6 @@ static function (): void { } } - Broker::registerInstance($originalBroker); ReflectionProviderStaticAccessor::registerInstance($originalReflectionProvider); PhpVersionStaticAccessor::registerInstance($originalPhpVersion); ObjectType::resetCaches(); diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 0bc27581520..06e94ae7188 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -75,7 +75,7 @@ final class BetterReflectionProvider implements ReflectionProvider private array $cachedConstants = []; /** - * @param string[] $universalObjectCratesClasses + * @param list $universalObjectCratesClasses */ public function __construct( private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, @@ -257,6 +257,11 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ return self::$anonymousClasses[$className]; } + public function getUniversalObjectCratesClasses(): array + { + return $this->universalObjectCratesClasses; + } + public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool { return $this->resolveFunctionName($nameNode, $namespaceAnswerer) !== null; diff --git a/src/Reflection/BrokerAwareExtension.php b/src/Reflection/BrokerAwareExtension.php deleted file mode 100644 index 9ca104d127d..00000000000 --- a/src/Reflection/BrokerAwareExtension.php +++ /dev/null @@ -1,16 +0,0 @@ -reflectionProvider, - $this->universalObjectCratesClasses, $this, )) { return true; diff --git a/src/Reflection/ClassReflectionExtensionRegistry.php b/src/Reflection/ClassReflectionExtensionRegistry.php index 2dd864952ac..1705f47461d 100644 --- a/src/Reflection/ClassReflectionExtensionRegistry.php +++ b/src/Reflection/ClassReflectionExtensionRegistry.php @@ -2,10 +2,8 @@ namespace PHPStan\Reflection; -use PHPStan\Broker\Broker; use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension; use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension; -use function array_merge; final class ClassReflectionExtensionRegistry { @@ -16,7 +14,6 @@ final class ClassReflectionExtensionRegistry * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions */ public function __construct( - Broker $broker, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, private array $allowedSubTypesClassReflectionExtensions, @@ -24,13 +21,6 @@ public function __construct( private RequireExtendsMethodsClassReflectionExtension $requireExtendsMethodsClassReflectionExtension, ) { - foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions, $allowedSubTypesClassReflectionExtensions) as $extension) { - if (!($extension instanceof BrokerAwareExtension)) { - continue; - } - - $extension->setBroker($broker); - } } /** diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index a39fe6c67b6..df064434368 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -14,7 +14,7 @@ final class UniversalObjectCratesClassReflectionExtension { /** - * @param string[] $classes + * @param list $classes */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -26,17 +26,29 @@ public function __construct( public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { - return self::isUniversalObjectCrate( + return self::isUniversalObjectCrateImplementation( $this->reflectionProvider, $this->classes, $classReflection, ); } + public static function isUniversalObjectCrate( + ReflectionProvider $reflectionProvider, + ClassReflection $classReflection, + ): bool + { + return self::isUniversalObjectCrateImplementation( + $reflectionProvider, + $reflectionProvider->getUniversalObjectCratesClasses(), + $classReflection, + ); + } + /** - * @param string[] $classes + * @param list $classes */ - public static function isUniversalObjectCrate( + private static function isUniversalObjectCrateImplementation( ReflectionProvider $reflectionProvider, array $classes, ClassReflection $classReflection, diff --git a/src/Reflection/ReflectionProvider.php b/src/Reflection/ReflectionProvider.php index e0a3c6efce2..2e0cc7ee208 100644 --- a/src/Reflection/ReflectionProvider.php +++ b/src/Reflection/ReflectionProvider.php @@ -21,6 +21,9 @@ public function getAnonymousClassReflection( Scope $scope, ): ClassReflection; + /** @return list */ + public function getUniversalObjectCratesClasses(): array; + public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool; public function getFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): FunctionReflection; diff --git a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php index 1fbe97ae5d9..2f96b7016f2 100644 --- a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php @@ -34,6 +34,11 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ throw new ShouldNotHappenException(); } + public function getUniversalObjectCratesClasses(): array + { + return []; + } + public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool { return false; diff --git a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php index 17dc5ad4ea8..48060fdfbd7 100644 --- a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php @@ -61,6 +61,11 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ return $this->provider->getAnonymousClassReflection($classNode, $scope); } + public function getUniversalObjectCratesClasses(): array + { + return $this->provider->getUniversalObjectCratesClasses(); + } + public function hasFunction(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool { return $this->provider->hasFunction($nameNode, $namespaceAnswerer); diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index c5d0a651a6b..73c2e199475 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -12,7 +12,6 @@ use PHPStan\BetterReflection\Reflector\ConstantReflector; use PHPStan\BetterReflection\Reflector\FunctionReflector; use PHPStan\BetterReflection\Reflector\Reflector; -use PHPStan\Broker\Broker; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; @@ -115,15 +114,6 @@ public static function getParser(): Parser return $parser; } - /** - * @api - * @deprecated Use createReflectionProvider() instead - */ - public function createBroker(): Broker - { - return self::getContainer()->getByType(Broker::class); - } - /** @api */ public static function createReflectionProvider(): ReflectionProvider { diff --git a/src/Type/DynamicReturnTypeExtensionRegistry.php b/src/Type/DynamicReturnTypeExtensionRegistry.php index 002a87f66e0..e2a30437b38 100644 --- a/src/Type/DynamicReturnTypeExtensionRegistry.php +++ b/src/Type/DynamicReturnTypeExtensionRegistry.php @@ -2,8 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; -use PHPStan\Reflection\BrokerAwareExtension; use PHPStan\Reflection\ReflectionProvider; use function array_merge; use function strtolower; @@ -23,20 +21,12 @@ final class DynamicReturnTypeExtensionRegistry * @param DynamicFunctionReturnTypeExtension[] $dynamicFunctionReturnTypeExtensions */ public function __construct( - Broker $broker, private ReflectionProvider $reflectionProvider, private array $dynamicMethodReturnTypeExtensions, private array $dynamicStaticMethodReturnTypeExtensions, private array $dynamicFunctionReturnTypeExtensions, ) { - foreach (array_merge($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions, $dynamicFunctionReturnTypeExtensions) as $extension) { - if (!($extension instanceof BrokerAwareExtension)) { - continue; - } - - $extension->setBroker($broker); - } } /** diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index a00324259a5..aeba44bb19b 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -3,7 +3,6 @@ namespace PHPStan\Type; use PHPStan\Analyser\OutOfClassScope; -use PHPStan\Broker\Broker; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -138,7 +137,6 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult foreach ($type->getObjectClassReflections() as $classReflection) { if (!UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( $reflectionProvider, - Broker::getInstance()->getUniversalObjectCratesClasses(), $classReflection, )) { continue; @@ -251,7 +249,6 @@ public function isSuperTypeOf(Type $type): TrinaryLogic foreach ($type->getObjectClassReflections() as $classReflection) { if (!UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( $reflectionProvider, - Broker::getInstance()->getUniversalObjectCratesClasses(), $classReflection, )) { continue; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 31a9613e918..672b1f57c85 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -13,7 +13,6 @@ use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; -use PHPStan\Broker\Broker; use PHPStan\Broker\ClassNotFoundException; use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -619,7 +618,6 @@ public function toArray(): Type !$classReflection->getNativeReflection()->isUserDefined() || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( $reflectionProvider, - Broker::getInstance()->getUniversalObjectCratesClasses(), $classReflection, ) ) { diff --git a/src/Type/OperatorTypeSpecifyingExtensionRegistry.php b/src/Type/OperatorTypeSpecifyingExtensionRegistry.php index 809ba5bd202..7b84648f28b 100644 --- a/src/Type/OperatorTypeSpecifyingExtensionRegistry.php +++ b/src/Type/OperatorTypeSpecifyingExtensionRegistry.php @@ -2,8 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; -use PHPStan\Reflection\BrokerAwareExtension; use function array_filter; use function array_values; @@ -14,21 +12,9 @@ final class OperatorTypeSpecifyingExtensionRegistry * @param OperatorTypeSpecifyingExtension[] $extensions */ public function __construct( - ?Broker $broker, private array $extensions, ) { - if ($broker === null) { - return; - } - - foreach ($extensions as $extension) { - if (!$extension instanceof BrokerAwareExtension) { - continue; - } - - $extension->setBroker($broker); - } } /** diff --git a/tests/PHPStan/Type/FileTypeMapperTest.php b/tests/PHPStan/Type/FileTypeMapperTest.php index a5be40c268e..c1af0c7740e 100644 --- a/tests/PHPStan/Type/FileTypeMapperTest.php +++ b/tests/PHPStan/Type/FileTypeMapperTest.php @@ -3,7 +3,6 @@ namespace PHPStan\Type; use DependentPhpDocs\Foo; -use PHPStan\Broker\Broker; use PHPStan\PhpDoc\Tag\ReturnTag; use PHPStan\ShouldNotHappenException; use PHPStan\Testing\PHPStanTestCase; @@ -161,7 +160,7 @@ public function testFileThrowsPhpDocs(): void public function testFileWithCyclicPhpDocs(): void { - self::getContainer()->getByType(Broker::class); + $this->createReflectionProvider(); /** @var FileTypeMapper $fileTypeMapper */ $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); From 4213c244ca3aff6d3fda7d045fecc4e3ad0564c4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:49:04 +0200 Subject: [PATCH 0468/3097] Fix build --- phpstan-baseline.neon | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 80700ff58ba..95c67cfed4f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,10 @@ parameters: ignoreErrors: + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + count: 1 + path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php + - message: "#^Method PHPStan\\\\Analyser\\\\AnalyserResultFinalizer\\:\\:finalize\\(\\) throws checked exception Throwable but it's missing from the PHPDoc @throws tag\\.$#" count: 1 @@ -159,11 +164,6 @@ parameters: count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" - count: 1 - path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php - - message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" count: 2 From 248ce53a7e52c2435c39df7785486ee0d5bbb866 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:50:49 +0200 Subject: [PATCH 0469/3097] [BCB] Remove `ArrayType::generalizeKeys()` --- UPGRADING.md | 1 + src/Type/ArrayType.php | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 729d6066b5b..d75f6b3a3c9 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -232,3 +232,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. * `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. * Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) +* Remove `ArrayType::generalizeKeys()` diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index db1c331ea64..98acab68dfb 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -183,14 +183,6 @@ function () use ($level, $isMixedKeyType, $isMixedItemType): string { ); } - /** - * @deprecated - */ - public function generalizeKeys(): self - { - return new self($this->keyType->generalize(GeneralizePrecision::lessSpecific()), $this->itemType); - } - public function generalizeValues(): self { return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific())); From 73a63f111b41744d2d43c5c8b04ade35e5b24a73 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:51:14 +0200 Subject: [PATCH 0470/3097] [BCB] Remove `ArrayType::count()` --- UPGRADING.md | 1 + src/Type/ArrayType.php | 6 ------ src/Type/Constant/ConstantArrayType.php | 6 ------ 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d75f6b3a3c9..ae40bcc88a5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -233,3 +233,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. * Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) * Remove `ArrayType::generalizeKeys()` +* Remove `ArrayType::count()` diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 98acab68dfb..4e18f428fc3 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -638,12 +638,6 @@ public function toArrayKey(): Type return new ErrorType(); } - /** @deprecated Use getArraySize() instead */ - public function count(): Type - { - return $this->getArraySize(); - } - /** @deprecated Use $offsetType->toArrayKey() instead */ public static function castToArrayKeyType(Type $offsetType): Type { diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 6063a96ef27..5b6700d80b0 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1421,12 +1421,6 @@ private function getKeysOrValuesArray(array $types): self return new self($keyTypes, $valueTypes, $autoIndexes, $optionalKeys, TrinaryLogic::createYes()); } - /** @deprecated Use getArraySize() instead */ - public function count(): Type - { - return $this->getArraySize(); - } - public function describe(VerbosityLevel $level): string { $describeValue = function (bool $truncate) use ($level): string { From 68f6ec0699b1749ca14ab3ca7a2c72bff77799ba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:51:46 +0200 Subject: [PATCH 0471/3097] [BCB] Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead --- UPGRADING.md | 1 + src/Type/ArrayType.php | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index ae40bcc88a5..b4a7fdfe321 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -234,3 +234,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) * Remove `ArrayType::generalizeKeys()` * Remove `ArrayType::count()` +* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 4e18f428fc3..dc9c6ec2286 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -638,12 +638,6 @@ public function toArrayKey(): Type return new ErrorType(); } - /** @deprecated Use $offsetType->toArrayKey() instead */ - public static function castToArrayKeyType(Type $offsetType): Type - { - return $offsetType->toArrayKey(); - } - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { From 8e2cf7bd957dbbab1c43f3d0983c8cf2108b55da Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:52:06 +0200 Subject: [PATCH 0472/3097] Upgrading note --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index b4a7fdfe321..154776b3317 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -233,5 +233,5 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. * Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) * Remove `ArrayType::generalizeKeys()` -* Remove `ArrayType::count()` +* Remove `ArrayType::count()`, use `Type::getArraySize()` instead * Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead From 9bab8ca6ebe55b2cbdba5c07bbed26411d6b25eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:53:06 +0200 Subject: [PATCH 0473/3097] Remove AppendedArrayKeyTypeRule and AppendedArrayItemTypeRule --- phpstan-baseline.neon | 32 ------- .../Arrays/AppendedArrayItemTypeRule.php | 93 ------------------- src/Rules/Arrays/AppendedArrayKeyTypeRule.php | 89 ------------------ .../Arrays/AppendedArrayItemTypeRuleTest.php | 65 ------------- .../Arrays/AppendedArrayKeyTypeRuleTest.php | 71 -------------- 5 files changed, 350 deletions(-) delete mode 100644 src/Rules/Arrays/AppendedArrayItemTypeRule.php delete mode 100644 src/Rules/Arrays/AppendedArrayKeyTypeRule.php delete mode 100644 tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php delete mode 100644 tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 95c67cfed4f..f7e243fc88f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1677,38 +1677,6 @@ parameters: count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - - - message: """ - #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayItemTypeRule\\: - Replaced by PHPStan\\\\Rules\\\\Properties\\\\TypesAssignedToPropertiesRule$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php - - - - message: """ - #^Return type of method PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayItemTypeRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayItemTypeRule\\: - Replaced by PHPStan\\\\Rules\\\\Properties\\\\TypesAssignedToPropertiesRule$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php - - - - message: """ - #^Instantiation of deprecated class PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayKeyTypeRule\\: - Replaced by PHPStan\\\\Rules\\\\Properties\\\\TypesAssignedToPropertiesRule$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php - - - - message: """ - #^Return type of method PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayKeyTypeRuleTest\\:\\:getRule\\(\\) has typehint with deprecated class PHPStan\\\\Rules\\\\Arrays\\\\AppendedArrayKeyTypeRule\\: - Replaced by PHPStan\\\\Rules\\\\Properties\\\\TypesAssignedToPropertiesRule$# - """ - count: 1 - path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" count: 1 diff --git a/src/Rules/Arrays/AppendedArrayItemTypeRule.php b/src/Rules/Arrays/AppendedArrayItemTypeRule.php deleted file mode 100644 index dee1dc6c308..00000000000 --- a/src/Rules/Arrays/AppendedArrayItemTypeRule.php +++ /dev/null @@ -1,93 +0,0 @@ - - */ -final class AppendedArrayItemTypeRule implements Rule -{ - - public function __construct( - private PropertyReflectionFinder $propertyReflectionFinder, - private RuleLevelHelper $ruleLevelHelper, - ) - { - } - - public function getNodeType(): string - { - return Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ( - !$node instanceof Assign - && !$node instanceof AssignOp - && !$node instanceof AssignRef - ) { - return []; - } - - if (!($node->var instanceof ArrayDimFetch)) { - return []; - } - - if ( - !$node->var->var instanceof Node\Expr\PropertyFetch - && !$node->var->var instanceof Node\Expr\StaticPropertyFetch - ) { - return []; - } - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); - if ($propertyReflection === null) { - return []; - } - - $assignedToType = $propertyReflection->getWritableType(); - if (!$assignedToType->isArray()->yes()) { - return []; - } - - if ($node instanceof Assign || $node instanceof AssignRef) { - $assignedValueType = $scope->getType($node->expr); - } else { - $assignedValueType = $scope->getType($node); - } - - $itemType = $assignedToType->getIterableValueType(); - $accepts = $this->ruleLevelHelper->acceptsWithReason($itemType, $assignedValueType, $scope->isDeclareStrictTypes()); - if (!$accepts->result) { - $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($itemType, $assignedValueType); - return [ - RuleErrorBuilder::message(sprintf( - 'Array (%s) does not accept %s.', - $assignedToType->describe($verbosityLevel), - $assignedValueType->describe($verbosityLevel), - )) - ->acceptsReasonsTip($accepts->reasons) - ->identifier('array.valueType') - ->build(), - ]; - } - - return []; - } - -} diff --git a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php deleted file mode 100644 index 3e91fe8dbea..00000000000 --- a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ -final class AppendedArrayKeyTypeRule implements Rule -{ - - public function __construct( - private PropertyReflectionFinder $propertyReflectionFinder, - private bool $checkUnionTypes, - ) - { - } - - public function getNodeType(): string - { - return Assign::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if (!($node->var instanceof ArrayDimFetch)) { - return []; - } - - if ( - !$node->var->var instanceof Node\Expr\PropertyFetch - && !$node->var->var instanceof Node\Expr\StaticPropertyFetch - ) { - return []; - } - - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->var->var, $scope); - if ($propertyReflection === null) { - return []; - } - - $arrayType = $propertyReflection->getReadableType(); - if (!$arrayType->isArray()->yes()) { - return []; - } - - if ($node->var->dim !== null) { - $dimensionType = $scope->getType($node->var->dim); - $isValidKey = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType); - if (!$isValidKey->yes()) { - // already handled by InvalidKeyInArrayDimFetchRule - return []; - } - - $keyType = $dimensionType->toArrayKey(); - if (!$this->checkUnionTypes && $keyType instanceof UnionType) { - return []; - } - } else { - $keyType = new IntegerType(); - } - - if (!$arrayType->getIterableKeyType()->isSuperTypeOf($keyType)->yes()) { - $verbosity = VerbosityLevel::getRecommendedLevelByType($arrayType->getIterableKeyType(), $keyType); - return [ - RuleErrorBuilder::message(sprintf( - 'Array (%s) does not accept key %s.', - $arrayType->describe($verbosity), - $keyType->describe(VerbosityLevel::value()), - ))->identifier('array.keyType')->build(), - ]; - } - - return []; - } - -} diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php deleted file mode 100644 index 3a6c6dfb4fa..00000000000 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ -class AppendedArrayItemTypeRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new AppendedArrayItemTypeRule( - new PropertyReflectionFinder(), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), - ); - } - - public function testAppendedArrayItemType(): void - { - $this->analyse( - [__DIR__ . '/data/appended-array-item.php'], - [ - [ - 'Array (array) does not accept string.', - 18, - ], - [ - 'Array (array) does not accept array{1, 2, 3}.', - 20, - ], - [ - 'Array (array) does not accept array{\'AppendedArrayItem\\\\Foo\', \'classMethod\'}.', - 23, - ], - [ - 'Array (array) does not accept array{\'Foo\', \'Hello world\'}.', - 25, - ], - [ - 'Array (array) does not accept string.', - 27, - ], - [ - 'Array (array) does not accept string.', - 32, - ], - [ - 'Array (array) does not accept Closure(): 1.', - 45, - ], - [ - 'Array (array) does not accept AppendedArrayItem\Baz.', - 79, - ], - ], - ); - } - -} diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php deleted file mode 100644 index d74a1ddd8e7..00000000000 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ -class AppendedArrayKeyTypeRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new AppendedArrayKeyTypeRule( - new PropertyReflectionFinder(), - true, - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/appended-array-key.php'], [ - [ - 'Array (array) does not accept key int|string.', - 28, - ], - [ - 'Array (array) does not accept key string.', - 30, - ], - [ - 'Array (array) does not accept key int.', - 31, - ], - [ - 'Array (array) does not accept key int|string.', - 33, - ], - [ - 'Array (array) does not accept key 0.', - 38, - ], - [ - 'Array (array) does not accept key 1.', - 46, - ], - [ - 'Array (array<1|2|3, string>) does not accept key int.', - 80, - ], - [ - 'Array (array<1|2|3, string>) does not accept key 4.', - 85, - ], - ]); - } - - public function testBug5372Two(): void - { - $this->analyse([__DIR__ . '/data/bug-5372_2.php'], []); - } - - public function testBug5447(): void - { - $this->analyse([__DIR__ . '/data/bug-5447.php'], []); - } - -} From 9b918e345dcc0c3864b88768138ff91b81001424 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:54:42 +0200 Subject: [PATCH 0474/3097] [BCB] Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead --- UPGRADING.md | 1 + src/Type/UnionType.php | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 154776b3317..99092f8c037 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -235,3 +235,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ArrayType::generalizeKeys()` * Remove `ArrayType::count()`, use `Type::getArraySize()` instead * Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead +* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 70b2a54a727..e6bfb30cab6 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -1105,18 +1105,6 @@ protected function unionTypes(callable $getType): Type return TypeCombinator::union(...array_map($getType, $this->types)); } - /** - * @template T of Type - * @param callable(Type $type): list $getTypes - * @return list - * - * @deprecated Use pickFromTypes() instead. - */ - protected function pickTypes(callable $getTypes): array - { - return $this->pickFromTypes($getTypes, static fn () => false); - } - /** * @template T * @param callable(Type $type): list $getValues From 78db3ac64f3c50258d90e852ea037087e171ba91 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:55:23 +0200 Subject: [PATCH 0475/3097] [BCB] Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead --- UPGRADING.md | 1 + src/Type/Php/RegexArrayShapeMatcher.php | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 99092f8c037..a4f61a2a80a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -236,3 +236,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ArrayType::count()`, use `Type::getArraySize()` instead * Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead * Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead +* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 7923e9744c4..e2b489ddab7 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -60,14 +60,6 @@ public function matchExpr(Expr $patternExpr, ?Type $flagsType, TrinaryLogic $was return $this->matchPatternType($this->getPatternType($patternExpr, $scope), $flagsType, $wasMatched, false); } - /** - * @deprecated use matchExpr() instead for a more precise result - */ - public function matchType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched): ?Type - { - return $this->matchPatternType($patternType, $flagsType, $wasMatched, false); - } - private function matchPatternType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched, bool $matchesAll): ?Type { if ($wasMatched->no()) { From 7186ce61bac221afffd6ef18d0ecb6d79aa322d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:56:25 +0200 Subject: [PATCH 0476/3097] [BCB] Remove unused `PHPStanTestCase::$useStaticReflectionProvider` --- UPGRADING.md | 1 + src/Testing/PHPStanTestCase.php | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index a4f61a2a80a..64ba4f5839e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -237,3 +237,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead * Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead * Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead +* Remove unused `PHPStanTestCase::$useStaticReflectionProvider` diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 73c2e199475..029e982babd 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -49,9 +49,6 @@ abstract class PHPStanTestCase extends TestCase { - /** @deprecated */ - public static bool $useStaticReflectionProvider = true; - /** @var array */ private static array $containers = []; From 2ab5f3d768fe39028b77506597663747dc3eccc3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:57:48 +0200 Subject: [PATCH 0477/3097] [BCB] Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead --- UPGRADING.md | 1 + src/Testing/PHPStanTestCase.php | 16 ---------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 64ba4f5839e..fc7ea494cd2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -238,3 +238,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead * Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead * Remove unused `PHPStanTestCase::$useStaticReflectionProvider` +* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 029e982babd..cbb555e4125 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -8,9 +8,6 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\ScopeFactory; use PHPStan\Analyser\TypeSpecifier; -use PHPStan\BetterReflection\Reflector\ClassReflector; -use PHPStan\BetterReflection\Reflector\ConstantReflector; -use PHPStan\BetterReflection\Reflector\FunctionReflector; use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; @@ -122,19 +119,6 @@ public static function getReflector(): Reflector return self::getContainer()->getService('betterReflectionReflector'); } - /** - * @deprecated Use getReflector() instead. - * @return array{ClassReflector, FunctionReflector, ConstantReflector} - */ - public static function getReflectors(): array - { - return [ - self::getContainer()->getService('betterReflectionClassReflector'), - self::getContainer()->getService('betterReflectionFunctionReflector'), - self::getContainer()->getService('betterReflectionConstantReflector'), - ]; - } - public static function getClassReflectionExtensionRegistryProvider(): ClassReflectionExtensionRegistryProvider { return self::getContainer()->getByType(ClassReflectionExtensionRegistryProvider::class); From db02a30ca11c7b9839c30e0321ed403dd14f6c73 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 20:58:48 +0200 Subject: [PATCH 0478/3097] Renamed NewOptimizedDirectorySourceLocator to OptimizedDirectorySourceLocator --- phpstan-baseline.neon | 5 - .../NewOptimizedDirectorySourceLocator.php | 217 ------------------ .../OptimizedDirectorySourceLocator.php | 164 +------------ ...OptimizedDirectorySourceLocatorFactory.php | 8 +- ...imizedDirectorySourceLocatorRepository.php | 4 +- 5 files changed, 12 insertions(+), 386 deletions(-) delete mode 100644 src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f7e243fc88f..3c0e92416c3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -247,11 +247,6 @@ parameters: count: 1 path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 diff --git a/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php deleted file mode 100644 index cc939049bc9..00000000000 --- a/src/Reflection/BetterReflection/SourceLocator/NewOptimizedDirectorySourceLocator.php +++ /dev/null @@ -1,217 +0,0 @@ - $classToFile - * @param array> $functionToFiles - * @param array $constantToFile - */ - public function __construct( - private FileNodesFetcher $fileNodesFetcher, - private array $classToFile, - private array $functionToFiles, - private array $constantToFile, - ) - { - } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection - { - if ($identifier->isClass()) { - $className = strtolower($identifier->getName()); - $file = $this->findFileByClass($className); - if ($file === null) { - return null; - } - - $fetchedClassNodes = $this->fileNodesFetcher->fetchNodes($file)->getClassNodes(); - - if (!array_key_exists($className, $fetchedClassNodes)) { - return null; - } - - /** @var FetchedNode $fetchedClassNode */ - $fetchedClassNode = current($fetchedClassNodes[$className]); - - return $this->nodeToReflection($reflector, $fetchedClassNode); - } - - if ($identifier->isFunction()) { - $functionName = strtolower($identifier->getName()); - $files = $this->findFilesByFunction($functionName); - - $fetchedFunctionNode = null; - foreach ($files as $file) { - $fetchedFunctionNodes = $this->fileNodesFetcher->fetchNodes($file)->getFunctionNodes(); - - if (!array_key_exists($functionName, $fetchedFunctionNodes)) { - continue; - } - - /** @var FetchedNode $fetchedFunctionNode */ - $fetchedFunctionNode = current($fetchedFunctionNodes[$functionName]); - } - - if ($fetchedFunctionNode === null) { - return null; - } - - return $this->nodeToReflection($reflector, $fetchedFunctionNode); - } - - if ($identifier->isConstant()) { - $constantName = ConstantNameHelper::normalize($identifier->getName()); - $file = $this->findFileByConstant($constantName); - - if ($file === null) { - return null; - } - - $fetchedConstantNodes = $this->fileNodesFetcher->fetchNodes($file)->getConstantNodes(); - - if (!array_key_exists($constantName, $fetchedConstantNodes)) { - return null; - } - - /** @var FetchedNode $fetchedConstantNode */ - $fetchedConstantNode = current($fetchedConstantNodes[$constantName]); - - return $this->nodeToReflection( - $reflector, - $fetchedConstantNode, - $this->findConstantPositionInConstNode($fetchedConstantNode->getNode(), $constantName), - ); - } - - return null; - } - - /** - * @param FetchedNode|FetchedNode|FetchedNode $fetchedNode - */ - private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode, ?int $positionInNode = null): Reflection - { - $nodeToReflection = new NodeToReflection(); - return $nodeToReflection->__invoke( - $reflector, - $fetchedNode->getNode(), - $fetchedNode->getLocatedSource(), - $fetchedNode->getNamespace(), - $positionInNode, - ); - } - - private function findFileByClass(string $className): ?string - { - if (!array_key_exists($className, $this->classToFile)) { - return null; - } - - return $this->classToFile[$className]; - } - - private function findFileByConstant(string $constantName): ?string - { - if (!array_key_exists($constantName, $this->constantToFile)) { - return null; - } - - return $this->constantToFile[$constantName]; - } - - /** - * @return string[] - */ - private function findFilesByFunction(string $functionName): array - { - if (!array_key_exists($functionName, $this->functionToFiles)) { - return []; - } - - return $this->functionToFiles[$functionName]; - } - - /** - * @return list - */ - public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array - { - $reflections = []; - if ($identifierType->isClass()) { - foreach ($this->classToFile as $file) { - $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); - foreach ($fetchedNodesResult->getClassNodes() as $identifierName => $fetchedClassNodes) { - foreach ($fetchedClassNodes as $fetchedClassNode) { - $reflections[$identifierName] = $this->nodeToReflection($reflector, $fetchedClassNode); - } - } - } - } elseif ($identifierType->isFunction()) { - foreach ($this->functionToFiles as $files) { - foreach ($files as $file) { - $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); - foreach ($fetchedNodesResult->getFunctionNodes() as $identifierName => $fetchedFunctionNodes) { - foreach ($fetchedFunctionNodes as $fetchedFunctionNode) { - $reflections[$identifierName] = $this->nodeToReflection($reflector, $fetchedFunctionNode); - continue 2; - } - } - } - } - } elseif ($identifierType->isConstant()) { - foreach ($this->constantToFile as $file) { - $fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($file); - foreach ($fetchedNodesResult->getConstantNodes() as $identifierName => $fetchedConstantNodes) { - foreach ($fetchedConstantNodes as $fetchedConstantNode) { - $reflections[$identifierName] = $this->nodeToReflection( - $reflector, - $fetchedConstantNode, - $this->findConstantPositionInConstNode($fetchedConstantNode->getNode(), $identifierName), - ); - } - } - } - } - - return array_values($reflections); - } - - private function findConstantPositionInConstNode(Node\Stmt\Const_|Node\Expr\FuncCall $constantNode, string $constantName): ?int - { - if ($constantNode instanceof Node\Expr\FuncCall) { - return null; - } - - /** @var int $position */ - foreach ($constantNode->consts as $position => $const) { - if ($const->namespacedName === null) { - throw new ShouldNotHappenException(); - } - - if (ConstantNameHelper::normalize($const->namespacedName->toString()) === $constantName) { - return $position; - } - } - - throw new ShouldNotHappenException(); - } - -} diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index 32fabbc7572..b56a981bab8 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -9,52 +9,28 @@ use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ConstantNameHelper; use PHPStan\ShouldNotHappenException; use function array_key_exists; use function array_values; -use function count; use function current; -use function in_array; -use function ltrim; -use function php_strip_whitespace; -use function preg_match_all; -use function preg_replace; -use function sprintf; use function strtolower; -/** - * @deprecated Use NewOptimizedDirectorySourceLocator - */ final class OptimizedDirectorySourceLocator implements SourceLocator { - private PhpFileCleaner $cleaner; - - private string $extraTypes; - - /** @var array|null */ - private ?array $classToFile = null; - - /** @var array|null */ - private ?array $constantToFile = null; - - /** @var array>|null */ - private ?array $functionToFiles = null; - /** - * @param string[] $files + * @param array $classToFile + * @param array> $functionToFiles + * @param array $constantToFile */ public function __construct( private FileNodesFetcher $fileNodesFetcher, - private PhpVersion $phpVersion, - private array $files, + private array $classToFile, + private array $functionToFiles, + private array $constantToFile, ) { - $this->extraTypes = $this->phpVersion->supportsEnums() ? '|enum' : ''; - - $this->cleaner = new PhpFileCleaner(); } public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection @@ -145,13 +121,6 @@ private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode private function findFileByClass(string $className): ?string { - if ($this->classToFile === null) { - $this->init(); - if ($this->classToFile === null) { - throw new ShouldNotHappenException(); - } - } - if (!array_key_exists($className, $this->classToFile)) { return null; } @@ -161,13 +130,6 @@ private function findFileByClass(string $className): ?string private function findFileByConstant(string $constantName): ?string { - if ($this->constantToFile === null) { - $this->init(); - if ($this->constantToFile === null) { - throw new ShouldNotHappenException(); - } - } - if (!array_key_exists($constantName, $this->constantToFile)) { return null; } @@ -180,13 +142,6 @@ private function findFileByConstant(string $constantName): ?string */ private function findFilesByFunction(string $functionName): array { - if ($this->functionToFiles === null) { - $this->init(); - if ($this->functionToFiles === null) { - throw new ShouldNotHappenException(); - } - } - if (!array_key_exists($functionName, $this->functionToFiles)) { return []; } @@ -194,118 +149,11 @@ private function findFilesByFunction(string $functionName): array return $this->functionToFiles[$functionName]; } - private function init(): void - { - $classToFile = []; - $constantToFile = []; - $functionToFiles = []; - foreach ($this->files as $file) { - $symbols = $this->findSymbols($file); - foreach ($symbols['classes'] as $classInFile) { - $classToFile[$classInFile] = $file; - } - foreach ($symbols['constants'] as $constantInFile) { - $constantToFile[$constantInFile] = $file; - } - foreach ($symbols['functions'] as $functionInFile) { - if (!array_key_exists($functionInFile, $functionToFiles)) { - $functionToFiles[$functionInFile] = []; - } - $functionToFiles[$functionInFile][] = $file; - } - } - - $this->classToFile = $classToFile; - $this->functionToFiles = $functionToFiles; - $this->constantToFile = $constantToFile; - } - - /** - * Inspired by Composer\Autoload\ClassMapGenerator::findClasses() - * @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216 - * - * @return array{classes: string[], functions: string[], constants: string[]} - */ - private function findSymbols(string $file): array - { - $contents = @php_strip_whitespace($file); - if ($contents === '') { - return ['classes' => [], 'functions' => [], 'constants' => []]; - } - - $matchResults = (bool) preg_match_all(sprintf('{\b(?:(?:class|interface|trait|const|function%s)\s)|(?:define\s*\()}i', $this->extraTypes), $contents, $matches); - if (!$matchResults) { - return ['classes' => [], 'functions' => [], 'constants' => []]; - } - - $contents = $this->cleaner->clean($contents, count($matches[0])); - - preg_match_all(sprintf('{ - (?: - \b(?])(?: - (?: (?Pclass|interface|trait%s) \s++ (?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) ) - | (?: (?Pfunction) \s++ (?:&\s*)? (?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [&\(] ) - | (?: (?Pconst) \s++ (?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [^;] ) - | (?: (?:\\\)? (?Pdefine) \s*+ \( \s*+ [\'"] (?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:[\\\\]{1,2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+) ) - | (?: (?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] ) - ) - ) - }ix', $this->extraTypes), $contents, $matches); - - $classes = []; - $functions = []; - $constants = []; - $namespace = ''; - - for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { - if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') { - $namespace = preg_replace('~\s+~', '', strtolower($matches['nsname'][$i])) . '\\'; - continue; - } - - if ($matches['function'][$i] !== '') { - $functions[] = strtolower(ltrim($namespace . $matches['fname'][$i], '\\')); - continue; - } - - if ($matches['constant'][$i] !== '') { - $constants[] = ConstantNameHelper::normalize(ltrim($namespace . $matches['cname'][$i], '\\')); - } - - if ($matches['define'][$i] !== '') { - $constants[] = ConstantNameHelper::normalize($matches['dname'][$i]); - continue; - } - - $name = $matches['name'][$i]; - - // skip anon classes extending/implementing - if (in_array($name, ['extends', 'implements'], true)) { - continue; - } - - $classes[] = strtolower(ltrim($namespace . $name, '\\')); - } - - return [ - 'classes' => $classes, - 'functions' => $functions, - 'constants' => $constants, - ]; - } - /** * @return list */ public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array { - if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) { - $this->init(); - if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) { - throw new ShouldNotHappenException(); - } - } - $reflections = []; if ($identifierType->isClass()) { foreach ($this->classToFile as $file) { diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php index 864f1fff218..620284912f3 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php @@ -35,7 +35,7 @@ public function __construct( $this->cleaner = new PhpFileCleaner(); } - public function createByDirectory(string $directory): NewOptimizedDirectorySourceLocator + public function createByDirectory(string $directory): OptimizedDirectorySourceLocator { $files = $this->fileFinder->findFiles([$directory])->getFiles(); $fileHashes = []; @@ -79,7 +79,7 @@ public function createByDirectory(string $directory): NewOptimizedDirectorySourc [$classToFile, $functionToFiles, $constantToFile] = $this->changeStructure($cached); - return new NewOptimizedDirectorySourceLocator( + return new OptimizedDirectorySourceLocator( $this->fileNodesFetcher, $classToFile, $functionToFiles, @@ -90,7 +90,7 @@ public function createByDirectory(string $directory): NewOptimizedDirectorySourc /** * @param string[] $files */ - public function createByFiles(array $files): NewOptimizedDirectorySourceLocator + public function createByFiles(array $files): OptimizedDirectorySourceLocator { $symbols = []; foreach ($files as $file) { @@ -100,7 +100,7 @@ public function createByFiles(array $files): NewOptimizedDirectorySourceLocator [$classToFile, $functionToFiles, $constantToFile] = $this->changeStructure($symbols); - return new NewOptimizedDirectorySourceLocator( + return new OptimizedDirectorySourceLocator( $this->fileNodesFetcher, $classToFile, $functionToFiles, diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php index 25f5bd3e84e..f71d4dcf8a0 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php @@ -7,14 +7,14 @@ final class OptimizedDirectorySourceLocatorRepository { - /** @var array */ + /** @var array */ private array $locators = []; public function __construct(private OptimizedDirectorySourceLocatorFactory $factory) { } - public function getOrCreate(string $directory): NewOptimizedDirectorySourceLocator + public function getOrCreate(string $directory): OptimizedDirectorySourceLocator { if (array_key_exists($directory, $this->locators)) { return $this->locators[$directory]; From 7888327799c86536c6b876334955338cf1f46147 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Sep 2024 21:00:47 +0200 Subject: [PATCH 0479/3097] [BCB] Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead --- UPGRADING.md | 1 + src/Reflection/ClassReflection.php | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index fc7ea494cd2..b87762ce062 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -239,3 +239,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead * Remove unused `PHPStanTestCase::$useStaticReflectionProvider` * Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead +* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index d0e68e6453a..2e1e7ae611e 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -198,14 +198,6 @@ public function getFileName(): ?string return $this->filename = $fileName; } - /** - * @deprecated Use getFileName() - */ - public function getFileNameWithPhpDocs(): ?string - { - return $this->getFileName(); - } - public function getParentClass(): ?ClassReflection { if (!is_bool($this->cachedParentClass)) { From 330054e275b86bd36c214545472dda168c573d32 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Fri, 27 Sep 2024 00:19:43 +0000 Subject: [PATCH 0480/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 86914b4b26d..a2f36452e23 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.109", + "phpstan/php-8-stubs": "0.3.110", "phpstan/phpdoc-parser": "1.32.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 366cbe5c061..275758e6a50 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9302f03bb175e275adfbd782511530c4", + "content-hash": "a217b1999ea1f07d7b371c0171fcc437", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.109", + "version": "0.3.110", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "66ae33a8ed0b88cbda063d82c1c9fdffa36b687d" + "reference": "e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/66ae33a8ed0b88cbda063d82c1c9fdffa36b687d", - "reference": "66ae33a8ed0b88cbda063d82c1c9fdffa36b687d", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e", + "reference": "e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.109" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.110" }, - "time": "2024-09-24T19:04:44+00:00" + "time": "2024-09-27T00:19:13+00:00" }, { "name": "phpstan/phpdoc-parser", From ee216bf006975257cde306350bc071e92a3e4357 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Sep 2024 12:02:17 +0200 Subject: [PATCH 0481/3097] Offset access on lowercase-string is lowercase-string --- tests/PHPStan/Analyser/nsrt/string-offsets.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/string-offsets.php b/tests/PHPStan/Analyser/nsrt/string-offsets.php index 6a2c2728824..dd968fd6b46 100644 --- a/tests/PHPStan/Analyser/nsrt/string-offsets.php +++ b/tests/PHPStan/Analyser/nsrt/string-offsets.php @@ -9,10 +9,11 @@ * @param int<3, 10> $threeToTen * @param int<10, max> $tenOrMore * @param int<-10, -5> $negative + * @param lowercase-string $lowercase * * @return void */ -function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i) { +function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $lowercase) { $s = "world"; if (rand(0, 1)) { $s = "hello"; @@ -29,4 +30,6 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i) { $longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR"; assertType("non-empty-string", $longString[$i]); + + assertType("lowercase-string&non-empty-string", $lowercase[$i]); } From eca394cd92720c946da8ec36135d55beaf44423c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Sep 2024 12:10:50 +0200 Subject: [PATCH 0482/3097] Test generalize() --- tests/PHPStan/Analyser/nsrt/string-offsets.php | 2 +- tests/PHPStan/Type/Constant/ConstantStringTypeTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/string-offsets.php b/tests/PHPStan/Analyser/nsrt/string-offsets.php index dd968fd6b46..6fc8eeabd0e 100644 --- a/tests/PHPStan/Analyser/nsrt/string-offsets.php +++ b/tests/PHPStan/Analyser/nsrt/string-offsets.php @@ -30,6 +30,6 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $ $longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR"; assertType("non-empty-string", $longString[$i]); - + assertType("lowercase-string&non-empty-string", $lowercase[$i]); } diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 3845352fd63..036d2b0f1fb 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -154,6 +154,7 @@ public function testGeneralize(): void $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('A'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-empty-string&numeric-string', (new ConstantStringType('0'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('1.123'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType(' 1 1 '))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); From 4273fbbb677c6a326ed2e73e6685db80f831f704 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 27 Sep 2024 12:44:53 +0200 Subject: [PATCH 0483/3097] Improve vsprintf return type --- ...intfFunctionDynamicReturnTypeExtension.php | 48 +++++++++++++++++-- tests/PHPStan/Analyser/nsrt/bug-7387.php | 2 +- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 2a9b991915a..7bb7e2da26f 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -23,6 +23,7 @@ use function array_fill; use function array_key_exists; use function array_shift; +use function array_values; use function count; use function in_array; use function intval; @@ -95,14 +96,13 @@ public function getTypeFromFunctionCall( $checkArg = 1; } - // constant string specifies a numbered argument that does not exist - if (!array_key_exists($checkArg, $args)) { + $checkArgType = $this->getValueType($functionReflection, $scope, $args, $checkArg); + if ($checkArgType === null) { return null; } // if the format string is just a placeholder and specified an argument // of stringy type, then the return value will be of the same type - $checkArgType = $scope->getType($args[$checkArg]->value); if ( $matches['specifier'] === 's' && ($checkArgType->isString()->yes() || $checkArgType->isInteger()->yes()) @@ -201,6 +201,48 @@ private function allValuesSatisfies(FunctionReflection $functionReflection, Scop return false; } + /** + * @param Arg[] $args + */ + private function getValueType(FunctionReflection $functionReflection, Scope $scope, array $args, int $argNumber): ?Type + { + if ($functionReflection->getName() === 'sprintf') { + // constant string specifies a numbered argument that does not exist + if (!array_key_exists($argNumber, $args)) { + return null; + } + + return $scope->getType($args[$argNumber]->value); + } + + if ($functionReflection->getName() === 'vsprintf') { + if (!array_key_exists(1, $args)) { + return null; + } + + $valuesType = $scope->getType($args[1]->value); + $resultTypes = []; + + $valuesConstantArrays = $valuesType->getConstantArrays(); + foreach ($valuesConstantArrays as $valuesConstantArray) { + // vsprintf does not care about the keys of the array, only the order + $types = array_values($valuesConstantArray->getValueTypes()); + if (!array_key_exists($argNumber - 1, $types)) { + return null; + } + + $resultTypes[] = $types[$argNumber - 1]; + } + if (count($resultTypes) === 0) { + return $valuesType->getIterableValueType(); + } + + return TypeCombinator::union(...$resultTypes); + } + + return null; + } + /** * Detect constant strings in the format which neither depend on placeholders nor on given value arguments. */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index fb7468ca333..cfb1a97642e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -110,7 +110,7 @@ public function vsprintf(array $array) assertType('numeric-string', vsprintf("%4d", explode('-', '1988-8-1'))); assertType('numeric-string', vsprintf("%4d", $array)); assertType('numeric-string', vsprintf("%4d", ['123'])); - assertType('non-empty-string', vsprintf("%s", ['123'])); // could be '123' + assertType('\'123\'', vsprintf("%s", ['123'])); // too many arguments.. php silently allows it assertType('numeric-string', vsprintf("%4d", ['123', '456'])); } From 4168fe597005ae3f05474e6ac3181273a4a5e878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 Sep 2024 12:57:55 +0200 Subject: [PATCH 0484/3097] Fix missing ltrim in regex parse --- src/Type/Regex/RegexExpressionHelper.php | 2 + .../Analyser/nsrt/preg_match_shapes.php | 10 +++ .../Type/Regex/RegexExpressionHelperTest.php | 61 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/PHPStan/Type/Regex/RegexExpressionHelperTest.php diff --git a/src/Type/Regex/RegexExpressionHelper.php b/src/Type/Regex/RegexExpressionHelper.php index 0334aead777..0df0dc011c3 100644 --- a/src/Type/Regex/RegexExpressionHelper.php +++ b/src/Type/Regex/RegexExpressionHelper.php @@ -88,6 +88,8 @@ public function getPatternModifiers(string $pattern): ?string public function removeDelimitersAndModifiers(string $pattern): string { + $pattern = ltrim($pattern); + $endDelimiterPos = $this->getEndDelimiterPos($pattern); if ($endDelimiterPos === false) { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 4b17f15ed40..f92af453fba 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -768,3 +768,13 @@ function bug11604b (string $string): void { assertType("array{0: string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); } } + +function testLtrimDelimiter (string $string): void { + if (preg_match(' /(x)/', $string, $matches)) { + assertType("array{string, 'x'}", $matches); + } + + if (preg_match(' /(x)/', $string, $matches)) { + assertType("array{string, 'x'}", $matches); + } +} diff --git a/tests/PHPStan/Type/Regex/RegexExpressionHelperTest.php b/tests/PHPStan/Type/Regex/RegexExpressionHelperTest.php new file mode 100644 index 00000000000..330594145f4 --- /dev/null +++ b/tests/PHPStan/Type/Regex/RegexExpressionHelperTest.php @@ -0,0 +1,61 @@ +getByType(RegexExpressionHelper::class); + + $this->assertSame( + $expectedPatternWithoutDelimiter, + $regexExpressionHelper->removeDelimitersAndModifiers($inputPattern), + ); + } + +} From 93b3bf58ad2ce6b0cf3558b3ee2697f56113b593 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:10:34 +0200 Subject: [PATCH 0485/3097] [BCB] Remove `AnalysisResult::getInternalErrors()` --- UPGRADING.md | 1 + src/Command/AnalysisResult.php | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index b87762ce062..ad9f8379e13 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -240,3 +240,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove unused `PHPStanTestCase::$useStaticReflectionProvider` * Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead * Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead +* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index 4b090e8a359..1697b5f38ac 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -5,7 +5,6 @@ use PHPStan\Analyser\Error; use PHPStan\Analyser\InternalError; use PHPStan\Collectors\CollectedData; -use function array_map; use function count; use function usort; @@ -82,15 +81,6 @@ public function getNotFileSpecificErrors(): array return $this->notFileSpecificErrors; } - /** - * @deprecated Use getInternalErrorObjects - * @return list - */ - public function getInternalErrors(): array - { - return array_map(static fn (InternalError $internalError) => $internalError->getMessage(), $this->internalErrors); - } - /** * @return list */ From 93201eb45a9ee9d4d8c8098849fe64bd97167c34 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:12:29 +0200 Subject: [PATCH 0486/3097] [BCB] Remove `ConstantReflection::getValue()` --- UPGRADING.md | 1 + src/Reflection/ClassConstantReflection.php | 15 --------------- src/Reflection/ConstantReflection.php | 6 ------ src/Reflection/Dummy/DummyConstantReflection.php | 10 ---------- 4 files changed, 1 insertion(+), 31 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index ad9f8379e13..1012b567957 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -241,3 +241,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead * Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead * Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead +* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 78cec7e379d..7afcb0d9e25 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -3,12 +3,10 @@ namespace PHPStan\Reflection; use PhpParser\Node\Expr; -use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; -use const NAN; /** * @api @@ -41,19 +39,6 @@ public function getFileName(): ?string return $this->declaringClass->getFileName(); } - /** - * @deprecated Use getValueExpr() - * @return mixed - */ - public function getValue() - { - try { - return $this->reflection->getValue(); - } catch (UnableToCompileNode) { - return NAN; - } - } - public function getValueExpr(): Expr { return $this->reflection->getValueExpression(); diff --git a/src/Reflection/ConstantReflection.php b/src/Reflection/ConstantReflection.php index 5e3d2548c1b..6a138779902 100644 --- a/src/Reflection/ConstantReflection.php +++ b/src/Reflection/ConstantReflection.php @@ -8,12 +8,6 @@ interface ConstantReflection extends ClassMemberReflection, GlobalConstantReflection { - /** - * @deprecated Use getValueExpr() - * @return mixed - */ - public function getValue(); - public function getValueExpr(): Expr; } diff --git a/src/Reflection/Dummy/DummyConstantReflection.php b/src/Reflection/Dummy/DummyConstantReflection.php index 330c77562c8..b7d563a6155 100644 --- a/src/Reflection/Dummy/DummyConstantReflection.php +++ b/src/Reflection/Dummy/DummyConstantReflection.php @@ -51,16 +51,6 @@ public function getName(): string return $this->name; } - /** - * @deprecated - * @return mixed - */ - public function getValue() - { - // so that Scope::getTypeFromValue() returns mixed - return new stdClass(); - } - public function getValueType(): Type { return new MixedType(); From fd561e213eb4ac68566378f483d66f1f8164f359 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:14:08 +0200 Subject: [PATCH 0487/3097] [BCB] Remove `PropertyTag::getType()` --- UPGRADING.md | 1 + src/PhpDoc/PhpDocNodeResolver.php | 3 --- src/PhpDoc/Tag/PropertyTag.php | 9 --------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1012b567957..9321607fa97 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -242,3 +242,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead * Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead * Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` +* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 75857e5eedb..7476af723d3 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -112,7 +112,6 @@ public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope $resolved[$propertyName] = new PropertyTag( $propertyType, $propertyType, - $propertyType, ); } } @@ -128,7 +127,6 @@ public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope } $resolved[$propertyName] = new PropertyTag( - $propertyType, $propertyType, $writableType, ); @@ -146,7 +144,6 @@ public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope } $resolved[$propertyName] = new PropertyTag( - $readableType ?? $propertyType, $readableType, $propertyType, ); diff --git a/src/PhpDoc/Tag/PropertyTag.php b/src/PhpDoc/Tag/PropertyTag.php index 372e8b3cb9d..16090c44b06 100644 --- a/src/PhpDoc/Tag/PropertyTag.php +++ b/src/PhpDoc/Tag/PropertyTag.php @@ -11,21 +11,12 @@ final class PropertyTag { public function __construct( - private Type $type, private ?Type $readableType, private ?Type $writableType, ) { } - /** - * @deprecated Use getReadableType() / getWritableType() - */ - public function getType(): Type - { - return $this->type; - } - public function getReadableType(): ?Type { return $this->readableType; From df7200d41b1f237259c825931d2411828ee59a4b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:15:35 +0200 Subject: [PATCH 0488/3097] [BCB] Remove `GenericTypeVariableResolver` --- UPGRADING.md | 1 + src/Type/GenericTypeVariableResolver.php | 52 ------------------------ 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 src/Type/GenericTypeVariableResolver.php diff --git a/UPGRADING.md b/UPGRADING.md index 9321607fa97..65733774f5c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -243,3 +243,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead * Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` * Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead +* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead diff --git a/src/Type/GenericTypeVariableResolver.php b/src/Type/GenericTypeVariableResolver.php deleted file mode 100644 index 732e57c10ac..00000000000 --- a/src/Type/GenericTypeVariableResolver.php +++ /dev/null @@ -1,52 +0,0 @@ -getClassReflection(); - if ($classReflection === null) { - return null; - } - $ancestorClassReflection = $classReflection->getAncestorWithClassName($genericClassName); - if ($ancestorClassReflection === null) { - return null; - } - - $activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap(); - - $type = $activeTemplateTypeMap->getType($typeVariableName); - if ($type instanceof ErrorType) { - $templateTypeMap = $ancestorClassReflection->getTemplateTypeMap(); - $templateType = $templateTypeMap->getType($typeVariableName); - if ($templateType === null) { - return $type; - } - - $bound = TemplateTypeHelper::resolveToBounds($templateType); - if ($bound instanceof MixedType && $bound->isExplicitMixed()) { - return new MixedType(false); - } - - return $bound; - } - - return $type; - } - -} From d904afcf06b277f12f51f71ff134331e1b8e83e7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:18:52 +0200 Subject: [PATCH 0489/3097] [BCB] Rename `Type::isClassStringType()` to `Type::isClassString()` --- UPGRADING.md | 1 + src/Dependency/DependencyResolver.php | 2 +- src/Reflection/InitializerExprTypeResolver.php | 2 +- src/Rules/Methods/MethodCallCheck.php | 2 +- src/Type/Accessory/AccessoryArrayListType.php | 2 +- .../Accessory/AccessoryLiteralStringType.php | 2 +- .../Accessory/AccessoryLowercaseStringType.php | 2 +- .../Accessory/AccessoryNonEmptyStringType.php | 2 +- .../Accessory/AccessoryNonFalsyStringType.php | 2 +- .../Accessory/AccessoryNumericStringType.php | 2 +- src/Type/Accessory/HasOffsetType.php | 2 +- src/Type/Accessory/HasOffsetValueType.php | 2 +- src/Type/Accessory/NonEmptyArrayType.php | 2 +- src/Type/Accessory/OversizedArrayType.php | 2 +- src/Type/ArrayType.php | 2 +- src/Type/CallableType.php | 2 +- src/Type/ClassStringType.php | 6 +++--- src/Type/ClosureType.php | 2 +- src/Type/Constant/ConstantStringType.php | 16 ++++------------ src/Type/FloatType.php | 2 +- src/Type/Generic/GenericClassStringType.php | 8 ++++---- src/Type/IntersectionType.php | 4 ++-- src/Type/IterableType.php | 2 +- src/Type/JustNullableTypeTrait.php | 2 +- src/Type/MixedType.php | 4 ++-- src/Type/NeverType.php | 2 +- src/Type/NullType.php | 2 +- src/Type/ObjectType.php | 2 +- ...lassImplementsFunctionReturnTypeExtension.php | 2 +- .../Php/LtrimFunctionReturnTypeExtension.php | 2 +- .../Php/MethodExistsTypeSpecifyingExtension.php | 2 +- src/Type/StaticType.php | 4 ++-- src/Type/StrictMixedType.php | 2 +- src/Type/StringType.php | 4 ++-- src/Type/Traits/LateResolvableTypeTrait.php | 4 ++-- src/Type/Traits/ObjectTypeTrait.php | 2 +- src/Type/Type.php | 2 +- src/Type/UnionType.php | 4 ++-- src/Type/VoidType.php | 2 +- tests/PHPStan/Type/MixedTypeTest.php | 2 +- 40 files changed, 54 insertions(+), 61 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 65733774f5c..8b14609497f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -244,3 +244,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` * Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead * Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead +* Rename `Type::isClassStringType()` to `Type::isClassString()` diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 86192c0a6ce..12635a0913d 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -477,7 +477,7 @@ private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): } $itemType = $scope->getType($items[0]->value); - return $itemType->isClassStringType()->yes(); + return $itemType->isClassString()->yes(); } /** diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 0c355a2160a..2f6d8a0cc81 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1916,7 +1916,7 @@ function (Type $type, callable $traverse): Type { ); } - if ($constantClassType->isClassStringType()->yes()) { + if ($constantClassType->isClassString()->yes()) { if ($constantClassType->isConstantScalarValue()->yes()) { $isObject = false; } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index 165f41741cc..d20760f847a 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -56,7 +56,7 @@ public function check( if ($type instanceof StaticType) { $typeForDescribe = $type->getStaticObjectType(); } - if (!$type->canCallMethods()->yes() || $type->isClassStringType()->yes()) { + if (!$type->canCallMethods()->yes() || $type->isClassString()->yes()) { return [ [ RuleErrorBuilder::message(sprintf( diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 62cd108a776..930473cf031 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -391,7 +391,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 110fbea58c7..1a3a37858ba 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -302,7 +302,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 195a807dbcf..c32ef8c506d 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -298,7 +298,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createYes(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index b911c21fbbe..cc8e90f2363 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -299,7 +299,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 5703420e9f8..82460ea1a1c 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -299,7 +299,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9cb274782d5..a7c56c56ed8 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -301,7 +301,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 2194a10cef7..969a99df7a4 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -302,7 +302,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 853645c91a1..0481c3939b5 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -358,7 +358,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 293e1f111f2..fbe23d45346 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -369,7 +369,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index fa64240e897..365431f4ac7 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -365,7 +365,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index dc9c6ec2286..c868b64613c 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -355,7 +355,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 82b3645a273..20cfeafa679 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -596,7 +596,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index eaca9d4572e..ed622e5817a 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -32,7 +32,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return new AcceptsResult($type->isClassStringType(), []); + return new AcceptsResult($type->isClassString(), []); } public function isSuperTypeOf(Type $type): TrinaryLogic @@ -41,7 +41,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $type->isSubTypeOf($this); } - return $type->isClassStringType(); + return $type->isClassString(); } public function isString(): TrinaryLogic @@ -74,7 +74,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createYes(); } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index e55847aae03..751239ab735 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -718,7 +718,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 7917cbfda3f..a5b39335cd5 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -82,7 +82,7 @@ public function getConstantStrings(): array return [$this]; } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { if ($this->isClassString) { return TrinaryLogic::createYes(); @@ -95,7 +95,7 @@ public function isClassStringType(): TrinaryLogic public function getClassStringObjectType(): Type { - if ($this->isClassStringType()->yes()) { + if ($this->isClassString()->yes()) { return new ObjectType($this->value); } @@ -107,14 +107,6 @@ public function getObjectTypeOrClassStringObjectType(): Type return $this->getClassStringObjectType(); } - /** - * @deprecated use isClassStringType() instead - */ - public function isClassString(): bool - { - return $this->isClassStringType()->yes(); - } - public function describe(VerbosityLevel $level): string { return $level->handle( @@ -176,7 +168,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } if ($type instanceof ClassStringType) { - return $this->isClassStringType()->yes() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + return $this->isClassString()->yes() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); } if ($type instanceof self) { @@ -534,7 +526,7 @@ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type public function canAccessConstants(): TrinaryLogic { - return $this->isClassStringType(); + return $this->isClassString(); } public function hasConstant(string $constantName): TrinaryLogic diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 0ffa96e002c..890e13994a7 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -234,7 +234,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index fd6ecd5f03d..d9b06d68bfd 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -67,7 +67,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } if ($type instanceof ConstantStringType) { - if (!$type->isClassStringType()->yes()) { + if (!$type->isClassString()->yes()) { return AcceptsResult::createNo(); } @@ -113,7 +113,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $isSuperType = $genericType->isSuperTypeOf($objectType); } - if (!$type->isClassStringType()->yes()) { + if (!$type->isClassString()->yes()) { $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); } @@ -157,7 +157,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap $typeToInfer = new ObjectType($receivedType->getValue()); } elseif ($receivedType instanceof self) { $typeToInfer = $receivedType->type; - } elseif ($receivedType->isClassStringType()->yes()) { + } elseif ($receivedType->isClassString()->yes()) { $typeToInfer = $this->type; if ($typeToInfer instanceof TemplateType) { $typeToInfer = $typeToInfer->getBound(); @@ -215,7 +215,7 @@ public function toPhpDocNode(): TypeNode public function tryRemove(Type $typeToRemove): ?Type { - if ($typeToRemove instanceof ConstantStringType && $typeToRemove->isClassStringType()->yes()) { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->isClassString()->yes()) { $generic = $this->getGenericType(); $genericObjectClassNames = $generic->getObjectClassNames(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index fb41aebbad1..f7d92dd5548 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -648,9 +648,9 @@ public function isLowercaseString(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { - return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isClassString()); } public function getClassStringObjectType(): Type diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 6ef8ff5ee3e..fe0af6a8125 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -377,7 +377,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 7f48131262a..912dd684992 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -152,7 +152,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 0a0fdd88efc..bf9f5bde68d 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -933,7 +933,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { if ($this->subtractedType !== null) { if ($this->subtractedType->isSuperTypeOf(new StringType())->yes()) { @@ -949,7 +949,7 @@ public function isClassStringType(): TrinaryLogic public function getClassStringObjectType(): Type { - if (!$this->isClassStringType()->no()) { + if (!$this->isClassString()->no()) { return new ObjectWithoutClassType(); } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 1523562cabe..68be84499a4 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -481,7 +481,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index a8601a0b9ed..c90a5b29e69 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -300,7 +300,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 672b1f57c85..0d12a7f5320 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1039,7 +1039,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php index 17e293973b6..df1b7022ab2 100644 --- a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php +++ b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php @@ -57,7 +57,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $variant->getReturnType(); } - if ($firstArgType->isClassStringType()->no()) { + if ($firstArgType->isClassString()->no()) { return new ConstantBooleanType(false); } diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index b8b5b38c6f7..284be58a82a 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -29,7 +29,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $string = $scope->getType($functionCall->getArgs()[0]->value); $trimChars = $scope->getType($functionCall->getArgs()[1]->value); - if ($trimChars instanceof ConstantStringType && $trimChars->getValue() === '\\' && $string->isClassStringType()->yes()) { + if ($trimChars instanceof ConstantStringType && $trimChars->getValue() === '\\' && $string->isClassString()->yes()) { if ($string instanceof ConstantStringType) { return new ConstantStringType(ltrim($string->getValue(), $trimChars->getValue()), true); } diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index bc00486cbfe..ba67e53322e 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -53,7 +53,7 @@ public function specifyTypes( $objectType = $scope->getType($node->getArgs()[0]->value); if ($objectType->isString()->yes()) { - if ($objectType->isClassStringType()->yes()) { + if ($objectType->isClassString()->yes()) { return $this->typeSpecifier->create( $node->getArgs()[0]->value, new IntersectionType([ diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index e0d29249510..55190656a4f 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -570,9 +570,9 @@ public function isLowercaseString(): TrinaryLogic return $this->getStaticObjectType()->isLowercaseString(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { - return $this->getStaticObjectType()->isClassStringType(); + return $this->getStaticObjectType()->isClassString(); } public function getClassStringObjectType(): Type diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 00a91412337..3f867768401 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -280,7 +280,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 9fcd1e1895a..b022f2b397e 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -245,7 +245,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createMaybe(); } @@ -272,7 +272,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType public function hasMethod(string $methodName): TrinaryLogic { - if ($this->isClassStringType()->yes()) { + if ($this->isClassString()->yes()) { return TrinaryLogic::createMaybe(); } return TrinaryLogic::createNo(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index e2cdc07e578..d3a0083da6f 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -457,9 +457,9 @@ public function isLowercaseString(): TrinaryLogic return $this->resolve()->isLowercaseString(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { - return $this->resolve()->isClassStringType(); + return $this->resolve()->isClassString(); } public function getClassStringObjectType(): Type diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 8ca9c69c293..2fd278e34b8 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -203,7 +203,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index a456a0e8466..ce306ee9f5d 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -263,7 +263,7 @@ public function isLiteralString(): TrinaryLogic; public function isLowercaseString(): TrinaryLogic; - public function isClassStringType(): TrinaryLogic; + public function isClassString(): TrinaryLogic; public function isVoid(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index e6bfb30cab6..1316f9369d9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -617,9 +617,9 @@ public function isLowercaseString(): TrinaryLogic return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { - return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassString()); } public function getClassStringObjectType(): Type diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index add4ffca45a..2bea11cb041 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -212,7 +212,7 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isClassStringType(): TrinaryLogic + public function isClassString(): TrinaryLogic { return TrinaryLogic::createNo(); } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 5fe7d0c4092..3ffc8f9db7b 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -598,7 +598,7 @@ public function dataSubstractedIsLiteralString(): array public function testSubstractedIsClassString(MixedType $mixedType, Type $typeToSubtract, TrinaryLogic $expectedResult): void { $subtracted = $mixedType->subtract($typeToSubtract); - $actualResult = $subtracted->isClassStringType(); + $actualResult = $subtracted->isClassString(); $this->assertSame( $expectedResult->describe(), From 49eb18f317b00238827084016e0d643ef39dd5fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:21:25 +0200 Subject: [PATCH 0490/3097] [BCB] Remove `Scope::isSpecified()` --- UPGRADING.md | 1 + src/Analyser/MutatingScope.php | 9 --------- src/Analyser/Scope.php | 3 --- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 8b14609497f..0d975f24293 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -245,3 +245,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead * Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead * Rename `Type::isClassStringType()` to `Type::isClassString()` +* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 686b70bab84..f619db2fefe 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2781,15 +2781,6 @@ public function getTypeFromValue($value): Type return ConstantTypeHelper::getTypeFromValue($value); } - /** - * @api - * @deprecated use hasExpressionType instead - */ - public function isSpecified(Expr $node): bool - { - return !$node instanceof Variable && $this->hasExpressionType($node)->yes(); - } - /** @api */ public function hasExpressionType(Expr $node): TrinaryLogic { diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 0c1682209d3..6284d454f19 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -105,9 +105,6 @@ public function resolveTypeByName(Name $name): TypeWithClassName; */ public function getTypeFromValue($value): Type; - /** @deprecated use hasExpressionType instead */ - public function isSpecified(Expr $node): bool; - public function hasExpressionType(Expr $node): TrinaryLogic; public function isInClassExists(string $className): bool; From acd35c8e1b5f0faafe79f677692aa3eee1ce1574 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:27:08 +0200 Subject: [PATCH 0491/3097] Remove `MutatingScope::enterCatch()` --- src/Analyser/MutatingScope.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f619db2fefe..6af143689b9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3748,17 +3748,6 @@ public function enterForeachKey(self $originalScope, Expr $iteratee, string $key return $scope; } - /** - * @deprecated Use enterCatchType - * @param Node\Name[] $classes - */ - public function enterCatch(array $classes, ?string $variableName): self - { - $type = TypeCombinator::union(...array_map(static fn (Node\Name $class): ObjectType => new ObjectType((string) $class), $classes)); - - return $this->enterCatchType($type, $variableName); - } - public function enterCatchType(Type $catchType, ?string $variableName): self { if ($variableName === null) { From 9663f0edbecc7918a289bd4f2ef89e80392c2071 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:30:41 +0200 Subject: [PATCH 0492/3097] [BCB] Remove `ConstantArrayType::isEmpty()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 0d975f24293..5609946f04f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -246,3 +246,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead * Rename `Type::isClassStringType()` to `Type::isClassString()` * Remove `Scope::isSpecified()`, use `hasExpressionType()` instead +* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 5b6700d80b0..33b1370100a 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -136,12 +136,6 @@ public function isConstantValue(): TrinaryLogic return TrinaryLogic::createYes(); } - /** @deprecated Use isIterableAtLeastOnce()->no() instead */ - public function isEmpty(): bool - { - return count($this->keyTypes) === 0; - } - /** * @return non-empty-list */ From 36ac734613290c400efa12397dc3f8062d72892f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:31:25 +0200 Subject: [PATCH 0493/3097] [BCB] Remove `ConstantArrayType::getNextAutoIndex()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 5609946f04f..9465e155188 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -247,3 +247,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Rename `Type::isClassStringType()` to `Type::isClassString()` * Remove `Scope::isSpecified()`, use `hasExpressionType()` instead * Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead +* Remove `ConstantArrayType::getNextAutoIndex()` diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 33b1370100a..95fc560c061 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -144,10 +144,7 @@ public function getNextAutoIndexes(): array return $this->nextAutoIndexes; } - /** - * @deprecated - */ - public function getNextAutoIndex(): int + private function getNextAutoIndex(): int { return $this->nextAutoIndexes[count($this->nextAutoIndexes) - 1]; } @@ -1076,7 +1073,7 @@ private function removeLastElements(int $length): self array_pop($valueTypes); $nextAutoindex = $removedKeyType instanceof ConstantIntegerType ? $removedKeyType->getValue() - : $this->getNextAutoIndex(); // @phpstan-ignore method.deprecated + : $this->getNextAutoIndex(); continue; } From d5a0ddb86998776a9e23e85e5657f054c86cc2f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:31:58 +0200 Subject: [PATCH 0494/3097] [BCB] Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` --- UPGRADING.md | 2 ++ src/Type/Constant/ConstantArrayType.php | 24 ------------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 9465e155188..ee245c430ba 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -248,3 +248,5 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `Scope::isSpecified()`, use `hasExpressionType()` instead * Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead * Remove `ConstantArrayType::getNextAutoIndex()` +* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` + * Use `getFirstIterable*Type` and `getLastIterable*Type` instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 95fc560c061..70c94efe76b 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -242,18 +242,6 @@ public function getKeyTypes(): array return $this->keyTypes; } - /** @deprecated Use getFirstIterableKeyType() instead */ - public function getFirstKeyType(): Type - { - return $this->getFirstIterableKeyType(); - } - - /** @deprecated Use getLastIterableKeyType() instead */ - public function getLastKeyType(): Type - { - return $this->getLastIterableKeyType(); - } - /** * @return array */ @@ -262,18 +250,6 @@ public function getValueTypes(): array return $this->valueTypes; } - /** @deprecated Use getFirstIterableValueType() instead */ - public function getFirstValueType(): Type - { - return $this->getFirstIterableValueType(); - } - - /** @deprecated Use getLastIterableValueType() instead */ - public function getLastValueType(): Type - { - return $this->getLastIterableValueType(); - } - public function isOptionalKey(int $i): bool { return in_array($i, $this->optionalKeys, true); From 50135a9fefffd9eb883780577e89f68512200023 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 11:33:45 +0200 Subject: [PATCH 0495/3097] [BCB] Remove `ConstantArrayType::generalizeToArray()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 20 -------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index ee245c430ba..0199fc4452d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -250,3 +250,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantArrayType::getNextAutoIndex()` * Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` * Use `getFirstIterable*Type` and `getLastIterable*Type` instead +* Remove `ConstantArrayType::generalizeToArray()` diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 70c94efe76b..0dc13355f21 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1305,26 +1305,6 @@ public function generalizeValues(): ArrayType return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); } - /** @deprecated */ - public function generalizeToArray(): Type - { - $isIterableAtLeastOnce = $this->isIterableAtLeastOnce(); - if ($isIterableAtLeastOnce->no()) { - return $this; - } - - $arrayType = new ArrayType($this->getIterableKeyType(), $this->getItemType()); - - if ($isIterableAtLeastOnce->yes()) { - $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); - } - if ($this->isList->yes()) { - $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType()); - } - - return $arrayType; - } - /** * @return self */ From 3f561479dbf419ffa9197c49c215c28fc663109c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 13:37:24 +0200 Subject: [PATCH 0496/3097] Fix E2E tests --- .github/workflows/e2e-tests.yml | 13 +++++++------ e2e/bug-9622-trait/baseline-1.neon | 2 +- e2e/bug-9622/baseline-1.neon | 2 +- e2e/discussion-11362/phpstan.neon | 2 -- e2e/env-parameter/phpstan.neon | 2 +- e2e/result-cache-5/phpstan.neon | 3 +++ e2e/result-cache-7/phpstan.neon | 3 +++ 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3020a8545cc..ba0c7ac476b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -137,17 +137,18 @@ jobs: ../../bin/phpstan -vvv - script: | cd e2e/env-parameter - export PHPSTAN_SCOPE_CLASS=MyTestScope - ACTUAL=$(../../bin/phpstan dump-parameters -c phpstan.neon --json -l 9 | jq --raw-output '.scopeClass') - [[ "$ACTUAL" == "MyTestScope" ]]; + export PHPSTAN_RESULT_CACHE_PATH=/some/path + ACTUAL=$(../../bin/phpstan dump-parameters -c phpstan.neon --json -l 9 | jq --raw-output '.resultCachePath') + [[ "$ACTUAL" == "/some/path" ]]; - script: | cd e2e/result-cache-8 composer install ../../bin/phpstan echo -en '\n' >> build/CustomRule.php - OUTPUT=$(../../bin/phpstan 2>&1) - grep 'Result cache might not behave correctly' <<< "$OUTPUT" - grep 'ResultCache8E2E\\CustomRule' <<< "$OUTPUT" + OUTPUT=$(../../bin/phpstan analyze 2>&1 || true) + echo "$OUTPUT" + ../bashunit -a contains 'Result cache might not behave correctly' "$OUTPUT" + ../bashunit -a contains 'ResultCache8E2E\\CustomRule' "$OUTPUT" - script: | cd e2e/env-int-key env 1=1 ../../bin/phpstan analyse test.php diff --git a/e2e/bug-9622-trait/baseline-1.neon b/e2e/bug-9622-trait/baseline-1.neon index 1548dbca10f..caa24d89e86 100644 --- a/e2e/bug-9622-trait/baseline-1.neon +++ b/e2e/bug-9622-trait/baseline-1.neon @@ -1,6 +1,6 @@ parameters: ignoreErrors: - - message: "#^Offset 'foo' does not exist on array\\{foo\\?\\: int\\}\\.$#" + message: "#^Offset 'foo' might not exist on array\\{foo\\?\\: int\\}\\.$#" count: 1 path: src/UsesBar.php diff --git a/e2e/bug-9622/baseline-1.neon b/e2e/bug-9622/baseline-1.neon index a7df7873903..0ae88bf65b9 100644 --- a/e2e/bug-9622/baseline-1.neon +++ b/e2e/bug-9622/baseline-1.neon @@ -1,6 +1,6 @@ parameters: ignoreErrors: - - message: "#^Offset 'foo' does not exist on array\\{foo\\?\\: int\\}\\.$#" + message: "#^Offset 'foo' might not exist on array\\{foo\\?\\: int\\}\\.$#" count: 1 path: src/Bar.php diff --git a/e2e/discussion-11362/phpstan.neon b/e2e/discussion-11362/phpstan.neon index d9a4bd0ab31..2e6178c1a74 100644 --- a/e2e/discussion-11362/phpstan.neon +++ b/e2e/discussion-11362/phpstan.neon @@ -1,7 +1,5 @@ parameters: excludePaths: - analyseAndScan: - - .git analyse: - vendor diff --git a/e2e/env-parameter/phpstan.neon b/e2e/env-parameter/phpstan.neon index 92870168092..b92bf25f4aa 100644 --- a/e2e/env-parameter/phpstan.neon +++ b/e2e/env-parameter/phpstan.neon @@ -1,2 +1,2 @@ parameters: - scopeClass: %env.PHPSTAN_SCOPE_CLASS% + resultCachePath: %env.PHPSTAN_RESULT_CACHE_PATH% diff --git a/e2e/result-cache-5/phpstan.neon b/e2e/result-cache-5/phpstan.neon index ddbf4c2114f..7ef7b4e149b 100644 --- a/e2e/result-cache-5/phpstan.neon +++ b/e2e/result-cache-5/phpstan.neon @@ -5,3 +5,6 @@ parameters: level: 8 paths: - src + ignoreErrors: + - + identifier: instanceof.alwaysTrue diff --git a/e2e/result-cache-7/phpstan.neon b/e2e/result-cache-7/phpstan.neon index ddbf4c2114f..66c19c7166c 100644 --- a/e2e/result-cache-7/phpstan.neon +++ b/e2e/result-cache-7/phpstan.neon @@ -5,3 +5,6 @@ parameters: level: 8 paths: - src + ignoreErrors: + - + identifier: trait.unused From 6a695613d09f4633e052ee8175c74c634c76b7a9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:04:15 +0200 Subject: [PATCH 0497/3097] Fix E2E tests --- .github/workflows/e2e-tests.yml | 2 +- e2e/result-cache-5/phpstan.neon | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ba0c7ac476b..2ad09cad2cd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -148,7 +148,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze 2>&1 || true) echo "$OUTPUT" ../bashunit -a contains 'Result cache might not behave correctly' "$OUTPUT" - ../bashunit -a contains 'ResultCache8E2E\\CustomRule' "$OUTPUT" + ../bashunit -a contains 'ResultCache8E2E\CustomRule' "$OUTPUT" - script: | cd e2e/env-int-key env 1=1 ../../bin/phpstan analyse test.php diff --git a/e2e/result-cache-5/phpstan.neon b/e2e/result-cache-5/phpstan.neon index 7ef7b4e149b..7c3f71ae982 100644 --- a/e2e/result-cache-5/phpstan.neon +++ b/e2e/result-cache-5/phpstan.neon @@ -8,3 +8,4 @@ parameters: ignoreErrors: - identifier: instanceof.alwaysTrue + reportUnmatched: false From 3d2f492e6d1bba07704c349ead9bfde31e2c20eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:10:59 +0200 Subject: [PATCH 0498/3097] [BCB] Remove `TypeUtils::getArrays()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 46 ------------------------------------------ 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 0199fc4452d..0580805b517 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -251,3 +251,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` +* Remove `TypeUtils::getArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 6987e9482b5..f3d7998da27 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -21,52 +21,6 @@ final class TypeUtils { - /** - * @return ArrayType[] - * - * @deprecated Use PHPStan\Type\Type::getArrays() instead and handle optional ConstantArrayType keys if necessary. - */ - public static function getArrays(Type $type): array - { - if ($type instanceof ConstantArrayType) { - return $type->getAllArrays(); - } - - if ($type instanceof ArrayType) { - return [$type]; - } - - if ($type instanceof UnionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ArrayType) { - return []; - } - foreach (self::getArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - if ($type instanceof IntersectionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ArrayType) { - continue; - } - foreach (self::getArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - return []; - } - /** * @return ConstantArrayType[] * From c507bd3c7fbb91fcedc83dcac57525ed5c99c6f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:12:48 +0200 Subject: [PATCH 0499/3097] [BCB] Remove `TypeUtils::getConstantArrays()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 28 ---------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 0580805b517..a3f6619a0fa 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -252,3 +252,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` * Remove `TypeUtils::getArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead +* Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index f3d7998da27..2e12de13669 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -21,34 +21,6 @@ final class TypeUtils { - /** - * @return ConstantArrayType[] - * - * @deprecated Use PHPStan\Type\Type::getConstantArrays() instead and handle optional keys if necessary. - */ - public static function getConstantArrays(Type $type): array - { - if ($type instanceof ConstantArrayType) { - return $type->getAllArrays(); - } - - if ($type instanceof UnionType) { - $matchingTypes = []; - foreach ($type->getTypes() as $innerType) { - if (!$innerType instanceof ConstantArrayType) { - return []; - } - foreach (self::getConstantArrays($innerType) as $innerInnerType) { - $matchingTypes[] = $innerInnerType; - } - } - - return $matchingTypes; - } - - return []; - } - /** * @return ConstantStringType[] * From 9278af76c90f77f4a40bdfe6937f2a1a741f7891 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:15:25 +0200 Subject: [PATCH 0500/3097] [BCB] Remove `TypeUtils::getConstantStrings()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index a3f6619a0fa..0a302e00d1d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -253,3 +253,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantArrayType::generalizeToArray()` * Remove `TypeUtils::getArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead +* Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 2e12de13669..e76089a2acd 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -6,7 +6,6 @@ use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateBenevolentUnionType; use PHPStan\Type\Generic\TemplateType; @@ -21,16 +20,6 @@ final class TypeUtils { - /** - * @return ConstantStringType[] - * - * @deprecated Use PHPStan\Type\Type::getConstantStrings() instead - */ - public static function getConstantStrings(Type $type): array - { - return self::map(ConstantStringType::class, $type, false); - } - /** * @return ConstantIntegerType[] */ From 3bfe27e06e9002068bfe74556fe208bba471963a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:17:45 +0200 Subject: [PATCH 0501/3097] [BCB] Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 18 ------------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 0a302e00d1d..065b68b2145 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -254,3 +254,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead +* Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index e76089a2acd..2094ae13b73 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -28,24 +28,6 @@ public static function getConstantIntegers(Type $type): array return self::map(ConstantIntegerType::class, $type, false); } - /** - * @deprecated Use Type::isConstantValue() or Type::generalize() - * @return ConstantType[] - */ - public static function getConstantTypes(Type $type): array - { - return self::map(ConstantType::class, $type, false); - } - - /** - * @deprecated Use Type::isConstantValue() or Type::generalize() - * @return ConstantType[] - */ - public static function getAnyConstantTypes(Type $type): array - { - return self::map(ConstantType::class, $type, false, false); - } - /** * @return ArrayType[] * From 5b447846e986ebfa5fdd96af6ecac12059bb02da Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:39:11 +0200 Subject: [PATCH 0502/3097] [BCB] Remove `TypeUtils::getAnyArrays()` --- UPGRADING.md | 2 +- src/Analyser/MutatingScope.php | 4 ++-- src/Type/TypeUtils.php | 10 ---------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 065b68b2145..b065245f9b1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -251,7 +251,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` -* Remove `TypeUtils::getArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead +* Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6af143689b9..02340153bbb 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5251,11 +5251,11 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type private static function getArrayDepth(Type $type): int { $depth = 0; - $arrays = TypeUtils::getAnyArrays($type); + $arrays = TypeUtils::toBenevolentUnion($type)->getArrays(); while (count($arrays) > 0) { $temp = $type->getIterableValueType(); $type = $temp; - $arrays = TypeUtils::getAnyArrays($type); + $arrays = TypeUtils::toBenevolentUnion($type)->getArrays(); $depth++; } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 2094ae13b73..81400df4c39 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -28,16 +28,6 @@ public static function getConstantIntegers(Type $type): array return self::map(ConstantIntegerType::class, $type, false); } - /** - * @return ArrayType[] - * - * @deprecated Use PHPStan\Type\Type::getArrays() instead. - */ - public static function getAnyArrays(Type $type): array - { - return self::map(ArrayType::class, $type, true, false); - } - /** * @deprecated Use PHPStan\Type\Type::generalize() instead. */ From 426d94831b34c1072ae7b4bed0a4ce16a64f69fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:44:30 +0200 Subject: [PATCH 0503/3097] [BCB] Remove `TypeUtils::generalizeType()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index b065245f9b1..100c8d213d6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -255,3 +255,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) +* Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 81400df4c39..0929b114991 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -28,14 +28,6 @@ public static function getConstantIntegers(Type $type): array return self::map(ConstantIntegerType::class, $type, false); } - /** - * @deprecated Use PHPStan\Type\Type::generalize() instead. - */ - public static function generalizeType(Type $type, GeneralizePrecision $precision): Type - { - return $type->generalize($precision); - } - /** * @return list * From 6e263d05fd2a14b8832a8b30ecc9b4d8554c6166 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:48:10 +0200 Subject: [PATCH 0504/3097] [BCB] Remove `TypeUtils::getDirectClassNames()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 27 --------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 100c8d213d6..31f50bcea03 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -256,3 +256,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead +* Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead. diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 0929b114991..2c8941dec0d 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -11,8 +11,6 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateUnionType; use function array_merge; -use function array_unique; -use function array_values; /** * @api @@ -28,31 +26,6 @@ public static function getConstantIntegers(Type $type): array return self::map(ConstantIntegerType::class, $type, false); } - /** - * @return list - * - * @deprecated Use Type::getObjectClassNames() instead. - */ - public static function getDirectClassNames(Type $type): array - { - if ($type instanceof TypeWithClassName) { - return [$type->getClassName()]; - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - $classNames = []; - foreach ($type->getTypes() as $innerType) { - foreach (self::getDirectClassNames($innerType) as $n) { - $classNames[] = $n; - } - } - - return array_values(array_unique($classNames)); - } - - return []; - } - /** * @return IntegerRangeType[] */ From c8e4ed97bc3f500201cd109f6cd4a6c45f8f5176 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:49:36 +0200 Subject: [PATCH 0505/3097] Stop using TypeUtils in InArrayFunctionTypeSpecifyingExtension --- .../Php/InArrayFunctionTypeSpecifyingExtension.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index a91974e4669..62c022c8e1b 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -18,7 +18,6 @@ use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\MixedType; use PHPStan\Type\TypeCombinator; -use PHPStan\Type\TypeUtils; use function count; use function strtolower; @@ -111,10 +110,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $context->true() || ( $context->false() - && ( - count(TypeUtils::getConstantScalars($arrayValueType)) > 0 - || count(TypeUtils::getEnumCaseObjects($arrayValueType)) > 0 - ) + && count($arrayValueType->getFiniteTypes()) === 1 ) ) { $specifiedTypes = $this->typeSpecifier->create( @@ -137,10 +133,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $context->true() || ( $context->false() - && ( - count(TypeUtils::getConstantScalars($needleType)) === 1 - || count(TypeUtils::getEnumCaseObjects($needleType)) === 1 - ) + && count($needleType->getFiniteTypes()) === 1 ) ) { if ($context->true()) { From c98dd894fcd32a9652f3ee910681a31c2754fa57 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:50:16 +0200 Subject: [PATCH 0506/3097] [BCB] Remove `TypeUtils::getOldConstantArrays()` --- UPGRADING.md | 2 +- src/Type/TypeUtils.php | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 31f50bcea03..5088ba6f558 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -252,7 +252,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead -* Remove `TypeUtils::getConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead +* Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 2c8941dec0d..bfa15b092e5 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -52,17 +52,6 @@ public static function getEnumCaseObjects(Type $type): array return self::map(EnumCaseObjectType::class, $type, false); } - /** - * @internal - * @return ConstantArrayType[] - * - * @deprecated Use PHPStan\Type\Type::getConstantArrays(). - */ - public static function getOldConstantArrays(Type $type): array - { - return self::map(ConstantArrayType::class, $type, false); - } - /** * @return mixed[] */ From b9ad2ecf81ea6bc1b3ca483a2b03bf4ec271be95 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:53:26 +0200 Subject: [PATCH 0507/3097] [BCB] Remove `TypeUtils::getConstantScalars()` --- UPGRADING.md | 3 ++- src/Type/TypeUtils.php | 9 --------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 5088ba6f558..1806a077683 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -256,4 +256,5 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead -* Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead. +* Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead +* Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index bfa15b092e5..ff44d43b2f1 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -34,15 +34,6 @@ public static function getIntegerRanges(Type $type): array return self::map(IntegerRangeType::class, $type, false); } - /** - * @deprecated Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() - * @return ConstantScalarType[] - */ - public static function getConstantScalars(Type $type): array - { - return self::map(ConstantScalarType::class, $type, false); - } - /** * @deprecated Use Type::getEnumCases() * @return EnumCaseObjectType[] From 7088d79490567ceadff35b286a82dcad397a627d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:54:24 +0200 Subject: [PATCH 0508/3097] [BCB] Remove `TypeUtils::getEnumCaseObjects()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1806a077683..831b93d9af0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -258,3 +258,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead +* Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index ff44d43b2f1..4d838b2b3ed 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -6,7 +6,6 @@ use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateBenevolentUnionType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateUnionType; @@ -34,15 +33,6 @@ public static function getIntegerRanges(Type $type): array return self::map(IntegerRangeType::class, $type, false); } - /** - * @deprecated Use Type::getEnumCases() - * @return EnumCaseObjectType[] - */ - public static function getEnumCaseObjects(Type $type): array - { - return self::map(EnumCaseObjectType::class, $type, false); - } - /** * @return mixed[] */ From 239db410533843df98f88c98846c38c4c7a15c26 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:56:05 +0200 Subject: [PATCH 0509/3097] [BCB] Remove `TypeUtils::containsCallable()` --- UPGRADING.md | 1 + src/Type/TypeUtils.php | 18 ------------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 831b93d9af0..7e316637903 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -259,3 +259,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead +* Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 4d838b2b3ed..c00c7602ecd 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -198,24 +198,6 @@ public static function getAccessoryTypes(Type $type): array return self::map(AccessoryType::class, $type, true, false); } - /** @deprecated Use PHPStan\Type\Type::isCallable() instead. */ - public static function containsCallable(Type $type): bool - { - if ($type->isCallable()->yes()) { - return true; - } - - if ($type instanceof UnionType) { - foreach ($type->getTypes() as $innerType) { - if ($innerType->isCallable()->yes()) { - return true; - } - } - } - - return false; - } - public static function containsTemplateType(Type $type): bool { $containsTemplateType = false; From 1c44510c8886911431f37a97887f243eded071a9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 14:56:28 +0200 Subject: [PATCH 0510/3097] Indent TypeUtils upgrading notes --- UPGRADING.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 7e316637903..59eec7ee5a7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -251,12 +251,13 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` -* Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead -* Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead -* Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead -* Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) -* Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead -* Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead -* Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead -* Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead -* Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead +* Made `TypeUtils` thinner by removing methods: + * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead + * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead + * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead + * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) + * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead + * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead + * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead + * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead + * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead From 65019b249ecd220bad0c225e2e28bd60199f88d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:06:12 +0200 Subject: [PATCH 0511/3097] Update baseline --- phpstan-baseline.neon | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3c0e92416c3..5c198cf5ac5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,14 +15,6 @@ parameters: count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - - - message: """ - #^Call to deprecated method getAnyArrays\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: - Use PHPStan\\\\Type\\\\Type\\:\\:getArrays\\(\\) instead\\.$# - """ - count: 2 - path: src/Analyser/MutatingScope.php - - message: """ #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: @@ -1278,22 +1270,6 @@ parameters: count: 1 path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - - message: """ - #^Call to deprecated method getConstantScalars\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: - Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\)$# - """ - count: 2 - path: src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php - - - - message: """ - #^Call to deprecated method getEnumCaseObjects\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: - Use Type\\:\\:getEnumCases\\(\\)$# - """ - count: 2 - path: src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 @@ -1522,24 +1498,14 @@ parameters: count: 1 path: src/Type/TypeCombinator.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" - count: 3 - path: src/Type/TypeUtils.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 5 + count: 2 path: src/Type/TypeUtils.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" - count: 5 - path: src/Type/TypeUtils.php - - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" - count: 1 + count: 3 path: src/Type/TypeUtils.php - From 3a83f6bec7eb4724d4f1b6c2dedbfe46c0b1112a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:33:54 +0200 Subject: [PATCH 0512/3097] Un-deprecate ConstantTypeHelper --- src/Type/ConstantTypeHelper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 48cc18025b9..a4d0e8d75e3 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -28,7 +28,6 @@ class ConstantTypeHelper { /** - * @deprecated Use PHPStan\Reflection\InitializerExprTypeResolver * @param mixed $value */ public static function getTypeFromValue($value): Type From 83845202806f9104af2788784dc02c42f3fde9eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:10:46 +0200 Subject: [PATCH 0513/3097] [BCB] Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead --- UPGRADING.md | 1 + src/Analyser/MutatingScope.php | 4 ---- src/Analyser/Scope.php | 5 ----- src/Rules/Comparison/ImpossibleCheckTypeHelper.php | 6 ++++++ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 59eec7ee5a7..264980ab6ae 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -261,3 +261,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead +* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 02340153bbb..d68b07e630f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2587,10 +2587,6 @@ public function getKeepVoidType(Expr $node): Type return $this->getType($clonedNode); } - /** - * @api - * @deprecated Use getNativeType() - */ public function doNotTreatPhpDocTypesAsCertain(): Scope { return $this->promoteNativeTypes(); diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 6284d454f19..96859d4c5ea 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -91,11 +91,6 @@ public function getNativeType(Expr $expr): Type; public function getKeepVoidType(Expr $node): Type; - /** - * @deprecated Use getNativeType() - */ - public function doNotTreatPhpDocTypesAsCertain(): self; - public function resolveName(Name $name): string; public function resolveTypeByName(Name $name): TypeWithClassName; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 45574a089ae..b61904f26b9 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -8,11 +8,13 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; +use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -234,6 +236,10 @@ public function findSpecifiedType( } } + if (!$scope instanceof MutatingScope) { + throw new ShouldNotHappenException(); + } + $typeSpecifierScope = $this->treatPhpDocTypesAsCertain ? $scope : $scope->doNotTreatPhpDocTypesAsCertain(); $specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($typeSpecifierScope, $node, $this->determineContext($typeSpecifierScope, $node)); From 0cb872021f595c331334d521b14740106f59c349 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:19:56 +0200 Subject: [PATCH 0514/3097] Removed some unnecessary `@api` in MutatingScope --- phpstan-baseline.neon | 16 ---------------- src/Analyser/MutatingScope.php | 4 +--- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5c198cf5ac5..9d922a2fc99 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -43,14 +43,6 @@ parameters: count: 1 path: src/Analyser/MutatingScope.php - - - message: """ - #^Call to deprecated method doNotTreatPhpDocTypesAsCertain\\(\\) of class PHPStan\\\\Analyser\\\\MutatingScope\\: - Use getNativeType\\(\\)$# - """ - count: 1 - path: src/Analyser/NodeScopeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 3 @@ -417,14 +409,6 @@ parameters: count: 2 path: src/Rules/Comparison/IfConstantConditionRule.php - - - message: """ - #^Call to deprecated method doNotTreatPhpDocTypesAsCertain\\(\\) of interface PHPStan\\\\Analyser\\\\Scope\\: - Use getNativeType\\(\\)$# - """ - count: 1 - path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d68b07e630f..659dc7f8333 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2639,8 +2639,7 @@ private function hasPropertyNativeType($propertyFetch): bool return !$propertyReflection->getNativeType() instanceof MixedType; } - /** @api */ - protected function getTypeFromArrayDimFetch( + private function getTypeFromArrayDimFetch( Expr\ArrayDimFetch $arrayDimFetch, Type $offsetType, Type $offsetAccessibleType, @@ -5574,7 +5573,6 @@ public function getMethodReflection(Type $typeWithMethod, string $methodName): ? return $type->getMethod($methodName, $this); } - /** @api */ public function getNakedMethod(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection { $type = $this->filterTypeWithMethod($typeWithMethod, $methodName); From 42bb08a9ecff9901952f6380d632e6acd552292c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:23:47 +0200 Subject: [PATCH 0515/3097] [BCB] Remove `ConstantArrayType::findTypeAndMethodName()` --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index 264980ab6ae..235bb0e9369 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -251,6 +251,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` +* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead From 8c752e4c7e94641009b7b2e86c86c3499b702457 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:28:45 +0200 Subject: [PATCH 0516/3097] [BCB] Remove `ConstantArrayType::removeLast()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 36 ------------------------- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 235bb0e9369..aa7e28968e5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -252,6 +252,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Use `getFirstIterable*Type` and `getLastIterable*Type` instead * Remove `ConstantArrayType::generalizeToArray()` * Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead +* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 0dc13355f21..957eb84b013 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -489,36 +489,6 @@ private function getClassOrObjectAndMethods(): array return [$classOrObject, $method]; } - /** @deprecated Use findTypeAndMethodNames() instead */ - public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod - { - $callableArray = $this->getClassOrObjectAndMethods(); - if ($callableArray === []) { - return null; - } - - [$classOrObject, $method] = $callableArray; - if (!$method instanceof ConstantStringType) { - return ConstantArrayTypeAndMethod::createUnknown(); - } - - $type = $classOrObject->getObjectTypeOrClassStringObjectType(); - if (!$type->isObject()->yes()) { - return ConstantArrayTypeAndMethod::createUnknown(); - } - - $has = $type->hasMethod($method->getValue()); - if (!$has->no()) { - if ($this->isOptionalKey(0) || $this->isOptionalKey(1)) { - $has = $has->and(TrinaryLogic::createMaybe()); - } - - return ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); - } - - return null; - } - /** @return ConstantArrayTypeAndMethod[] */ public function findTypeAndMethodNames(): array { @@ -1010,12 +980,6 @@ public function isList(): TrinaryLogic return $this->isList; } - /** @deprecated Use popArray() instead */ - public function removeLast(): self - { - return $this->removeLastElements(1); - } - /** @param positive-int $length */ private function removeLastElements(int $length): self { From bb488992346f121b4b0cb6ff9d7174bac9bb683e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:29:20 +0200 Subject: [PATCH 0517/3097] [BCB] Remove `ConstantArrayType::removeFirst()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index aa7e28968e5..e1b2c2406c4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -253,6 +253,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantArrayType::generalizeToArray()` * Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead * Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead +* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 957eb84b013..f9f9be47c82 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1034,12 +1034,6 @@ private function removeLastElements(int $length): self ); } - /** @deprecated Use shiftArray() instead */ - public function removeFirst(): self - { - return $this->removeFirstElements(1); - } - /** @param positive-int $length */ private function removeFirstElements(int $length, bool $reindex = true): self { From ee32a25452371d831e21ac70d38fe830b628edda Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:30:02 +0200 Subject: [PATCH 0518/3097] [BCB] Remove `ConstantArrayType::reverse()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index e1b2c2406c4..1d56408d069 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -254,6 +254,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead * Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead * Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead +* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index f9f9be47c82..bf0c70d01e1 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1147,12 +1147,6 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel return $preserveKeys ? $slice : $slice->reindex(); } - /** @deprecated Use reverseArray() instead */ - public function reverse(bool $preserveKeys = false): self - { - return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys)); - } - /** * @deprecated Use chunkArray() instead * @param positive-int $length From 7d38ffc6a7c70cec7a25fba3077f411273206e83 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:30:52 +0200 Subject: [PATCH 0519/3097] [BCB] Remove `ConstantArrayType::chunk()` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 22 ---------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1d56408d069..568b326d88c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -255,6 +255,7 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead * Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead * Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead +* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index bf0c70d01e1..e54da2ad1e8 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1147,28 +1147,6 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel return $preserveKeys ? $slice : $slice->reindex(); } - /** - * @deprecated Use chunkArray() instead - * @param positive-int $length - */ - public function chunk(int $length, bool $preserveKeys = false): self - { - $builder = ConstantArrayTypeBuilder::createEmpty(); - - $keyTypesCount = count($this->keyTypes); - for ($i = 0; $i < $keyTypesCount; $i += $length) { - $chunk = $this->slice($i, $length, true); - $builder->setOffsetValueType(null, $preserveKeys ? $chunk : $chunk->getValuesArray()); - } - - $chunks = $builder->getArray(); - if (!$chunks instanceof self) { - throw new ShouldNotHappenException(); - } - - return $chunks; - } - private function reindex(): self { $keyTypes = []; From 83e8c1d09d01435b6848240ef69ad6edf4a6226b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:32:10 +0200 Subject: [PATCH 0520/3097] Update baseline --- phpstan-baseline.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9d922a2fc99..b64fc25c617 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -731,12 +731,12 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 8 + count: 7 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" - count: 3 + count: 2 path: src/Type/Constant/ConstantArrayType.php - From f51a00c59ed5db2e5af8223cbb90c826cdb32a5a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Sep 2024 15:42:07 +0200 Subject: [PATCH 0521/3097] Fix build --- phpstan-baseline.neon | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b7c18db8d9b..3f40a5c43ca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -28,14 +28,6 @@ parameters: count: 2 path: src/Analyser/MutatingScope.php - - - message: """ - #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: - Use PHPStan\\\\Reflection\\\\InitializerExprTypeResolver$# - """ - count: 1 - path: src/Analyser/MutatingScope.php - - message: "#^Casting to string something that's already string\\.$#" count: 3 @@ -257,14 +249,6 @@ parameters: count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - - message: """ - #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: - Use PHPStan\\\\Reflection\\\\InitializerExprTypeResolver$# - """ - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 @@ -360,14 +344,6 @@ parameters: count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - - message: """ - #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: - Use PHPStan\\\\Reflection\\\\InitializerExprTypeResolver$# - """ - count: 1 - path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 22 @@ -1399,14 +1375,6 @@ parameters: count: 2 path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php - - - message: """ - #^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\: - Use PHPStan\\\\Reflection\\\\InitializerExprTypeResolver$# - """ - count: 1 - path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 From 74e854997683fb7a64b6ccaf20742eb4b79f569c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 28 Sep 2024 09:49:41 +0200 Subject: [PATCH 0522/3097] [BCB] ConstantArrayType no longer extends ArrayType --- UPGRADING.md | 6 + phpstan-baseline.neon | 19 +- src/Analyser/NodeScopeResolver.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 3 + .../MethodParameterComparisonHelper.php | 4 + src/Type/ArrayType.php | 198 +---------------- src/Type/Constant/ConstantArrayType.php | 140 +++++++++--- src/Type/Traits/ArrayTypeTrait.php | 201 ++++++++++++++++++ src/Type/Type.php | 2 +- src/Type/TypeCombinator.php | 10 +- src/Type/TypehintHelper.php | 7 +- 11 files changed, 354 insertions(+), 238 deletions(-) create mode 100644 src/Type/Traits/ArrayTypeTrait.php diff --git a/UPGRADING.md b/UPGRADING.md index 568b326d88c..00d7c0766d3 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -203,6 +203,12 @@ If you want to change `$overwrite` or `$rootExpr` (previous parameters also used If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). +### `ConstantArrayType` no longer extends `ArrayType` + +`Type::getArrays()` now returns `list`. + +Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal. + ### Changed `TypeSpecifier::specifyTypesInCondition()` This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 511ab3b9ebe..4477db7fcea 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -175,6 +175,11 @@ parameters: count: 1 path: src/PhpDoc/TypeNodeResolver.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + count: 1 + path: src/PhpDoc/TypeNodeResolver.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 @@ -505,6 +510,11 @@ parameters: count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + count: 1 + path: src/Rules/Methods/MethodParameterComparisonHelper.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 @@ -657,7 +667,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 1 + count: 2 path: src/Type/ArrayType.php - @@ -1392,7 +1402,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 10 + count: 14 path: src/Type/TypeCombinator.php - @@ -1465,6 +1475,11 @@ parameters: count: 3 path: src/Type/TypehintHelper.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + count: 3 + path: src/Type/TypehintHelper.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 4bdc4342943..f2e43632a60 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3868,7 +3868,7 @@ private function getArraySortPreserveListFunctionType(Type $type): Type return $traverse($type); } - if (!$type instanceof ArrayType) { + if (!$type instanceof ArrayType && !$type instanceof ConstantArrayType) { return $type; } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 6ef403a0de9..facec026440 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -59,6 +59,7 @@ use PHPStan\Type\ClosureType; use PHPStan\Type\ConditionalType; use PHPStan\Type\ConditionalTypeForParameter; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -571,6 +572,8 @@ private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameSc $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]); } elseif ($type instanceof ArrayType) { $type = new ArrayType(new MixedType(), $arrayTypeType); + } elseif ($type instanceof ConstantArrayType) { + $type = new ArrayType(new MixedType(), $arrayTypeType); } elseif ($type instanceof IterableType) { $type = new IterableType(new MixedType(), $arrayTypeType); } else { diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 37fd21932a7..34f66bb8bb1 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -9,6 +9,7 @@ use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\IterableType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -392,6 +393,9 @@ private function isTypeCompatible(Type $methodParameterType, Type $prototypePara if ($prototypeParameterType instanceof ArrayType) { return true; } + if ($prototypeParameterType instanceof ConstantArrayType) { + return true; + } if ($prototypeParameterType->isObject()->yes() && $prototypeParameterType->getObjectClassNames() === [Traversable::class]) { return true; } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index c868b64613c..4a9a03dd6b6 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -11,7 +11,6 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; -use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; @@ -23,6 +22,7 @@ use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; +use PHPStan\Type\Traits\ArrayTypeTrait; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -36,6 +36,7 @@ class ArrayType implements Type { + use ArrayTypeTrait; use MaybeCallableTypeTrait; use NonObjectTypeTrait; use UndecidedBooleanTypeTrait; @@ -74,21 +75,6 @@ public function getReferencedClasses(): array ); } - public function getObjectClassNames(): array - { - return []; - } - - public function getObjectClassReflections(): array - { - return []; - } - - public function getArrays(): array - { - return [$this]; - } - public function getConstantArrays(): array { return []; @@ -129,7 +115,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): TrinaryLogic { - if ($type instanceof self) { + if ($type instanceof self || $type instanceof ConstantArrayType) { return $this->getItemType()->isSuperTypeOf($type->getItemType()) ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); } @@ -144,7 +130,6 @@ public function isSuperTypeOf(Type $type): TrinaryLogic public function equals(Type $type): bool { return $type instanceof self - && $type->isConstantArray()->no() && $this->getItemType()->equals($type->getIterableValueType()) && $this->keyType->equals($type->keyType); } @@ -198,11 +183,6 @@ public function getValuesArray(): Type return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType()); } - public function isIterable(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - public function isIterableAtLeastOnce(): TrinaryLogic { return TrinaryLogic::createMaybe(); @@ -251,21 +231,11 @@ public function getLastIterableValueType(): Type return $this->getItemType(); } - public function isArray(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - public function isConstantArray(): TrinaryLogic { return TrinaryLogic::createNo(); } - public function isOversizedArray(): TrinaryLogic - { - return TrinaryLogic::createMaybe(); - } - public function isList(): TrinaryLogic { if (IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($this->getKeyType())->no()) { @@ -275,126 +245,16 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isNull(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - public function isConstantValue(): TrinaryLogic { return TrinaryLogic::createNo(); } - public function isConstantScalarValue(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getConstantScalarTypes(): array - { - return []; - } - - public function getConstantScalarValues(): array - { - return []; - } - - public function isTrue(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isFalse(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isBoolean(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isFloat(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isInteger(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNumericString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNonEmptyString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isNonFalsyString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isLiteralString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isLowercaseString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isClassString(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function getClassStringObjectType(): Type - { - return new ErrorType(); - } - - public function getObjectTypeOrClassStringObjectType(): Type - { - return new ErrorType(); - } - - public function isVoid(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - public function isScalar(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { return new BooleanType(); } - public function isOffsetAccessible(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - - public function isOffsetAccessLegal(): TrinaryLogic - { - return TrinaryLogic::createYes(); - } - public function hasOffsetValueType(Type $offsetType): TrinaryLogic { $offsetType = $offsetType->toArrayKey(); @@ -510,20 +370,6 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type - { - $chunkType = $preserveKeys->yes() - ? $this - : TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getIterableValueType()), new AccessoryArrayListType()); - $chunkType = TypeCombinator::intersect($chunkType, new NonEmptyArrayType()); - - $arrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $chunkType), new AccessoryArrayListType()); - - return $this->isIterableAtLeastOnce()->yes() - ? TypeCombinator::intersect($arrayType, new NonEmptyArrayType()) - : $arrayType; - } - public function fillKeysArray(Type $valueType): Type { $itemType = $this->getItemType(); @@ -597,21 +443,6 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) return [new TrivialParametersAcceptor()]; } - public function toNumber(): Type - { - return new ErrorType(); - } - - public function toAbsoluteNumber(): Type - { - return new ErrorType(); - } - - public function toString(): Type - { - return new ErrorType(); - } - public function toInteger(): Type { return TypeCombinator::union( @@ -628,16 +459,6 @@ public function toFloat(): Type ); } - public function toArray(): Type - { - return $this; - } - - public function toArrayKey(): Type - { - return new ErrorType(); - } - public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { @@ -733,22 +554,9 @@ public function tryRemove(Type $typeToRemove): ?Type return new ConstantArrayType([], []); } - if ($this->isConstantArray()->yes() && $typeToRemove instanceof HasOffsetType) { - return $this->unsetOffset($typeToRemove->getOffsetType()); - } - - if ($this->isConstantArray()->yes() && $typeToRemove instanceof HasOffsetValueType) { - return $this->unsetOffset($typeToRemove->getOffsetType()); - } - return null; } - public function exponentiate(Type $exponent): Type - { - return new ErrorType(); - } - public function getFiniteTypes(): array { return []; diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index e54da2ad1e8..5e24881e614 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -23,6 +23,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; @@ -38,6 +39,9 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\Traits\ArrayTypeTrait; +use PHPStan\Type\Traits\NonObjectTypeTrait; +use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; @@ -68,9 +72,15 @@ /** * @api */ -class ConstantArrayType extends ArrayType implements ConstantType +class ConstantArrayType implements ConstantType { + use ArrayTypeTrait { + chunkArray as traitChunkArray; + } + use NonObjectTypeTrait; + use UndecidedComparisonTypeTrait; + private const DESCRIBE_LIMIT = 8; private const CHUNK_FINITE_TYPES_LIMIT = 5; @@ -82,6 +92,10 @@ class ConstantArrayType extends ArrayType implements ConstantType /** @var non-empty-list */ private array $nextAutoIndexes; + private ?Type $iterableKeyType = null; + + private ?Type $iterableValueType = null; + /** * @api * @param array $keyTypes @@ -107,23 +121,13 @@ public function __construct( $keyTypesCount = count($this->keyTypes); if ($keyTypesCount === 0) { - $keyType = new NeverType(true); $isList = TrinaryLogic::createYes(); - } elseif ($keyTypesCount === 1) { - $keyType = $this->keyTypes[0]; - } else { - $keyType = new UnionType($this->keyTypes); } if (is_bool($isList)) { $isList = TrinaryLogic::createFromBoolean($isList); } $this->isList = $isList; - - parent::__construct( - $keyType, - count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType(true), - ); } public function getConstantArrays(): array @@ -131,6 +135,61 @@ public function getConstantArrays(): array return [$this]; } + public function getReferencedClasses(): array + { + $referencedClasses = []; + foreach ($this->getKeyTypes() as $keyType) { + foreach ($keyType->getReferencedClasses() as $referencedClass) { + $referencedClasses[] = $referencedClass; + } + } + + foreach ($this->getValueTypes() as $valueType) { + foreach ($valueType->getReferencedClasses() as $referencedClass) { + $referencedClasses[] = $referencedClass; + } + } + + return $referencedClasses; + } + + public function getIterableKeyType(): Type + { + if ($this->iterableKeyType !== null) { + return $this->iterableKeyType; + } + + $keyTypesCount = count($this->keyTypes); + if ($keyTypesCount === 0) { + $keyType = new NeverType(true); + } elseif ($keyTypesCount === 1) { + $keyType = $this->keyTypes[0]; + } else { + $keyType = new UnionType($this->keyTypes); + } + + return $this->iterableKeyType = $keyType; + } + + public function getIterableValueType(): Type + { + if ($this->iterableValueType !== null) { + return $this->iterableValueType; + } + + return $this->iterableValueType = count($this->valueTypes) > 0 ? TypeCombinator::union(...$this->valueTypes) : new NeverType(true); + } + + public function getKeyType(): Type + { + return $this->getIterableKeyType(); + } + + public function getItemType(): Type + { + return $this->getIterableValueType(); + } + public function isConstantValue(): TrinaryLogic { return TrinaryLogic::createYes(); @@ -363,12 +422,12 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $result; } - $isKeySuperType = $this->getKeyType()->isSuperTypeOf($type->getKeyType()); + $isKeySuperType = $this->getIterableKeyType()->isSuperTypeOf($type->getKeyType()); if ($isKeySuperType->no()) { return TrinaryLogic::createNo(); } - return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOf($type->getItemType())); + return $result->and($isKeySuperType, $this->getIterableValueType()->isSuperTypeOf($type->getIterableKeyType())); } if ($type instanceof CompoundType) { @@ -738,7 +797,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type $results = []; foreach ($finiteTypes as $finiteType) { if (!$finiteType instanceof ConstantIntegerType || $finiteType->getValue() < 1) { - return parent::chunkArray($lengthType, $preserveKeys); + return $this->traitChunkArray($lengthType, $preserveKeys); } $length = $finiteType->getValue(); @@ -757,7 +816,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type return TypeCombinator::union(...$results); } - return parent::chunkArray($lengthType, $preserveKeys); + return $this->traitChunkArray($lengthType, $preserveKeys); } public function fillKeysArray(Type $valueType): Type @@ -880,7 +939,7 @@ public function shuffleArray(): Type return $valuesArray; } - $generalizedArray = new ArrayType($valuesArray->getIterableKeyType(), $valuesArray->getItemType()); + $generalizedArray = new ArrayType($valuesArray->getIterableKeyType(), $valuesArray->getIterableValueType()); if ($isIterableAtLeastOnce->yes()) { $generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType()); @@ -1192,7 +1251,7 @@ public function generalize(GeneralizePrecision $precision): Type $arrayType = new ArrayType( $this->getIterableKeyType()->generalize($precision), - $this->getItemType()->generalize($precision), + $this->getIterableValueType()->generalize($precision), ); $keyTypesCount = count($this->keyTypes); @@ -1222,10 +1281,7 @@ public function generalize(GeneralizePrecision $precision): Type return $arrayType; } - /** - * @return self - */ - public function generalizeValues(): ArrayType + public function generalizeValues(): self { $valueTypes = []; foreach ($this->valueTypes as $valueType) { @@ -1235,18 +1291,12 @@ public function generalizeValues(): ArrayType return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); } - /** - * @return self - */ - public function getKeysArray(): Type + public function getKeysArray(): self { return $this->getKeysOrValuesArray($this->keyTypes); } - /** - * @return self - */ - public function getValuesArray(): Type + public function getValuesArray(): self { return $this->getKeysOrValuesArray($this->valueTypes); } @@ -1343,7 +1393,7 @@ public function describe(VerbosityLevel $level): string ); }; return $level->handle( - fn (): string => parent::describe($level), + fn (): string => $this->isIterableAtLeastOnce()->no() ? 'array' : sprintf('array<%s, %s>', $this->getIterableKeyType()->describe($level), $this->getIterableValueType()->describe($level)), static fn (): string => $describeValue(true), static fn (): string => $describeValue(false), ); @@ -1369,7 +1419,14 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap return $typeMap; } - return parent::inferTemplateTypes($receivedType); + if ($receivedType->isArray()->yes()) { + $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); + $itemTypeMap = $this->getIterableValueType()->inferTemplateTypes($receivedType->getIterableValueType()); + + return $keyTypeMap->union($itemTypeMap); + } + + return TemplateTypeMap::createEmpty(); } public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array @@ -1392,6 +1449,27 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc return $references; } + public function tryRemove(Type $typeToRemove): ?Type + { + if ($typeToRemove->isConstantArray()->yes() && $typeToRemove->isIterableAtLeastOnce()->no()) { + return TypeCombinator::intersect($this, new NonEmptyArrayType()); + } + + if ($typeToRemove instanceof NonEmptyArrayType) { + return new ConstantArrayType([], []); + } + + if ($typeToRemove instanceof HasOffsetType) { + return $this->unsetOffset($typeToRemove->getOffsetType()); + } + + if ($typeToRemove instanceof HasOffsetValueType) { + return $this->unsetOffset($typeToRemove->getOffsetType()); + } + + return null; + } + public function traverse(callable $cb): Type { $valueTypes = []; diff --git a/src/Type/Traits/ArrayTypeTrait.php b/src/Type/Traits/ArrayTypeTrait.php new file mode 100644 index 00000000000..ee77d471620 --- /dev/null +++ b/src/Type/Traits/ArrayTypeTrait.php @@ -0,0 +1,201 @@ +yes() + ? $this + : TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getIterableValueType()), new AccessoryArrayListType()); + $chunkType = TypeCombinator::intersect($chunkType, new NonEmptyArrayType()); + + $arrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $chunkType), new AccessoryArrayListType()); + + return $this->isIterableAtLeastOnce()->yes() + ? TypeCombinator::intersect($arrayType, new NonEmptyArrayType()) + : $arrayType; + } + +} diff --git a/src/Type/Type.php b/src/Type/Type.php index ce306ee9f5d..fec6ead7284 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -56,7 +56,7 @@ public function isObject(): TrinaryLogic; public function isEnum(): TrinaryLogic; - /** @return list */ + /** @return list */ public function getArrays(): array; /** @return list */ diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 7a016c7bee0..c67cb05ff79 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -807,7 +807,7 @@ private static function optimizeConstantArrays(array $types): array $innerValueType = $type->getValueTypes()[$i]; $generalizedValueType = TypeTraverser::map($innerValueType, static function (Type $type, callable $innerTraverse) use ($traverse): Type { - if ($type instanceof ArrayType) { + if ($type instanceof ArrayType || $type instanceof ConstantArrayType) { return TypeCombinator::intersect($type, new OversizedArrayType()); } @@ -1207,7 +1207,7 @@ public static function intersect(Type ...$types): Type continue 2; } - if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof ArrayType) { + if ($types[$i] instanceof ConstantArrayType && ($types[$j] instanceof ArrayType || $types[$j] instanceof ConstantArrayType)) { $newArray = ConstantArrayTypeBuilder::createEmpty(); $valueTypes = $types[$i]->getValueTypes(); foreach ($types[$i]->getKeyTypes() as $k => $keyType) { @@ -1223,7 +1223,7 @@ public static function intersect(Type ...$types): Type continue 2; } - if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof ArrayType) { + if ($types[$j] instanceof ConstantArrayType && ($types[$i] instanceof ArrayType || $types[$i] instanceof ConstantArrayType)) { $newArray = ConstantArrayTypeBuilder::createEmpty(); $valueTypes = $types[$j]->getValueTypes(); foreach ($types[$j]->getKeyTypes() as $k => $keyType) { @@ -1240,8 +1240,8 @@ public static function intersect(Type ...$types): Type } if ( - ($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) && - ($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType) + ($types[$i] instanceof ArrayType || $types[$i] instanceof ConstantArrayType || $types[$i] instanceof IterableType) && + ($types[$j] instanceof ArrayType || $types[$j] instanceof ConstantArrayType || $types[$j] instanceof IterableType) ) { $keyType = self::intersect($types[$i]->getIterableKeyType(), $types[$j]->getKeyType()); $itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType()); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 5538370ccb6..f2e21098200 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Generic\TemplateTypeHelper; use ReflectionType; use function array_map; @@ -30,7 +31,7 @@ public static function decideTypeFromReflection( ): Type { if ($reflectionType === null) { - if ($isVariadic && $phpDocType instanceof ArrayType) { + if ($isVariadic && ($phpDocType instanceof ArrayType || $phpDocType instanceof ConstantArrayType)) { $phpDocType = $phpDocType->getItemType(); } return $phpDocType ?? new MixedType(); @@ -109,7 +110,7 @@ public static function decideType( if ($phpDocType instanceof UnionType) { $innerTypes = []; foreach ($phpDocType->getTypes() as $innerType) { - if ($innerType instanceof ArrayType) { + if ($innerType instanceof ArrayType || $innerType instanceof ConstantArrayType) { $innerTypes[] = new IterableType( $innerType->getIterableKeyType(), $innerType->getItemType(), @@ -119,7 +120,7 @@ public static function decideType( } } $phpDocType = new UnionType($innerTypes); - } elseif ($phpDocType instanceof ArrayType) { + } elseif ($phpDocType instanceof ArrayType || $phpDocType instanceof ConstantArrayType) { $phpDocType = new IterableType( $phpDocType->getKeyType(), $phpDocType->getItemType(), From ff1f73788692e83fad3567dadf3aa418149f999e Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sun, 29 Sep 2024 00:21:46 +0000 Subject: [PATCH 0523/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index a2f36452e23..612ca1c5d5b 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^4.17.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", - "phpstan/php-8-stubs": "0.3.110", + "phpstan/php-8-stubs": "0.3.111", "phpstan/phpdoc-parser": "1.32.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 275758e6a50..edd298b6c82 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a217b1999ea1f07d7b371c0171fcc437", + "content-hash": "49e816aaa49ffc406de3c7ad3c73072e", "packages": [ { "name": "clue/ndjson-react", @@ -2248,16 +2248,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.110", + "version": "0.3.111", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e" + "reference": "0013252145df5d84112764d4ea57ed1c6f074018" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e", - "reference": "e33a7dcf7cb4a8d7552e6a9d56d88a6cf5fd962e", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/0013252145df5d84112764d4ea57ed1c6f074018", + "reference": "0013252145df5d84112764d4ea57ed1c6f074018", "shasum": "" }, "type": "library", @@ -2274,9 +2274,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.110" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.111" }, - "time": "2024-09-27T00:19:13+00:00" + "time": "2024-09-29T00:21:10+00:00" }, { "name": "phpstan/phpdoc-parser", From 45b4a854ada757044ddab1b2f783027c1f54e8cd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Sep 2024 13:55:23 +0200 Subject: [PATCH 0524/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/7173 --- ...rictComparisonOfDifferentTypesRuleTest.php | 5 +++++ .../Rules/Comparison/data/bug-7173.php | 19 +++++++++++++++++++ .../WrongVariableNameInVarTagRuleTest.php | 4 ++++ .../PhpDoc/data/wrong-variable-name-var.php | 17 +++++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-7173.php diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 08a7ecfb87c..65e47459f00 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -906,4 +906,9 @@ public function testBug10493(): void $this->analyse([__DIR__ . '/data/bug-10493.php'], []); } + public function testBug7173(): void + { + $this->analyse([__DIR__ . '/data/bug-7173.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-7173.php b/tests/PHPStan/Rules/Comparison/data/bug-7173.php new file mode 100644 index 00000000000..039b2bd6678 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-7173.php @@ -0,0 +1,19 @@ + 0, + 'item1' => 0, + ]; + + call_user_func(function () use (&$a1) { + $a1['item2'] = 3; + $a1['item1'] = 1; + }); + + if (['item2' => 3, 'item1' => 1] === $a1) { + throw new \Exception(); + } +}; diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 155c0d6ea49..f0c8cd0025f 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -140,6 +140,10 @@ public function testRule(): void 'PHPDoc tag @var above a function has no effect.', 313, ], + [ + "PHPDoc tag @var with type array is not subtype of native type array{: 'empty', 1: '1'}.", + 324, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/wrong-variable-name-var.php b/tests/PHPStan/Rules/PhpDoc/data/wrong-variable-name-var.php index a769c5f2dd1..30f7dd3182c 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/wrong-variable-name-var.php +++ b/tests/PHPStan/Rules/PhpDoc/data/wrong-variable-name-var.php @@ -314,3 +314,20 @@ function doFoo(): void { } + +class VarTagAboveLiteralArray +{ + + public function doFoo(): void + { + /** @var array */ + $arr = ['' => 'empty', 1 => '1']; + } + + public function doFoo2(): void + { + /** @var array */ + $arr = ['' => 'empty', 1 => '1']; + } + +} From e75996b626ccef553e413c6535107ba5669c6938 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Sep 2024 14:02:03 +0200 Subject: [PATCH 0525/3097] [BCB] Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 7 +++---- src/Type/Php/RegexArrayShapeMatcher.php | 4 ++-- .../Type/Constant/ConstantArrayTypeTest.php | 20 +++++++++---------- tests/PHPStan/Type/TypeGetFiniteTypesTest.php | 9 +++++---- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 00d7c0766d3..bfba8f8dd95 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -273,3 +273,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead * Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead +* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer bool diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 5e24881e614..498d59f41d8 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -59,7 +59,6 @@ use function count; use function implode; use function in_array; -use function is_bool; use function is_int; use function is_string; use function min; @@ -108,7 +107,7 @@ public function __construct( private array $valueTypes, int|array $nextAutoIndexes = [0], private array $optionalKeys = [], - bool|TrinaryLogic $isList = false, + ?TrinaryLogic $isList = null, ) { assert(count($keyTypes) === count($valueTypes)); @@ -124,8 +123,8 @@ public function __construct( $isList = TrinaryLogic::createYes(); } - if (is_bool($isList)) { - $isList = TrinaryLogic::createFromBoolean($isList); + if ($isList === null) { + $isList = TrinaryLogic::createNo(); } $this->isList = $isList; } diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index e2b489ddab7..a433c4a89b9 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -149,7 +149,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched if (!$this->containsUnmatchedAsNull($flags, $matchesAll)) { // positive match has a subject but not any capturing group $combiType = TypeCombinator::union( - new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], true), + new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], TrinaryLogic::createYes()), $combiType, ); } @@ -214,7 +214,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ) ) { // positive match has a subject but not any capturing group - $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], true); + $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], TrinaryLogic::createYes()); } return TypeCombinator::union(...$combiTypes); diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 727fd65d010..62ed2ebb9b5 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -886,14 +886,14 @@ public function dataValuesArray(): iterable ], [ new ConstantStringType('a'), new ConstantStringType('b'), - ], [20], [], false), + ], [20], [], TrinaryLogic::createNo()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantStringType('a'), new ConstantStringType('b'), - ], [2], [], true), + ], [2], [], TrinaryLogic::createYes()), ]; yield 'optional-1' => [ @@ -909,7 +909,7 @@ public function dataValuesArray(): iterable new ConstantStringType('c'), new ConstantStringType('d'), new ConstantStringType('e'), - ], [15], [1, 3], false), + ], [15], [1, 3], TrinaryLogic::createNo()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), @@ -922,7 +922,7 @@ public function dataValuesArray(): iterable new UnionType([new ConstantStringType('c'), new ConstantStringType('d'), new ConstantStringType('e')]), new UnionType([new ConstantStringType('d'), new ConstantStringType('e')]), new ConstantStringType('e'), - ], [3, 4, 5], [3, 4], true), + ], [3, 4, 5], [3, 4], TrinaryLogic::createYes()), ]; yield 'optional-2' => [ @@ -938,7 +938,7 @@ public function dataValuesArray(): iterable new ConstantStringType('c'), new ConstantStringType('d'), new ConstantStringType('e'), - ], [15], [0, 2, 4], false), + ], [15], [0, 2, 4], TrinaryLogic::createNo()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), @@ -951,7 +951,7 @@ public function dataValuesArray(): iterable new UnionType([new ConstantStringType('c'), new ConstantStringType('d'), new ConstantStringType('e')]), new UnionType([new ConstantStringType('d'), new ConstantStringType('e')]), new ConstantStringType('e'), - ], [2, 3, 4, 5], [2, 3, 4], true), + ], [2, 3, 4, 5], [2, 3, 4], TrinaryLogic::createYes()), ]; yield 'optional-at-end-and-list' => [ @@ -963,7 +963,7 @@ public function dataValuesArray(): iterable new ConstantStringType('a'), new ConstantStringType('b'), new ConstantStringType('c'), - ], [11, 12, 13], [1, 2], true), + ], [11, 12, 13], [1, 2], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), @@ -972,7 +972,7 @@ public function dataValuesArray(): iterable new ConstantStringType('a'), new ConstantStringType('b'), new ConstantStringType('c'), - ], [1, 2, 3], [1, 2], true), + ], [1, 2, 3], [1, 2], TrinaryLogic::createYes()), ]; yield 'optional-at-end-but-not-list' => [ @@ -984,7 +984,7 @@ public function dataValuesArray(): iterable new ConstantStringType('a'), new ConstantStringType('b'), new ConstantStringType('c'), - ], [11, 12, 13], [1, 2], false), + ], [11, 12, 13], [1, 2], TrinaryLogic::createNo()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), @@ -993,7 +993,7 @@ public function dataValuesArray(): iterable new ConstantStringType('a'), new UnionType([new ConstantStringType('b'), new ConstantStringType('c')]), new ConstantStringType('c'), - ], [1, 2, 3], [1, 2], true), + ], [1, 2, 3], [1, 2], TrinaryLogic::createYes()), ]; } diff --git a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php index ce605f3d5ab..c2f0f961919 100644 --- a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php +++ b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -96,28 +97,28 @@ public function dataGetFiniteTypes(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], 2, [], true), + ], 2, [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(true), new ConstantBooleanType(false), - ], 2, [], true), + ], 2, [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(true), - ], 2, [], true), + ], 2, [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(false), - ], 2, [], true), + ], 2, [], TrinaryLogic::createYes()), ], ]; } From f302c9069274afa63ec1b4f313ca72340699e9d8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 19 Nov 2023 10:28:35 +0100 Subject: [PATCH 0526/3097] Remove unneded abstraction --- .../Native/NativeMethodReflection.php | 8 +- .../Php/BuiltinMethodReflection.php | 60 ------- .../Php/NativeBuiltinMethodReflection.php | 148 ------------------ .../Php/PhpClassReflectionExtension.php | 53 +++---- src/Reflection/Php/PhpMethodReflection.php | 7 +- .../Php/PhpMethodReflectionFactory.php | 3 +- src/Rules/Methods/MethodSignatureRule.php | 3 +- src/Rules/Methods/OverridingMethodRule.php | 3 +- 8 files changed, 34 insertions(+), 251 deletions(-) delete mode 100644 src/Reflection/Php/BuiltinMethodReflection.php delete mode 100644 src/Reflection/Php/NativeBuiltinMethodReflection.php diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index d588cea5586..68ee421e388 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -2,13 +2,13 @@ namespace PHPStan\Reflection\Native; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; -use PHPStan\Reflection\Php\BuiltinMethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -28,7 +28,7 @@ final class NativeMethodReflection implements ExtendedMethodReflection public function __construct( private ReflectionProvider $reflectionProvider, private ClassReflection $declaringClass, - private BuiltinMethodReflection $reflection, + private ReflectionMethod $reflection, private array $variants, private ?array $namedArgumentsVariants, private TrinaryLogic $hasSideEffects, @@ -133,7 +133,7 @@ public function getDeprecatedDescription(): ?string public function isDeprecated(): TrinaryLogic { - return $this->reflection->isDeprecated(); + return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); } public function isInternal(): TrinaryLogic @@ -212,7 +212,7 @@ public function getSelfOutType(): ?Type public function returnsByReference(): TrinaryLogic { - return $this->reflection->returnsByReference(); + return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } } diff --git a/src/Reflection/Php/BuiltinMethodReflection.php b/src/Reflection/Php/BuiltinMethodReflection.php deleted file mode 100644 index d2d55336aa1..00000000000 --- a/src/Reflection/Php/BuiltinMethodReflection.php +++ /dev/null @@ -1,60 +0,0 @@ -reflection->getName(); - } - - public function getReflection(): ReflectionMethod - { - return $this->reflection; - } - - public function getFileName(): ?string - { - $fileName = $this->reflection->getFileName(); - if ($fileName === false) { - return null; - } - - return $fileName; - } - - public function getDeclaringClass(): ReflectionClass - { - return $this->reflection->getDeclaringClass(); - } - - public function getStartLine(): ?int - { - $line = $this->reflection->getStartLine(); - if ($line === false) { - return null; - } - - return $line; - } - - public function getEndLine(): ?int - { - $line = $this->reflection->getEndLine(); - if ($line === false) { - return null; - } - - return $line; - } - - public function getDocComment(): ?string - { - $docComment = $this->reflection->getDocComment(); - if ($docComment === false) { - return null; - } - - return $docComment; - } - - public function isStatic(): bool - { - return $this->reflection->isStatic(); - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function isConstructor(): bool - { - return $this->reflection->isConstructor(); - } - - public function getPrototype(): BuiltinMethodReflection - { - return new self($this->reflection->getPrototype()); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); - } - - public function isFinal(): bool - { - return $this->reflection->isFinal(); - } - - public function isInternal(): bool - { - return $this->reflection->isInternal(); - } - - public function isAbstract(): bool - { - return $this->reflection->isAbstract(); - } - - public function isVariadic(): bool - { - return $this->reflection->isVariadic(); - } - - public function getReturnType(): ReflectionIntersectionType|ReflectionNamedType|ReflectionUnionType|null - { - return $this->reflection->getReturnType(); - } - - public function getTentativeReturnType(): ReflectionIntersectionType|ReflectionNamedType|ReflectionUnionType|null - { - return $this->reflection->getTentativeReturnType(); - } - - /** - * @return ReflectionParameter[] - */ - public function getParameters(): array - { - return $this->reflection->getParameters(); - } - - public function returnsByReference(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); - } - -} diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 9b9a9132e1a..b7f438737ad 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -10,6 +10,7 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\ScopeContext; use PHPStan\Analyser\ScopeFactory; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\Parser\Parser; @@ -384,7 +385,7 @@ public function getMethod(ClassReflection $classReflection, string $methodName): return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName]; } - $nativeMethodReflection = new NativeBuiltinMethodReflection($classReflection->getNativeReflection()->getMethod($methodName)); + $nativeMethodReflection = $classReflection->getNativeReflection()->getMethod($methodName); if (!isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { $method = $this->createMethod($classReflection, $nativeMethodReflection, true); $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$nativeMethodReflection->getName()] = $method; @@ -411,8 +412,7 @@ public function getNativeMethod(ClassReflection $classReflection, string $method throw new ShouldNotHappenException(); } - $reflectionMethod = $classReflection->getNativeReflection()->getMethod($methodName); - $nativeMethodReflection = new NativeBuiltinMethodReflection($reflectionMethod); + $nativeMethodReflection = $classReflection->getNativeReflection()->getMethod($methodName); if (!isset($this->nativeMethods[$classReflection->getCacheKey()][$nativeMethodReflection->getName()])) { $method = $this->createMethod($classReflection, $nativeMethodReflection, false); @@ -424,7 +424,7 @@ public function getNativeMethod(ClassReflection $classReflection, string $method private function createMethod( ClassReflection $classReflection, - BuiltinMethodReflection $methodReflection, + ReflectionMethod $methodReflection, bool $includingAnnotations, ): ExtendedMethodReflection { @@ -642,27 +642,25 @@ private function createMethod( ); } - public function createUserlandMethodReflection(ClassReflection $fileDeclaringClass, ClassReflection $actualDeclaringClass, BuiltinMethodReflection $methodReflection, ?string $declaringTraitName): PhpMethodReflection + public function createUserlandMethodReflection(ClassReflection $fileDeclaringClass, ClassReflection $actualDeclaringClass, ReflectionMethod $methodReflection, ?string $declaringTraitName): PhpMethodReflection { $resolvedPhpDoc = null; $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($fileDeclaringClass, $fileDeclaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters())); $phpDocBlockClassReflection = $fileDeclaringClass; - if ($methodReflection->getReflection() !== null) { - $methodDeclaringClass = $methodReflection->getReflection()->getBetterReflection()->getDeclaringClass(); - - if ($stubPhpDocPair === null && $methodDeclaringClass->isTrait()) { - if (! $methodReflection->getDeclaringClass()->isTrait() || $methodDeclaringClass->getName() !== $methodReflection->getDeclaringClass()->getName()) { - $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors( - $this->reflectionProviderProvider->getReflectionProvider()->getClass($methodDeclaringClass->getName()), - $this->reflectionProviderProvider->getReflectionProvider()->getClass($methodReflection->getDeclaringClass()->getName()), - $methodReflection->getName(), - array_map( - static fn (ReflectionParameter $parameter): string => $parameter->getName(), - $methodReflection->getParameters(), - ), - ); - } + $methodDeclaringClass = $methodReflection->getBetterReflection()->getDeclaringClass(); + + if ($stubPhpDocPair === null && $methodDeclaringClass->isTrait()) { + if (! $methodReflection->getDeclaringClass()->isTrait() || $methodDeclaringClass->getName() !== $methodReflection->getDeclaringClass()->getName()) { + $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors( + $this->reflectionProviderProvider->getReflectionProvider()->getClass($methodDeclaringClass->getName()), + $this->reflectionProviderProvider->getReflectionProvider()->getClass($methodReflection->getDeclaringClass()->getName()), + $methodReflection->getName(), + array_map( + static fn (ReflectionParameter $parameter): string => $parameter->getName(), + $methodReflection->getParameters(), + ), + ); } } @@ -671,7 +669,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } if ($resolvedPhpDoc === null) { - $docComment = $methodReflection->getDocComment(); + $docComment = $methodReflection->getDocComment() !== false ? $methodReflection->getDocComment() : null; $positionalParameterNames = array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()); $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( @@ -694,10 +692,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } $phpDocParameterTypes = []; - if ( - $methodReflection instanceof NativeBuiltinMethodReflection - && $methodReflection->isConstructor() - ) { + if ($methodReflection->isConstructor()) { foreach ($methodReflection->getParameters() as $parameter) { if (!$parameter->isPromoted()) { continue; @@ -922,14 +917,10 @@ private function findPropertyTrait(ReflectionProperty $propertyReflection): ?str } private function findMethodTrait( - BuiltinMethodReflection $methodReflection, + ReflectionMethod $methodReflection, ): ?string { - if ($methodReflection->getReflection() === null) { - return null; - } - - $declaringClass = $methodReflection->getReflection()->getBetterReflection()->getDeclaringClass(); + $declaringClass = $methodReflection->getBetterReflection()->getDeclaringClass(); if ($declaringClass->isTrait()) { if ($methodReflection->getDeclaringClass()->isTrait() && $declaringClass->getName() === $methodReflection->getDeclaringClass()->getName()) { return null; diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 8de0a7870e2..71885511370 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Declare_; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Namespace_; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\Cache\Cache; use PHPStan\Parser\FunctionCallStatementFinder; @@ -71,7 +72,7 @@ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ClassReflection $declaringClass, private ?ClassReflection $declaringTrait, - private BuiltinMethodReflection $reflection, + private ReflectionMethod $reflection, private ReflectionProvider $reflectionProvider, private Parser $parser, private FunctionCallStatementFinder $functionCallStatementFinder, @@ -409,7 +410,7 @@ public function isDeprecated(): TrinaryLogic return TrinaryLogic::createYes(); } - return $this->reflection->isDeprecated(); + return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); } public function isInternal(): TrinaryLogic @@ -478,7 +479,7 @@ public function getDocComment(): ?string public function returnsByReference(): TrinaryLogic { - return $this->reflection->returnsByReference(); + return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } public function isPure(): TrinaryLogic diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 77de1aa1b76..22028286d3d 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassReflection; use PHPStan\TrinaryLogic; @@ -20,7 +21,7 @@ interface PhpMethodReflectionFactory public function create( ClassReflection $declaringClass, ?ClassReflection $declaringTrait, - BuiltinMethodReflection $reflection, + ReflectionMethod $reflection, TemplateTypeMap $templateTypeMap, array $phpDocParameterTypes, ?Type $phpDocReturnType, diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 5a1a0072536..bac3f273ad6 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -9,7 +9,6 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; -use PHPStan\Reflection\Php\NativeBuiltinMethodReflection; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -182,7 +181,7 @@ private function collectParentMethods(string $methodName, ClassReflection $class $this->phpClassReflectionExtension->createUserlandMethodReflection( $trait, $class, - new NativeBuiltinMethodReflection($methodReflection), + $methodReflection, $declaringTrait->getName(), ), $declaringTrait, diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 37de68b4f66..0a78f8d3820 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -11,7 +11,6 @@ use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\Native\NativeMethodReflection; -use PHPStan\Reflection\Php\NativeBuiltinMethodReflection; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Rules\IdentifierRuleError; @@ -368,7 +367,7 @@ private function findPrototype(ClassReflection $classReflection, string $methodN $this->phpClassReflectionExtension->createUserlandMethodReflection( $trait, $classReflection, - new NativeBuiltinMethodReflection($methodReflection), + $methodReflection, $declaringTrait->getName(), ), $declaringTrait, From a5297b0c2354e08ba7b3e5d53110a4504db1cd66 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Sep 2024 14:15:08 +0200 Subject: [PATCH 0527/3097] [BCB] Remove `ConstantType` interface --- UPGRADING.md | 1 + phpstan-baseline.neon | 5 ----- src/Rules/Api/ApiInstanceofTypeRule.php | 2 -- src/Type/Constant/ConstantArrayType.php | 3 +-- src/Type/ConstantScalarType.php | 2 +- src/Type/ConstantType.php | 9 --------- src/Type/Php/MinMaxFunctionReturnTypeExtension.php | 4 +--- 7 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 src/Type/ConstantType.php diff --git a/UPGRADING.md b/UPGRADING.md index bfba8f8dd95..4fb0823cf3a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -274,3 +274,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead * Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead * Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer bool +* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4477db7fcea..23d822be892 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1285,11 +1285,6 @@ parameters: count: 4 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantValue\\(\\) or Type\\:\\:generalize\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index 35ed6d3cf81..118c8d5b9fc 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -29,7 +29,6 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantType; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericClassStringType; @@ -75,7 +74,6 @@ final class ApiInstanceofTypeRule implements Rule GenericClassStringType::class => 'Type::isClassStringType() and Type::getClassStringObjectType()', GenericObjectType::class => null, IntersectionType::class => null, - ConstantType::class => 'Type::isConstantValue() or Type::generalize()', ConstantScalarType::class => 'Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues()', ObjectShapeType::class => 'Type::isObject() and Type::hasProperty()', diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 498d59f41d8..09eda329e35 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -30,7 +30,6 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\TemplateTypeMap; @@ -71,7 +70,7 @@ /** * @api */ -class ConstantArrayType implements ConstantType +class ConstantArrayType implements Type { use ArrayTypeTrait { diff --git a/src/Type/ConstantScalarType.php b/src/Type/ConstantScalarType.php index f44e18333b7..b84b3817176 100644 --- a/src/Type/ConstantScalarType.php +++ b/src/Type/ConstantScalarType.php @@ -3,7 +3,7 @@ namespace PHPStan\Type; /** @api */ -interface ConstantScalarType extends ConstantType +interface ConstantScalarType extends Type { /** diff --git a/src/Type/ConstantType.php b/src/Type/ConstantType.php deleted file mode 100644 index d5fab2b652e..00000000000 --- a/src/Type/ConstantType.php +++ /dev/null @@ -1,9 +0,0 @@ -isConstantValue()->yes()) { return TypeCombinator::union(...$types); } - if ($resultType === null) { $resultType = $type; continue; From bd4c3ed608d9554c6f468bd11cc27a92a41b4e14 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Sep 2024 14:26:42 +0200 Subject: [PATCH 0528/3097] [BCB] Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list` --- UPGRADING.md | 1 + src/Type/Constant/ConstantArrayType.php | 29 ++++--------- .../Type/Constant/ConstantArrayTypeTest.php | 42 +++++++++---------- .../Type/SimultaneousTypeTraverserTest.php | 6 +-- tests/PHPStan/Type/TypeCombinatorTest.php | 10 ++--- tests/PHPStan/Type/TypeGetFiniteTypesTest.php | 10 ++--- tests/PHPStan/Type/UnionTypeTest.php | 16 +++---- 7 files changed, 50 insertions(+), 64 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 4fb0823cf3a..cd3d44175a5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -274,4 +274,5 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead * Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead * Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer bool +* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer int * Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 09eda329e35..a257437061d 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -58,7 +58,6 @@ use function count; use function implode; use function in_array; -use function is_int; use function is_string; use function min; use function pow; @@ -87,9 +86,6 @@ class ConstantArrayType implements Type /** @var self[]|null */ private ?array $allArrays = null; - /** @var non-empty-list */ - private array $nextAutoIndexes; - private ?Type $iterableKeyType = null; private ?Type $iterableValueType = null; @@ -98,25 +94,19 @@ class ConstantArrayType implements Type * @api * @param array $keyTypes * @param array $valueTypes - * @param non-empty-list|int $nextAutoIndexes + * @param non-empty-list $nextAutoIndexes * @param int[] $optionalKeys */ public function __construct( private array $keyTypes, private array $valueTypes, - int|array $nextAutoIndexes = [0], + private array $nextAutoIndexes = [0], private array $optionalKeys = [], ?TrinaryLogic $isList = null, ) { assert(count($keyTypes) === count($valueTypes)); - if (is_int($nextAutoIndexes)) { - $nextAutoIndexes = [$nextAutoIndexes]; - } - - $this->nextAutoIndexes = $nextAutoIndexes; - $keyTypesCount = count($this->keyTypes); if ($keyTypesCount === 0) { $isList = TrinaryLogic::createYes(); @@ -201,11 +191,6 @@ public function getNextAutoIndexes(): array return $this->nextAutoIndexes; } - private function getNextAutoIndex(): int - { - return $this->nextAutoIndexes[count($this->nextAutoIndexes) - 1]; - } - /** * @return int[] */ @@ -1048,7 +1033,7 @@ private function removeLastElements(int $length): self $keyTypes = $this->keyTypes; $valueTypes = $this->valueTypes; $optionalKeys = $this->optionalKeys; - $nextAutoindex = $this->nextAutoIndexes; + $nextAutoindexes = $this->nextAutoIndexes; $optionalKeysRemoved = 0; $newLength = $keyTypesCount - $length; @@ -1068,9 +1053,9 @@ private function removeLastElements(int $length): self $removedKeyType = array_pop($keyTypes); array_pop($valueTypes); - $nextAutoindex = $removedKeyType instanceof ConstantIntegerType - ? $removedKeyType->getValue() - : $this->getNextAutoIndex(); + $nextAutoindexes = $removedKeyType instanceof ConstantIntegerType + ? [$removedKeyType->getValue()] + : $this->nextAutoIndexes; continue; } @@ -1085,7 +1070,7 @@ private function removeLastElements(int $length): self return new self( $keyTypes, $valueTypes, - $nextAutoindex, + $nextAutoindexes, array_values($optionalKeys), $this->isList, ); diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 62ed2ebb9b5..9e814dfaf83 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -189,7 +189,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -225,7 +225,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], 0, [1]), + ], [0], [1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -241,7 +241,7 @@ public function dataAccepts(): iterable new ConstantStringType('limit'), ], [ new IntegerType(), - ], 0, [0]), + ], [0], [0]), new ConstantArrayType([ new ConstantStringType('limit'), ], [ @@ -255,7 +255,7 @@ public function dataAccepts(): iterable new ConstantStringType('limit'), ], [ new IntegerType(), - ], 0), + ], [0]), new ConstantArrayType([ new ConstantStringType('limit'), ], [ @@ -271,7 +271,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -289,7 +289,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('color'), ], [ @@ -305,7 +305,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('sound'), ], [ @@ -321,14 +321,14 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('foo'), new ConstantStringType('bar'), ], [ new ConstantStringType('s'), new ConstantStringType('m'), - ], 0, [0, 1]), + ], [0], [0, 1]), TrinaryLogic::createYes(), ]; @@ -339,7 +339,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], 0, [0, 1]), + ], [0], [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -522,7 +522,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2), + ], [2]), new ConstantArrayType([], []), TrinaryLogic::createNo(), ]; @@ -534,7 +534,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0]), + ], [2], [0]), new ConstantArrayType([], []), TrinaryLogic::createNo(), ]; @@ -546,7 +546,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), new ConstantArrayType([], []), TrinaryLogic::createYes(), ]; @@ -558,12 +558,12 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), new ConstantArrayType([ new ConstantStringType('foo'), ], [ new IntegerType(), - ], 1, [0]), + ], [1], [0]), TrinaryLogic::createYes(), ]; @@ -579,7 +579,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), TrinaryLogic::createMaybe(), ]; @@ -595,7 +595,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), TrinaryLogic::createNo(), ]; @@ -606,7 +606,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), new ConstantArrayType([ new ConstantStringType('foo'), ], [ @@ -623,7 +623,7 @@ public function dataIsSuperTypeOf(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0, 1]), + ], [2], [0, 1]), TrinaryLogic::createMaybe(), ]; @@ -632,7 +632,7 @@ public function dataIsSuperTypeOf(): iterable new ConstantStringType('foo'), ], [ new IntegerType(), - ], 1, [0]), + ], [1], [0]), new ConstantArrayType([ new ConstantStringType('foo'), ], [ @@ -651,7 +651,7 @@ public function dataIsSuperTypeOf(): iterable new ConstantStringType('foo'), ], [ new IntegerType(), - ], 1, [0]), + ], [1], [0]), TrinaryLogic::createMaybe(), ]; } diff --git a/tests/PHPStan/Type/SimultaneousTypeTraverserTest.php b/tests/PHPStan/Type/SimultaneousTypeTraverserTest.php index e9cb8ada700..3917423ff3e 100644 --- a/tests/PHPStan/Type/SimultaneousTypeTraverserTest.php +++ b/tests/PHPStan/Type/SimultaneousTypeTraverserTest.php @@ -22,7 +22,7 @@ public function dataChangeStringIntoNonEmptyString(): iterable new ConstantArrayType( [new ConstantIntegerType(0)], [new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()])], - 1, + [1], ), 'array', ]; @@ -31,7 +31,7 @@ public function dataChangeStringIntoNonEmptyString(): iterable new ConstantArrayType( [new ConstantIntegerType(0)], [new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()])], - 1, + [1], ), 'array', ]; @@ -40,7 +40,7 @@ public function dataChangeStringIntoNonEmptyString(): iterable new ConstantArrayType( [new ConstantIntegerType(0)], [new IntegerType()], - 1, + [1], ), 'array', ]; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 55dc30542bd..d9d6eefe66a 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1794,7 +1794,7 @@ public function dataUnion(): iterable new ConstantIntegerType(0), ], [ new StringType(), - ], 1, [0]), + ], [1], [0]), ], UnionType::class, 'array{}|array{0?: string}', @@ -3766,7 +3766,7 @@ public function dataIntersect(): iterable ], [ new IntegerType(), new IntegerType(), - ], 2, [0]), + ], [2], [0]), new HasOffsetType(new ConstantStringType('a')), ], ConstantArrayType::class, @@ -4900,7 +4900,7 @@ public function dataRemove(): array ], [ new StringType(), new StringType(), - ], 2), + ], [2]), new HasOffsetType(new ConstantIntegerType(1)), NeverType::class, '*NEVER*=implicit', @@ -4912,7 +4912,7 @@ public function dataRemove(): array ], [ new StringType(), new StringType(), - ], 2, [1]), + ], [2], [1]), new HasOffsetType(new ConstantIntegerType(1)), ConstantArrayType::class, 'array{string}', @@ -4924,7 +4924,7 @@ public function dataRemove(): array ], [ new StringType(), new StringType(), - ], 2, [1]), + ], [2], [1]), new HasOffsetType(new ConstantIntegerType(0)), NeverType::class, '*NEVER*=implicit', diff --git a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php index c2f0f961919..9770a62a725 100644 --- a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php +++ b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php @@ -89,7 +89,7 @@ public function dataGetFiniteTypes(): iterable ], [ new BooleanType(), new BooleanType(), - ], 2), + ], [2]), [ new ConstantArrayType([ new ConstantIntegerType(0), @@ -97,28 +97,28 @@ public function dataGetFiniteTypes(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], 2, [], TrinaryLogic::createYes()), + ], [2], [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(true), new ConstantBooleanType(false), - ], 2, [], TrinaryLogic::createYes()), + ], [2], [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(true), - ], 2, [], TrinaryLogic::createYes()), + ], [2], [], TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(false), - ], 2, [], TrinaryLogic::createYes()), + ], [2], [], TrinaryLogic::createYes()), ], ]; } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index cd0ee180cc9..83ec097e239 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -111,7 +111,7 @@ public function dataSelfCompare(): Iterator yield [new CallableType([$mixedParam, $integerParam], $stringType, false)]; yield [new ClassStringType()]; yield [new ClosureType([$mixedParam, $integerParam], $stringType, false)]; - yield [new ConstantArrayType([$constantStringType, $constantIntegerType], [$mixedType, $stringType], 10, [1])]; + yield [new ConstantArrayType([$constantStringType, $constantIntegerType], [$mixedType, $stringType], [10], [1])]; yield [new ConstantBooleanType(true)]; yield [new ConstantFloatType(3.14)]; yield [$constantIntegerType]; @@ -1418,7 +1418,7 @@ public function dataGetConstantArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(1), new ConstantIntegerType(2)], [new IntegerType(), new StringType()], - 2, + [2], [0, 1], ), new NonEmptyArrayType(), @@ -1426,7 +1426,7 @@ public function dataGetConstantArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(0), new ConstantIntegerType(1)], [new ObjectType(Foo::class), new ObjectType(stdClass::class)], - 2, + [2], ), ], [ @@ -1440,7 +1440,7 @@ public function dataGetConstantArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(1), new ConstantIntegerType(2)], [new IntegerType(), new StringType()], - 2, + [2], [0, 1], ), ), @@ -1602,7 +1602,7 @@ public function dataGetArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(1), new ConstantIntegerType(2)], [new IntegerType(), new StringType()], - 2, + [2], [0, 1], ), new NonEmptyArrayType(), @@ -1610,7 +1610,7 @@ public function dataGetArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(0), new ConstantIntegerType(1)], [new ObjectType(Foo::class), new ObjectType(stdClass::class)], - 2, + [2], ), ), [ @@ -1624,13 +1624,13 @@ public function dataGetArrays(): iterable new ConstantArrayType( [new ConstantIntegerType(1), new ConstantIntegerType(2)], [new IntegerType(), new StringType()], - 2, + [2], [0, 1], ), new ConstantArrayType( [new ConstantIntegerType(0), new ConstantIntegerType(1)], [new ObjectType(Foo::class), new ObjectType(stdClass::class)], - 2, + [2], ), ), [ From 0c0f683042d5ccac249dc34e355daa6d01aa8b48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 00:11:18 +0000 Subject: [PATCH 0529/3097] Update dependency knplabs/github-api to v3.15.0 --- issue-bot/composer.lock | 62 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index 9f4f4c42a5a..2af3d80b5f2 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -474,16 +474,16 @@ }, { "name": "knplabs/github-api", - "version": "v3.14.1", + "version": "v3.15.0", "source": { "type": "git", "url": "https://github.com/KnpLabs/php-github-api.git", - "reference": "71fec50e228737ec23c0b69801b85bf596fbdaca" + "reference": "d4b7a1c00e22c1ca32408ecdd4e33c674196b1bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/71fec50e228737ec23c0b69801b85bf596fbdaca", - "reference": "71fec50e228737ec23c0b69801b85bf596fbdaca", + "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/d4b7a1c00e22c1ca32408ecdd4e33c674196b1bc", + "reference": "d4b7a1c00e22c1ca32408ecdd4e33c674196b1bc", "shasum": "" }, "require": { @@ -503,7 +503,7 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.2", - "guzzlehttp/psr7": "^1.7", + "guzzlehttp/psr7": "^2.7", "http-interop/http-factory-guzzle": "^1.0", "php-http/mock-client": "^1.4.1", "phpstan/extension-installer": "^1.0.5", @@ -550,7 +550,7 @@ ], "support": { "issues": "https://github.com/KnpLabs/php-github-api/issues", - "source": "https://github.com/KnpLabs/php-github-api/tree/v3.14.1" + "source": "https://github.com/KnpLabs/php-github-api/tree/v3.15.0" }, "funding": [ { @@ -558,7 +558,7 @@ "type": "github" } ], - "time": "2024-03-24T18:21:15+00:00" + "time": "2024-09-23T19:00:43+00:00" }, { "name": "league/commonmark", @@ -1021,16 +1021,16 @@ }, { "name": "php-http/client-common", - "version": "2.7.1", + "version": "2.7.2", "source": { "type": "git", "url": "https://github.com/php-http/client-common.git", - "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612" + "reference": "0cfe9858ab9d3b213041b947c881d5b19ceeca46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/client-common/zipball/1e19c059b0e4d5f717bf5d524d616165aeab0612", - "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612", + "url": "https://api.github.com/repos/php-http/client-common/zipball/0cfe9858ab9d3b213041b947c881d5b19ceeca46", + "reference": "0cfe9858ab9d3b213041b947c881d5b19ceeca46", "shasum": "" }, "require": { @@ -1084,9 +1084,9 @@ ], "support": { "issues": "https://github.com/php-http/client-common/issues", - "source": "https://github.com/php-http/client-common/tree/2.7.1" + "source": "https://github.com/php-http/client-common/tree/2.7.2" }, - "time": "2023-11-30T10:31:25+00:00" + "time": "2024-09-24T06:21:48+00:00" }, { "name": "php-http/discovery", @@ -1169,16 +1169,16 @@ }, { "name": "php-http/httplug", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", "shasum": "" }, "require": { @@ -1220,9 +1220,9 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.4.0" + "source": "https://github.com/php-http/httplug/tree/2.4.1" }, - "time": "2023-04-14T15:10:03+00:00" + "time": "2024-09-23T11:39:58+00:00" }, { "name": "php-http/message", @@ -1295,16 +1295,16 @@ }, { "name": "php-http/multipart-stream-builder", - "version": "1.3.0", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/php-http/multipart-stream-builder.git", - "reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a" + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/f5938fd135d9fa442cc297dc98481805acfe2b6a", - "reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a", + "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/10086e6de6f53489cca5ecc45b6f468604d3460e", + "reference": "10086e6de6f53489cca5ecc45b6f468604d3460e", "shasum": "" }, "require": { @@ -1345,9 +1345,9 @@ ], "support": { "issues": "https://github.com/php-http/multipart-stream-builder/issues", - "source": "https://github.com/php-http/multipart-stream-builder/tree/1.3.0" + "source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.2" }, - "time": "2023-04-28T14:10:22+00:00" + "time": "2024-09-04T13:22:54+00:00" }, { "name": "php-http/promise", @@ -2093,16 +2093,16 @@ }, { "name": "symfony/options-resolver", - "version": "v7.0.0", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", "shasum": "" }, "require": { @@ -2140,7 +2140,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" }, "funding": [ { @@ -2156,7 +2156,7 @@ "type": "tidelift" } ], - "time": "2023-08-08T10:20:21+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/polyfill-ctype", From 1b45eaee2dc3011cf4fca4bd706cdad1cb7273b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 09:24:58 +0200 Subject: [PATCH 0530/3097] [BCB] `acceptsNamedArguments()` returns `TrinaryLogic` instead of `bool` --- UPGRADING.md | 1 + src/Analyser/ArgumentsNormalizer.php | 7 ++++--- src/Analyser/MutatingScope.php | 10 +++++----- .../Annotations/AnnotationMethodReflection.php | 4 ++-- src/Reflection/CallableFunctionVariantWithPhpDocs.php | 4 ++-- .../Callables/CallableParametersAcceptor.php | 2 +- src/Reflection/Callables/FunctionCallableVariant.php | 2 +- src/Reflection/Dummy/ChangedTypeMethodReflection.php | 2 +- src/Reflection/Dummy/DummyConstructorReflection.php | 4 ++-- src/Reflection/Dummy/DummyMethodReflection.php | 4 ++-- src/Reflection/ExtendedMethodReflection.php | 2 +- src/Reflection/FunctionReflection.php | 2 +- src/Reflection/InaccessibleMethod.php | 2 +- src/Reflection/Native/NativeFunctionReflection.php | 4 ++-- src/Reflection/Native/NativeMethodReflection.php | 4 ++-- src/Reflection/ParametersAcceptorSelector.php | 4 ++-- src/Reflection/Php/ClosureCallMethodReflection.php | 2 +- src/Reflection/Php/EnumCasesMethodReflection.php | 4 ++-- src/Reflection/Php/ExitFunctionReflection.php | 4 ++-- .../Php/PhpFunctionFromParserNodeReflection.php | 4 ++-- src/Reflection/Php/PhpFunctionReflection.php | 4 ++-- src/Reflection/Php/PhpMethodReflection.php | 6 ++++-- .../ResolvedFunctionVariantWithCallable.php | 4 ++-- src/Reflection/ResolvedMethodReflection.php | 2 +- src/Reflection/TrivialParametersAcceptor.php | 4 ++-- .../Type/IntersectionTypeMethodReflection.php | 9 ++------- src/Reflection/Type/UnionTypeMethodReflection.php | 9 ++------- src/Reflection/WrappedExtendedMethodReflection.php | 4 ++-- src/Rules/FunctionCallParametersCheck.php | 5 +++-- src/Rules/Functions/CallCallablesRule.php | 5 +++-- src/Type/CallableType.php | 4 ++-- src/Type/ClosureType.php | 11 +++++++++-- 32 files changed, 71 insertions(+), 68 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index cd3d44175a5..378e11d2ca0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -276,3 +276,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer bool * Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer int * Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead +* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index 6baa6e0c6b3..0b68559b426 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use function array_key_exists; use function array_keys; @@ -27,7 +28,7 @@ final class ArgumentsNormalizer public const ORIGINAL_ARG_ATTRIBUTE = 'originalArg'; /** - * @return array{ParametersAcceptor, FuncCall, bool}|null + * @return array{ParametersAcceptor, FuncCall, TrinaryLogic}|null */ public static function reorderCallUserFuncArguments( FuncCall $callUserFuncCall, @@ -73,9 +74,9 @@ public static function reorderCallUserFuncArguments( null, ); - $acceptsNamedArguments = true; + $acceptsNamedArguments = TrinaryLogic::createYes(); foreach ($callableParametersAcceptors as $callableParametersAcceptor) { - $acceptsNamedArguments = $acceptsNamedArguments && $callableParametersAcceptor->acceptsNamedArguments(); + $acceptsNamedArguments = $acceptsNamedArguments->and($callableParametersAcceptor->acceptsNamedArguments()); } return [$parametersAcceptor, new FuncCall( diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 659dc7f8333..99cd3e3c5b7 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1357,7 +1357,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $cachedClosureData['impurePoints'], $cachedClosureData['invalidateExpressions'], $cachedClosureData['usedVariables'], - true, + TrinaryLogic::createYes(), ); } if (self::$resolveClosureTypeDepth >= 2) { @@ -1572,7 +1572,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $impurePointsForClosureType, $invalidateExpressions, $usedVariables, - true, + TrinaryLogic::createYes(), ); } elseif ($node instanceof New_) { if ($node->class instanceof Name) { @@ -2521,7 +2521,7 @@ private function createFirstClassCallable( $throwPoints = []; $impurePoints = []; - $acceptsNamedArguments = true; + $acceptsNamedArguments = TrinaryLogic::createYes(); if ($variant instanceof CallableParametersAcceptor) { $throwPoints = $variant->getThrowPoints(); $impurePoints = $variant->getImpurePoints(); @@ -3151,7 +3151,7 @@ private function enterFunctionLike( $paramExprString = '$' . $parameter->getName(); if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { + if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) { $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType); } else { $parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType()); @@ -3166,7 +3166,7 @@ private function enterFunctionLike( $nativeParameterType = $parameter->getNativeType(); if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) { + if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) { $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType); } else { $nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType()); diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index b4065332ce8..6b2ff2afdb6 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -147,9 +147,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->declaringClass->acceptsNamedArguments(); + return TrinaryLogic::createFromBoolean($this->declaringClass->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/CallableFunctionVariantWithPhpDocs.php b/src/Reflection/CallableFunctionVariantWithPhpDocs.php index 9e6644c0f1e..fd6c62afd53 100644 --- a/src/Reflection/CallableFunctionVariantWithPhpDocs.php +++ b/src/Reflection/CallableFunctionVariantWithPhpDocs.php @@ -35,7 +35,7 @@ public function __construct( private array $impurePoints, private array $invalidateExpressions, private array $usedVariables, - private bool $acceptsNamedArguments, + private TrinaryLogic $acceptsNamedArguments, ) { parent::__construct( @@ -75,7 +75,7 @@ public function getUsedVariables(): array return $this->usedVariables; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->acceptsNamedArguments; } diff --git a/src/Reflection/Callables/CallableParametersAcceptor.php b/src/Reflection/Callables/CallableParametersAcceptor.php index 259ede81fab..25ff7d770ca 100644 --- a/src/Reflection/Callables/CallableParametersAcceptor.php +++ b/src/Reflection/Callables/CallableParametersAcceptor.php @@ -19,7 +19,7 @@ public function getThrowPoints(): array; public function isPure(): TrinaryLogic; - public function acceptsNamedArguments(): bool; + public function acceptsNamedArguments(): TrinaryLogic; /** * @return SimpleImpurePoint[] diff --git a/src/Reflection/Callables/FunctionCallableVariant.php b/src/Reflection/Callables/FunctionCallableVariant.php index aef72101403..82eb478fc3f 100644 --- a/src/Reflection/Callables/FunctionCallableVariant.php +++ b/src/Reflection/Callables/FunctionCallableVariant.php @@ -163,7 +163,7 @@ public function getUsedVariables(): array return []; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->function->acceptsNamedArguments(); } diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index b81e30e8465..3dc1cd56b0d 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -119,7 +119,7 @@ public function getAsserts(): Assertions return $this->reflection->getAsserts(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->reflection->acceptsNamedArguments(); } diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index ca67c3c2e27..36b143ed12c 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -117,9 +117,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->declaringClass->acceptsNamedArguments(); + return TrinaryLogic::createFromBoolean($this->declaringClass->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index ba4faeded3b..c6157c17dbd 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -114,9 +114,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return true; + return TrinaryLogic::createYes(); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 7cb583f1dca..43f27903f96 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -37,7 +37,7 @@ public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; */ public function getNamedArgumentsVariants(): ?array; - public function acceptsNamedArguments(): bool; + public function acceptsNamedArguments(): TrinaryLogic; public function getAsserts(): Assertions; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index e09ceb27edd..66ef61928f6 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -28,7 +28,7 @@ public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; */ public function getNamedArgumentsVariants(): ?array; - public function acceptsNamedArguments(): bool; + public function acceptsNamedArguments(): TrinaryLogic; public function isDeprecated(): TrinaryLogic; diff --git a/src/Reflection/InaccessibleMethod.php b/src/Reflection/InaccessibleMethod.php index eaf63ef8a1e..fef9716d6c0 100644 --- a/src/Reflection/InaccessibleMethod.php +++ b/src/Reflection/InaccessibleMethod.php @@ -86,7 +86,7 @@ public function getUsedVariables(): array return []; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->methodReflection->acceptsNamedArguments(); } diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 82a26d08f7e..7a7e137d9c7 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -142,9 +142,9 @@ public function returnsByReference(): TrinaryLogic return $this->returnsByReference; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->acceptsNamedArguments; + return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 68ee421e388..1671c55aab1 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -200,9 +200,9 @@ public function getAsserts(): Assertions return $this->assertions; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments; + return TrinaryLogic::createFromBoolean($this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index d78cb09f33f..93db8e9834f 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -581,7 +581,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $impurePoints = []; $invalidateExpressions = []; $usedVariables = []; - $acceptsNamedArguments = false; + $acceptsNamedArguments = TrinaryLogic::createNo(); foreach ($acceptors as $acceptor) { $returnTypes[] = $acceptor->getReturnType(); @@ -597,7 +597,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $impurePoints = array_merge($impurePoints, $acceptor->getImpurePoints()); $invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions()); $usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables()); - $acceptsNamedArguments = $acceptsNamedArguments || $acceptor->acceptsNamedArguments(); + $acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments()); } $isVariadic = $isVariadic || $acceptor->isVariadic(); diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index be5d97660fe..1bb9d201c8d 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -156,7 +156,7 @@ public function getAsserts(): Assertions return $this->nativeMethodReflection->getAsserts(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->nativeMethodReflection->acceptsNamedArguments(); } diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index ff5fd5f9a54..bd706e7c98d 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -126,9 +126,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->declaringClass->acceptsNamedArguments(); + return TrinaryLogic::createFromBoolean($this->declaringClass->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index dc8f74eceab..85e62106993 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -82,9 +82,9 @@ public function getNamedArgumentsVariants(): array return $this->getVariants(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return true; + return TrinaryLogic::createYes(); } public function isDeprecated(): TrinaryLogic diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 96ba2f6be1a..5710ce56a01 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -280,9 +280,9 @@ public function isGenerator(): bool return $this->nodeIsOrContainsYield($this->functionLike); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->acceptsNamedArguments; + return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } private function nodeIsOrContainsYield(Node $node): bool diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 1f3ffee131e..5bdbd199be6 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -300,9 +300,9 @@ public function returnsByReference(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->acceptsNamedArguments; + return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } private function isFunctionNodeVariadic(Function_ $node): bool diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 71885511370..ea1bc0c6c07 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -462,9 +462,11 @@ public function getAsserts(): Assertions return $this->asserts; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments; + return TrinaryLogic::createFromBoolean( + $this->declaringClass->acceptsNamedArguments() && $this->acceptsNamedArguments, + ); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/ResolvedFunctionVariantWithCallable.php b/src/Reflection/ResolvedFunctionVariantWithCallable.php index ab121486a14..7dbd3824057 100644 --- a/src/Reflection/ResolvedFunctionVariantWithCallable.php +++ b/src/Reflection/ResolvedFunctionVariantWithCallable.php @@ -27,7 +27,7 @@ public function __construct( private array $impurePoints, private array $invalidateExpressions, private array $usedVariables, - private bool $acceptsNamedArguments, + private TrinaryLogic $acceptsNamedArguments, ) { } @@ -112,7 +112,7 @@ public function getUsedVariables(): array return $this->usedVariables; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->acceptsNamedArguments; } diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 477cbdea74c..1ce0c0dc715 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -175,7 +175,7 @@ public function getAsserts(): Assertions )); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->reflection->acceptsNamedArguments(); } diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index 1d9f2aa6288..05a4888c277 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -93,9 +93,9 @@ public function getUsedVariables(): array return []; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return true; + return TrinaryLogic::createYes(); } } diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index 65c3b8b1528..d27576767f2 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -198,14 +198,9 @@ public function getAsserts(): Assertions return $assertions; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - $accepts = true; - foreach ($this->methods as $method) { - $accepts = $accepts && $method->acceptsNamedArguments(); - } - - return $accepts; + return TrinaryLogic::lazyMaxMin($this->methods, static fn (MethodReflection $method): TrinaryLogic => $method->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 38083044444..140ce0bd2e1 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -175,14 +175,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - $accepts = true; - foreach ($this->methods as $method) { - $accepts = $accepts && $method->acceptsNamedArguments(); - } - - return $accepts; + return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index 12d263b0e69..2f348095ff6 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -142,9 +142,9 @@ public function getAsserts(): Assertions return Assertions::createEmpty(); } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return $this->getDeclaringClass()->acceptsNamedArguments(); + return TrinaryLogic::createFromBoolean($this->getDeclaringClass()->acceptsNamedArguments()); } public function getSelfOutType(): ?Type diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 9f427732372..787d6136da5 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -14,6 +14,7 @@ use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\ConditionalType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -63,7 +64,7 @@ public function check( $funcCall, array $messages, string $nodeType, - bool $acceptsNamedArguments, + TrinaryLogic $acceptsNamedArguments, ): array { $functionParametersMinCount = 0; @@ -289,7 +290,7 @@ public function check( } } - if (!$acceptsNamedArguments && isset($messages[14])) { + if (!$acceptsNamedArguments->yes() && isset($messages[14])) { if ($argumentName !== null) { $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('named argument $%s', $argumentName))) ->identifier('argument.named') diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index d8fb352e529..05cd89d0a74 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -12,6 +12,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\TrinaryLogic; use PHPStan\Type\ClosureType; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; @@ -79,9 +80,9 @@ public function processNode( $parametersAcceptors = $type->getCallableParametersAcceptors($scope); $messages = []; - $acceptsNamedArguments = true; + $acceptsNamedArguments = TrinaryLogic::createYes(); foreach ($parametersAcceptors as $parametersAcceptor) { - $acceptsNamedArguments = $acceptsNamedArguments && $parametersAcceptor->acceptsNamedArguments(); + $acceptsNamedArguments = $acceptsNamedArguments->and($parametersAcceptor->acceptsNamedArguments()); } if ( diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 20cfeafa679..de594f96838 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -294,9 +294,9 @@ public function getUsedVariables(): array return []; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { - return true; + return TrinaryLogic::createYes(); } public function toNumber(): Type diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 751239ab735..708257ea1f5 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -77,6 +77,8 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor /** @var SimpleImpurePoint[] */ private array $impurePoints; + private TrinaryLogic $acceptsNamedArguments; + /** * @api * @param array|null $parameters @@ -98,9 +100,14 @@ public function __construct( ?array $impurePoints = null, private array $invalidateExpressions = [], private array $usedVariables = [], - private bool $acceptsNamedArguments = true, + ?TrinaryLogic $acceptsNamedArguments = null, ) { + if ($acceptsNamedArguments === null) { + $acceptsNamedArguments = TrinaryLogic::createYes(); + } + $this->acceptsNamedArguments = $acceptsNamedArguments; + $this->parameters = $parameters ?? []; $this->returnType = $returnType ?? new MixedType(); $this->isCommonCallable = $parameters === null && $returnType === null; @@ -407,7 +414,7 @@ public function getUsedVariables(): array return $this->usedVariables; } - public function acceptsNamedArguments(): bool + public function acceptsNamedArguments(): TrinaryLogic { return $this->acceptsNamedArguments; } From b1ea97a4da3751ff0206e5491d1790b732e08a74 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 10:04:37 +0200 Subject: [PATCH 0531/3097] Remove nette/neon patch for backward compatibility with old multi-line string encoding --- composer.json | 1 - composer.lock | 2 +- patches/NetteNeonStringNode.patch | 40 -- phpstan-baseline.neon | 642 +++++++++++++++--------------- 4 files changed, 322 insertions(+), 363 deletions(-) delete mode 100644 patches/NetteNeonStringNode.patch diff --git a/composer.json b/composer.json index e7d2b401bb2..a97d4011045 100644 --- a/composer.json +++ b/composer.json @@ -119,7 +119,6 @@ "patches/DependencyChecker.patch" ], "nette/neon": [ - "patches/NetteNeonStringNode.patch", "patches/NeonParser.patch" ] } diff --git a/composer.lock b/composer.lock index 2bf4e1b5f2b..f836c7ee65e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3745d8358f113e8d1a7c6b1066a9db0a", + "content-hash": "3a133a74db439ecb38147f64988135c9", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/NetteNeonStringNode.patch b/patches/NetteNeonStringNode.patch deleted file mode 100644 index ff7332693fa..00000000000 --- a/patches/NetteNeonStringNode.patch +++ /dev/null @@ -1,40 +0,0 @@ ---- src/Neon/Node/StringNode.php 2022-03-10 03:04:26 -+++ src/Neon/Node/StringNode.php 2024-08-26 14:53:45 -@@ -79,27 +79,18 @@ - - public function toString(): string - { -- if (strpos($this->value, "\n") === false) { -- return "'" . str_replace("'", "''", $this->value) . "'"; -+ $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); -+ if ($res === false) { -+ throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); -+ } - -- } elseif (preg_match('~\n[\t ]+\'{3}~', $this->value)) { -- $s = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); -- $s = preg_replace_callback( -- '#[^\\\\]|\\\\(.)#s', -- function ($m) { -- return ['n' => "\n", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; -- }, -- substr($s, 1, -1) -- ); -- $s = str_replace('"""', '""\"', $s); -- $delim = '"""'; -- -- } else { -- $s = $this->value; -- $delim = "'''"; -+ if (strpos($this->value, "\n") !== false) { -+ $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { -+ return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; -+ }, $res); -+ $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; - } - -- $s = preg_replace('#^(?=.)#m', "\t", $s); -- return $delim . "\n" . $s . "\n" . $delim; -+ return $res; - } - } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 23d822be892..0f3487007a2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,1606 +1,1606 @@ parameters: ignoreErrors: - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php - - message: "#^Method PHPStan\\\\Analyser\\\\AnalyserResultFinalizer\\:\\:finalize\\(\\) throws checked exception Throwable but it's missing from the PHPDoc @throws tag\\.$#" + message: '#^Method PHPStan\\Analyser\\AnalyserResultFinalizer\:\:finalize\(\) throws checked exception Throwable but it''s missing from the PHPDoc @throws tag\.$#' count: 1 path: src/Analyser/AnalyserResultFinalizer.php - - message: "#^Cannot assign offset 'realCount' to array\\|string\\.$#" + message: '#^Cannot assign offset ''realCount'' to array\|string\.$#' count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - - message: "#^Casting to string something that's already string\\.$#" + message: '#^Casting to string something that''s already string\.$#' count: 3 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 4 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 3 path: src/Analyser/MutatingScope.php - - message: "#^Only numeric types are allowed in pre\\-increment, float\\|int\\|string\\|null given\\.$#" + message: '#^Only numeric types are allowed in pre\-increment, float\|int\|string\|null given\.$#' count: 1 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 3 path: src/Analyser/NodeScopeResolver.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' count: 1 path: src/Analyser/NodeScopeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Analyser/TypeSpecifier.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 5 path: src/Analyser/TypeSpecifier.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 3 path: src/Analyser/TypeSpecifier.php - - message: "#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\\\Collectors\\\\Collector\\:\\:processNode\\(\\)\\.$#" + message: '#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\Collectors\\Collector\:\:processNode\(\)\.$#' count: 1 path: src/Collectors/Collector.php - - message: "#^Method PHPStan\\\\Collectors\\\\Registry\\:\\:__construct\\(\\) has parameter \\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector but does not specify its types\\: TNodeType, TValue$#" + message: '#^Method PHPStan\\Collectors\\Registry\:\:__construct\(\) has parameter \$collectors with generic interface PHPStan\\Collectors\\Collector but does not specify its types\: TNodeType, TValue$#' count: 1 path: src/Collectors/Registry.php - - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$cache with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + message: '#^Property PHPStan\\Collectors\\Registry\:\:\$cache with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' count: 1 path: src/Collectors/Registry.php - - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + message: '#^Property PHPStan\\Collectors\\Registry\:\:\$collectors with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' count: 1 path: src/Collectors/Registry.php - - message: "#^Anonymous function has an unused use \\$container\\.$#" + message: '#^Anonymous function has an unused use \$container\.$#' count: 1 path: src/Command/CommandHelper.php - - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' count: 1 path: src/Command/CommandHelper.php - - message: "#^Static property PHPStan\\\\Command\\\\CommandHelper\\:\\:\\$reservedMemory is never read, only written\\.$#" + message: '#^Static property PHPStan\\Command\\CommandHelper\:\:\$reservedMemory is never read, only written\.$#' count: 1 path: src/Command/CommandHelper.php - - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Variable method call on Nette\\\\Schema\\\\Elements\\\\AnyOf\\|Nette\\\\Schema\\\\Elements\\\\Structure\\|Nette\\\\Schema\\\\Elements\\\\Type\\.$#" + message: '#^Variable method call on Nette\\Schema\\Elements\\AnyOf\|Nette\\Schema\\Elements\\Structure\|Nette\\Schema\\Elements\\Type\.$#' count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: "#^Variable static method call on Nette\\\\Schema\\\\Expect\\.$#" + message: '#^Variable static method call on Nette\\Schema\\Expect\.$#' count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: "#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\\\DI\\\\Config\\\\Helpers\\.$#" + message: '#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\DI\\Config\\Helpers\.$#' count: 1 path: src/DependencyInjection/NeonAdapter.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" + message: '#^Variable method call on PHPStan\\Reflection\\ClassReflection\.$#' count: 2 path: src/PhpDoc/PhpDocBlock.php - - message: "#^Variable static method call on PHPStan\\\\PhpDoc\\\\PhpDocBlock\\.$#" + message: '#^Variable static method call on PHPStan\\PhpDoc\\PhpDocBlock\.$#' count: 1 path: src/PhpDoc/PhpDocBlock.php - - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV…' will always evaluate to true\\.$#" + message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getParamOutTypeTagV…'' will always evaluate to true\.$#' count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getSelfOutTypeTagVa…' will always evaluate to true\\.$#" + message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getSelfOutTypeTagVa…'' will always evaluate to true\.$#' count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: "#^Method PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock\\:\\:getNameScope\\(\\) should return PHPStan\\\\Analyser\\\\NameScope but returns PHPStan\\\\Analyser\\\\NameScope\\|null\\.$#" + message: '#^Method PHPStan\\PhpDoc\\ResolvedPhpDocBlock\:\:getNameScope\(\) should return PHPStan\\Analyser\\NameScope but returns PHPStan\\Analyser\\NameScope\|null\.$#' count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 2 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\Identifier\\\\Exception\\\\InvalidIdentifierName is never thrown in the try block\\.$#" + message: '#^Dead catch \- PHPStan\\BetterReflection\\Identifier\\Exception\\InvalidIdentifierName is never thrown in the try block\.$#' count: 3 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\NodeCompiler\\\\Exception\\\\UnableToCompileNode is never thrown in the try block\\.$#" + message: '#^Dead catch \- PHPStan\\BetterReflection\\NodeCompiler\\Exception\\UnableToCompileNode is never thrown in the try block\.$#' count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Method PHPStan\\\\Reflection\\\\BetterReflection\\\\SourceLocator\\\\FileReadTrapStreamWrapper\\:\\:invokeWithRealFileStreamWrapper\\(\\) has parameter \\$cb with no signature specified for callable\\.$#" + message: '#^Method PHPStan\\Reflection\\BetterReflection\\SourceLocator\\FileReadTrapStreamWrapper\:\:invokeWithRealFileStreamWrapper\(\) has parameter \$cb with no signature specified for callable\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\ClassLike\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Function_ given\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 3 path: src/Reflection/ClassReflection.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Reflection/ClassReflection.php - - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Method PHPStan\\Reflection\\ClassReflection\:\:getCacheKey\(\) should return string but returns string\|null\.$#' count: 1 path: src/Reflection/ClassReflection.php - - message: "#^Binary operation \"&\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "&" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\*\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\*" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\+\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\+" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\-\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\-" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\^" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\|\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\|" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 22 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 3 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 10 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int\\|null is not subtype of type int\\|null\\.$#" + message: '#^PHPDoc tag @var with type float\|int\|null is not subtype of type int\|null\.$#' count: 6 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 2 path: src/Rules/Classes/RequireExtendsRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Rules/Classes/RequireImplementsRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 6 path: src/Rules/Comparison/BooleanAndConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/BooleanNotConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/ConstantLooseComparisonRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/DoWhileLoopConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/ElseIfConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/IfConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 3 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 4 path: src/Rules/Comparison/LogicalXorConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 4 path: src/Rules/Comparison/MatchExpressionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Rules/Comparison/TernaryOperatorConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php - - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Method PHPStan\\\\Rules\\\\DirectRegistry\\:\\:__construct\\(\\) has parameter \\$rules with generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Rules\\DirectRegistry\:\:__construct\(\) has parameter \$rules with generic interface PHPStan\\Rules\\Rule but does not specify its types\: TNodeType$#' count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 1 path: src/Rules/Generics/GenericAncestorsCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Rules/Generics/TemplateTypeCheck.php - - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Method PHPStan\\\\Rules\\\\LazyRegistry\\:\\:getRulesFromContainer\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Rules\\LazyRegistry\:\:getRulesFromContainer\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Rules/PhpDoc/RequireExtendsCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 1 path: src/Rules/PhpDoc/VarTagTypeRuleHelper.php - - message: "#^Access to an undefined property T of PHPStan\\\\Rules\\\\RuleError\\:\\:\\$tip\\.$#" + message: '#^Access to an undefined property T of PHPStan\\Rules\\RuleError\:\:\$tip\.$#' count: 2 path: src/Rules/RuleErrorBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Rules/RuleLevelHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Rules/UnusedFunctionParametersCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: "#^Anonymous function has an unused use \\$container\\.$#" + message: '#^Anonymous function has an unused use \$container\.$#' count: 1 path: src/Testing/PHPStanTestCase.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Testing/TypeInferenceTestCase.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryArrayListType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryLiteralStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/HasMethodType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/HasOffsetType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/HasPropertyType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/NonEmptyArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Accessory/OversizedArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 3 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 2 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' count: 2 path: src/Type/BooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Type/BooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 4 path: src/Type/CallableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/CallableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/ClosureType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 7 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType\\|PHPStan\\\\Type\\\\Constant\\\\ConstantStringType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\Constant\\ConstantIntegerType\|PHPStan\\Type\\Constant\\ConstantStringType but it''s error\-prone and dangerous\.$#' count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' count: 1 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 3 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/Constant/ConstantFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/Constant/ConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 4 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^PHPDoc tag @var with type int\\|string is not subtype of type string\\.$#" + message: '#^PHPDoc tag @var with type int\|string is not subtype of type string\.$#' count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/Type/Constant/OversizedArrayBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' count: 2 path: src/Type/Enum/EnumCaseObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Type/ExponentiateHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 1 path: src/Type/FileTypeMapper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' count: 2 path: src/Type/FloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' count: 2 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' count: 2 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateBenevolentUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: "#^Method PHPStan\\\\Type\\\\Generic\\\\TemplateConstantIntegerType\\:\\:toPhpDocNode\\(\\) should return PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\ConstTypeNode but returns PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\IdentifierTypeNode\\.$#" + message: '#^Method PHPStan\\Type\\Generic\\TemplateConstantIntegerType\:\:toPhpDocNode\(\) should return PHPStan\\PhpDocParser\\Ast\\Type\\ConstTypeNode but returns PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode\.$#' count: 1 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateGenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\IntersectionType will always evaluate to false\.$#' count: 2 path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateKeyOfType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/Generic/TemplateMixedType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/Generic/TemplateStrictMixedType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/Generic/TemplateUnionType.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\UnionType will always evaluate to false\.$#' count: 2 path: src/Type/Generic/TemplateUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 3 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 2 path: src/Type/IntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' count: 3 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' count: 1 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 2 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 4 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/IterableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 2 path: src/Type/IterableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 3 path: src/Type/NullType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' count: 3 path: src/Type/NullType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 1 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' count: 2 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' count: 1 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' count: 1 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' count: 1 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 6 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' count: 3 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' count: 2 path: src/Type/ObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' count: 4 path: src/Type/ObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 2 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 4 path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 1 path: src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 16 path: src/Type/Php/BcMathStringOrNullReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/DefineConstantTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/DefinedConstantTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' count: 1 path: src/Type/Php/DsMapDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 2 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/IsAFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 2 path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/MethodExistsTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 2 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/Php/RangeFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php - - message: "#^Cannot access offset int\\<0, max\\> on \\(float\\|int\\)\\.$#" + message: '#^Cannot access offset int\<0, max\> on \(float\|int\)\.$#' count: 2 path: src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/StrRepeatFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/Php/StrlenFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' count: 2 path: src/Type/StaticType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' count: 1 path: src/Type/StaticType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 1 path: src/Type/StringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' count: 2 path: src/Type/StringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 5 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 14 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 5 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 8 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType and PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType will always evaluate to true\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Constant\\ConstantIntegerType and PHPStan\\Type\\Constant\\ConstantIntegerType will always evaluate to true\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Result of \\|\\| is always true\\.$#" + message: '#^Result of \|\| is always true\.$#' count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 2 path: src/Type/TypeUtils.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 3 path: src/Type/TypeUtils.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' count: 3 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' count: 3 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 1 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 2 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' count: 1 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' count: 2 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' count: 1 path: src/Type/UnionType.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\BooleanType but it''s error\-prone and dangerous\.$#' count: 1 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' count: 3 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' count: 4 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\VoidType is error\\-prone and deprecated\\. Use Type\\:\\:isVoid\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\VoidType is error\-prone and deprecated\. Use Type\:\:isVoid\(\) instead\.$#' count: 2 path: src/Type/VoidType.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" + message: '#^Unreachable statement \- code above always terminates\.$#' count: 1 path: tests/PHPStan/Analyser/AnalyserTest.php - - message: "#^Class PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Analyser\\AnonymousClassNameRuleTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: "#^Method PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Analyser\\AnonymousClassNameRuleTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: "#^Class PHPStan\\\\Analyser\\\\EvaluationOrderTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Analyser\\EvaluationOrderTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: "#^Method PHPStan\\\\Analyser\\\\EvaluationOrderTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Analyser\\EvaluationOrderTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: "#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\\.$#" + message: '#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\.$#' count: 1 path: tests/PHPStan/Command/AnalyseCommandTest.php - - message: "#^Class PHPStan\\\\Node\\\\FileNodeTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Node\\FileNodeTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: "#^Method PHPStan\\\\Node\\\\FileNodeTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Node\\FileNodeTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: "#^PHPDoc tag @var with type string is not subtype of type class\\-string\\.$#" + message: '#^PHPDoc tag @var with type string is not subtype of type class\-string\.$#' count: 1 path: tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' count: 1 path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#' count: 1 path: tests/PHPStan/Type/IterableTypeTest.php From 977f2b4999ccde42372589124aae3b85b63184d0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 10:21:37 +0200 Subject: [PATCH 0532/3097] [BCB] Remove `FunctionReflection::isFinal()` --- UPGRADING.md | 1 + src/Analyser/MutatingScope.php | 2 -- src/Analyser/NodeScopeResolver.php | 3 +-- src/Reflection/FunctionReflection.php | 2 -- .../Native/NativeFunctionReflection.php | 5 ----- src/Reflection/Php/ExitFunctionReflection.php | 5 ----- .../PhpFunctionFromParserNodeReflection.php | 19 ------------------- src/Reflection/Php/PhpFunctionReflection.php | 5 ----- .../Php/PhpMethodFromParserNodeReflection.php | 15 ++++++++++++--- .../Annotations/FinalAnnotationsTest.php | 11 ----------- .../Annotations/data/annotations-final.php | 13 ------------- .../ReflectionProviderGoldenTest.php | 2 +- 12 files changed, 15 insertions(+), 68 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 378e11d2ca0..c3bf1e89956 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -277,3 +277,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer int * Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead * `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` +* Remove `FunctionReflection::isFinal()` diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 99cd3e3c5b7..4317d5945aa 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3075,7 +3075,6 @@ public function enterFunction( ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal, - bool $isFinal, ?bool $isPure = null, bool $acceptsNamedArguments = true, ?Assertions $asserts = null, @@ -3099,7 +3098,6 @@ public function enterFunction( $deprecatedDescription, $isDeprecated, $isInternal, - $isFinal, $isPure, $acceptsNamedArguments, $asserts ?? Assertions::createEmpty(), diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f2e43632a60..74f2c58c71e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -513,7 +513,7 @@ private function processStmtNode( $throwPoints = []; $impurePoints = []; $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); - [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); + [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, , $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt); foreach ($stmt->params as $param) { $this->processParamNode($stmt, $param, $scope, $nodeCallback); @@ -532,7 +532,6 @@ private function processStmtNode( $deprecatedDescription, $isDeprecated, $isInternal, - $isFinal, $isPure, $acceptsNamedArguments, $asserts, diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 66ef61928f6..09232a4d8a1 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -34,8 +34,6 @@ public function isDeprecated(): TrinaryLogic; public function getDeprecatedDescription(): ?string; - public function isFinal(): TrinaryLogic; - public function isInternal(): TrinaryLogic; public function getThrowType(): ?Type; diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 7a7e137d9c7..529bd5fbbfe 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -88,11 +88,6 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - public function hasSideEffects(): TrinaryLogic { if ($this->isVoid()) { diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index 85e62106993..331105aceba 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -97,11 +97,6 @@ public function getDeprecatedDescription(): ?string return null; } - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - public function isInternal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 5710ce56a01..227b23ddab3 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -58,7 +58,6 @@ public function __construct( private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, - private bool $isFinal, protected ?bool $isPure, private bool $acceptsNamedArguments, private Assertions $assertions, @@ -235,24 +234,6 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->isInternal); } - public function isFinal(): TrinaryLogic - { - $finalMethod = false; - if ($this->functionLike instanceof ClassMethod) { - $finalMethod = $this->functionLike->isFinal(); - } - return TrinaryLogic::createFromBoolean($finalMethod || $this->isFinal); - } - - public function isFinalByKeyword(): TrinaryLogic - { - $finalMethod = false; - if ($this->functionLike instanceof ClassMethod) { - $finalMethod = $this->functionLike->isFinal(); - } - return TrinaryLogic::createFromBoolean($finalMethod); - } - public function getThrowType(): ?Type { return $this->throwType; diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 5bdbd199be6..6aba725f1cd 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -249,11 +249,6 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->isInternal); } - public function isFinal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isFinal); - } - public function getThrowType(): ?Type { return $this->phpDocThrowType; diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 329ad1de61d..8cd703c8b22 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -38,7 +38,7 @@ final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeR */ public function __construct( private ClassReflection $declaringClass, - ClassMethod $classMethod, + private ClassMethod $classMethod, string $fileName, TemplateTypeMap $templateTypeMap, array $realParameterTypes, @@ -50,7 +50,7 @@ public function __construct( ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal, - bool $isFinal, + private bool $isFinal, ?bool $isPure, bool $acceptsNamedArguments, Assertions $assertions, @@ -107,7 +107,6 @@ public function __construct( $deprecatedDescription, $isDeprecated, $isInternal, - $isFinal || $classMethod->isFinal(), $isPure, $acceptsNamedArguments, $assertions, @@ -154,6 +153,16 @@ public function isPublic(): bool return $this->getClassMethod()->isPublic(); } + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->classMethod->isFinal() || $this->isFinal); + } + + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->classMethod->isFinal()); + } + public function isBuiltin(): bool { return false; diff --git a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php index fb61a5c90e0..6df44ad440f 100644 --- a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php @@ -4,7 +4,6 @@ use FinalAnnotations\FinalFoo; use FinalAnnotations\Foo; -use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Testing\PHPStanTestCase; @@ -58,14 +57,4 @@ public function testFinalAnnotations(bool $final, string $className, array $fina } } - public function testFinalUserFunctions(): void - { - require_once __DIR__ . '/data/annotations-final.php'; - - $reflectionProvider = $this->createReflectionProvider(); - - $this->assertFalse($reflectionProvider->getFunction(new Name\FullyQualified('FinalAnnotations\foo'), null)->isFinal()->yes()); - $this->assertTrue($reflectionProvider->getFunction(new Name\FullyQualified('FinalAnnotations\finalFoo'), null)->isFinal()->yes()); - } - } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-final.php b/tests/PHPStan/Reflection/Annotations/data/annotations-final.php index cb3932500fc..f7c7c2da25a 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-final.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-final.php @@ -2,19 +2,6 @@ namespace FinalAnnotations; -function foo() -{ - -} - -/** - * @final - */ -function finalFoo() -{ - -} - class Foo { diff --git a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php index d7b8ee45990..bfafb5996e3 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php @@ -341,7 +341,7 @@ private static function generateFunctionMethodBaseDescription($reflection): stri $result .= 'Is deprecated: ' . $reflection->isDeprecated()->describe() . "\n"; } - if (! $reflection->isFinal()->no()) { + if ($reflection instanceof MethodReflection && ! $reflection->isFinal()->no()) { $result .= 'Is final: ' . $reflection->isFinal()->describe() . "\n"; } From ce1f2bfdf99910921ae315df5e42b6459994e44b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 10:46:46 +0200 Subject: [PATCH 0533/3097] min/max fix --- src/Type/Php/MinMaxFunctionReturnTypeExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index d6e1361dab2..c10150b7482 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -160,6 +160,10 @@ private function processType( } $compareResult = $this->compareTypes($resultType, $type); + if ($compareResult === null) { + return TypeCombinator::union(...$types); + } + if ($functionName === 'min') { if ($compareResult === $type) { $resultType = $type; From 96d74b1973240baec2676432fd3b568a5a09510e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 11:11:06 +0200 Subject: [PATCH 0534/3097] Revert "Remove nette/neon patch for backward compatibility with old multi-line string encoding" This reverts commit b1ea97a4da3751ff0206e5491d1790b732e08a74. --- composer.json | 1 + composer.lock | 2 +- patches/NetteNeonStringNode.patch | 40 ++ phpstan-baseline.neon | 642 +++++++++++++++--------------- 4 files changed, 363 insertions(+), 322 deletions(-) create mode 100644 patches/NetteNeonStringNode.patch diff --git a/composer.json b/composer.json index a97d4011045..e7d2b401bb2 100644 --- a/composer.json +++ b/composer.json @@ -119,6 +119,7 @@ "patches/DependencyChecker.patch" ], "nette/neon": [ + "patches/NetteNeonStringNode.patch", "patches/NeonParser.patch" ] } diff --git a/composer.lock b/composer.lock index f836c7ee65e..2bf4e1b5f2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3a133a74db439ecb38147f64988135c9", + "content-hash": "3745d8358f113e8d1a7c6b1066a9db0a", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/NetteNeonStringNode.patch b/patches/NetteNeonStringNode.patch new file mode 100644 index 00000000000..ff7332693fa --- /dev/null +++ b/patches/NetteNeonStringNode.patch @@ -0,0 +1,40 @@ +--- src/Neon/Node/StringNode.php 2022-03-10 03:04:26 ++++ src/Neon/Node/StringNode.php 2024-08-26 14:53:45 +@@ -79,27 +79,18 @@ + + public function toString(): string + { +- if (strpos($this->value, "\n") === false) { +- return "'" . str_replace("'", "''", $this->value) . "'"; ++ $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ++ if ($res === false) { ++ throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); ++ } + +- } elseif (preg_match('~\n[\t ]+\'{3}~', $this->value)) { +- $s = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +- $s = preg_replace_callback( +- '#[^\\\\]|\\\\(.)#s', +- function ($m) { +- return ['n' => "\n", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; +- }, +- substr($s, 1, -1) +- ); +- $s = str_replace('"""', '""\"', $s); +- $delim = '"""'; +- +- } else { +- $s = $this->value; +- $delim = "'''"; ++ if (strpos($this->value, "\n") !== false) { ++ $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { ++ return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; ++ }, $res); ++ $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; + } + +- $s = preg_replace('#^(?=.)#m', "\t", $s); +- return $delim . "\n" . $s . "\n" . $delim; ++ return $res; + } + } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0f3487007a2..23d822be892 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,1606 +1,1606 @@ parameters: ignoreErrors: - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php - - message: '#^Method PHPStan\\Analyser\\AnalyserResultFinalizer\:\:finalize\(\) throws checked exception Throwable but it''s missing from the PHPDoc @throws tag\.$#' + message: "#^Method PHPStan\\\\Analyser\\\\AnalyserResultFinalizer\\:\\:finalize\\(\\) throws checked exception Throwable but it's missing from the PHPDoc @throws tag\\.$#" count: 1 path: src/Analyser/AnalyserResultFinalizer.php - - message: '#^Cannot assign offset ''realCount'' to array\|string\.$#' + message: "#^Cannot assign offset 'realCount' to array\\|string\\.$#" count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - - message: '#^Casting to string something that''s already string\.$#' + message: "#^Casting to string something that's already string\\.$#" count: 3 path: src/Analyser/MutatingScope.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 4 path: src/Analyser/MutatingScope.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 3 path: src/Analyser/MutatingScope.php - - message: '#^Only numeric types are allowed in pre\-increment, float\|int\|string\|null given\.$#' + message: "#^Only numeric types are allowed in pre\\-increment, float\\|int\\|string\\|null given\\.$#" count: 1 path: src/Analyser/MutatingScope.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 3 path: src/Analyser/NodeScopeResolver.php - - message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Analyser/NodeScopeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Analyser/TypeSpecifier.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 5 path: src/Analyser/TypeSpecifier.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 3 path: src/Analyser/TypeSpecifier.php - - message: '#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\Collectors\\Collector\:\:processNode\(\)\.$#' + message: "#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\\\Collectors\\\\Collector\\:\\:processNode\\(\\)\\.$#" count: 1 path: src/Collectors/Collector.php - - message: '#^Method PHPStan\\Collectors\\Registry\:\:__construct\(\) has parameter \$collectors with generic interface PHPStan\\Collectors\\Collector but does not specify its types\: TNodeType, TValue$#' + message: "#^Method PHPStan\\\\Collectors\\\\Registry\\:\\:__construct\\(\\) has parameter \\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector but does not specify its types\\: TNodeType, TValue$#" count: 1 path: src/Collectors/Registry.php - - message: '#^Property PHPStan\\Collectors\\Registry\:\:\$cache with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' + message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$cache with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" count: 1 path: src/Collectors/Registry.php - - message: '#^Property PHPStan\\Collectors\\Registry\:\:\$collectors with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' + message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" count: 1 path: src/Collectors/Registry.php - - message: '#^Anonymous function has an unused use \$container\.$#' + message: "#^Anonymous function has an unused use \\$container\\.$#" count: 1 path: src/Command/CommandHelper.php - - message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' + message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" count: 1 path: src/Command/CommandHelper.php - - message: '#^Static property PHPStan\\Command\\CommandHelper\:\:\$reservedMemory is never read, only written\.$#' + message: "#^Static property PHPStan\\\\Command\\\\CommandHelper\\:\\:\\$reservedMemory is never read, only written\\.$#" count: 1 path: src/Command/CommandHelper.php - - message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' + message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' + message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' + message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' + message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: '#^Variable method call on Nette\\Schema\\Elements\\AnyOf\|Nette\\Schema\\Elements\\Structure\|Nette\\Schema\\Elements\\Type\.$#' + message: "#^Variable method call on Nette\\\\Schema\\\\Elements\\\\AnyOf\\|Nette\\\\Schema\\\\Elements\\\\Structure\\|Nette\\\\Schema\\\\Elements\\\\Type\\.$#" count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: '#^Variable static method call on Nette\\Schema\\Expect\.$#' + message: "#^Variable static method call on Nette\\\\Schema\\\\Expect\\.$#" count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: '#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\DI\\Config\\Helpers\.$#' + message: "#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\\\DI\\\\Config\\\\Helpers\\.$#" count: 1 path: src/DependencyInjection/NeonAdapter.php - - message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' + message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: '#^Variable method call on PHPStan\\Reflection\\ClassReflection\.$#' + message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" count: 2 path: src/PhpDoc/PhpDocBlock.php - - message: '#^Variable static method call on PHPStan\\PhpDoc\\PhpDocBlock\.$#' + message: "#^Variable static method call on PHPStan\\\\PhpDoc\\\\PhpDocBlock\\.$#" count: 1 path: src/PhpDoc/PhpDocBlock.php - - message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getParamOutTypeTagV…'' will always evaluate to true\.$#' + message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV…' will always evaluate to true\\.$#" count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getSelfOutTypeTagVa…'' will always evaluate to true\.$#' + message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getSelfOutTypeTagVa…' will always evaluate to true\\.$#" count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: '#^Method PHPStan\\PhpDoc\\ResolvedPhpDocBlock\:\:getNameScope\(\) should return PHPStan\\Analyser\\NameScope but returns PHPStan\\Analyser\\NameScope\|null\.$#' + message: "#^Method PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock\\:\\:getNameScope\\(\\) should return PHPStan\\\\Analyser\\\\NameScope but returns PHPStan\\\\Analyser\\\\NameScope\\|null\\.$#" count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 2 path: src/PhpDoc/TypeNodeResolver.php - - message: '#^Dead catch \- PHPStan\\BetterReflection\\Identifier\\Exception\\InvalidIdentifierName is never thrown in the try block\.$#' + message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\Identifier\\\\Exception\\\\InvalidIdentifierName is never thrown in the try block\\.$#" count: 3 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: '#^Dead catch \- PHPStan\\BetterReflection\\NodeCompiler\\Exception\\UnableToCompileNode is never thrown in the try block\.$#' + message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\NodeCompiler\\\\Exception\\\\UnableToCompileNode is never thrown in the try block\\.$#" count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: '#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: '#^Method PHPStan\\Reflection\\BetterReflection\\SourceLocator\\FileReadTrapStreamWrapper\:\:invokeWithRealFileStreamWrapper\(\) has parameter \$cb with no signature specified for callable\.$#' + message: "#^Method PHPStan\\\\Reflection\\\\BetterReflection\\\\SourceLocator\\\\FileReadTrapStreamWrapper\\:\\:invokeWithRealFileStreamWrapper\\(\\) has parameter \\$cb with no signature specified for callable\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\ClassLike\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Function_ given\.$#' + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' + message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php - - message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php - - message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php - - message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 3 path: src/Reflection/ClassReflection.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Reflection/ClassReflection.php - - message: '#^Method PHPStan\\Reflection\\ClassReflection\:\:getCacheKey\(\) should return string but returns string\|null\.$#' + message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#" count: 1 path: src/Reflection/ClassReflection.php - - message: '#^Binary operation "&" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"&\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Binary operation "\*" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"\\*\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Binary operation "\+" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"\\+\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Binary operation "\-" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"\\-\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Binary operation "\^" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Binary operation "\|" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' + message: "#^Binary operation \"\\|\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 22 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 3 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 10 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' + message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' + message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^PHPDoc tag @var with type float\|int\|null is not subtype of type int\|null\.$#' + message: "#^PHPDoc tag @var with type float\\|int\\|null is not subtype of type int\\|null\\.$#" count: 6 path: src/Reflection/InitializerExprTypeResolver.php - - message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 2 path: src/Rules/Classes/RequireExtendsRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Rules/Classes/RequireImplementsRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 6 path: src/Rules/Comparison/BooleanAndConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/BooleanNotConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/ConstantLooseComparisonRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/DoWhileLoopConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/ElseIfConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/IfConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 3 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 4 path: src/Rules/Comparison/LogicalXorConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 4 path: src/Rules/Comparison/MatchExpressionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Rules/Comparison/TernaryOperatorConstantConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php - - message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Rules/DirectRegistry.php - - message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Rules/DirectRegistry.php - - message: '#^Method PHPStan\\Rules\\DirectRegistry\:\:__construct\(\) has parameter \$rules with generic interface PHPStan\\Rules\\Rule but does not specify its types\: TNodeType$#' + message: "#^Method PHPStan\\\\Rules\\\\DirectRegistry\\:\\:__construct\\(\\) has parameter \\$rules with generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/DirectRegistry.php - - message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/DirectRegistry.php - - message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/DirectRegistry.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 1 path: src/Rules/Generics/GenericAncestorsCheck.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Rules/Generics/TemplateTypeCheck.php - - message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Rules/LazyRegistry.php - - message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" count: 1 path: src/Rules/LazyRegistry.php - - message: '#^Method PHPStan\\Rules\\LazyRegistry\:\:getRulesFromContainer\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Method PHPStan\\\\Rules\\\\LazyRegistry\\:\\:getRulesFromContainer\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/LazyRegistry.php - - message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/LazyRegistry.php - - message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: src/Rules/LazyRegistry.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Rules/PhpDoc/RequireExtendsCheck.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 1 path: src/Rules/PhpDoc/VarTagTypeRuleHelper.php - - message: '#^Access to an undefined property T of PHPStan\\Rules\\RuleError\:\:\$tip\.$#' + message: "#^Access to an undefined property T of PHPStan\\\\Rules\\\\RuleError\\:\\:\\$tip\\.$#" count: 2 path: src/Rules/RuleErrorBuilder.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Rules/RuleLevelHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Rules/UnusedFunctionParametersCheck.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: '#^Anonymous function has an unused use \$container\.$#' + message: "#^Anonymous function has an unused use \\$container\\.$#" count: 1 path: src/Testing/PHPStanTestCase.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Testing/TypeInferenceTestCase.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryArrayListType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryLiteralStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/HasMethodType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/HasOffsetType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/HasOffsetValueType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/HasPropertyType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/NonEmptyArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Accessory/OversizedArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 3 path: src/Type/ArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 path: src/Type/ArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/ArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/ArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 2 path: src/Type/BooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Type/BooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 4 path: src/Type/CallableType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/CallableType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/ClosureType.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 7 path: src/Type/Constant/ConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\Constant\\ConstantIntegerType\|PHPStan\\Type\\Constant\\ConstantStringType but it''s error\-prone and dangerous\.$#' + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType\\|PHPStan\\\\Type\\\\Constant\\\\ConstantStringType but it's error\\-prone and dangerous\\.$#" count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' + message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' + message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" count: 1 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantBooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/Constant/ConstantBooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 3 path: src/Type/Constant/ConstantBooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/Constant/ConstantFloatType.php - - message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantFloatType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/Constant/ConstantIntegerType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantIntegerType.php - - message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/Constant/ConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 4 path: src/Type/Constant/ConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/ConstantStringType.php - - message: '#^PHPDoc tag @var with type int\|string is not subtype of type string\.$#' + message: "#^PHPDoc tag @var with type int\\|string is not subtype of type string\\.$#" count: 1 path: src/Type/Constant/ConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/Type/Constant/OversizedArrayBuilder.php - - message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" count: 2 path: src/Type/Enum/EnumCaseObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Type/ExponentiateHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/FileTypeMapper.php - - message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" count: 2 path: src/Type/FloatType.php - - message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" count: 2 path: src/Type/Generic/GenericClassStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/GenericObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Generic/GenericObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/GenericObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" count: 2 path: src/Type/Generic/GenericObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateBenevolentUnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateBooleanType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateConstantArrayType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: '#^Method PHPStan\\Type\\Generic\\TemplateConstantIntegerType\:\:toPhpDocNode\(\) should return PHPStan\\PhpDocParser\\Ast\\Type\\ConstTypeNode but returns PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode\.$#' + message: "#^Method PHPStan\\\\Type\\\\Generic\\\\TemplateConstantIntegerType\\:\\:toPhpDocNode\\(\\) should return PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\ConstTypeNode but returns PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\IdentifierTypeNode\\.$#" count: 1 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateConstantStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateFloatType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateGenericObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateIntegerType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateIntersectionType.php - - message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\IntersectionType will always evaluate to false\.$#' + message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" count: 2 path: src/Type/Generic/TemplateIntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateKeyOfType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/Generic/TemplateMixedType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateObjectShapeType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateObjectWithoutClassType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/Generic/TemplateStrictMixedType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateStringType.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/Generic/TemplateUnionType.php - - message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\UnionType will always evaluate to false\.$#' + message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" count: 2 path: src/Type/Generic/TemplateUnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Type/IntegerRangeType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 3 path: src/Type/IntegerRangeType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/IntegerRangeType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 2 path: src/Type/IntegerType.php - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" count: 3 path: src/Type/IntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 1 path: src/Type/IntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 2 path: src/Type/IntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 4 path: src/Type/IntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/IterableType.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 2 path: src/Type/IterableType.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 3 path: src/Type/NullType.php - - message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" count: 3 path: src/Type/NullType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/ObjectShapeType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" count: 2 path: src/Type/ObjectShapeType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 1 path: src/Type/ObjectShapeType.php - - message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" count: 1 path: src/Type/ObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" count: 1 path: src/Type/ObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 6 path: src/Type/ObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 3 path: src/Type/ObjectType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" count: 2 path: src/Type/ObjectWithoutClassType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 4 path: src/Type/ObjectWithoutClassType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 4 path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 16 path: src/Type/Php/BcMathStringOrNullReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/DefineConstantTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/DefinedConstantTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" count: 1 path: src/Type/Php/DsMapDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/IsAFunctionTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 2 path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/MethodExistsTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/Php/RangeFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php - - message: '#^Cannot access offset int\<0, max\> on \(float\|int\)\.$#' + message: "#^Cannot access offset int\\<0, max\\> on \\(float\\|int\\)\\.$#" count: 2 path: src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/StrRepeatFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/Php/StrlenFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" count: 2 path: src/Type/StaticType.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 1 path: src/Type/StaticType.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 path: src/Type/StringType.php - - message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" count: 2 path: src/Type/StringType.php - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 5 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 14 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 5 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 2 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 8 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" count: 2 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" count: 2 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Instanceof between PHPStan\\Type\\Constant\\ConstantIntegerType and PHPStan\\Type\\Constant\\ConstantIntegerType will always evaluate to true\.$#' + message: "#^Instanceof between PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType and PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType will always evaluate to true\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Result of \|\| is always true\.$#' + message: "#^Result of \\|\\| is always true\\.$#" count: 1 path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 2 path: src/Type/TypeUtils.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 path: src/Type/TypeUtils.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" count: 3 path: src/Type/TypehintHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" count: 3 path: src/Type/TypehintHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 path: src/Type/TypehintHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 2 path: src/Type/UnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" count: 1 path: src/Type/UnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 2 path: src/Type/UnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 path: src/Type/UnionType.php - - message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\BooleanType but it''s error\-prone and dangerous\.$#' + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#" count: 1 path: src/Type/UnionType.php - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" count: 3 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" count: 2 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 4 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 2 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" count: 2 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" count: 2 path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\VoidType is error\-prone and deprecated\. Use Type\:\:isVoid\(\) instead\.$#' + message: "#^Doing instanceof PHPStan\\\\Type\\\\VoidType is error\\-prone and deprecated\\. Use Type\\:\\:isVoid\\(\\) instead\\.$#" count: 2 path: src/Type/VoidType.php - - message: '#^Unreachable statement \- code above always terminates\.$#' + message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 path: tests/PHPStan/Analyser/AnalyserTest.php - - message: '#^Class PHPStan\\Analyser\\AnonymousClassNameRuleTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' + message: "#^Class PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: '#^Method PHPStan\\Analyser\\AnonymousClassNameRuleTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Method PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: '#^Class PHPStan\\Analyser\\EvaluationOrderTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' + message: "#^Class PHPStan\\\\Analyser\\\\EvaluationOrderTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: '#^Method PHPStan\\Analyser\\EvaluationOrderTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Method PHPStan\\\\Analyser\\\\EvaluationOrderTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: '#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\.$#' + message: "#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\\.$#" count: 1 path: tests/PHPStan/Command/AnalyseCommandTest.php - - message: '#^Class PHPStan\\Node\\FileNodeTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' + message: "#^Class PHPStan\\\\Node\\\\FileNodeTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: '#^Method PHPStan\\Node\\FileNodeTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' + message: "#^Method PHPStan\\\\Node\\\\FileNodeTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: '#^PHPDoc tag @var with type string is not subtype of type class\-string\.$#' + message: "#^PHPDoc tag @var with type string is not subtype of type class\\-string\\.$#" count: 1 path: tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php - - message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php - - message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' + message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - - message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#' + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" count: 1 path: tests/PHPStan/Type/IterableTypeTest.php From ec6fbe2cab1a1687959b3bbaca4c77bb93c30d7c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:29:42 +0200 Subject: [PATCH 0535/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- conf/config.neon | 21 --------------------- 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index e7d2b401bb2..1ebc818e9ff 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.2.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.42.0.8", + "ondrejmirtes/better-reflection": "6.42.0.9", "phpstan/php-8-stubs": "0.3.111", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 2bf4e1b5f2b..184b4f03206 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3745d8358f113e8d1a7c6b1066a9db0a", + "content-hash": "25426f4d76b14416d019ce2466d96971", "packages": [ { "name": "clue/ndjson-react", @@ -2179,16 +2179,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.42.0.8", + "version": "6.42.0.9", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "00d3413be6f1f9c012b935fbf7aec900bcdde4db" + "reference": "28d0aa833b53a038c6e10480770b17fb643323b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/00d3413be6f1f9c012b935fbf7aec900bcdde4db", - "reference": "00d3413be6f1f9c012b935fbf7aec900bcdde4db", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/28d0aa833b53a038c6e10480770b17fb643323b1", + "reference": "28d0aa833b53a038c6e10480770b17fb643323b1", "shasum": "" }, "require": { @@ -2244,9 +2244,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.8" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.9" }, - "time": "2024-09-10T09:57:34+00:00" + "time": "2024-09-30T10:27:49+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/conf/config.neon b/conf/config.neon index 5dc867fc1be..3d05cf2669f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1971,27 +1971,6 @@ services: reflector: @originalBetterReflectionReflector autowired: false - # deprecated - betterReflectionClassReflector: - class: PHPStan\BetterReflection\Reflector\ClassReflector - arguments: - sourceLocator: @betterReflectionSourceLocator - autowired: false - - # deprecated - betterReflectionFunctionReflector: - class: PHPStan\BetterReflection\Reflector\FunctionReflector - arguments: - sourceLocator: @betterReflectionSourceLocator - autowired: false - - # deprecated - betterReflectionConstantReflector: - class: PHPStan\BetterReflection\Reflector\ConstantReflector - arguments: - sourceLocator: @betterReflectionSourceLocator - autowired: false - nodeScopeResolverReflector: factory: @betterReflectionReflector autowired: false From 392f090066bfc9946b4ad524ffecf3d420c23114 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:35:15 +0200 Subject: [PATCH 0536/3097] These can be a native return type thanks to PHP 7.4 return type covariance --- src/Dependency/ExportedNode/ExportedAttributeNode.php | 6 ++---- .../ExportedNode/ExportedClassConstantNode.php | 6 ++---- .../ExportedNode/ExportedClassConstantsNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedClassNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedEnumCaseNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedEnumNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedFunctionNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedInterfaceNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedMethodNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedParameterNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedPhpDocNode.php | 6 ++---- .../ExportedNode/ExportedPropertiesNode.php | 6 ++---- src/Dependency/ExportedNode/ExportedTraitNode.php | 6 ++---- .../ExportedNode/ExportedTraitUseAdaptation.php | 6 ++---- src/PhpDoc/Tag/ParamClosureThisTag.php | 5 +---- src/PhpDoc/Tag/ParamOutTag.php | 5 +---- src/PhpDoc/Tag/ParamTag.php | 5 +---- src/PhpDoc/Tag/ReturnTag.php | 5 +---- src/PhpDoc/Tag/SelfOutTypeTag.php | 5 +---- src/PhpDoc/Tag/VarTag.php | 5 +---- .../AnnotationsMethodsClassReflectionExtension.php | 6 +----- .../AnnotationsPropertiesClassReflectionExtension.php | 6 +----- src/Reflection/Php/PhpClassReflectionExtension.php | 11 ++--------- .../RequireExtendsMethodsClassReflectionExtension.php | 11 ++--------- ...quireExtendsPropertiesClassReflectionExtension.php | 6 +----- src/Type/ObjectType.php | 5 +---- src/Type/Type.php | 1 - 27 files changed, 42 insertions(+), 118 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedAttributeNode.php b/src/Dependency/ExportedNode/ExportedAttributeNode.php index 5612e74ea10..f7d00465add 100644 --- a/src/Dependency/ExportedNode/ExportedAttributeNode.php +++ b/src/Dependency/ExportedNode/ExportedAttributeNode.php @@ -45,9 +45,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -72,9 +71,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index 8827656c48b..2aec4d44fa8 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -45,9 +45,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -58,9 +57,8 @@ public static function __set_state(array $properties): ExportedNode /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedClassConstantsNode.php b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php index 23ebd61ae0a..e555874fc77 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantsNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php @@ -54,9 +54,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['constants'], @@ -69,9 +68,8 @@ public static function __set_state(array $properties): ExportedNode /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( array_map(static function (array $constantData): ExportedClassConstantNode { diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index b420a0a9ada..cb57bddb4c3 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -96,9 +96,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -139,9 +138,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedEnumCaseNode.php b/src/Dependency/ExportedNode/ExportedEnumCaseNode.php index 19f8f6a22bd..65b32733152 100644 --- a/src/Dependency/ExportedNode/ExportedEnumCaseNode.php +++ b/src/Dependency/ExportedNode/ExportedEnumCaseNode.php @@ -37,9 +37,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -50,9 +49,8 @@ public static function __set_state(array $properties): ExportedNode /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedEnumNode.php b/src/Dependency/ExportedNode/ExportedEnumNode.php index ea5ce3970bc..b703518ab9b 100644 --- a/src/Dependency/ExportedNode/ExportedEnumNode.php +++ b/src/Dependency/ExportedNode/ExportedEnumNode.php @@ -76,9 +76,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -111,9 +110,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedFunctionNode.php b/src/Dependency/ExportedNode/ExportedFunctionNode.php index c7e98d36439..d0ebd50613f 100644 --- a/src/Dependency/ExportedNode/ExportedFunctionNode.php +++ b/src/Dependency/ExportedNode/ExportedFunctionNode.php @@ -74,9 +74,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -109,9 +108,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index ef72841c5e8..0e6d6632db9 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -56,9 +56,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -87,9 +86,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedMethodNode.php b/src/Dependency/ExportedNode/ExportedMethodNode.php index 817482ef933..af2b4255ce2 100644 --- a/src/Dependency/ExportedNode/ExportedMethodNode.php +++ b/src/Dependency/ExportedNode/ExportedMethodNode.php @@ -83,9 +83,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -128,9 +127,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedParameterNode.php b/src/Dependency/ExportedNode/ExportedParameterNode.php index c4a7769e28a..9f4cf02d61e 100644 --- a/src/Dependency/ExportedNode/ExportedParameterNode.php +++ b/src/Dependency/ExportedNode/ExportedParameterNode.php @@ -51,9 +51,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['name'], @@ -86,9 +85,8 @@ public function jsonSerialize() /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['name'], diff --git a/src/Dependency/ExportedNode/ExportedPhpDocNode.php b/src/Dependency/ExportedNode/ExportedPhpDocNode.php index a39cb4ef8a1..9288a58107d 100644 --- a/src/Dependency/ExportedNode/ExportedPhpDocNode.php +++ b/src/Dependency/ExportedNode/ExportedPhpDocNode.php @@ -48,18 +48,16 @@ public function jsonSerialize() /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self($properties['phpDocString'], $properties['namespace'], $properties['uses'], $properties['constUses'] ?? []); } /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self($data['phpDocString'], $data['namespace'], $data['uses'], $data['constUses'] ?? []); } diff --git a/src/Dependency/ExportedNode/ExportedPropertiesNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php index de0b4d2b490..af58d51738e 100644 --- a/src/Dependency/ExportedNode/ExportedPropertiesNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -76,9 +76,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['names'], @@ -94,9 +93,8 @@ public static function __set_state(array $properties): ExportedNode /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['names'], diff --git a/src/Dependency/ExportedNode/ExportedTraitNode.php b/src/Dependency/ExportedNode/ExportedTraitNode.php index f0f47ae0219..8f6808f2b39 100644 --- a/src/Dependency/ExportedNode/ExportedTraitNode.php +++ b/src/Dependency/ExportedNode/ExportedTraitNode.php @@ -21,18 +21,16 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self($properties['traitName']); } /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self($data['traitName']); } diff --git a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php index 38e92276247..85c515fac4d 100644 --- a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php +++ b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php @@ -59,9 +59,8 @@ public function equals(ExportedNode $node): bool /** * @param mixed[] $properties - * @return self */ - public static function __set_state(array $properties): ExportedNode + public static function __set_state(array $properties): self { return new self( $properties['traitName'], @@ -74,9 +73,8 @@ public static function __set_state(array $properties): ExportedNode /** * @param mixed[] $data - * @return self */ - public static function decode(array $data): ExportedNode + public static function decode(array $data): self { return new self( $data['traitName'], diff --git a/src/PhpDoc/Tag/ParamClosureThisTag.php b/src/PhpDoc/Tag/ParamClosureThisTag.php index eba2903f214..92a91a4da88 100644 --- a/src/PhpDoc/Tag/ParamClosureThisTag.php +++ b/src/PhpDoc/Tag/ParamClosureThisTag.php @@ -21,10 +21,7 @@ public function getType(): Type return $this->type; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type); } diff --git a/src/PhpDoc/Tag/ParamOutTag.php b/src/PhpDoc/Tag/ParamOutTag.php index 50d289fc871..f720897debd 100644 --- a/src/PhpDoc/Tag/ParamOutTag.php +++ b/src/PhpDoc/Tag/ParamOutTag.php @@ -19,10 +19,7 @@ public function getType(): Type return $this->type; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type); } diff --git a/src/PhpDoc/Tag/ParamTag.php b/src/PhpDoc/Tag/ParamTag.php index 50a3e98cc82..498dd64ce7d 100644 --- a/src/PhpDoc/Tag/ParamTag.php +++ b/src/PhpDoc/Tag/ParamTag.php @@ -27,10 +27,7 @@ public function isVariadic(): bool return $this->isVariadic; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type, $this->isVariadic); } diff --git a/src/PhpDoc/Tag/ReturnTag.php b/src/PhpDoc/Tag/ReturnTag.php index c2354fa3b1a..b501dd67e13 100644 --- a/src/PhpDoc/Tag/ReturnTag.php +++ b/src/PhpDoc/Tag/ReturnTag.php @@ -24,10 +24,7 @@ public function isExplicit(): bool return $this->isExplicit; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type, $this->isExplicit); } diff --git a/src/PhpDoc/Tag/SelfOutTypeTag.php b/src/PhpDoc/Tag/SelfOutTypeTag.php index 63d275cc4c7..10bb054179c 100644 --- a/src/PhpDoc/Tag/SelfOutTypeTag.php +++ b/src/PhpDoc/Tag/SelfOutTypeTag.php @@ -19,10 +19,7 @@ public function getType(): Type return $this->type; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type); } diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index c4d5842474c..85c26f1b6cc 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -19,10 +19,7 @@ public function getType(): Type return $this->type; } - /** - * @return self - */ - public function withType(Type $type): TypedTag + public function withType(Type $type): self { return new self($type); } diff --git a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php index c234e8e2d13..9ad470b0ee7 100644 --- a/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsMethodsClassReflectionExtension.php @@ -5,7 +5,6 @@ use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeHelper; @@ -35,10 +34,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): return isset($this->methods[$classReflection->getCacheKey()][$methodName]); } - /** - * @return ExtendedMethodReflection - */ - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + public function getMethod(ClassReflection $classReflection, string $methodName): ExtendedMethodReflection { return $this->methods[$classReflection->getCacheKey()][$methodName]; } diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index d6d69179d56..5d25367e705 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -5,7 +5,6 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\NeverType; @@ -29,10 +28,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return isset($this->properties[$classReflection->getCacheKey()][$propertyName]); } - /** - * @return ExtendedPropertyReflection - */ - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection { return $this->properties[$classReflection->getCacheKey()][$propertyName]; } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index b7f438737ad..5a3606a66f4 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -29,7 +29,6 @@ use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\FunctionSignature; use PHPStan\Reflection\SignatureMap\ParameterSignature; @@ -154,10 +153,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return $classReflection->getNativeReflection()->hasProperty($propertyName); } - /** - * @return ExtendedPropertyReflection - */ - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection { if (!isset($this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); @@ -376,10 +372,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): return $classReflection->getNativeReflection()->hasMethod($methodName); } - /** - * @return ExtendedMethodReflection - */ - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + public function getMethod(ClassReflection $classReflection, string $methodName): ExtendedMethodReflection { if (isset($this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName])) { return $this->methodsIncludingAnnotations[$classReflection->getCacheKey()][$methodName]; diff --git a/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php index f439f811e90..ee6ada6f6f8 100644 --- a/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsMethodsClassReflectionExtension.php @@ -5,7 +5,6 @@ use PHPStan\Analyser\OutOfClassScope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\ShouldNotHappenException; @@ -17,10 +16,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): return $this->findMethod($classReflection, $methodName) !== null; } - /** - * @return ExtendedMethodReflection - */ - public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection + public function getMethod(ClassReflection $classReflection, string $methodName): ExtendedMethodReflection { $method = $this->findMethod($classReflection, $methodName); if ($method === null) { @@ -30,10 +26,7 @@ public function getMethod(ClassReflection $classReflection, string $methodName): return $method; } - /** - * @return ExtendedMethodReflection|null - */ - private function findMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection + private function findMethod(ClassReflection $classReflection, string $methodName): ?ExtendedMethodReflection { if (!$classReflection->isInterface()) { return null; diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php index 294cc94b620..550a7bee591 100644 --- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php @@ -6,7 +6,6 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\ShouldNotHappenException; final class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension @@ -17,10 +16,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa return $this->findProperty($classReflection, $propertyName) !== null; } - /** - * @return ExtendedPropertyReflection - */ - public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection + public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection { $property = $this->findProperty($classReflection, $propertyName); if ($property === null) { diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0d12a7f5320..6db7a03eec1 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1485,10 +1485,7 @@ public function getClassReflection(): ?ClassReflection return $classReflection; } - /** - * @return self|null - */ - public function getAncestorWithClassName(string $className): ?TypeWithClassName + public function getAncestorWithClassName(string $className): ?self { if ($this->className === $className) { return $this; diff --git a/src/Type/Type.php b/src/Type/Type.php index fec6ead7284..e439af445b9 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -10,7 +10,6 @@ use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; From d1d7d4abcca47bc1355099e2e5c540dabfb68b63 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:37:52 +0200 Subject: [PATCH 0537/3097] [BCB] `Type::getProperty()` now returns `ExtendedPropertyReflection` --- UPGRADING.md | 1 + src/Type/ClosureType.php | 4 ++-- src/Type/Generic/GenericObjectType.php | 4 ++-- src/Type/IntersectionType.php | 4 ++-- src/Type/MixedType.php | 4 ++-- src/Type/NeverType.php | 4 ++-- src/Type/NonexistentParentClassType.php | 4 ++-- src/Type/ObjectShapeType.php | 4 ++-- src/Type/ObjectType.php | 3 ++- src/Type/StaticType.php | 4 ++-- src/Type/StrictMixedType.php | 4 ++-- src/Type/Traits/LateResolvableTypeTrait.php | 4 ++-- src/Type/Traits/MaybeObjectTypeTrait.php | 4 ++-- src/Type/Traits/NonObjectTypeTrait.php | 4 ++-- src/Type/Traits/ObjectTypeTrait.php | 4 ++-- src/Type/Type.php | 5 +---- src/Type/UnionType.php | 4 ++-- 17 files changed, 32 insertions(+), 33 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index c3bf1e89956..a22375dd11a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -278,3 +278,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead * `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` * Remove `FunctionReflection::isFinal()` +* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 708257ea1f5..e09269ad830 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -20,13 +20,13 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\ClosureCallUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Php\DummyParameter; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; @@ -307,7 +307,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return $this->objectType->hasProperty($propertyName); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->objectType->getProperty($propertyName, $scope); } diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 775134aab6b..2747320c75e 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; @@ -228,7 +228,7 @@ public function getClassReflection(): ?ClassReflection ->withVariances($this->variances); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index f7d92dd5548..27e7e623c1a 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -10,8 +10,8 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Reflection\Type\IntersectionTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\IntersectionTypeUnresolvedPropertyPrototypeReflection; @@ -466,7 +466,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasProperty($propertyName)); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index bf9f5bde68d..def0bbd74a3 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -12,7 +12,7 @@ use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; @@ -381,7 +381,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createYes(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 68be84499a4..bb2e9448f2c 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; @@ -141,7 +141,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index bbf9dceec80..3caabeee83f 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; @@ -67,7 +67,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index aeba44bb19b..43b68b8dac5 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -10,9 +10,9 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassMemberAccessAnswerer; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\MissingPropertyFromReflectionException; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; @@ -102,7 +102,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createYes(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 6db7a03eec1..e3711b8d73b 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -23,6 +23,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; @@ -160,7 +161,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 55190656a4f..5ef9ebc6c5d 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -9,7 +9,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; @@ -219,7 +219,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return $this->getStaticObjectType()->hasProperty($propertyName); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 3f867768401..944d0f27566 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; @@ -135,7 +135,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index d3a0083da6f..03120df9f42 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -6,7 +6,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; @@ -107,7 +107,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return $this->resolve()->hasProperty($propertyName); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->resolve()->getProperty($propertyName, $scope); } diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php index b8097947b4e..cc13c23a994 100644 --- a/src/Type/Traits/MaybeObjectTypeTrait.php +++ b/src/Type/Traits/MaybeObjectTypeTrait.php @@ -8,7 +8,7 @@ use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -45,7 +45,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php index 3cba4b5bca0..048ef5fb333 100644 --- a/src/Type/Traits/NonObjectTypeTrait.php +++ b/src/Type/Traits/NonObjectTypeTrait.php @@ -5,7 +5,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; @@ -36,7 +36,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 2fd278e34b8..174a6a2509c 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -9,7 +9,7 @@ use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -56,7 +56,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index e439af445b9..2d152cd73a4 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -85,10 +85,7 @@ public function canAccessProperties(): TrinaryLogic; public function hasProperty(string $propertyName): TrinaryLogic; - /** - * @return ExtendedPropertyReflection - */ - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection; + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 1316f9369d9..05d3f3ec928 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -11,8 +11,8 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\Type\UnionTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -435,7 +435,7 @@ public function hasProperty(string $propertyName): TrinaryLogic return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasProperty($propertyName)); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } From f0a629685de2202687b9f92bd0e1a516daf2443e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:42:48 +0200 Subject: [PATCH 0538/3097] ReadWritePropertiesExtension - use ExtendedPropertyReflection in parameter type --- src/Rules/Properties/ReadWritePropertiesExtension.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rules/Properties/ReadWritePropertiesExtension.php b/src/Rules/Properties/ReadWritePropertiesExtension.php index 1b1c695ef9e..804619781cf 100644 --- a/src/Rules/Properties/ReadWritePropertiesExtension.php +++ b/src/Rules/Properties/ReadWritePropertiesExtension.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Properties; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; /** * This is the extension interface to implement if you want to describe @@ -25,10 +25,10 @@ interface ReadWritePropertiesExtension { - public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool; + public function isAlwaysRead(ExtendedPropertyReflection $property, string $propertyName): bool; - public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool; + public function isAlwaysWritten(ExtendedPropertyReflection $property, string $propertyName): bool; - public function isInitialized(PropertyReflection $property, string $propertyName): bool; + public function isInitialized(ExtendedPropertyReflection $property, string $propertyName): bool; } From 950a491485c46068074ca3f4f6dc5b970d41465a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:44:11 +0200 Subject: [PATCH 0539/3097] Revert "Dumb down parameter types in recently added stubs" This reverts commit 2d79c624d5b4e6a1659cf506328f8d697e4ac828. --- stubs/core.stub | 30 +++++++++---------- .../CallToFunctionParametersRuleTest.php | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/stubs/core.stub b/stubs/core.stub index 853222642c7..f2327d728fc 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -222,9 +222,9 @@ function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int function preg_match($pattern, $subject, &$matches = [], int $flags = 0, int $offset = 0) {} /** - * @param string|array $pattern - * @param callable(array):string $callback - * @param string|array $subject + * @param string|string[] $pattern + * @param callable(string[]):string $callback + * @param string|array $subject * @param int $count * @param-out 0|positive-int $count * @return ($subject is array ? list|null : string|null) @@ -232,9 +232,9 @@ function preg_match($pattern, $subject, &$matches = [], int $flags = 0, int $off function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, &$count = null, int $flags = 0) {} /** - * @param string|array $pattern - * @param string|array $replacement - * @param string|array $subject + * @param string|string[] $pattern + * @param string|array $replacement + * @param string|array $subject * @param int $count * @param-out 0|positive-int $count * @return ($subject is array ? list|null : string|null) @@ -242,9 +242,9 @@ function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, & function preg_replace($pattern, $replacement, $subject, int $limit = -1, &$count = null) {} /** - * @param string|array $pattern - * @param string|array $replacement - * @param string|array $subject + * @param string|string[] $pattern + * @param string|array $replacement + * @param string|array $subject * @param int $count * @param-out 0|positive-int $count * @return ($subject is array ? list : string|null) @@ -252,18 +252,18 @@ function preg_replace($pattern, $replacement, $subject, int $limit = -1, &$count function preg_filter($pattern, $replacement, $subject, int $limit = -1, &$count = null) {} /** - * @param array|string $search - * @param array|string $replace - * @param array|string $subject + * @param array|string $search + * @param array|string $replace + * @param array|string $subject * @param-out int $count * @return list|string */ function str_replace($search, $replace, $subject, ?int &$count = null) {} /** - * @param array|string $search - * @param array|string $replace - * @param array|string $subject + * @param array|string $search + * @param array|string $replace + * @param array|string $subject * @param-out int $count * @return list|string */ diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 7bdc4d1e5f9..eda1ad4ee3b 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -654,19 +654,19 @@ public function testPregReplaceCallback(): void { $this->analyse([__DIR__ . '/data/preg_replace_callback.php'], [ [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', 6, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(string): string given.', 13, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(array): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(array): void given.', 20, ], [ - 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(): void given.', + 'Parameter #2 $callback of function preg_replace_callback expects callable(array): string, Closure(): void given.', 25, ], ]); From 0972d76222f464841ebb5feecd9b0bfc7ccf31a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:45:37 +0200 Subject: [PATCH 0540/3097] [BCB] additionalConfigFiles must be a list --- UPGRADING.md | 1 + conf/parametersSchema.neon | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index a22375dd11a..1e33daa557b 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -279,3 +279,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` * Remove `FunctionReflection::isFinal()` * [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) +* `additionalConfigFiles` config parameter must be a list diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 143187dd1d5..1b399f95dd0 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -170,7 +170,7 @@ parametersSchema: __validate: bool() # internal parameters only for DerivativeContainerFactory - additionalConfigFiles: arrayOf(string()) + additionalConfigFiles: listOf(string()) generateBaselineFile: schema(string(), nullable()) analysedPaths: listOf(string()) allConfigFiles: listOf(string()) From 933b48743ef363f3a7e8f39ec525e015d43b99ec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:47:27 +0200 Subject: [PATCH 0541/3097] [BCB] Virtual node `PHPStan\Node\ClassMethod` is no longer a node --- src/Node/ClassMethod.php | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Node/ClassMethod.php b/src/Node/ClassMethod.php index 3a30a402d65..2aec877cf5d 100644 --- a/src/Node/ClassMethod.php +++ b/src/Node/ClassMethod.php @@ -2,32 +2,22 @@ namespace PHPStan\Node; -use PhpParser\Node\Stmt\ClassMethod as PhpParserClassMethod; - /** * @api */ -final class ClassMethod extends PhpParserClassMethod +final class ClassMethod { public function __construct( - \PhpParser\Node\Stmt\ClassMethod $node, + private \PhpParser\Node\Stmt\ClassMethod $node, private bool $isDeclaredInTrait, ) { - parent::__construct($node->name, [ - 'flags' => $node->flags, - 'byRef' => $node->byRef, - 'params' => $node->params, - 'returnType' => $node->returnType, - 'stmts' => $node->stmts, - 'attrGroups' => $node->attrGroups, - ], $node->attributes); } - public function getNode(): PhpParserClassMethod + public function getNode(): \PhpParser\Node\Stmt\ClassMethod { - return $this; + return $this->node; } public function isDeclaredInTrait(): bool From b41c19fdcda2ec68f5f9af7ceab452a49fb3b7cb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 12:48:58 +0200 Subject: [PATCH 0542/3097] Fix test --- .../Analyser/nsrt/preg_replace_callback_shapes-php72.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php index 2a3f437a810..f7230851e4e 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php @@ -8,7 +8,7 @@ function (string $s): void { preg_replace_callback( $s, function ($matches) { - assertType('array', $matches); + assertType('array', $matches); return ''; }, $s From 0b9ce98cfad4a1f7e98e85c2cbd813025dbafa77 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 16:50:35 +0200 Subject: [PATCH 0543/3097] Update nikic/php-parser --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 612ca1c5d5b..35b021fe668 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", - "nikic/php-parser": "^4.17.1", + "nikic/php-parser": "^4.19.4", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.19", "phpstan/php-8-stubs": "0.3.111", diff --git a/composer.lock b/composer.lock index edd298b6c82..f1585bc0a9f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "49e816aaa49ffc406de3c7ad3c73072e", + "content-hash": "9d6cc3eb297a7d5f481c67f8c671b208", "packages": [ { "name": "clue/ndjson-react", @@ -2048,16 +2048,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.2", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ed4c8949a32986043e977dbe14776c14d644c45" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ed4c8949a32986043e977dbe14776c14d644c45", - "reference": "0ed4c8949a32986043e977dbe14776c14d644c45", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { @@ -2066,7 +2066,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -2098,9 +2098,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2024-09-17T19:36:00+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "ondram/ci-detector", From 84a3354543a798567fa5358a1065a9edc162061f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 17:11:03 +0200 Subject: [PATCH 0544/3097] No need to absolutize phpstan-symfony options with underscores --- src/DependencyInjection/NeonAdapter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index 4fb9f47156d..0cd90db6452 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -31,7 +31,7 @@ final class NeonAdapter implements Adapter { - public const CACHE_KEY = 'v29-excludes-analyse'; + public const CACHE_KEY = 'v30-no-underscore'; private const PREVENT_MERGING_SUFFIX = '!'; @@ -134,9 +134,7 @@ public function process(array $arr, string $fileKey, string $file): array '[parameters][memoryLimitFile]', '[parameters][benchmarkFile]', '[parameters][stubFiles][]', - '[parameters][symfony][console_application_loader]', '[parameters][symfony][consoleApplicationLoader]', - '[parameters][symfony][container_xml_path]', '[parameters][symfony][containerXmlPath]', '[parameters][doctrine][objectManagerLoader]', ], true) && is_string($val) && !str_contains($val, '%') && !str_starts_with($val, '*')) { From fc66c24113e9fe88c3155703224eb03768846fdd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 17:34:50 +0200 Subject: [PATCH 0545/3097] TableErrorFormatter - always output identifiers --- .../ErrorFormatter/TableErrorFormatter.php | 28 +------------------ .../TableErrorFormatterTest.php | 7 +++-- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index fb3d949c4e6..7da56bdff1b 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -9,12 +9,10 @@ use PHPStan\File\RelativePathHelper; use PHPStan\File\SimpleRelativePathHelper; use Symfony\Component\Console\Formatter\OutputFormatter; -use function array_key_exists; use function array_map; use function count; use function explode; use function getenv; -use function in_array; use function is_string; use function ltrim; use function sprintf; @@ -69,36 +67,12 @@ public function formatErrors( /** @var array $fileErrors */ $fileErrors = []; - $outputIdentifiers = $output->isVerbose(); - $outputIdentifiersInFile = []; foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { if (!isset($fileErrors[$fileSpecificError->getFile()])) { $fileErrors[$fileSpecificError->getFile()] = []; } $fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError; - if ($outputIdentifiers) { - continue; - } - - $filePath = $fileSpecificError->getTraitFilePath() ?? $fileSpecificError->getFilePath(); - if (array_key_exists($filePath, $outputIdentifiersInFile)) { - continue; - } - - if ($fileSpecificError->getIdentifier() === null) { - continue; - } - - if (!in_array($fileSpecificError->getIdentifier(), [ - 'ignore.unmatchedIdentifier', - 'ignore.parseError', - 'ignore.unmatched', - ], true)) { - continue; - } - - $outputIdentifiersInFile[$filePath] = true; } foreach ($fileErrors as $file => $errors) { @@ -106,7 +80,7 @@ public function formatErrors( foreach ($errors as $error) { $message = $error->getMessage(); $filePath = $error->getTraitFilePath() ?? $error->getFilePath(); - if (($outputIdentifiers || array_key_exists($filePath, $outputIdentifiersInFile)) && $error->getIdentifier() !== null && $error->canBeIgnored()) { + if ($error->getIdentifier() !== null && $error->canBeIgnored()) { $message .= "\n"; $message .= '🪪 ' . $error->getIdentifier(); } diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 85121ad337a..40db6547a86 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -190,12 +190,13 @@ public function dataFormatterOutputProvider(): iterable 'numGenericErrors' => 0, 'verbose' => false, 'extraEnvVars' => [], - 'expected' => ' ------ ------------ + 'expected' => ' ------ ---------------- Line foo.php - ------ ------------ + ------ ---------------- 5 Foobar\Buz + 🪪 foobar.buz 💡 a tip - ------ ------------ + ------ ---------------- [ERROR] Found 1 error From fff8f095988a66f298aa4037fe8e6ba98266063c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Jan 2022 14:14:04 +0100 Subject: [PATCH 0546/3097] Do not apply heuristics of Collection<...>|Foo[] being resolved to Collection of Foo Closes https://github.com/phpstan/phpstan/issues/6228 --- phpstan-baseline.neon | 5 +++++ src/PhpDoc/TypeNodeResolver.php | 2 +- tests/PHPStan/Analyser/data/bug-4715.php | 4 ++-- tests/PHPStan/Analyser/nsrt/bug-6228.php | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6228.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 23d822be892..b1010cff7b6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -180,6 +180,11 @@ parameters: count: 1 path: src/PhpDoc/TypeNodeResolver.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + count: 1 + path: src/PhpDoc/TypeNodeResolver.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" count: 1 diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index facec026440..ee7ef34786c 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -568,7 +568,7 @@ private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameSc continue; } - if ($type instanceof ObjectType) { + if ($type instanceof ObjectType && !$type instanceof GenericObjectType) { $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]); } elseif ($type instanceof ArrayType) { $type = new ArrayType(new MixedType(), $arrayTypeType); diff --git a/tests/PHPStan/Analyser/data/bug-4715.php b/tests/PHPStan/Analyser/data/bug-4715.php index d51a97b3e45..508320fb8b1 100644 --- a/tests/PHPStan/Analyser/data/bug-4715.php +++ b/tests/PHPStan/Analyser/data/bug-4715.php @@ -30,7 +30,7 @@ class Administration {} class Company { /** - * @var Collection|Administration[] + * @var Collection */ protected Collection $administrations; @@ -40,7 +40,7 @@ public function __construct() } /** - * @return Collection|Administration[] + * @return Collection */ public function getAdministrations() : Collection { diff --git a/tests/PHPStan/Analyser/nsrt/bug-6228.php b/tests/PHPStan/Analyser/nsrt/bug-6228.php new file mode 100644 index 00000000000..aee5112addd --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6228.php @@ -0,0 +1,16 @@ +|\DOMNode|\DOMNode[]|string|null $node + */ + public function __construct($node) + { + assertType('array|DOMNode|DOMNodeList|string|null', $node); + } +} From 1fb2cddebcde59c1b059eedc2d694146fc878951 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 30 Sep 2024 17:44:35 +0200 Subject: [PATCH 0547/3097] Declare more precise `getClass()` return types in extension interfaces --- src/Type/DynamicMethodReturnTypeExtension.php | 1 + src/Type/DynamicStaticMethodReturnTypeExtension.php | 1 + src/Type/MethodTypeSpecifyingExtension.php | 1 + src/Type/StaticMethodTypeSpecifyingExtension.php | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Type/DynamicMethodReturnTypeExtension.php b/src/Type/DynamicMethodReturnTypeExtension.php index 6d03b43f10c..e8af9137b0b 100644 --- a/src/Type/DynamicMethodReturnTypeExtension.php +++ b/src/Type/DynamicMethodReturnTypeExtension.php @@ -26,6 +26,7 @@ interface DynamicMethodReturnTypeExtension { + /** @return class-string */ public function getClass(): string; public function isMethodSupported(MethodReflection $methodReflection): bool; diff --git a/src/Type/DynamicStaticMethodReturnTypeExtension.php b/src/Type/DynamicStaticMethodReturnTypeExtension.php index 87b74c9af41..e74f69460f1 100644 --- a/src/Type/DynamicStaticMethodReturnTypeExtension.php +++ b/src/Type/DynamicStaticMethodReturnTypeExtension.php @@ -26,6 +26,7 @@ interface DynamicStaticMethodReturnTypeExtension { + /** @return class-string */ public function getClass(): string; public function isStaticMethodSupported(MethodReflection $methodReflection): bool; diff --git a/src/Type/MethodTypeSpecifyingExtension.php b/src/Type/MethodTypeSpecifyingExtension.php index 9e56ea330ff..f0f11de2f43 100644 --- a/src/Type/MethodTypeSpecifyingExtension.php +++ b/src/Type/MethodTypeSpecifyingExtension.php @@ -28,6 +28,7 @@ interface MethodTypeSpecifyingExtension { + /** @return class-string */ public function getClass(): string; public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool; diff --git a/src/Type/StaticMethodTypeSpecifyingExtension.php b/src/Type/StaticMethodTypeSpecifyingExtension.php index dbb6a49ffac..7421f3b896c 100644 --- a/src/Type/StaticMethodTypeSpecifyingExtension.php +++ b/src/Type/StaticMethodTypeSpecifyingExtension.php @@ -28,6 +28,7 @@ interface StaticMethodTypeSpecifyingExtension { + /** @return class-string */ public function getClass(): string; public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool; From 38cb5a315e5573231d8695df343c8ee87a8c3b2e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 28 Mar 2022 21:29:10 +0200 Subject: [PATCH 0548/3097] Remove `__set_state()` on objects that should not be serialized in cache --- UPGRADING.md | 1 + src/Analyser/NameScope.php | 17 --------------- .../Native/NativeParameterReflection.php | 15 ------------- .../NativeParameterWithPhpDocsReflection.php | 20 ------------------ src/Reflection/PassedByReference.php | 8 ------- src/TrinaryLogic.php | 8 ------- src/Type/Accessory/AccessoryArrayListType.php | 5 ----- .../Accessory/AccessoryLiteralStringType.php | 5 ----- .../AccessoryLowercaseStringType.php | 5 ----- .../Accessory/AccessoryNonEmptyStringType.php | 5 ----- .../Accessory/AccessoryNonFalsyStringType.php | 5 ----- .../Accessory/AccessoryNumericStringType.php | 5 ----- src/Type/Accessory/HasMethodType.php | 5 ----- src/Type/Accessory/HasOffsetType.php | 5 ----- src/Type/Accessory/HasOffsetValueType.php | 5 ----- src/Type/Accessory/HasPropertyType.php | 5 ----- src/Type/Accessory/NonEmptyArrayType.php | 5 ----- src/Type/Accessory/OversizedArrayType.php | 5 ----- src/Type/ArrayType.php | 11 ---------- src/Type/BenevolentUnionType.php | 8 ------- src/Type/BooleanType.php | 8 ------- src/Type/CallableType.php | 16 -------------- src/Type/ClassStringType.php | 8 ------- src/Type/ClosureType.php | 21 ------------------- src/Type/ConditionalType.php | 14 ------------- src/Type/ConditionalTypeForParameter.php | 14 ------------- src/Type/Constant/ConstantArrayType.php | 8 ------- src/Type/Constant/ConstantBooleanType.php | 8 ------- src/Type/Constant/ConstantFloatType.php | 8 ------- src/Type/Constant/ConstantIntegerType.php | 8 ------- src/Type/Constant/ConstantStringType.php | 8 ------- src/Type/Enum/EnumCaseObjectType.php | 8 ------- src/Type/ErrorType.php | 8 ------- src/Type/FloatType.php | 8 ------- src/Type/Generic/GenericClassStringType.php | 8 ------- src/Type/Generic/GenericObjectType.php | 14 ------------- .../Generic/TemplateTypeArgumentStrategy.php | 8 ------- src/Type/Generic/TemplateTypeMap.php | 11 ---------- .../Generic/TemplateTypeParameterStrategy.php | 8 ------- src/Type/Generic/TemplateTypeScope.php | 11 ---------- src/Type/Generic/TemplateTypeTrait.php | 14 ------------- src/Type/Generic/TemplateTypeVariance.php | 8 ------- src/Type/Helper/GetTemplateTypeType.php | 12 ----------- src/Type/IntegerRangeType.php | 8 ------- src/Type/IntegerType.php | 8 ------- src/Type/IntersectionType.php | 8 ------- src/Type/IterableType.php | 8 ------- src/Type/KeyOfType.php | 10 --------- src/Type/MixedType.php | 11 ---------- src/Type/NeverType.php | 8 ------- src/Type/NewObjectType.php | 10 --------- src/Type/NonexistentParentClassType.php | 8 ------- src/Type/NullType.php | 8 ------- src/Type/ObjectShapeType.php | 8 ------- src/Type/ObjectType.php | 11 ---------- src/Type/ObjectWithoutClassType.php | 8 ------- src/Type/OffsetAccessType.php | 11 ---------- src/Type/ResourceType.php | 8 ------- src/Type/StaticType.php | 14 ------------- src/Type/StrictMixedType.php | 8 ------- src/Type/StringType.php | 8 ------- src/Type/ThisType.php | 14 ------------- src/Type/Type.php | 5 ----- src/Type/UnionType.php | 8 ------- src/Type/ValueOfType.php | 10 --------- src/Type/VoidType.php | 8 ------- .../data/class-implements-out-of-phpstan.php | 6 ------ 67 files changed, 1 insertion(+), 600 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1e33daa557b..c7756d49ca7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -280,3 +280,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `FunctionReflection::isFinal()` * [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) * `additionalConfigFiles` config parameter must be a list +* Remove `__set_state()` on objects that should not be serialized in cache diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index f7f54f0a6a1..1426b804a10 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -222,21 +222,4 @@ public function hasTypeAlias(string $alias): bool return array_key_exists($alias, $this->typeAliasesMap); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['namespace'], - $properties['uses'], - $properties['className'], - $properties['functionName'], - $properties['templateTypeMap'], - $properties['typeAliasesMap'], - $properties['bypassTypeAliases'], - $properties['constUses'], - ); - } - } diff --git a/src/Reflection/Native/NativeParameterReflection.php b/src/Reflection/Native/NativeParameterReflection.php index bdfce9f04c6..e8120868302 100644 --- a/src/Reflection/Native/NativeParameterReflection.php +++ b/src/Reflection/Native/NativeParameterReflection.php @@ -63,19 +63,4 @@ public function union(self $other): self ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['name'], - $properties['optional'], - $properties['type'], - $properties['passedByReference'], - $properties['variadic'], - $properties['defaultValue'], - ); - } - } diff --git a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php index 3e303b82b9e..64aa5930674 100644 --- a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php +++ b/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php @@ -81,24 +81,4 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['name'], - $properties['optional'], - $properties['type'], - $properties['phpDocType'], - $properties['nativeType'], - $properties['passedByReference'], - $properties['variadic'], - $properties['defaultValue'], - $properties['outType'], - $properties['immediatelyInvokedCallable'], - $properties['closureThisType'], - ); - } - } diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index 9a5c95f806b..804d049b43b 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -76,12 +76,4 @@ public function combine(self $other): self return $this; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self($properties['value']); - } - } diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index 569d5d2ec10..a5870998446 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -251,12 +251,4 @@ public function describe(): string return $labels[$this->value]; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return self::create($properties['value']); - } - } diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 930473cf031..d41d3c510f5 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -472,11 +472,6 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function exponentiate(Type $exponent): Type { return new ErrorType(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 1a3a37858ba..5f8705cfc52 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -352,11 +352,6 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function exponentiate(Type $exponent): Type { return new BenevolentUnionType([ diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index c32ef8c506d..48bb4b31798 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -348,11 +348,6 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function exponentiate(Type $exponent): Type { return new BenevolentUnionType([ diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index cc8e90f2363..98152250621 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -344,11 +344,6 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function tryRemove(Type $typeToRemove): ?Type { if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 82460ea1a1c..6dca5f0514b 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -344,11 +344,6 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function exponentiate(Type $exponent): Type { return new BenevolentUnionType([ diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index a7c56c56ed8..ae15b4623ef 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -346,11 +346,6 @@ public function generalize(GeneralizePrecision $precision): Type return new StringType(); } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function tryRemove(Type $typeToRemove): ?Type { if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '0') { diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 98503a13a8d..a28497912d3 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -200,11 +200,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self($properties['methodName']); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode(''); // no PHPDoc representation diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 969a99df7a4..635f7d37eea 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -402,11 +402,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self($properties['offsetType']); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode(''); // no PHPDoc representation diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 0481c3939b5..ec3a9908d1c 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -458,11 +458,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self($properties['offsetType'], $properties['valueType']); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode(''); // no PHPDoc representation diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index f5084471356..fdbc9b0bccc 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -162,11 +162,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self($properties['propertyName']); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode(''); // no PHPDoc representation diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index fbe23d45346..50ccdb308e9 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -454,11 +454,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode('non-empty-array'); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 365431f4ac7..113d0eb689a 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -450,11 +450,6 @@ public function getFiniteTypes(): array return []; } - public static function __set_state(array $properties): Type - { - return new self(); - } - public function toPhpDocNode(): TypeNode { return new IdentifierTypeNode(''); // no PHPDoc representation diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 4a9a03dd6b6..93d0094378a 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -562,15 +562,4 @@ public function getFiniteTypes(): array return []; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['keyType'], - $properties['itemType'], - ); - } - } diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index dc1245ad45a..c27a3d6cd0d 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -173,12 +173,4 @@ public function traverseSimultaneously(Type $right, callable $cb): Type return $this; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types']); - } - } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 72be662c956..df059481e6a 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -163,12 +163,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('bool'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index de594f96838..0f733ca60a2 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -680,20 +680,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - (bool) $properties['isCommonCallable'] ? null : $properties['parameters'], - (bool) $properties['isCommonCallable'] ? null : $properties['returnType'], - $properties['variadic'], - $properties['templateTypeMap'], - $properties['resolvedTemplateTypeMap'], - $properties['templateTags'], - $properties['isPure'], - ); - } - } diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index ed622e5817a..4a74ec015ad 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -94,12 +94,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('class-string'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index e09269ad830..90bef922557 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -799,25 +799,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['parameters'], - $properties['returnType'], - $properties['variadic'], - $properties['templateTypeMap'], - $properties['resolvedTemplateTypeMap'], - $properties['callSiteVarianceMap'], - $properties['templateTags'], - $properties['throwPoints'], - $properties['impurePoints'], - $properties['invalidateExpressions'], - $properties['usedVariables'], - $properties['acceptsNamedArguments'], - ); - } - } diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index aa5d8af0a61..96a1776e3f1 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -188,20 +188,6 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['subject'], - $properties['target'], - $properties['if'], - $properties['else'], - $properties['negated'], - ); - } - private function getNormalizedIf(): Type { return $this->normalizedIf ??= TypeTraverser::map( diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index 57c2fe5d8d2..a7c8095e690 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -175,18 +175,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['parameterName'], - $properties['target'], - $properties['if'], - $properties['else'], - $properties['negated'], - ); - } - } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index a257437061d..0d8d254effa 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1712,12 +1712,4 @@ public function getFiniteTypes(): array return $finiteTypes; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndexes'] ?? $properties['nextAutoIndex'], $properties['optionalKeys'] ?? [], $properties['isList'] ?? TrinaryLogic::createNo()); - } - } diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index cbebe5b48ba..a01321c1383 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -127,14 +127,6 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode($this->value ? 'true' : 'false'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { if ($type->isObject()->yes()) { diff --git a/src/Type/Constant/ConstantFloatType.php b/src/Type/Constant/ConstantFloatType.php index 3aeb1e2e181..0ac763af769 100644 --- a/src/Type/Constant/ConstantFloatType.php +++ b/src/Type/Constant/ConstantFloatType.php @@ -100,12 +100,4 @@ public function toPhpDocNode(): TypeNode return new ConstTypeNode(new ConstExprFloatNode($this->castFloatToString($this->value))); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - } diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 9226acacc24..779dc831699 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -105,12 +105,4 @@ public function toPhpDocNode(): TypeNode return new ConstTypeNode(new ConstExprIntegerNode((string) $this->value)); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value']); - } - } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index a5b39335cd5..a6fda677b7b 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -553,12 +553,4 @@ public function toPhpDocNode(): TypeNode return new ConstTypeNode(new ConstExprStringNode($this->value, ConstExprStringNode::SINGLE_QUOTED)); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['value'], $properties['isClassString'] ?? false); - } - } diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index e0bc8c33404..5bb5d0b5b76 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -205,12 +205,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['className'], $properties['enumCaseName'], null); - } - } diff --git a/src/Type/ErrorType.php b/src/Type/ErrorType.php index 345465b306b..751271aef33 100644 --- a/src/Type/ErrorType.php +++ b/src/Type/ErrorType.php @@ -41,12 +41,4 @@ public function equals(Type $type): bool return $type instanceof self; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 890e13994a7..21d0a969048 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -289,12 +289,4 @@ public function getFiniteTypes(): array return []; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index d9b06d68bfd..f7ecec9d865 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -195,14 +195,6 @@ public function equals(Type $type): bool return true; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['type']); - } - public function toPhpDocNode(): TypeNode { return new GenericTypeNode( diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 2747320c75e..f13d52d4e3b 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -398,18 +398,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['className'], - $properties['types'], - $properties['subtractedType'] ?? null, - null, - $properties['variances'] ?? [], - ); - } - } diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index deeb9b6ddd0..8c98baaacc7 100644 --- a/src/Type/Generic/TemplateTypeArgumentStrategy.php +++ b/src/Type/Generic/TemplateTypeArgumentStrategy.php @@ -43,12 +43,4 @@ public function isArgument(): bool return true; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self(); - } - } diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index e631819818e..f1c598c7aa4 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -213,15 +213,4 @@ public function resolveToBounds(): self return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::resolveToBounds($type)); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['types'], - $properties['lowerBoundTypes'] ?? [], - ); - } - } diff --git a/src/Type/Generic/TemplateTypeParameterStrategy.php b/src/Type/Generic/TemplateTypeParameterStrategy.php index 1ec43f153af..949f3bfa52a 100644 --- a/src/Type/Generic/TemplateTypeParameterStrategy.php +++ b/src/Type/Generic/TemplateTypeParameterStrategy.php @@ -26,12 +26,4 @@ public function isArgument(): bool return false; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self(); - } - } diff --git a/src/Type/Generic/TemplateTypeScope.php b/src/Type/Generic/TemplateTypeScope.php index 8cc0e54e5e0..f362ecadd4c 100644 --- a/src/Type/Generic/TemplateTypeScope.php +++ b/src/Type/Generic/TemplateTypeScope.php @@ -68,15 +68,4 @@ public function describe(): string return sprintf('method %s::%s()', $this->className, $this->functionName); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self - { - return new self( - $properties['className'], - $properties['functionName'], - ); - } - } diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 13440a54a70..e1a170a57d5 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -351,18 +351,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode($this->name); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['scope'], - $properties['strategy'], - $properties['variance'], - $properties['name'], - $properties['bound'], - ); - } - } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index ff7d6098812..24f8fe29274 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -242,12 +242,4 @@ public function toPhpDocNodeVariance(): string throw new ShouldNotHappenException(); } - /** - * @param array{value: int} $properties - */ - public static function __set_state(array $properties): self - { - return new self($properties['value']); - } - } diff --git a/src/Type/Helper/GetTemplateTypeType.php b/src/Type/Helper/GetTemplateTypeType.php index e8af52e322b..c45a7be5fa6 100644 --- a/src/Type/Helper/GetTemplateTypeType.php +++ b/src/Type/Helper/GetTemplateTypeType.php @@ -103,16 +103,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['type'], - $properties['ancestorClassName'], - $properties['templateTypeName'], - ); - } - } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 9c9f8417036..e957bee8ea7 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -704,12 +704,4 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return parent::looseCompare($type, $phpVersion); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['min'], $properties['max']); - } - } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index f91a646c9dd..cd192377335 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -49,14 +49,6 @@ public function getConstantStrings(): array return []; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - public function toNumber(): Type { return $this; diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 27e7e623c1a..3c193d15f59 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1086,14 +1086,6 @@ public function getFiniteTypes(): array return $result; } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types']); - } - /** * @param callable(Type $type): TrinaryLogic $getResult */ diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index fe0af6a8125..b1dcbec2fe1 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -517,12 +517,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['keyType'], $properties['itemType']); - } - } diff --git a/src/Type/KeyOfType.php b/src/Type/KeyOfType.php index 32eab8b0192..10a4cb2ea59 100644 --- a/src/Type/KeyOfType.php +++ b/src/Type/KeyOfType.php @@ -91,14 +91,4 @@ public function toPhpDocNode(): TypeNode return new GenericTypeNode(new IdentifierTypeNode('key-of'), [$this->type->toPhpDocNode()]); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['type'], - ); - } - } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index def0bbd74a3..08f7f0f205c 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -1023,15 +1023,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('mixed'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['isExplicitMixed'], - $properties['subtractedType'] ?? null, - ); - } - } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index bb2e9448f2c..66193ded71f 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -531,12 +531,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('never'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['isExplicit']); - } - } diff --git a/src/Type/NewObjectType.php b/src/Type/NewObjectType.php index 57b932a398c..93d14c69368 100644 --- a/src/Type/NewObjectType.php +++ b/src/Type/NewObjectType.php @@ -91,14 +91,4 @@ public function toPhpDocNode(): TypeNode return new GenericTypeNode(new IdentifierTypeNode('new'), [$this->type->toPhpDocNode()]); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['type'], - ); - } - } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 3caabeee83f..29ae4f752ce 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -192,12 +192,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('parent'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index c90a5b29e69..753dccc5d87 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -399,12 +399,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('null'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 43b68b8dac5..6878401ef09 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -527,12 +527,4 @@ public function toPhpDocNode(): TypeNode return new ObjectShapeNode($items); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['properties'], $properties['optionalProperties']); - } - } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index e3711b8d73b..2026e447f04 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1320,17 +1320,6 @@ public function isCloneable(): TrinaryLogic return TrinaryLogic::createYes(); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['className'], - $properties['subtractedType'] ?? null, - ); - } - public function isInstanceOf(string $className): TrinaryLogic { $classReflection = $this->getClassReflection(); diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 1144acd2f83..5b388ba712a 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -230,12 +230,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('object'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['subtractedType'] ?? null); - } - } diff --git a/src/Type/OffsetAccessType.php b/src/Type/OffsetAccessType.php index 430d38332c9..5e4ef1aec39 100644 --- a/src/Type/OffsetAccessType.php +++ b/src/Type/OffsetAccessType.php @@ -114,15 +114,4 @@ public function toPhpDocNode(): TypeNode ); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['type'], - $properties['offset'], - ); - } - } diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index 4327f30bdea..f62023f2c61 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -121,12 +121,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('resource'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 5ef9ebc6c5d..ab8fc9d1a01 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -10,7 +10,6 @@ use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -737,17 +736,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('static'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if ($reflectionProvider->hasClass($properties['baseClass'])) { - return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null); - } - - return new ErrorType(); - } - } diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 944d0f27566..c5d76697c62 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -440,12 +440,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('mixed'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index b022f2b397e..55fb28c2c1f 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -306,12 +306,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('string'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 963f98c1918..954a7fffdf5 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -5,7 +5,6 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use function sprintf; @@ -87,17 +86,4 @@ public function toPhpDocNode(): TypeNode return new ThisTypeNode(); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if ($reflectionProvider->hasClass($properties['baseClass'])) { - return new self($reflectionProvider->getClass($properties['baseClass']), $properties['subtractedType'] ?? null); - } - - return new ErrorType(); - } - } diff --git a/src/Type/Type.php b/src/Type/Type.php index 2d152cd73a4..0f89e01a936 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -349,9 +349,4 @@ public function tryRemove(Type $typeToRemove): ?Type; public function generalize(GeneralizePrecision $precision): Type; - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): self; - } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 05d3f3ec928..454f511c753 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -1073,14 +1073,6 @@ public function getFiniteTypes(): array return array_values($uniquedTypes); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self($properties['types'], $properties['normalized']); - } - /** * @param callable(Type $type): TrinaryLogic $getResult */ diff --git a/src/Type/ValueOfType.php b/src/Type/ValueOfType.php index 9df5411c471..d02b7d11f7f 100644 --- a/src/Type/ValueOfType.php +++ b/src/Type/ValueOfType.php @@ -100,14 +100,4 @@ public function toPhpDocNode(): TypeNode return new GenericTypeNode(new IdentifierTypeNode('value-of'), [$this->type->toPhpDocNode()]); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self( - $properties['type'], - ); - } - } diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 2bea11cb041..16bf2033511 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -267,12 +267,4 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('void'); } - /** - * @param mixed[] $properties - */ - public static function __set_state(array $properties): Type - { - return new self(); - } - } diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index c5211fd6501..ca38d7ec38c 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -328,12 +328,6 @@ public function tryRemove(Type $typeToRemove): ?Type // TODO: Implement tryRemove() method. } - public static function __set_state(array $properties): \PHPStan\Type\Type - { - // TODO: Implement __set_state() method. - } - - } abstract class Dolor implements ReflectionProvider From fd35d2faf35e4c09d7394a36b40f60d3bb0c1379 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 18:01:08 +0200 Subject: [PATCH 0549/3097] Fix build --- .../AdapterReflectionEnumCaseDynamicReturnTypeExtension.php | 3 +++ .../NativeReflectionEnumReturnDynamicReturnTypeExtension.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php index 5ab5e725b97..d242403fb6c 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php @@ -21,6 +21,9 @@ final class AdapterReflectionEnumCaseDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** + * @param class-string $class + */ public function __construct(private PhpVersion $phpVersion, private string $class) { } diff --git a/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php b/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php index 683a341f2e5..fc9b44f79a6 100644 --- a/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php +++ b/src/Reflection/PHPStan/NativeReflectionEnumReturnDynamicReturnTypeExtension.php @@ -14,6 +14,9 @@ final class NativeReflectionEnumReturnDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** + * @param class-string $className + */ public function __construct(private PhpVersion $phpVersion, private string $className, private string $methodName) { } From 2b0963bf9896c6d5e0b65ebe7b16d32cca3f9a49 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 18:04:37 +0200 Subject: [PATCH 0550/3097] [BCB] `TypehintHelper::decideTypeFromReflection()` parameter `$selfClass` no longer accepts string --- UPGRADING.md | 1 + src/Reflection/Php/PhpMethodReflection.php | 2 +- src/Reflection/Php/PhpParameterReflection.php | 7 ++++--- src/Rules/Traits/ConflictingTraitConstantsRule.php | 10 +++++++--- src/Type/TypehintHelper.php | 12 +----------- .../Traits/ConflictingTraitConstantsRuleTest.php | 2 +- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index c7756d49ca7..4778a9efd9a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -281,3 +281,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) * `additionalConfigFiles` config parameter must be a list * Remove `__set_state()` on objects that should not be serialized in cache +* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts string diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index ea1bc0c6c07..d71fce1a4c4 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -232,7 +232,7 @@ private function getParameters(): array $this->initializerExprTypeResolver, $reflection, $this->phpDocParameterTypes[$reflection->getName()] ?? null, - $this->getDeclaringClass()->getName(), + $this->getDeclaringClass(), $this->phpDocParameterOutTypes[$reflection->getName()] ?? null, $this->immediatelyInvokedCallableParameters[$reflection->getName()] ?? TrinaryLogic::createMaybe(), $this->phpDocClosureThisTypeParameters[$reflection->getName()] ?? null, diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index 26cb1be0076..548d9bde47e 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; @@ -24,7 +25,7 @@ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionParameter $reflection, private ?Type $phpDocType, - private ?string $declaringClassName, + private ?ClassReflection $declaringClass, private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, @@ -62,7 +63,7 @@ public function getType(): Type $this->type = TypehintHelper::decideTypeFromReflection( $this->reflection->getType(), $phpDocType, - $this->declaringClassName, + $this->declaringClass, $this->isVariadic(), ); } @@ -97,7 +98,7 @@ public function getNativeType(): Type $this->nativeType = TypehintHelper::decideTypeFromReflection( $this->reflection->getType(), null, - $this->declaringClassName, + $this->declaringClass, $this->isVariadic(), ); } diff --git a/src/Rules/Traits/ConflictingTraitConstantsRule.php b/src/Rules/Traits/ConflictingTraitConstantsRule.php index 4dd23f32bd0..4e38e44bcb9 100644 --- a/src/Rules/Traits/ConflictingTraitConstantsRule.php +++ b/src/Rules/Traits/ConflictingTraitConstantsRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -22,7 +23,10 @@ final class ConflictingTraitConstantsRule implements Rule { - public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + public function __construct( + private InitializerExprTypeResolver $initializerExprTypeResolver, + private ReflectionProvider $reflectionProvider, + ) { } @@ -186,7 +190,7 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect ->build(); } } elseif ($constantNativeType === null) { - $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $traitDeclaringClass->getName()); + $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $this->reflectionProvider->getClass($traitDeclaringClass->getName())); $errors[] = RuleErrorBuilder::message(sprintf( 'Constant %s::%s overriding constant %s::%s (%s) should also have native type %s.', $classReflection->getDisplayName(), @@ -200,7 +204,7 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect ->identifier('classConstant.missingNativeType') ->build(); } else { - $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $traitDeclaringClass->getName()); + $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $this->reflectionProvider->getClass($traitDeclaringClass->getName())); $constantNativeTypeType = ParserNodeTypeToPHPStanType::resolve($constantNativeType, $classReflection); if (!$traitNativeTypeType->equals($constantNativeTypeType)) { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index f2e21098200..333d9de4a79 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -8,7 +8,6 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Generic\TemplateTypeHelper; @@ -16,7 +15,6 @@ use function array_map; use function count; use function get_class; -use function is_string; use function sprintf; final class TypehintHelper @@ -26,7 +24,7 @@ final class TypehintHelper public static function decideTypeFromReflection( ?ReflectionType $reflectionType, ?Type $phpDocType = null, - ClassReflection|string|null $selfClass = null, + ClassReflection|null $selfClass = null, bool $isVariadic = false, ): Type { @@ -67,14 +65,6 @@ public static function decideTypeFromReflection( $typeNode = new FullyQualified($reflectionType->getName()); } - if (is_string($selfClass)) { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if ($reflectionProvider->hasClass($selfClass)) { - $selfClass = $reflectionProvider->getClass($selfClass); - } else { - $selfClass = null; - } - } $type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass); if ($reflectionType->allowsNull()) { $type = TypeCombinator::addNull($type); diff --git a/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php b/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php index fe5b5c69842..c3b06e73d0f 100644 --- a/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php +++ b/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php @@ -14,7 +14,7 @@ class ConflictingTraitConstantsRuleTest extends RuleTestCase protected function getRule(): TRule { - return new ConflictingTraitConstantsRule(self::getContainer()->getByType(InitializerExprTypeResolver::class)); + return new ConflictingTraitConstantsRule(self::getContainer()->getByType(InitializerExprTypeResolver::class), $this->createReflectionProvider()); } public function testRule(): void From 0296a91505fee34b474a31917d51e847d6a7d416 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 18:08:16 +0200 Subject: [PATCH 0551/3097] Fix test --- tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php index f2b13d25c21..e14d95f33e9 100644 --- a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php @@ -42,22 +42,22 @@ public function testRuleOutOfPhpStan(): void ], [ 'Implementing PHPStan\Reflection\ReflectionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 339, + 333, $tip, ], [ 'Implementing PHPStan\Analyser\Scope is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 344, + 338, $tip, ], [ 'Implementing PHPStan\Reflection\FunctionReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 349, + 343, $tip, ], [ 'Implementing PHPStan\Reflection\ExtendedMethodReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 353, + 347, $tip, ], ]); From b5accb3f6bbcffc8a44934539b88903e09b6a174 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 18:09:17 +0200 Subject: [PATCH 0552/3097] HasOffsetType - put constructor parameter type natively --- build/phpstan.neon | 1 - src/Type/Accessory/HasOffsetType.php | 3 +- ...yExistsFunctionTypeSpecifyingExtension.php | 15 ++++- .../Type/Accessory/HasMethodTypeTest.php | 5 -- .../Type/Accessory/HasPropertyTypeTest.php | 5 -- tests/PHPStan/Type/IntersectionTypeTest.php | 47 ------------- tests/PHPStan/Type/TypeCombinatorTest.php | 67 ------------------- 7 files changed, 14 insertions(+), 129 deletions(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index 86610dc028b..1b1bf800f9c 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -89,7 +89,6 @@ parameters: message: "#^Parameter \\#1 (?:\\$argument|\\$objectOrClass) of class ReflectionClass constructor expects class\\-string\\\\|PHPStan\\\\ExtensionInstaller\\\\GeneratedConfig, string given\\.$#" count: 1 path: ../src/Diagnose/PHPStanDiagnoseExtension.php - - '#^Parameter \#1 \$offsetType of class PHPStan\\Type\\Accessory\\HasOffsetType constructor expects PHPStan\\Type\\Constant\\ConstantIntegerType\|PHPStan\\Type\\Constant\\ConstantStringType#' - '#^Short ternary operator is not allowed#' reportStaticMethodSignatures: true tmpDir: %rootDir%/tmp diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 635f7d37eea..132ae28edd7 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -44,9 +44,8 @@ class HasOffsetType implements CompoundType, AccessoryType /** * @api - * @param ConstantStringType|ConstantIntegerType $offsetType */ - public function __construct(private Type $offsetType) + public function __construct(private ConstantStringType|ConstantIntegerType $offsetType) { } diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index 35a3ecb8ea4..7d2eb3b2a4a 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -14,6 +14,7 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\HasOffsetType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -58,10 +59,20 @@ public function specifyTypes( $keyType = $scope->getType($key); $arrayType = $scope->getType($array); - if (!$keyType instanceof ConstantIntegerType + if ( + !$keyType instanceof ConstantIntegerType && !$keyType instanceof ConstantStringType - && !$arrayType->isIterableAtLeastOnce()->no()) { + ) { if ($context->true()) { + if ($arrayType->isIterableAtLeastOnce()->no()) { + return $this->typeSpecifier->create( + $array, + new NonEmptyArrayType(), + $context, + $scope, + ); + } + $arrayKeyType = $arrayType->getIterableKeyType(); if ($keyType->isString()->yes()) { $arrayKeyType = $arrayKeyType->toString(); diff --git a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php index a70ff82ceb0..a941dd86ee6 100644 --- a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php @@ -70,11 +70,6 @@ public function dataIsSuperTypeOf(): array new HasPropertyType('bar'), TrinaryLogic::createMaybe(), ], - [ - new HasMethodType('foo'), - new HasOffsetType(new MixedType()), - TrinaryLogic::createMaybe(), - ], [ new HasMethodType('foo'), new IterableType(new MixedType(), new MixedType()), diff --git a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php index 52b44a61686..4cecca14c15 100644 --- a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php @@ -60,11 +60,6 @@ public function dataIsSuperTypeOf(): array new HasPropertyType('bar'), TrinaryLogic::createMaybe(), ], - [ - new HasPropertyType('foo'), - new HasOffsetType(new MixedType()), - TrinaryLogic::createMaybe(), - ], [ new HasPropertyType('foo'), new IterableType(new MixedType(), new MixedType()), diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 7c9bcc0722c..b6fa0c0b4a9 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -8,7 +8,6 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; -use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Accessory\OversizedArrayType; @@ -154,52 +153,6 @@ public function dataIsSuperTypeOf(): Iterator TrinaryLogic::createNo(), ]; - yield [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - new ConstantStringType('c'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - ]), - TrinaryLogic::createMaybe(), - ]; - - yield [ - new IntersectionType([ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - ]), - new ConstantArrayType([ - new ConstantStringType('a'), - new ConstantStringType('b'), - new ConstantStringType('c'), - new ConstantStringType('d'), - new ConstantStringType('e'), - new ConstantStringType('f'), - new ConstantStringType('g'), - new ConstantStringType('h'), - new ConstantStringType('i'), - ], [ - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - new ConstantIntegerType(3), - ]), - TrinaryLogic::createMaybe(), - ]; - yield [ new IntersectionType([ new ObjectType(Traversable::class), diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index d9d6eefe66a..6084574e73a 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -873,20 +873,6 @@ public function dataUnion(): iterable UnionType::class, "'bar'|'barr'|'baz'|'bazz'|'foo'|'fooo'|'lorem'|'loremm'|'loremmm'", ], - [ - [ - new IntersectionType([ - new ArrayType(new MixedType(), new StringType()), - new HasOffsetType(new StringType()), - ]), - new IntersectionType([ - new ArrayType(new MixedType(), new StringType()), - new HasOffsetType(new StringType()), - ]), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], [ [ new IntersectionType([ @@ -1857,22 +1843,6 @@ public function dataUnion(): iterable UnionType::class, 'array{a: int, b: int}|array{b: int, c: int}', ], - [ - [ - TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), - TypeCombinator::intersect(new StringType(), new HasOffsetType(new IntegerType())), - ], - IntersectionType::class, - 'string&hasOffset(int)', - ], - [ - [ - TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), - TypeCombinator::intersect(new ConstantStringType('abc'), new HasOffsetType(new IntegerType())), - ], - IntersectionType::class, - '\'abc\'&hasOffset(int)', - ], [ [ StaticTypeFactory::falsey(), @@ -3150,24 +3120,6 @@ public function dataIntersect(): iterable IntersectionType::class, 'array&hasOffset(\'a\')', ], - [ - [ - new ArrayType(new StringType(), new StringType()), - new HasOffsetType(new StringType()), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], - [ - [ - new ArrayType(new MixedType(), new MixedType()), - new HasOffsetType(new StringType()), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], [ [ new ConstantArrayType( @@ -3253,17 +3205,6 @@ public function dataIntersect(): iterable ClosureType::class, 'Closure(): mixed', ], - [ - [ - new UnionType([ - new ArrayType(new MixedType(), new StringType()), - new NullType(), - ]), - new HasOffsetType(new StringType()), - ], - IntersectionType::class, - 'array&hasOffset(string)', - ], [ [ new ArrayType(new MixedType(), new MixedType()), @@ -3772,14 +3713,6 @@ public function dataIntersect(): iterable ConstantArrayType::class, 'array{a: int, b: int}', ], - [ - [ - new StringType(), - new HasOffsetType(new IntegerType()), - ], - IntersectionType::class, - 'string&hasOffset(int)', - ], [ [ new BenevolentUnionType([new IntegerType(), new StringType()]), From c5c03dd5bf0541aaf7272f32e5ce97e1b5cbb657 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:19:50 +0200 Subject: [PATCH 0553/3097] [BCB] Remove `fixerTmpDir` config parameter --- UPGRADING.md | 1 + conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 4778a9efd9a..85a1d46a93c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -282,3 +282,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * `additionalConfigFiles` config parameter must be a list * Remove `__set_state()` on objects that should not be serialized in cache * Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts string +* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead diff --git a/conf/config.neon b/conf/config.neon index 3d05cf2669f..72d5042dea9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -76,7 +76,6 @@ parameters: polluteScopeWithBlock: true propertyAlwaysWrittenTags: [] propertyAlwaysReadTags: [] - fixerTmpDir: %pro.tmpDir% #unused additionalConstructors: [] treatPhpDocTypesAsCertain: true usePathConstantsAsConstantString: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 1b399f95dd0..2969aeca732 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -149,7 +149,6 @@ parametersSchema: mixinExcludeClasses: listOf(string()) scanFiles: listOf(string()) scanDirectories: listOf(string()) - fixerTmpDir: string() #unused editorUrl: schema(string(), nullable()) editorUrlTitle: schema(string(), nullable()) errorFormat: schema(string(), nullable()) From 343a93a64f37a58fb8efd7cd99c19e68aee2d4ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:20:24 +0200 Subject: [PATCH 0554/3097] [BCB] `LevelsTestCase::dataTopics()` data provider made static --- UPGRADING.md | 1 + src/Testing/LevelsTestCase.php | 2 +- tests/PHPStan/Generics/GenericsIntegrationTest.php | 2 +- .../InferPrivatePropertyTypeFromConstructorIntegrationTest.php | 2 +- tests/PHPStan/Levels/LevelsIntegrationTest.php | 2 +- tests/PHPStan/Levels/NamedArgumentsIntegrationTest.php | 2 +- tests/PHPStan/Levels/StubValidatorIntegrationTest.php | 2 +- tests/PHPStan/Levels/StubsIntegrationTest.php | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 85a1d46a93c..087dc7ee33a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -283,3 +283,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `__set_state()` on objects that should not be serialized in cache * Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts string * Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* `LevelsTestCase::dataTopics()` data provider made static diff --git a/src/Testing/LevelsTestCase.php b/src/Testing/LevelsTestCase.php index 7d0b1998a96..9946e97c053 100644 --- a/src/Testing/LevelsTestCase.php +++ b/src/Testing/LevelsTestCase.php @@ -30,7 +30,7 @@ abstract class LevelsTestCase extends TestCase /** * @return array> */ - abstract public function dataTopics(): array; + abstract public static function dataTopics(): array; abstract public function getDataPath(): string; diff --git a/tests/PHPStan/Generics/GenericsIntegrationTest.php b/tests/PHPStan/Generics/GenericsIntegrationTest.php index 6f25d37cd00..a1008093559 100644 --- a/tests/PHPStan/Generics/GenericsIntegrationTest.php +++ b/tests/PHPStan/Generics/GenericsIntegrationTest.php @@ -10,7 +10,7 @@ class GenericsIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { return [ ['functions'], diff --git a/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php b/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php index df0dd443c20..d8d3c288782 100644 --- a/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php +++ b/tests/PHPStan/Levels/InferPrivatePropertyTypeFromConstructorIntegrationTest.php @@ -10,7 +10,7 @@ class InferPrivatePropertyTypeFromConstructorIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { return [ ['inferPropertyType'], diff --git a/tests/PHPStan/Levels/LevelsIntegrationTest.php b/tests/PHPStan/Levels/LevelsIntegrationTest.php index ee86187c8ce..7ac3a0276ff 100644 --- a/tests/PHPStan/Levels/LevelsIntegrationTest.php +++ b/tests/PHPStan/Levels/LevelsIntegrationTest.php @@ -11,7 +11,7 @@ class LevelsIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { $topics = [ ['returnTypes'], diff --git a/tests/PHPStan/Levels/NamedArgumentsIntegrationTest.php b/tests/PHPStan/Levels/NamedArgumentsIntegrationTest.php index 854d9ded9ea..d35d7dade76 100644 --- a/tests/PHPStan/Levels/NamedArgumentsIntegrationTest.php +++ b/tests/PHPStan/Levels/NamedArgumentsIntegrationTest.php @@ -10,7 +10,7 @@ class NamedArgumentsIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { return [ ['namedArguments'], diff --git a/tests/PHPStan/Levels/StubValidatorIntegrationTest.php b/tests/PHPStan/Levels/StubValidatorIntegrationTest.php index f4138d043f0..59ded119986 100644 --- a/tests/PHPStan/Levels/StubValidatorIntegrationTest.php +++ b/tests/PHPStan/Levels/StubValidatorIntegrationTest.php @@ -10,7 +10,7 @@ class StubValidatorIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { return [ ['stubValidator'], diff --git a/tests/PHPStan/Levels/StubsIntegrationTest.php b/tests/PHPStan/Levels/StubsIntegrationTest.php index dfec2e90f53..089c9b5495e 100644 --- a/tests/PHPStan/Levels/StubsIntegrationTest.php +++ b/tests/PHPStan/Levels/StubsIntegrationTest.php @@ -10,7 +10,7 @@ class StubsIntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { require_once __DIR__ . '/data/stubs-functions.php'; From 25fbf7f38d77df993c5d6abc4a20c55a0aca81df Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:23:10 +0200 Subject: [PATCH 0555/3097] [BCB] Remove `tempResultCachePath` config parameter --- UPGRADING.md | 1 + conf/config.neon | 1 - conf/parametersSchema.neon | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 087dc7ee33a..1afa0a19404 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -283,4 +283,5 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `__set_state()` on objects that should not be serialized in cache * Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts string * Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead * `LevelsTestCase::dataTopics()` data provider made static diff --git a/conf/config.neon b/conf/config.neon index 72d5042dea9..79b8fce3b43 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -121,7 +121,6 @@ parameters: - ../stubs/Countable.stub earlyTerminatingMethodCalls: [] earlyTerminatingFunctionCalls: [] - tempResultCachePath: %tmpDir%/resultCaches resultCachePath: %tmpDir%/resultCache.php resultCacheChecksProjectExtensionFilesDependencies: false dynamicConstantNames: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 2969aeca732..d6e4a348828 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -137,7 +137,6 @@ parametersSchema: stubFiles: listOf(string()) earlyTerminatingMethodCalls: arrayOf(listOf(string())) earlyTerminatingFunctionCalls: listOf(string()) - tempResultCachePath: string() resultCachePath: string() resultCacheChecksProjectExtensionFilesDependencies: bool() dynamicConstantNames: listOf(string()) From 11afcb0dc92c4ff1402d0aed292e7bcdb852e645 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:27:00 +0200 Subject: [PATCH 0556/3097] Upgrading note about Docker images --- UPGRADING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 1afa0a19404..22a6c35d398 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -96,6 +96,10 @@ parameters: Appending `(?)` in `ignoreErrors` is not supported. +### Docker images no longer tagged without a PHP version + +Tags without a PHP version are no longer published - `nightly`, `2`, `latest` are no longer updated. Instead, use `nightly-php8.3`, `2-php8.3`, `latest-php8.3`. You can replace `8.3` with PHP versions `8.0`-`8.3`. + ### Minor backward compatibility breaks * Removed unused config parameter `cache.nodesByFileCountMax` From f347f22922ca970129cfa6a7df3310aa52ec923c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:31:12 +0200 Subject: [PATCH 0557/3097] Fix --- src/Command/CommandHelper.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c3cfbbedfde..fdd3d743170 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -523,9 +523,6 @@ public static function begin( throw new InceptionNotSuccessfulException(); } - $tempResultCachePath = $container->getParameter('tempResultCachePath'); - $createDir($tempResultCachePath); - /** @var FileFinder $fileFinder */ $fileFinder = $container->getService('fileFinderAnalyse'); From 3999a7891b2aa409c8da1c570e29180cca7de542 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:34:09 +0200 Subject: [PATCH 0558/3097] [BCB] `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard` --- UPGRADING.md | 1 + conf/config.neon | 2 ++ 2 files changed, 3 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 22a6c35d398..679ada1d759 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -289,3 +289,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead * Remove `tempResultCachePath` config parameter, use `resultCachePath` instead * `LevelsTestCase::dataTopics()` data provider made static +* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint diff --git a/conf/config.neon b/conf/config.neon index 79b8fce3b43..5ab30e7e537 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -311,6 +311,8 @@ services: - class: PHPStan\Node\Printer\Printer + autowired: + - PHPStan\Node\Printer\Printer - class: PHPStan\Broker\AnonymousClassNameHelper From b711b3b58e50fd64976e2ebfbd68cb3d3b151042 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:35:46 +0200 Subject: [PATCH 0559/3097] Update phpstan-strict-rules --- composer.lock | 8 ++++---- issue-bot/composer.lock | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index f71bf5c34de..60fb2a048d4 100644 --- a/composer.lock +++ b/composer.lock @@ -4831,12 +4831,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c" + "reference": "e208c9311872047b903511e2e03cb0df795014b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/63956f7896780551ed1ab29e75a6a645d8a0919c", - "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e208c9311872047b903511e2e03cb0df795014b0", + "reference": "e208c9311872047b903511e2e03cb0df795014b0", "shasum": "" }, "require": { @@ -4872,7 +4872,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-24T15:32:27+00:00" + "time": "2024-09-30T19:35:25+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index c3d88dbcb35..be0c0518424 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -1407,12 +1407,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b1165b76fe8d451783d63ac99e3e31377353a90a" + "reference": "f347f223a7235178f056f34dc104557095998614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b1165b76fe8d451783d63ac99e3e31377353a90a", - "reference": "b1165b76fe8d451783d63ac99e3e31377353a90a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f347f223a7235178f056f34dc104557095998614", + "reference": "f347f223a7235178f056f34dc104557095998614", "shasum": "" }, "require": { @@ -1458,7 +1458,7 @@ "type": "github" } ], - "time": "2024-09-24T12:23:49+00:00" + "time": "2024-09-30T19:33:02+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -1466,12 +1466,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c" + "reference": "e208c9311872047b903511e2e03cb0df795014b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/63956f7896780551ed1ab29e75a6a645d8a0919c", - "reference": "63956f7896780551ed1ab29e75a6a645d8a0919c", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e208c9311872047b903511e2e03cb0df795014b0", + "reference": "e208c9311872047b903511e2e03cb0df795014b0", "shasum": "" }, "require": { @@ -1507,7 +1507,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" }, - "time": "2024-09-24T15:32:27+00:00" + "time": "2024-09-30T19:35:25+00:00" }, { "name": "psr/cache", From 1b1da3e2ce3acf10dde03d9656638cda4f7389a4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:40:28 +0200 Subject: [PATCH 0560/3097] Config option `exceptions.check.tooWideThrowType` made true by default --- conf/config.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/config.neon b/conf/config.neon index 5ab30e7e537..d6272372827 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -19,7 +19,7 @@ parameters: checkedExceptionClasses: [] check: missingCheckedExceptionInThrows: false - tooWideThrowType: false + tooWideThrowType: true featureToggles: bleedingEdge: false skipCheckGenericClasses: [] From d7798d7f2c47f426efe91c566e6cafd5a4e2410c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:42:26 +0200 Subject: [PATCH 0561/3097] Rules about tooWideThrowType moved to level 4 --- conf/config.level4.neon | 12 ++++++++++++ conf/config.neon | 10 ---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 56364170466..e77344de282 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -25,6 +25,12 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWidePropertyTypeRule - PHPStan\Rules\Traits\NotAnalysedTraitRule +conditionalTags: + PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule: + phpstan.rules.rule: %exceptions.check.tooWideThrowType% + PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule: + phpstan.rules.rule: %exceptions.check.tooWideThrowType% + parameters: checkAdvancedIsset: true @@ -228,6 +234,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule + + - + class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule + - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/conf/config.neon b/conf/config.neon index d6272372827..e5082b1ce4c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -205,10 +205,6 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% - PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule: - phpstan.rules.rule: %exceptions.check.tooWideThrowType% - PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule: - phpstan.rules.rule: %exceptions.check.tooWideThrowType% services: - @@ -881,12 +877,6 @@ services: arguments: exceptionTypeResolver: @exceptionTypeResolver - - - class: PHPStan\Rules\Exceptions\TooWideFunctionThrowTypeRule - - - - class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule - - class: PHPStan\Rules\Exceptions\TooWideThrowTypeCheck From b0858332efc7aa2f2fde7544a2a821ba81bde13b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Sep 2024 21:52:02 +0200 Subject: [PATCH 0562/3097] Printer is covered by BC promise --- src/Node/Printer/Printer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 78455184f0b..131376d66d3 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -19,6 +19,9 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; +/** + * @api + */ final class Printer extends Standard { From 3b6d0612c2165286000a99c404136e2293344128 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:56:55 +0000 Subject: [PATCH 0563/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8d69f8840b7..6b268bb1463 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.9", - "phpstan/php-8-stubs": "0.3.111", + "phpstan/php-8-stubs": "0.4.0", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 60fb2a048d4..35ad14bc749 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "108d46d88ea46d66d8fab6acc4765c04", + "content-hash": "9485ba4e0af44d8602eb360c34f92b8d", "packages": [ { "name": "clue/ndjson-react", @@ -2250,16 +2250,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.3.111", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "0013252145df5d84112764d4ea57ed1c6f074018" + "reference": "693817d86d0d0de1d39b97a70bff4fa728384aa1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/0013252145df5d84112764d4ea57ed1c6f074018", - "reference": "0013252145df5d84112764d4ea57ed1c6f074018", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/693817d86d0d0de1d39b97a70bff4fa728384aa1", + "reference": "693817d86d0d0de1d39b97a70bff4fa728384aa1", "shasum": "" }, "type": "library", @@ -2276,9 +2276,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.111" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.0" }, - "time": "2024-09-29T00:21:10+00:00" + "time": "2024-09-30T19:56:21+00:00" }, { "name": "phpstan/phpdoc-parser", From 1d3f4313955dc6fa5c6ce60fa58afe765964e5b0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 09:19:43 +0200 Subject: [PATCH 0564/3097] Collected PHP errors cannot be ignored --- src/Analyser/FileAnalyser.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 8d8cf590162..570c6370927 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -31,6 +31,7 @@ use const E_NOTICE; use const E_PARSE; use const E_STRICT; +use const E_USER_DEPRECATED; use const E_USER_ERROR; use const E_USER_NOTICE; use const E_USER_WARNING; @@ -322,7 +323,7 @@ private function collectErrors(array $analysedFiles): void $errorMessage = sprintf('%s: %s', $this->getErrorLabel($errno), $errstr); - $this->allPhpErrors[] = (new Error($errorMessage, $errfile, $errline, true))->withIdentifier('phpstan.php'); + $this->allPhpErrors[] = (new Error($errorMessage, $errfile, $errline, false))->withIdentifier('phpstan.php'); if ($errno === E_DEPRECATED) { return true; @@ -332,7 +333,7 @@ private function collectErrors(array $analysedFiles): void return true; } - $this->filteredPhpErrors[] = (new Error($errorMessage, $errfile, $errline, true))->withIdentifier('phpstan.php'); + $this->filteredPhpErrors[] = (new Error($errorMessage, $errfile, $errline, $errno === E_USER_DEPRECATED))->withIdentifier('phpstan.php'); return true; }); @@ -354,12 +355,16 @@ private function getErrorLabel(int $errno): string return 'Parse error'; case E_NOTICE: return 'Notice'; + case E_DEPRECATED: + return 'Deprecated'; case E_USER_ERROR: return 'User error (E_USER_ERROR)'; case E_USER_WARNING: return 'User warning (E_USER_WARNING)'; case E_USER_NOTICE: return 'User notice (E_USER_NOTICE)'; + case E_USER_DEPRECATED: + return 'Deprecated (E_USER_DEPRECATED)'; case E_STRICT: return 'Strict error (E_STRICT)'; } From d899a429adc44779751b23c99a4c359f6cd9b775 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 09:25:58 +0200 Subject: [PATCH 0565/3097] Move UselessFunctionReturnValueRule to level 4 --- changelog-2.0.md | 2 +- conf/config.level0.neon | 1 - conf/config.level4.neon | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index d9fe72d6c90..3b76ab17665 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -32,7 +32,7 @@ Major new features 🚀 * MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! -* Report useless return values of function calls like `var_export` without `$return=true` (level 0) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! +* Report useless return values of function calls like `var_export` without `$return=true` (level 4) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! * Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! * Report useless `array_filter()` calls (level 5) ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! * Report useless `array_values()` calls (level 5) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d8b1b775cc5..30d798cdaa7 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -67,7 +67,6 @@ rules: - PHPStan\Rules\Functions\PrintfParametersRule - PHPStan\Rules\Functions\RedefinedParametersRule - PHPStan\Rules\Functions\ReturnNullsafeByRefRule - - PHPStan\Rules\Functions\UselessFunctionReturnValueRule - PHPStan\Rules\Ignore\IgnoreParseErrorRule - PHPStan\Rules\Functions\VariadicParametersDeclarationRule - PHPStan\Rules\Keywords\ContinueBreakInLoopRule diff --git a/conf/config.level4.neon b/conf/config.level4.neon index e77344de282..bda46632c20 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -13,6 +13,7 @@ rules: - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - PHPStan\Rules\Functions\CallToFunctionStatementWithoutSideEffectsRule + - PHPStan\Rules\Functions\UselessFunctionReturnValueRule - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToMethodStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToStaticMethodStatementWithoutSideEffectsRule From 4b974a4698f9477a6530c4a9a78f906278d24419 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 09:28:25 +0200 Subject: [PATCH 0566/3097] Moved RegularExpressionQuotingRule to level 5 --- changelog-2.0.md | 2 +- conf/config.level0.neon | 1 - conf/config.level5.neon | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 3b76ab17665..9243a742880 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -28,7 +28,7 @@ Major new features 🚀 * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! * LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 * Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) -* Check preg_quote delimiter sanity (level 0) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! +* Check preg_quote delimiter sanity (level 5) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! * MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! * MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! * Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 30d798cdaa7..fbad323697c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -98,7 +98,6 @@ rules: - PHPStan\Rules\Properties\ReadOnlyPropertyRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule - PHPStan\Rules\Regexp\RegularExpressionPatternRule - - PHPStan\Rules\Regexp\RegularExpressionQuotingRule - PHPStan\Rules\Traits\ConflictingTraitConstantsRule - PHPStan\Rules\Traits\ConstantsInTraitsRule - PHPStan\Rules\Types\InvalidTypesInUnionRule diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 0cbccea5d6b..b89a6dbddf5 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -11,6 +11,7 @@ rules: - PHPStan\Rules\Functions\ParameterCastableToStringRule - PHPStan\Rules\Functions\ImplodeParameterCastableToStringRule - PHPStan\Rules\Functions\SortParameterCastableToStringRule + - PHPStan\Rules\Regexp\RegularExpressionQuotingRule services: - From a0e688c1d1e4c5e82f989b26485eb9162f47aa97 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 09:35:21 +0200 Subject: [PATCH 0567/3097] Use `implicitThrows` to only look for explicit throw points in too-wide `@throws` rules when set to `false` --- conf/config.neon | 2 ++ .../Exceptions/TooWideThrowTypeCheck.php | 8 +++-- .../TooWideFunctionThrowTypeRuleTest.php | 4 ++- .../TooWideMethodThrowTypeRuleTest.php | 31 ++++++++++++++++++- .../data/too-wide-throws-explicit.php | 22 +++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Exceptions/data/too-wide-throws-explicit.php diff --git a/conf/config.neon b/conf/config.neon index e5082b1ce4c..08a5c8c0996 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -879,6 +879,8 @@ services: - class: PHPStan\Rules\Exceptions\TooWideThrowTypeCheck + arguments: + implicitThrows: %exceptions.implicitThrows% - class: PHPStan\Rules\FunctionCallParametersCheck diff --git a/src/Rules/Exceptions/TooWideThrowTypeCheck.php b/src/Rules/Exceptions/TooWideThrowTypeCheck.php index 830d0f9a62b..5c5e33c8031 100644 --- a/src/Rules/Exceptions/TooWideThrowTypeCheck.php +++ b/src/Rules/Exceptions/TooWideThrowTypeCheck.php @@ -13,6 +13,10 @@ final class TooWideThrowTypeCheck { + public function __construct(private bool $implicitThrows) + { + } + /** * @param ThrowPoint[] $throwPoints * @return string[] @@ -23,8 +27,8 @@ public function check(Type $throwType, array $throwPoints): array return []; } - $throwPointType = TypeCombinator::union(...array_map(static function (ThrowPoint $throwPoint): Type { - if (!$throwPoint->isExplicit()) { + $throwPointType = TypeCombinator::union(...array_map(function (ThrowPoint $throwPoint): Type { + if (!$this->implicitThrows && !$throwPoint->isExplicit()) { return new NeverType(); } diff --git a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php index 82871e61453..affb0fc679e 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -11,9 +11,11 @@ class TooWideFunctionThrowTypeRuleTest extends RuleTestCase { + private bool $implicitThrows = true; + protected function getRule(): Rule { - return new TooWideFunctionThrowTypeRule(new TooWideThrowTypeCheck()); + return new TooWideFunctionThrowTypeRule(new TooWideThrowTypeCheck($this->implicitThrows)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php index 5a323519860..1ebd091d9d7 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php @@ -13,9 +13,11 @@ class TooWideMethodThrowTypeRuleTest extends RuleTestCase { + private bool $implicitThrows = true; + protected function getRule(): Rule { - return new TooWideMethodThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck()); + return new TooWideMethodThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck($this->implicitThrows)); } public function testRule(): void @@ -72,4 +74,31 @@ public function testFirstClassCallable(): void $this->analyse([__DIR__ . '/data/immediately-called-fcc.php'], []); } + public static function dataRuleLookOnlyForExplicitThrowPoints(): iterable + { + yield [ + true, + [], + ]; + yield [ + false, + [ + [ + 'Method TooWideThrowsExplicit\Foo::doFoo() has Exception in PHPDoc @throws tag but it\'s not thrown.', + 11, + ], + ], + ]; + } + + /** + * @dataProvider dataRuleLookOnlyForExplicitThrowPoints + * @param list $errors + */ + public function testRuleLookOnlyForExplicitThrowPoints(bool $implicitThrows, array $errors): void + { + $this->implicitThrows = $implicitThrows; + $this->analyse([__DIR__ . '/data/too-wide-throws-explicit.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-explicit.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-explicit.php new file mode 100644 index 00000000000..834df2b1f01 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-explicit.php @@ -0,0 +1,22 @@ +doBar(); + } + + public function doBar(): void + { + + } + +} From 7ba8abcba1e9687c7c5eee38571e2fae09e5edd8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 09:55:29 +0200 Subject: [PATCH 0568/3097] [BCB] `acceptsWithReason` renamed to `accepts` --- UPGRADING.md | 3 ++ .../ValueAssignedToClassConstantRule.php | 4 +-- src/Rules/FunctionCallParametersCheck.php | 2 +- src/Rules/FunctionReturnTypeCheck.php | 2 +- ...eArrowFunctionDefaultParameterTypeRule.php | 2 +- ...patibleClosureDefaultParameterTypeRule.php | 2 +- .../IncompatibleDefaultParameterTypeRule.php | 2 +- src/Rules/Generators/YieldFromTypeRule.php | 4 +-- src/Rules/Generators/YieldTypeRule.php | 4 +-- .../IncompatibleDefaultParameterTypeRule.php | 2 +- ...aultValueTypesAssignedToPropertiesRule.php | 2 +- .../TypesAssignedToPropertiesRule.php | 2 +- src/Rules/RuleLevelHelper.php | 11 ++----- src/Rules/RuleLevelHelperAcceptsResult.php | 3 ++ src/Type/Accessory/AccessoryArrayListType.php | 16 ++-------- .../Accessory/AccessoryLiteralStringType.php | 16 ++-------- .../AccessoryLowercaseStringType.php | 16 ++-------- .../Accessory/AccessoryNonEmptyStringType.php | 16 ++-------- .../Accessory/AccessoryNonFalsyStringType.php | 16 ++-------- .../Accessory/AccessoryNumericStringType.php | 16 ++-------- src/Type/Accessory/HasMethodType.php | 16 ++-------- src/Type/Accessory/HasOffsetType.php | 16 ++-------- src/Type/Accessory/HasOffsetValueType.php | 18 +++-------- src/Type/Accessory/HasPropertyType.php | 16 ++-------- src/Type/Accessory/NonEmptyArrayType.php | 16 ++-------- src/Type/Accessory/OversizedArrayType.php | 16 ++-------- src/Type/ArrayType.php | 17 ++++------ src/Type/BenevolentUnionType.php | 9 ++---- src/Type/CallableType.php | 16 ++-------- src/Type/CallableTypeHelper.php | 4 +-- src/Type/ClassStringType.php | 9 ++---- src/Type/ClosureType.php | 11 ++----- src/Type/CompoundType.php | 4 +-- src/Type/Constant/ConstantArrayType.php | 11 ++----- src/Type/Enum/EnumCaseObjectType.php | 7 +---- src/Type/FloatType.php | 9 ++---- src/Type/Generic/GenericClassStringType.php | 6 ++-- src/Type/Generic/GenericObjectType.php | 13 +++----- src/Type/Generic/TemplateMixedType.php | 7 +---- src/Type/Generic/TemplateStrictMixedType.php | 7 +---- src/Type/Generic/TemplateType.php | 5 +-- .../Generic/TemplateTypeArgumentStrategy.php | 4 +-- .../Generic/TemplateTypeParameterStrategy.php | 4 +-- src/Type/Generic/TemplateTypeTrait.php | 31 +++++-------------- src/Type/Generic/TemplateTypeVariance.php | 10 ++---- src/Type/IntegerRangeType.php | 16 ++-------- src/Type/IntersectionType.php | 18 +++-------- src/Type/IterableType.php | 20 +++--------- src/Type/JustNullableTypeTrait.php | 9 ++---- src/Type/MixedType.php | 14 ++------- src/Type/NeverType.php | 14 ++------- src/Type/NonAcceptingNeverType.php | 2 +- src/Type/NullType.php | 9 ++---- src/Type/ObjectShapeType.php | 11 ++----- src/Type/ObjectType.php | 9 ++---- src/Type/ObjectWithoutClassType.php | 9 ++---- src/Type/StaticType.php | 11 ++----- src/Type/StrictMixedType.php | 14 ++------- ...gAlwaysAcceptingObjectWithToStringType.php | 4 +-- src/Type/StringType.php | 9 ++---- src/Type/Traits/ConstantScalarTypeTrait.php | 11 ++----- src/Type/Traits/LateResolvableTypeTrait.php | 18 +++-------- src/Type/Type.php | 4 +-- src/Type/UnionType.php | 24 +++++--------- src/Type/VoidType.php | 9 ++---- tests/PHPStan/Type/ArrayTypeTest.php | 2 +- tests/PHPStan/Type/BooleanTypeTest.php | 2 +- tests/PHPStan/Type/CallableTypeTest.php | 2 +- tests/PHPStan/Type/ClassStringTypeTest.php | 2 +- .../Type/Constant/ConstantArrayTypeTest.php | 2 +- .../Type/Constant/ConstantIntegerTypeTest.php | 2 +- .../Type/Enum/EnumCaseObjectTypeTest.php | 2 +- tests/PHPStan/Type/FloatTypeTest.php | 2 +- .../Generic/GenericClassStringTypeTest.php | 2 +- .../Type/Generic/GenericObjectTypeTest.php | 2 +- .../Type/Generic/TemplateTypeVarianceTest.php | 5 +-- tests/PHPStan/Type/IntegerTypeTest.php | 2 +- tests/PHPStan/Type/IntersectionTypeTest.php | 2 +- tests/PHPStan/Type/IterableTypeTest.php | 2 +- tests/PHPStan/Type/ObjectTypeTest.php | 2 +- tests/PHPStan/Type/StringTypeTest.php | 2 +- tests/PHPStan/Type/TemplateTypeTest.php | 4 +-- tests/PHPStan/Type/UnionTypeTest.php | 2 +- 83 files changed, 184 insertions(+), 515 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 679ada1d759..cdaad24fcfe 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -290,3 +290,6 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `tempResultCachePath` config parameter, use `resultCachePath` instead * `LevelsTestCase::dataTopics()` data provider made static * `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint +* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) diff --git a/src/Rules/Constants/ValueAssignedToClassConstantRule.php b/src/Rules/Constants/ValueAssignedToClassConstantRule.php index afdbf2c5e74..728b5a039f5 100644 --- a/src/Rules/Constants/ValueAssignedToClassConstantRule.php +++ b/src/Rules/Constants/ValueAssignedToClassConstantRule.php @@ -63,7 +63,7 @@ private function processSingleConstant(ClassReflection $classReflection, string return []; } - $accepts = $nativeType->acceptsWithReason($valueExprType, true); + $accepts = $nativeType->accepts($valueExprType, true); if ($accepts->yes()) { return []; } @@ -107,7 +107,7 @@ private function processSingleConstant(ClassReflection $classReflection, string } $type = $constantReflection->getValueType(); - $accepts = $type->acceptsWithReason($valueExprType, true); + $accepts = $type->accepts($valueExprType, true); if ($accepts->yes()) { return []; } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 787d6136da5..aa8b9f356ae 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -312,7 +312,7 @@ public function check( $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); if (!$parameter->passedByReference()->createsNewVariable() || !$isBuiltin) { - $accepts = $this->ruleLevelHelper->acceptsWithReason($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()); + $accepts = $this->ruleLevelHelper->accepts($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()); if (!$accepts->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType); diff --git a/src/Rules/FunctionReturnTypeCheck.php b/src/Rules/FunctionReturnTypeCheck.php index be2eb86b1aa..994b3943f25 100644 --- a/src/Rules/FunctionReturnTypeCheck.php +++ b/src/Rules/FunctionReturnTypeCheck.php @@ -90,7 +90,7 @@ public function checkReturnType( ]; } - $accepts = $this->ruleLevelHelper->acceptsWithReason($returnType, $returnValueType, $scope->isDeclareStrictTypes()); + $accepts = $this->ruleLevelHelper->accepts($returnType, $returnValueType, $scope->isDeclareStrictTypes()); if (!$accepts->result) { return [ RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php index 7d5967fb2b1..80ab58e8bb0 100644 --- a/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleArrowFunctionDefaultParameterTypeRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array $parameterType = $parameters[$paramI]->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $accepts = $parameterType->acceptsWithReason($defaultValueType, true); + $accepts = $parameterType->accepts($defaultValueType, true); if ($accepts->yes()) { continue; } diff --git a/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php index ab3565a612c..f4d39650268 100644 --- a/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleClosureDefaultParameterTypeRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array $parameterType = $parameters[$paramI]->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $accepts = $parameterType->acceptsWithReason($defaultValueType, true); + $accepts = $parameterType->accepts($defaultValueType, true); if ($accepts->yes()) { continue; } diff --git a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php index af9d403b348..68f9fffc6d7 100644 --- a/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Functions/IncompatibleDefaultParameterTypeRule.php @@ -43,7 +43,7 @@ public function processNode(Node $node, Scope $scope): array $parameterType = $function->getParameters()[$paramI]->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $accepts = $parameterType->acceptsWithReason($defaultValueType, true); + $accepts = $parameterType->accepts($defaultValueType, true); if ($accepts->yes()) { continue; } diff --git a/src/Rules/Generators/YieldFromTypeRule.php b/src/Rules/Generators/YieldFromTypeRule.php index b73b5a35090..5bac445f13e 100644 --- a/src/Rules/Generators/YieldFromTypeRule.php +++ b/src/Rules/Generators/YieldFromTypeRule.php @@ -78,7 +78,7 @@ public function processNode(Node $node, Scope $scope): array } $messages = []; - $acceptsKey = $this->ruleLevelHelper->acceptsWithReason($returnType->getIterableKeyType(), $exprType->getIterableKeyType(), $scope->isDeclareStrictTypes()); + $acceptsKey = $this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $exprType->getIterableKeyType(), $scope->isDeclareStrictTypes()); if (!$acceptsKey->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $exprType->getIterableKeyType()); $messages[] = RuleErrorBuilder::message(sprintf( @@ -92,7 +92,7 @@ public function processNode(Node $node, Scope $scope): array ->build(); } - $acceptsValue = $this->ruleLevelHelper->acceptsWithReason($returnType->getIterableValueType(), $exprType->getIterableValueType(), $scope->isDeclareStrictTypes()); + $acceptsValue = $this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $exprType->getIterableValueType(), $scope->isDeclareStrictTypes()); if (!$acceptsValue->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $exprType->getIterableValueType()); $messages[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Generators/YieldTypeRule.php b/src/Rules/Generators/YieldTypeRule.php index c8475e23a83..eccc960d2ae 100644 --- a/src/Rules/Generators/YieldTypeRule.php +++ b/src/Rules/Generators/YieldTypeRule.php @@ -53,7 +53,7 @@ public function processNode(Node $node, Scope $scope): array } $messages = []; - $acceptsKey = $this->ruleLevelHelper->acceptsWithReason($returnType->getIterableKeyType(), $keyType, $scope->isDeclareStrictTypes()); + $acceptsKey = $this->ruleLevelHelper->accepts($returnType->getIterableKeyType(), $keyType, $scope->isDeclareStrictTypes()); if (!$acceptsKey->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableKeyType(), $keyType); $messages[] = RuleErrorBuilder::message(sprintf( @@ -72,7 +72,7 @@ public function processNode(Node $node, Scope $scope): array $valueType = $scope->getType($node->value); } - $acceptsValue = $this->ruleLevelHelper->acceptsWithReason($returnType->getIterableValueType(), $valueType, $scope->isDeclareStrictTypes()); + $acceptsValue = $this->ruleLevelHelper->accepts($returnType->getIterableValueType(), $valueType, $scope->isDeclareStrictTypes()); if (!$acceptsValue->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($returnType->getIterableValueType(), $valueType); $messages[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php index d8814af6012..85059abcac4 100644 --- a/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php +++ b/src/Rules/Methods/IncompatibleDefaultParameterTypeRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array $parameterType = $parameter->getType(); $parameterType = TemplateTypeHelper::resolveToBounds($parameterType); - $accepts = $parameterType->acceptsWithReason($defaultValueType, true); + $accepts = $parameterType->accepts($defaultValueType, true); if ($accepts->yes()) { continue; } diff --git a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php index 3076d2d3d86..63cd185b7ad 100644 --- a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array } } $defaultValueType = $scope->getType($default); - $accepts = $this->ruleLevelHelper->acceptsWithReason($propertyType, $defaultValueType, true); + $accepts = $this->ruleLevelHelper->accepts($propertyType, $defaultValueType, true); if ($accepts->result) { return []; } diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index bb2c2e83c09..f043cf3c3e3 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -63,7 +63,7 @@ private function processSingleProperty( $scope = $propertyReflection->getScope(); $assignedValueType = $scope->getType($assignedExpr); - $accepts = $this->ruleLevelHelper->acceptsWithReason($propertyType, $assignedValueType, $scope->isDeclareStrictTypes()); + $accepts = $this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes()); if (!$accepts->result) { $propertyDescription = $this->describePropertyByName($propertyReflection, $propertyReflection->getName()); $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedValueType); diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 1fe931aca52..94e1c914b22 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -46,12 +46,6 @@ public function isThis(Expr $expression): bool return $expression instanceof Expr\Variable && $expression->name === 'this'; } - /** @api */ - public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTypes): bool - { - return $this->acceptsWithReason($acceptingType, $acceptedType, $strictTypes)->result; - } - private function transformCommonType(Type $type): Type { if (!$this->checkExplicitMixed && !$this->checkImplicitMixed) { @@ -144,12 +138,13 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType): return [$acceptedType, $checkForUnion]; } - public function acceptsWithReason(Type $acceptingType, Type $acceptedType, bool $strictTypes): RuleLevelHelperAcceptsResult + /** @api */ + public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTypes): RuleLevelHelperAcceptsResult { [$acceptedType, $checkForUnion] = $this->transformAcceptedType($acceptingType, $acceptedType); $acceptingType = $this->transformCommonType($acceptingType); - $accepts = $acceptingType->acceptsWithReason($acceptedType, $strictTypes); + $accepts = $acceptingType->accepts($acceptedType, $strictTypes); return new RuleLevelHelperAcceptsResult( $checkForUnion ? $accepts->yes() : !$accepts->no(), diff --git a/src/Rules/RuleLevelHelperAcceptsResult.php b/src/Rules/RuleLevelHelperAcceptsResult.php index e33db8f0dab..1b421c60a4f 100644 --- a/src/Rules/RuleLevelHelperAcceptsResult.php +++ b/src/Rules/RuleLevelHelperAcceptsResult.php @@ -4,6 +4,9 @@ use function array_merge; +/** + * @api + */ final class RuleLevelHelperAcceptsResult { diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index d41d3c510f5..637b8e75b0b 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -74,15 +74,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } $isArray = $type->isArray(); @@ -116,12 +111,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 5f8705cfc52..52c2eeba10f 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -67,18 +67,13 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof MixedType) { return AcceptsResult::createNo(); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isLiteralString(), []); @@ -107,12 +102,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 48bb4b31798..2e4c9406fcd 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -66,15 +66,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isLowercaseString(), []); @@ -103,12 +98,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 98152250621..61b9b601078 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -68,15 +68,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isNonEmptyString(), []); @@ -109,12 +104,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 6dca5f0514b..e2a08f54e0f 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -68,15 +68,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isNonFalsyString(), []); @@ -109,12 +104,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index ae15b4623ef..b319a340fd4 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -67,15 +67,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isNumericString(), []); @@ -104,12 +99,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { if ($acceptingType->isNonFalsyString()->yes()) { return AcceptsResult::createMaybe(); diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index a28497912d3..f556022dcaf 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -61,15 +61,10 @@ private function getCanonicalMethodName(): string return strtolower($this->methodName); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createFromBoolean($this->equals($type)); @@ -99,12 +94,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return $limit->and($otherType->hasMethod($this->methodName)); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 132ae28edd7..0903b3e8778 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -77,15 +77,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); @@ -111,12 +106,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index ec3a9908d1c..a762290d5b7 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -78,21 +78,16 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult( $type->isOffsetAccessible() ->and($type->hasOffsetValueType($this->offsetType)) - ->and($this->valueType->accepts($type->getOffsetValueType($this->offsetType), $strictTypes)), + ->and($this->valueType->accepts($type->getOffsetValueType($this->offsetType), $strictTypes)->result), [], ); } @@ -119,12 +114,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index fdbc9b0bccc..f65b2bbe48c 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -63,15 +63,10 @@ public function getPropertyName(): string return $this->propertyName; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createFromBoolean($this->equals($type)); @@ -97,12 +92,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return $limit->and($otherType->hasProperty($this->propertyName)); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 50ccdb308e9..2afc15de798 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -72,15 +72,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } $isArray = $type->isArray(); @@ -114,12 +109,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 113d0eb689a..0d49eaf6cb5 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -71,15 +71,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isArray()->and($type->isIterableAtLeastOnce()), []); @@ -110,12 +105,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 93d0094378a..97ebb9a1963 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -80,15 +80,10 @@ public function getConstantArrays(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof ConstantArrayType) { @@ -97,8 +92,8 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $itemType = $this->getItemType(); foreach ($type->getKeyTypes() as $i => $keyType) { $valueType = $type->getValueTypes()[$i]; - $acceptsKey = $thisKeyType->acceptsWithReason($keyType, $strictTypes); - $acceptsValue = $itemType->acceptsWithReason($valueType, $strictTypes); + $acceptsKey = $thisKeyType->accepts($keyType, $strictTypes); + $acceptsValue = $itemType->accepts($valueType, $strictTypes); $result = $result->and($acceptsKey)->and($acceptsValue); } @@ -106,8 +101,8 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } if ($type instanceof ArrayType) { - return $this->getItemType()->acceptsWithReason($type->getItemType(), $strictTypes) - ->and($this->keyType->acceptsWithReason($type->keyType, $strictTypes)); + return $this->getItemType()->accepts($type->getItemType(), $strictTypes) + ->and($this->keyType->accepts($type->keyType, $strictTypes)); } return AcceptsResult::createNo(); diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index c27a3d6cd0d..b4ed647e252 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -87,16 +87,11 @@ protected function unionResults(callable $getResult): TrinaryLogic return TrinaryLogic::createNo()->lazyOr($this->getTypes(), $getResult); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { $result = AcceptsResult::createNo(); foreach ($this->getTypes() as $innerType) { - $result = $result->or($acceptingType->acceptsWithReason($innerType, $strictTypes)); + $result = $result->or($acceptingType->accepts($innerType, $strictTypes)); } return $result; diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 0f733ca60a2..325a195d279 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -129,15 +129,10 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType && !$type instanceof self) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $this->isSuperTypeOfInternal($type, true); @@ -204,12 +199,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 29f416d3aa4..e8253d1bfbe 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -72,7 +72,7 @@ public static function isParametersAcceptorSuperTypeOf( } if ($treatMixedAsAny) { - $isSuperType = $theirParameter->getType()->acceptsWithReason($ourParameterType, true); + $isSuperType = $theirParameter->getType()->accepts($ourParameterType, true); } else { $isSuperType = new AcceptsResult($theirParameter->getType()->isSuperTypeOf($ourParameterType), []); } @@ -98,7 +98,7 @@ public static function isParametersAcceptorSuperTypeOf( $theirReturnType = $theirs->getReturnType(); if ($treatMixedAsAny) { - $isReturnTypeSuperType = $ours->getReturnType()->acceptsWithReason($theirReturnType, true); + $isReturnTypeSuperType = $ours->getReturnType()->accepts($theirReturnType, true); } else { $isReturnTypeSuperType = new AcceptsResult($ours->getReturnType()->isSuperTypeOf($theirReturnType), []); } diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 4a74ec015ad..41a9d16c741 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -21,15 +21,10 @@ public function describe(VerbosityLevel $level): string return 'class-string'; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isClassString(), []); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 90bef922557..ccd6c6cccb6 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -188,19 +188,14 @@ public function getObjectClassReflections(): array return $this->objectType->getObjectClassReflections(); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if (!$type instanceof ClosureType) { - return $this->objectType->acceptsWithReason($type, $strictTypes); + return $this->objectType->accepts($type, $strictTypes); } return $this->isSuperTypeOfInternal($type, true); diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index 775a4eb50f8..8c6d8fd00df 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -11,9 +11,7 @@ interface CompoundType extends Type public function isSubTypeOf(Type $otherType): TrinaryLogic; - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic; - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult; + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult; public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 0d8d254effa..de722864897 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -297,15 +297,10 @@ public function isOptionalKey(int $i): bool return in_array($i, $this->optionalKeys, true); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType && !$type instanceof IntersectionType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof self && count($this->keyTypes) === 0) { @@ -333,7 +328,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $result = $result->and($hasOffset); $otherValueType = $type->getOffsetValueType($keyType); $verbosity = VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType); - $acceptsValue = $valueType->acceptsWithReason($otherValueType, $strictTypes)->decorateReasons( + $acceptsValue = $valueType->accepts($otherValueType, $strictTypes)->decorateReasons( static fn (string $reason) => sprintf( 'Offset %s (%s) does not accept type %s: %s', $keyType->describe(VerbosityLevel::precise()), diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 5bb5d0b5b76..cad4e394d1f 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -59,12 +59,7 @@ public function equals(Type $type): bool $this->getClassName() === $type->getClassName(); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSuperTypeOf($type), []); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 21d0a969048..a9bea4a0707 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -64,19 +64,14 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof self || $type->isInteger()->yes()) { return AcceptsResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createNo(); diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index f7ecec9d865..ea2f583d8fd 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -60,10 +60,10 @@ public function describe(VerbosityLevel $level): string return sprintf('%s<%s>', parent::describe($level), $this->type->describe($level)); } - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof ConstantStringType) { @@ -82,7 +82,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - return $this->type->acceptsWithReason($objectType, $strictTypes); + return $this->type->accepts($objectType, $strictTypes); } public function isSuperTypeOf(Type $type): TrinaryLogic diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index f13d52d4e3b..b73a7efc940 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -118,15 +118,10 @@ public function getVariances(): array return $this->variances; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $this->isSuperTypeOfInternal($type, true); @@ -192,9 +187,9 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Accept $thisVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant(); $ancestorVariance = $ancestor->variances[$i] ?? TemplateTypeVariance::createInvariant(); if (!$thisVariance->invariant()) { - $results[] = $thisVariance->isValidVarianceWithReason($templateType, $this->types[$i], $ancestor->types[$i]); + $results[] = $thisVariance->isValidVariance($templateType, $this->types[$i], $ancestor->types[$i]); } else { - $results[] = $templateType->isValidVarianceWithReason($this->types[$i], $ancestor->types[$i]); + $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); } $results[] = AcceptsResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 3363818673a..e0c579074c7 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -40,12 +40,7 @@ public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic return $this->isSuperTypeOf($type); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); if ($isSuperType->no()) { diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index 6ae56cc2284..897fb71524f 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -38,12 +38,7 @@ public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic return $this->isSuperTypeOf($type); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 7661078ca1b..367c94e7095 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -2,7 +2,6 @@ namespace PHPStan\Type\Generic; -use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; use PHPStan\Type\Type; @@ -22,9 +21,7 @@ public function toArgument(): TemplateType; public function isArgument(): bool; - public function isValidVariance(Type $a, Type $b): TrinaryLogic; - - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult; + public function isValidVariance(Type $a, Type $b): AcceptsResult; public function getVariance(): TemplateTypeVariance; diff --git a/src/Type/Generic/TemplateTypeArgumentStrategy.php b/src/Type/Generic/TemplateTypeArgumentStrategy.php index 8c98baaacc7..0e8f59cf5e2 100644 --- a/src/Type/Generic/TemplateTypeArgumentStrategy.php +++ b/src/Type/Generic/TemplateTypeArgumentStrategy.php @@ -18,9 +18,9 @@ final class TemplateTypeArgumentStrategy implements TemplateTypeStrategy public function accepts(TemplateType $left, Type $right, bool $strictTypes): AcceptsResult { if ($right instanceof CompoundType) { - $accepts = $right->isAcceptedWithReasonBy($left, $strictTypes); + $accepts = $right->isAcceptedBy($left, $strictTypes); } else { - $accepts = $left->getBound()->acceptsWithReason($right, $strictTypes) + $accepts = $left->getBound()->accepts($right, $strictTypes) ->and(AcceptsResult::createMaybe()); if ($accepts->maybe()) { $verbosity = VerbosityLevel::getRecommendedLevelByType($left, $right); diff --git a/src/Type/Generic/TemplateTypeParameterStrategy.php b/src/Type/Generic/TemplateTypeParameterStrategy.php index 949f3bfa52a..3e18bccf2d6 100644 --- a/src/Type/Generic/TemplateTypeParameterStrategy.php +++ b/src/Type/Generic/TemplateTypeParameterStrategy.php @@ -15,10 +15,10 @@ final class TemplateTypeParameterStrategy implements TemplateTypeStrategy public function accepts(TemplateType $left, Type $right, bool $strictTypes): AcceptsResult { if ($right instanceof CompoundType) { - return $right->isAcceptedWithReasonBy($left, $strictTypes); + return $right->isAcceptedBy($left, $strictTypes); } - return $left->getBound()->acceptsWithReason($right, $strictTypes); + return $left->getBound()->accepts($right, $strictTypes); } public function isArgument(): bool diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index e1a170a57d5..979867b4581 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -91,14 +91,9 @@ public function toArgument(): TemplateType ); } - public function isValidVariance(Type $a, Type $b): TrinaryLogic + public function isValidVariance(Type $a, Type $b): AcceptsResult { - return $this->isValidVarianceWithReason($a, $b)->result; - } - - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult - { - return $this->variance->isValidVarianceWithReason($this, $a, $b); + return $this->variance->isValidVariance($this, $a, $b); } public function subtract(Type $typeToRemove): Type @@ -163,12 +158,7 @@ public function equals(Type $type): bool && $this->bound->equals($type->bound); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { /** @var TBound $bound */ $bound = $this->getBound(); @@ -178,27 +168,22 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): && !$acceptingType instanceof TemplateType && ($acceptingType instanceof UnionType || $acceptingType instanceof IntersectionType) ) { - return $acceptingType->acceptsWithReason($this, $strictTypes); + return $acceptingType->accepts($this, $strictTypes); } if (!$acceptingType instanceof TemplateType) { - return $acceptingType->acceptsWithReason($this->getBound(), $strictTypes); + return $acceptingType->accepts($this->getBound(), $strictTypes); } if ($this->getScope()->equals($acceptingType->getScope()) && $this->getName() === $acceptingType->getName()) { - return $acceptingType->getBound()->acceptsWithReason($this->getBound(), $strictTypes); + return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes); } - return $acceptingType->getBound()->acceptsWithReason($this->getBound(), $strictTypes) + return $acceptingType->getBound()->accepts($this->getBound(), $strictTypes) ->and(new AcceptsResult(TrinaryLogic::createMaybe(), [])); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return $this->strategy->accepts($this, $type, $strictTypes); } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 24f8fe29274..4f4c704aa03 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -126,12 +126,7 @@ public function compose(self $other): self return $other; } - public function isValidVariance(Type $a, Type $b): TrinaryLogic - { - return $this->isValidVarianceWithReason(null, $a, $b)->result; - } - - public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): AcceptsResult + public function isValidVariance(TemplateType $templateType, Type $a, Type $b): AcceptsResult { if ($b instanceof NeverType) { return AcceptsResult::createYes(); @@ -162,8 +157,7 @@ public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, $reasons = []; if (!$result) { if ( - $templateType !== null - && $templateType->getScope()->getClassName() !== null + $templateType->getScope()->getClassName() !== null && $a->isSuperTypeOf($b)->yes() ) { $reasons[] = sprintf( diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index e957bee8ea7..8c0031ca102 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -201,19 +201,14 @@ public function shift(int $amount): Type return self::fromInterval($min, $max); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof parent) { return new AcceptsResult($this->isSuperTypeOf($type), []); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createNo(); @@ -288,12 +283,7 @@ private function isSubTypeOfUnion(UnionType $otherType): TrinaryLogic return TrinaryLogic::createNo()->lazyOr($otherType->getTypes(), fn (Type $innerType) => $this->isSubTypeOf($innerType)); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 3c193d15f59..0117d149992 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -179,16 +179,11 @@ public function getConstantStrings(): array return $strings; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $otherType, bool $strictTypes): AcceptsResult + public function accepts(Type $otherType, bool $strictTypes): AcceptsResult { $result = AcceptsResult::createYes(); foreach ($this->types as $type) { - $result = $result->and($type->acceptsWithReason($otherType, $strictTypes)); + $result = $result->and($type->accepts($otherType, $strictTypes)); } if (!$result->yes()) { @@ -249,14 +244,9 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return $result; } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $result = AcceptsResult::maxMin(...array_map(static fn (Type $innerType) => $acceptingType->acceptsWithReason($innerType, $strictTypes), $this->types)); + $result = AcceptsResult::maxMin(...array_map(static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes), $this->types)); if ($this->isOversizedArray()->yes()) { if (!$result->no()) { return AcceptsResult::createYes(); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index b1dcbec2fe1..512f88ac7a5 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -78,23 +78,18 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) { return AcceptsResult::createYes(); } if ($type->isIterable()->yes()) { - return $this->getIterableValueType()->acceptsWithReason($type->getIterableValueType(), $strictTypes) - ->and($this->getIterableKeyType()->acceptsWithReason($type->getIterableKeyType(), $strictTypes)); + return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes) + ->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes)); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createNo(); @@ -168,12 +163,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic ); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 912dd684992..481e9fb3ac6 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -26,19 +26,14 @@ public function getObjectClassReflections(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof static) { return AcceptsResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 08f7f0f205c..43814c3643e 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -94,12 +94,7 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return AcceptsResult::createYes(); } @@ -332,12 +327,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); if ($isSuperType->no()) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 66193ded71f..50266db06bf 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -72,12 +72,7 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return AcceptsResult::createYes(); } @@ -101,12 +96,7 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return TrinaryLogic::createYes(); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { return new AcceptsResult($this->isSubTypeOf($acceptingType), []); } diff --git a/src/Type/NonAcceptingNeverType.php b/src/Type/NonAcceptingNeverType.php index 3eaddf53cb3..3424c664e5a 100644 --- a/src/Type/NonAcceptingNeverType.php +++ b/src/Type/NonAcceptingNeverType.php @@ -26,7 +26,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof NeverType) { return AcceptsResult::createYes(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 753dccc5d87..b8ba6be3367 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -72,19 +72,14 @@ public function generalize(GeneralizePrecision $precision): Type return $this; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof self) { return AcceptsResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createNo(); diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 6878401ef09..946fb597d1b 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -122,15 +122,10 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); @@ -207,7 +202,7 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $otherPropertyType = $otherProperty->getReadableType(); $verbosity = VerbosityLevel::getRecommendedLevelByType($propertyType, $otherPropertyType); - $acceptsValue = $propertyType->acceptsWithReason($otherPropertyType, $strictTypes)->decorateReasons( + $acceptsValue = $propertyType->accepts($otherPropertyType, $strictTypes)->decorateReasons( static fn (string $reason) => sprintf( 'Property ($%s) type %s does not accept type %s: %s', $propertyName, diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 2026e447f04..30f8be46409 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -278,19 +278,14 @@ public function getObjectClassReflections(): array return [$classReflection]; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof StaticType) { return $this->checkSubclassAcceptability($type->getClassName()); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof ClosureType) { diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 5b388ba712a..e9cd001bdc4 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -52,15 +52,10 @@ public function getObjectClassReflections(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return AcceptsResult::createFromBoolean( diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index ab8fc9d1a01..9c720b5e52a 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -132,22 +132,17 @@ public function getConstantStrings(): array return $this->getStaticObjectType()->getConstantStrings(); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if (!$type instanceof static) { return AcceptsResult::createNo(); } - return $this->getStaticObjectType()->acceptsWithReason($type->getStaticObjectType(), $strictTypes); + return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes); } public function isSuperTypeOf(Type $type): TrinaryLogic diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index c5d76697c62..1a269cb65fc 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -51,22 +51,12 @@ public function getConstantStrings(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return AcceptsResult::createYes(); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { if ($acceptingType instanceof self) { return AcceptsResult::createYes(); diff --git a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php index 0130d7e0948..c393a956d89 100644 --- a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php +++ b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php @@ -33,11 +33,11 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $result; } - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { $thatClassNames = $type->getObjectClassNames(); if ($thatClassNames === []) { - return parent::acceptsWithReason($type, $strictTypes); + return parent::accepts($type, $strictTypes); } $result = AcceptsResult::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 55fb28c2c1f..d6e48cd851c 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -109,19 +109,14 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof self) { return AcceptsResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } $thatClassNames = $type->getObjectClassNames(); diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 7452cd3c833..618b3b54949 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -16,22 +16,17 @@ trait ConstantScalarTypeTrait { - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof self) { return AcceptsResult::createFromBoolean($this->equals($type)); } if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } - return parent::acceptsWithReason($type, $strictTypes)->and(AcceptsResult::createMaybe()); + return parent::accepts($type, $strictTypes)->and(AcceptsResult::createMaybe()); } public function isSuperTypeOf(Type $type): TrinaryLogic diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 03120df9f42..f5d2b1dce2c 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -48,16 +48,11 @@ public function getConstantStrings(): array return $this->resolve()->getConstantStrings(); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic + public function accepts(Type $type, bool $strictTypes): AcceptsResult { return $this->resolve()->accepts($type, $strictTypes); } - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult - { - return $this->resolve()->acceptsWithReason($type, $strictTypes); - } - public function isSuperTypeOf(Type $type): TrinaryLogic { return $this->isSuperTypeOfDefault($type); @@ -528,20 +523,15 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return $otherType->isSuperTypeOf($result); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isAcceptedWithReasonBy($acceptingType, $strictTypes); + return $result->isAcceptedBy($acceptingType, $strictTypes); } - return $acceptingType->acceptsWithReason($result, $strictTypes); + return $acceptingType->accepts($result, $strictTypes); } public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic diff --git a/src/Type/Type.php b/src/Type/Type.php index 0f89e01a936..21b15c221b3 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -64,8 +64,6 @@ public function getConstantArrays(): array; /** @return list */ public function getConstantStrings(): array; - public function accepts(Type $type, bool $strictTypes): TrinaryLogic; - /** * This is like accepts() but gives reasons * why the type was not/might not be accepted in some non-intuitive scenarios. @@ -73,7 +71,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic; * In PHPStan 2.0 this method will be removed and the return type of accepts() * will change to AcceptsResult. */ - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult; + public function accepts(Type $type, bool $strictTypes): AcceptsResult; public function isSuperTypeOf(Type $type): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 454f511c753..704bd421b2c 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -161,12 +161,7 @@ public function getConstantStrings(): array ); } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ( $type->equals(new ObjectType(DateTimeInterface::class)) @@ -180,24 +175,24 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult $result = AcceptsResult::createNo(); foreach ($this->getSortedTypes() as $i => $innerType) { - $result = $result->or($innerType->acceptsWithReason($type, $strictTypes)->decorateReasons(static fn (string $reason) => sprintf('Type #%d from the union: %s', $i + 1, $reason))); + $result = $result->or($innerType->accepts($type, $strictTypes)->decorateReasons(static fn (string $reason) => sprintf('Type #%d from the union: %s', $i + 1, $reason))); } if ($result->yes()) { return $result; } if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateType && !$type instanceof IntersectionType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof TemplateUnionType) { - return $result->or($type->isAcceptedWithReasonBy($this, $strictTypes)); + return $result->or($type->isAcceptedBy($this, $strictTypes)); } if ($type->isEnum()->yes() && !$this->isEnum()->no()) { $enumCasesUnion = TypeCombinator::union(...$type->getEnumCases()); if (!$type->equals($enumCasesUnion)) { - return $this->acceptsWithReason($enumCasesUnion, $strictTypes); + return $this->accepts($enumCasesUnion, $strictTypes); } } @@ -234,14 +229,9 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); } - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic - { - return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; - } - - public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return AcceptsResult::extremeIdentity(...array_map(static fn (Type $innerType) => $acceptingType->acceptsWithReason($innerType, $strictTypes), $this->types)); + return AcceptsResult::extremeIdentity(...array_map(static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes), $this->types)); } public function equals(Type $type): bool diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 16bf2033511..a49c642acaf 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -55,15 +55,10 @@ public function getObjectClassReflections(): array return []; } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic - { - return $this->acceptsWithReason($type, $strictTypes)->result; - } - - public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { - return $type->isAcceptedWithReasonBy($this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return new AcceptsResult($type->isVoid()->or($type->isNull()), []); diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index cd8ddce5bfa..98332b70316 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -145,7 +145,7 @@ public function testAccepts( TrinaryLogic $expectedResult, ): void { - $actualResult = $acceptingType->accepts($acceptedType, true); + $actualResult = $acceptingType->accepts($acceptedType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/BooleanTypeTest.php b/tests/PHPStan/Type/BooleanTypeTest.php index 375210eea27..f7552cba513 100644 --- a/tests/PHPStan/Type/BooleanTypeTest.php +++ b/tests/PHPStan/Type/BooleanTypeTest.php @@ -52,7 +52,7 @@ public function dataAccepts(): array */ public function testAccepts(BooleanType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 87d3bae274f..a59308f40c9 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -423,7 +423,7 @@ public function testAccepts( { $this->assertSame( $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), + $type->accepts($acceptedType, true)->result->describe(), sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())), ); } diff --git a/tests/PHPStan/Type/ClassStringTypeTest.php b/tests/PHPStan/Type/ClassStringTypeTest.php index 2d827f72718..f4d19a27601 100644 --- a/tests/PHPStan/Type/ClassStringTypeTest.php +++ b/tests/PHPStan/Type/ClassStringTypeTest.php @@ -108,7 +108,7 @@ public function dataAccepts(): iterable */ public function testAccepts(ClassStringType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 9e814dfaf83..f2ee31c44f2 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -413,7 +413,7 @@ public function dataAccepts(): iterable */ public function testAccepts(Type $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php index 1b39e5ff557..c33c33e5a62 100644 --- a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php @@ -38,7 +38,7 @@ public function dataAccepts(): iterable */ public function testAccepts(ConstantIntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Enum/EnumCaseObjectTypeTest.php b/tests/PHPStan/Type/Enum/EnumCaseObjectTypeTest.php index e700d67a3d7..1bf9013c42a 100644 --- a/tests/PHPStan/Type/Enum/EnumCaseObjectTypeTest.php +++ b/tests/PHPStan/Type/Enum/EnumCaseObjectTypeTest.php @@ -219,7 +219,7 @@ public function testAccepts( $this->assertSame( $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), + $type->accepts($acceptedType, true)->result->describe(), sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())), ); } diff --git a/tests/PHPStan/Type/FloatTypeTest.php b/tests/PHPStan/Type/FloatTypeTest.php index ef878bf6dd1..04a9c270ca6 100644 --- a/tests/PHPStan/Type/FloatTypeTest.php +++ b/tests/PHPStan/Type/FloatTypeTest.php @@ -66,7 +66,7 @@ public function dataAccepts(): array public function testAccepts(Type $otherType, TrinaryLogic $expectedResult): void { $type = new FloatType(); - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index 3efd727f4c7..ef6a0faf8d3 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -284,7 +284,7 @@ public function testAccepts( TrinaryLogic $expectedResult, ): void { - $actualResult = $acceptingType->accepts($acceptedType, true); + $actualResult = $acceptingType->accepts($acceptedType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 273394c6b63..30539fae857 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -340,7 +340,7 @@ public function testAccepts( TrinaryLogic $expectedResult, ): void { - $actualResult = $acceptingType->accepts($acceptedType, true); + $actualResult = $acceptingType->accepts($acceptedType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php b/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php index 71a57dda6d8..c5bd9ac0a6c 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeVarianceTest.php @@ -87,14 +87,15 @@ public function testIsValidVariance( TrinaryLogic $expectedInversed, ): void { + $templateType = TemplateTypeFactory::create(TemplateTypeScope::createWithFunction('foo'), 'T', null, $variance); $this->assertSame( $expected->describe(), - $variance->isValidVariance($a, $b)->describe(), + $variance->isValidVariance($templateType, $a, $b)->result->describe(), sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $a->describe(VerbosityLevel::precise()), $b->describe(VerbosityLevel::precise())), ); $this->assertSame( $expectedInversed->describe(), - $variance->isValidVariance($b, $a)->describe(), + $variance->isValidVariance($templateType, $b, $a)->result->describe(), sprintf('%s->isValidVariance(%s, %s)', $variance->describe(), $b->describe(VerbosityLevel::precise()), $a->describe(VerbosityLevel::precise())), ); } diff --git a/tests/PHPStan/Type/IntegerTypeTest.php b/tests/PHPStan/Type/IntegerTypeTest.php index a1c83f78853..79c3ef95604 100644 --- a/tests/PHPStan/Type/IntegerTypeTest.php +++ b/tests/PHPStan/Type/IntegerTypeTest.php @@ -53,7 +53,7 @@ public function dataAccepts(): array */ public function testAccepts(IntegerType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index b6fa0c0b4a9..c5dbfc07ab0 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -71,7 +71,7 @@ public function dataAccepts(): Iterator */ public function testAccepts(IntersectionType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/IterableTypeTest.php b/tests/PHPStan/Type/IterableTypeTest.php index 2094ebf589a..013c0fd15c3 100644 --- a/tests/PHPStan/Type/IterableTypeTest.php +++ b/tests/PHPStan/Type/IterableTypeTest.php @@ -329,7 +329,7 @@ public function dataAccepts(): array */ public function testAccepts(IterableType $iterableType, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $iterableType->accepts($otherType, true); + $actualResult = $iterableType->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index 6d8cfb28ee4..f17c18ea976 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -531,7 +531,7 @@ public function testAccepts( { $this->assertSame( $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), + $type->accepts($acceptedType, true)->result->describe(), sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())), ); } diff --git a/tests/PHPStan/Type/StringTypeTest.php b/tests/PHPStan/Type/StringTypeTest.php index 8f22550446c..813349b91ac 100644 --- a/tests/PHPStan/Type/StringTypeTest.php +++ b/tests/PHPStan/Type/StringTypeTest.php @@ -179,7 +179,7 @@ public function dataAccepts(): iterable */ public function testAccepts(StringType $type, Type $otherType, TrinaryLogic $expectedResult): void { - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedResult->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/TemplateTypeTest.php b/tests/PHPStan/Type/TemplateTypeTest.php index 3a173119894..6b184eef8a8 100644 --- a/tests/PHPStan/Type/TemplateTypeTest.php +++ b/tests/PHPStan/Type/TemplateTypeTest.php @@ -108,7 +108,7 @@ public function testAccepts( { assert($type instanceof TemplateType); - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedAccept->describe(), $actualResult->describe(), @@ -117,7 +117,7 @@ public function testAccepts( $type = $type->toArgument(); - $actualResult = $type->accepts($otherType, true); + $actualResult = $type->accepts($otherType, true)->result; $this->assertSame( $expectedAcceptArg->describe(), $actualResult->describe(), diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 83ec097e239..46b58033743 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -1305,7 +1305,7 @@ public function testAccepts( { $this->assertSame( $expectedResult->describe(), - $type->accepts($acceptedType, true)->describe(), + $type->accepts($acceptedType, true)->result->describe(), sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $acceptedType->describe(VerbosityLevel::precise())), ); } From c067207bdeed8134d054de63444c53083132ddac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 10:11:13 +0200 Subject: [PATCH 0569/3097] Fix build --- tests/PHPStan/Levels/data/unreachable-0.json | 12 ------------ tests/PHPStan/Levels/data/unreachable-4.json | 10 ++++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 tests/PHPStan/Levels/data/unreachable-0.json diff --git a/tests/PHPStan/Levels/data/unreachable-0.json b/tests/PHPStan/Levels/data/unreachable-0.json deleted file mode 100644 index 5091fec1532..00000000000 --- a/tests/PHPStan/Levels/data/unreachable-0.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "message": "Return value of function print_r() is always true and the result is printed instead of being returned. Pass in true as parameter #2 $return to return the output instead.", - "line": 38, - "ignorable": true - }, - { - "message": "Return value of function print_r() is always true and the result is printed instead of being returned. Pass in true as parameter #2 $return to return the output instead.", - "line": 89, - "ignorable": true - } -] diff --git a/tests/PHPStan/Levels/data/unreachable-4.json b/tests/PHPStan/Levels/data/unreachable-4.json index 6f937312aea..4e6216b4662 100644 --- a/tests/PHPStan/Levels/data/unreachable-4.json +++ b/tests/PHPStan/Levels/data/unreachable-4.json @@ -19,6 +19,11 @@ "line": 38, "ignorable": true }, + { + "message": "Return value of function print_r() is always true and the result is printed instead of being returned. Pass in true as parameter #2 $return to return the output instead.", + "line": 38, + "ignorable": true + }, { "message": "If condition is always true.", "line": 47, @@ -64,6 +69,11 @@ "line": 84, "ignorable": true }, + { + "message": "Return value of function print_r() is always true and the result is printed instead of being returned. Pass in true as parameter #2 $return to return the output instead.", + "line": 89, + "ignorable": true + }, { "message": "Ternary operator condition is always true.", "line": 89, From 41275dcaca934a0956a6a50af4bba6f0ee4356b7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 10:15:59 +0200 Subject: [PATCH 0570/3097] [BCB] ConstantReflection --- UPGRADING.md | 5 + src/Analyser/MutatingScope.php | 6 +- src/Analyser/OutOfClassScope.php | 4 +- src/Analyser/Scope.php | 4 +- src/PhpDoc/PhpDocBlock.php | 4 +- .../BetterReflectionProvider.php | 6 +- src/Reflection/ClassConstantReflection.php | 135 +---------------- src/Reflection/ClassMemberAccessAnswerer.php | 2 +- src/Reflection/ClassReflection.php | 4 +- .../Constant/RuntimeConstantReflection.php | 4 +- src/Reflection/ConstantReflection.php | 17 ++- ...n.php => DummyClassConstantReflection.php} | 29 +++- src/Reflection/GlobalConstantReflection.php | 24 --- .../RealClassClassConstantReflection.php | 140 ++++++++++++++++++ src/Reflection/ReflectionProvider.php | 2 +- .../DummyReflectionProvider.php | 4 +- .../MemoizingReflectionProvider.php | 4 +- .../AlwaysUsedClassConstantsExtension.php | 4 +- .../Constants/OverridingConstantRule.php | 5 +- src/Type/ClosureType.php | 4 +- src/Type/Constant/ConstantStringType.php | 4 +- src/Type/IntersectionType.php | 4 +- src/Type/MixedType.php | 8 +- src/Type/NeverType.php | 4 +- src/Type/NonexistentParentClassType.php | 4 +- src/Type/ObjectType.php | 4 +- src/Type/StaticType.php | 4 +- src/Type/StrictMixedType.php | 4 +- src/Type/Traits/LateResolvableTypeTrait.php | 4 +- src/Type/Traits/MaybeObjectTypeTrait.php | 8 +- src/Type/Traits/NonObjectTypeTrait.php | 4 +- src/Type/Traits/ObjectTypeTrait.php | 8 +- src/Type/Type.php | 4 +- src/Type/UnionType.php | 6 +- .../data/class-implements-out-of-phpstan.php | 2 +- .../UnusedPrivateConstantRuleTest.php | 4 +- 36 files changed, 260 insertions(+), 223 deletions(-) rename src/Reflection/Dummy/{DummyConstantReflection.php => DummyClassConstantReflection.php} (75%) delete mode 100644 src/Reflection/GlobalConstantReflection.php create mode 100644 src/Reflection/RealClassClassConstantReflection.php diff --git a/UPGRADING.md b/UPGRADING.md index cdaad24fcfe..dc6ac842e1a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -293,3 +293,8 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Changes around `ClassConstantReflection` + * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` + * Interface `ConstantReflection` renamed to `ClassConstantReflection` + * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` + * Interface `GlobalConstantReflection` renamed to `ConstantReflection` diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4317d5945aa..5d11b826c65 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -52,9 +52,9 @@ use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\Callables\SimpleThrowPoint; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\Dummy\DummyConstructorReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; @@ -5326,7 +5326,7 @@ public function canCallMethod(MethodReflection $methodReflection): bool } /** @api */ - public function canAccessConstant(ConstantReflection $constantReflection): bool + public function canAccessConstant(ClassConstantReflection $constantReflection): bool { return $this->canAccessClassMember($constantReflection); } @@ -5690,7 +5690,7 @@ private function propertyFetchType(Type $fetchedOnType, string $propertyName, Ex return $propertyReflection->getReadableType(); } - public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ConstantReflection + public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection { if ($typeWithConstant instanceof UnionType) { $newTypes = []; diff --git a/src/Analyser/OutOfClassScope.php b/src/Analyser/OutOfClassScope.php index 42a85108c99..925e35a50ea 100644 --- a/src/Analyser/OutOfClassScope.php +++ b/src/Analyser/OutOfClassScope.php @@ -2,9 +2,9 @@ namespace PHPStan\Analyser; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\PropertyReflection; @@ -36,7 +36,7 @@ public function canCallMethod(MethodReflection $methodReflection): bool return $methodReflection->isPublic(); } - public function canAccessConstant(ConstantReflection $constantReflection): bool + public function canAccessConstant(ClassConstantReflection $constantReflection): bool { return $constantReflection->isPublic(); } diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 96859d4c5ea..963a4e21944 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -6,9 +6,9 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Param; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; @@ -73,7 +73,7 @@ public function getPropertyReflection(Type $typeWithProperty, string $propertyNa public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection; - public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ConstantReflection; + public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection; public function getIterableKeyType(Type $iteratee): Type; diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index fd75fa53067..c64a74cea27 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -3,8 +3,8 @@ namespace PHPStan\PhpDoc; use PHPStan\PhpDoc\Tag\AssertTagParameter; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\Php\PhpPropertyReflection; @@ -334,7 +334,7 @@ private static function resolvePhpDocBlockFromClass( ): ?self { if ($classReflection->$hasMethodName($name)) { - /** @var PropertyReflection|MethodReflection|ConstantReflection $parentReflection */ + /** @var PropertyReflection|MethodReflection|ClassConstantReflection $parentReflection */ $parentReflection = $classReflection->$getMethodName($name); if ($parentReflection->isPrivate()) { return null; diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 06e94ae7188..7542e3ccdf6 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -34,9 +34,9 @@ use PHPStan\Reflection\ClassNameHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Constant\RuntimeConstantReflection; +use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\FunctionReflectionFactory; -use PHPStan\Reflection\GlobalConstantReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\NamespaceAnswerer; @@ -71,7 +71,7 @@ final class BetterReflectionProvider implements ReflectionProvider /** @var ClassReflection[] */ private static array $anonymousClasses = []; - /** @var array */ + /** @var array */ private array $cachedConstants = []; /** @@ -389,7 +389,7 @@ public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return $this->resolveConstantName($nameNode, $namespaceAnswerer) !== null; } - public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): GlobalConstantReflection + public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ConstantReflection { $constantName = $this->resolveConstantName($nameNode, $namespaceAnswerer); if ($constantName === null) { diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 7afcb0d9e25..cafc2013416 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -3,141 +3,22 @@ namespace PHPStan\Reflection; use PhpParser\Node\Expr; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; -use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -use PHPStan\Type\TypehintHelper; -/** - * @api - */ -final class ClassConstantReflection implements ConstantReflection +/** @api */ +interface ClassConstantReflection extends ClassMemberReflection, ConstantReflection { - private ?Type $valueType = null; + public function getValueExpr(): Expr; - public function __construct( - private InitializerExprTypeResolver $initializerExprTypeResolver, - private ClassReflection $declaringClass, - private ReflectionClassConstant $reflection, - private ?Type $nativeType, - private ?Type $phpDocType, - private ?string $deprecatedDescription, - private bool $isDeprecated, - private bool $isInternal, - ) - { - } + public function isFinal(): bool; - public function getName(): string - { - return $this->reflection->getName(); - } + public function hasPhpDocType(): bool; - public function getFileName(): ?string - { - return $this->declaringClass->getFileName(); - } + public function getPhpDocType(): ?Type; - public function getValueExpr(): Expr - { - return $this->reflection->getValueExpression(); - } + public function hasNativeType(): bool; - public function hasPhpDocType(): bool - { - return $this->phpDocType !== null; - } - - public function getPhpDocType(): ?Type - { - return $this->phpDocType; - } - - public function hasNativeType(): bool - { - return $this->nativeType !== null; - } - - public function getNativeType(): ?Type - { - return $this->nativeType; - } - - public function getValueType(): Type - { - if ($this->valueType === null) { - if ($this->phpDocType !== null) { - if ($this->nativeType !== null) { - return $this->valueType = TypehintHelper::decideType( - $this->nativeType, - $this->phpDocType, - ); - } - - return $this->phpDocType; - } elseif ($this->nativeType !== null) { - return $this->nativeType; - } - - $this->valueType = $this->initializerExprTypeResolver->getType($this->getValueExpr(), InitializerExprContext::fromClassReflection($this->declaringClass)); - } - - return $this->valueType; - } - - public function getDeclaringClass(): ClassReflection - { - return $this->declaringClass; - } - - public function isStatic(): bool - { - return true; - } - - public function isPrivate(): bool - { - return $this->reflection->isPrivate(); - } - - public function isPublic(): bool - { - return $this->reflection->isPublic(); - } - - public function isFinal(): bool - { - return $this->reflection->isFinal(); - } - - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isDeprecated); - } - - public function getDeprecatedDescription(): ?string - { - if ($this->isDeprecated) { - return $this->deprecatedDescription; - } - - return null; - } - - public function isInternal(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean($this->isInternal); - } - - public function getDocComment(): ?string - { - $docComment = $this->reflection->getDocComment(); - if ($docComment === false) { - return null; - } - - return $docComment; - } + public function getNativeType(): ?Type; } diff --git a/src/Reflection/ClassMemberAccessAnswerer.php b/src/Reflection/ClassMemberAccessAnswerer.php index ed1803e7861..e1c62c60cae 100644 --- a/src/Reflection/ClassMemberAccessAnswerer.php +++ b/src/Reflection/ClassMemberAccessAnswerer.php @@ -17,6 +17,6 @@ public function canAccessProperty(PropertyReflection $propertyReflection): bool; public function canCallMethod(MethodReflection $methodReflection): bool; - public function canAccessConstant(ConstantReflection $constantReflection): bool; + public function canAccessConstant(ClassConstantReflection $constantReflection): bool; } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 2e1e7ae611e..7d966f83d5b 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -81,7 +81,7 @@ final class ClassReflection /** @var ExtendedPropertyReflection[] */ private array $properties = []; - /** @var ClassConstantReflection[] */ + /** @var RealClassClassConstantReflection[] */ private array $constants = []; /** @var EnumCaseReflection[]|null */ @@ -1082,7 +1082,7 @@ public function getConstant(string $name): ClassConstantReflection $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType']; } - $this->constants[$name] = new ClassConstantReflection( + $this->constants[$name] = new RealClassClassConstantReflection( $this->initializerExprTypeResolver, $declaringClass, $reflectionConstant, diff --git a/src/Reflection/Constant/RuntimeConstantReflection.php b/src/Reflection/Constant/RuntimeConstantReflection.php index 9940b285057..4b31de502d2 100644 --- a/src/Reflection/Constant/RuntimeConstantReflection.php +++ b/src/Reflection/Constant/RuntimeConstantReflection.php @@ -2,11 +2,11 @@ namespace PHPStan\Reflection\Constant; -use PHPStan\Reflection\GlobalConstantReflection; +use PHPStan\Reflection\ConstantReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class RuntimeConstantReflection implements GlobalConstantReflection +final class RuntimeConstantReflection implements ConstantReflection { public function __construct( diff --git a/src/Reflection/ConstantReflection.php b/src/Reflection/ConstantReflection.php index 6a138779902..ebae7558496 100644 --- a/src/Reflection/ConstantReflection.php +++ b/src/Reflection/ConstantReflection.php @@ -2,12 +2,23 @@ namespace PHPStan\Reflection; -use PhpParser\Node\Expr; +use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; /** @api */ -interface ConstantReflection extends ClassMemberReflection, GlobalConstantReflection +interface ConstantReflection { - public function getValueExpr(): Expr; + public function getName(): string; + + public function getValueType(): Type; + + public function isDeprecated(): TrinaryLogic; + + public function getDeprecatedDescription(): ?string; + + public function isInternal(): TrinaryLogic; + + public function getFileName(): ?string; } diff --git a/src/Reflection/Dummy/DummyConstantReflection.php b/src/Reflection/Dummy/DummyClassConstantReflection.php similarity index 75% rename from src/Reflection/Dummy/DummyConstantReflection.php rename to src/Reflection/Dummy/DummyClassConstantReflection.php index b7d563a6155..e38a8740dc5 100644 --- a/src/Reflection/Dummy/DummyConstantReflection.php +++ b/src/Reflection/Dummy/DummyClassConstantReflection.php @@ -4,15 +4,15 @@ use PhpParser\Node\Expr; use PHPStan\Node\Expr\TypeExpr; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use stdClass; -final class DummyConstantReflection implements ConstantReflection +final class DummyClassConstantReflection implements ClassConstantReflection { public function __construct(private string $name) @@ -26,6 +26,11 @@ public function getDeclaringClass(): ClassReflection return $reflectionProvider->getClass(stdClass::class); } + public function isFinal(): bool + { + return false; + } + public function getFileName(): ?string { return null; @@ -81,4 +86,24 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): ?Type + { + return null; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): ?Type + { + return null; + } + } diff --git a/src/Reflection/GlobalConstantReflection.php b/src/Reflection/GlobalConstantReflection.php deleted file mode 100644 index 6483e72e863..00000000000 --- a/src/Reflection/GlobalConstantReflection.php +++ /dev/null @@ -1,24 +0,0 @@ -reflection->getName(); + } + + public function getFileName(): ?string + { + return $this->declaringClass->getFileName(); + } + + public function getValueExpr(): Expr + { + return $this->reflection->getValueExpression(); + } + + public function hasPhpDocType(): bool + { + return $this->phpDocType !== null; + } + + public function getPhpDocType(): ?Type + { + return $this->phpDocType; + } + + public function hasNativeType(): bool + { + return $this->nativeType !== null; + } + + public function getNativeType(): ?Type + { + return $this->nativeType; + } + + public function getValueType(): Type + { + if ($this->valueType === null) { + if ($this->phpDocType !== null) { + if ($this->nativeType !== null) { + return $this->valueType = TypehintHelper::decideType( + $this->nativeType, + $this->phpDocType, + ); + } + + return $this->phpDocType; + } elseif ($this->nativeType !== null) { + return $this->nativeType; + } + + $this->valueType = $this->initializerExprTypeResolver->getType($this->getValueExpr(), InitializerExprContext::fromClassReflection($this->declaringClass)); + } + + return $this->valueType; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return true; + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function isFinal(): bool + { + return $this->reflection->isFinal(); + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isDeprecated); + } + + public function getDeprecatedDescription(): ?string + { + if ($this->isDeprecated) { + return $this->deprecatedDescription; + } + + return null; + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function getDocComment(): ?string + { + $docComment = $this->reflection->getDocComment(); + if ($docComment === false) { + return null; + } + + return $docComment; + } + +} diff --git a/src/Reflection/ReflectionProvider.php b/src/Reflection/ReflectionProvider.php index 2e0cc7ee208..3b7387f85ad 100644 --- a/src/Reflection/ReflectionProvider.php +++ b/src/Reflection/ReflectionProvider.php @@ -32,7 +32,7 @@ public function resolveFunctionName(Node\Name $nameNode, ?NamespaceAnswerer $nam public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): bool; - public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): GlobalConstantReflection; + public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ConstantReflection; public function resolveConstantName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string; diff --git a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php index 2f96b7016f2..7d18639f8ca 100644 --- a/src/Reflection/ReflectionProvider/DummyReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/DummyReflectionProvider.php @@ -5,8 +5,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\GlobalConstantReflection; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; @@ -59,7 +59,7 @@ public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return false; } - public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): GlobalConstantReflection + public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ConstantReflection { throw new ShouldNotHappenException(); } diff --git a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php index 48060fdfbd7..00f4301c7e6 100644 --- a/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/MemoizingReflectionProvider.php @@ -5,8 +5,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\GlobalConstantReflection; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use function strtolower; @@ -86,7 +86,7 @@ public function hasConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn return $this->provider->hasConstant($nameNode, $namespaceAnswerer); } - public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): GlobalConstantReflection + public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ConstantReflection { return $this->provider->getConstant($nameNode, $namespaceAnswerer); } diff --git a/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php b/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php index 4e3bdcc92da..977bac234be 100644 --- a/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php +++ b/src/Rules/Constants/AlwaysUsedClassConstantsExtension.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Constants; -use PHPStan\Reflection\ConstantReflection; +use PHPStan\Reflection\ClassConstantReflection; /** * This is the extension interface to implement if you want to describe @@ -25,6 +25,6 @@ interface AlwaysUsedClassConstantsExtension { - public function isAlwaysUsed(ConstantReflection $constant): bool; + public function isAlwaysUsed(ClassConstantReflection $constant): bool; } diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index ca1a822f99b..2b3fa162d1e 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -53,7 +52,7 @@ public function processNode(Node $node, Scope $scope): array private function processSingleConstant(ClassReflection $classReflection, string $constantName): array { $prototype = $this->findPrototype($classReflection, $constantName); - if (!$prototype instanceof ClassConstantReflection) { + if ($prototype === null) { return []; } @@ -145,7 +144,7 @@ private function processSingleConstant(ClassReflection $classReflection, string return $errors; } - private function findPrototype(ClassReflection $classReflection, string $constantName): ?ConstantReflection + private function findPrototype(ClassReflection $classReflection, string $constantName): ?ClassConstantReflection { foreach ($classReflection->getImmediateInterfaces() as $immediateInterface) { if ($immediateInterface->hasConstant($constantName)) { diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index ccd6c6cccb6..46101a4bfe8 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -16,9 +16,9 @@ use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\Callables\SimpleThrowPoint; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Native\NativeParameterReflection; @@ -349,7 +349,7 @@ public function hasConstant(string $constantName): TrinaryLogic return $this->objectType->hasConstant($constantName); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { return $this->objectType->getConstant($constantName); } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index a6fda677b7b..6d2e32c4a0a 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -11,8 +11,8 @@ use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\Callables\FunctionCallableVariant; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\InaccessibleMethod; use PHPStan\Reflection\PhpVersionStaticAccessor; use PHPStan\Reflection\ReflectionProviderStaticAccessor; @@ -534,7 +534,7 @@ public function hasConstant(string $constantName): TrinaryLogic return $this->getObjectType()->hasConstant($constantName); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { return $this->getObjectType()->getConstant($constantName); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 0117d149992..c6c6d6b6449 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -7,8 +7,8 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -532,7 +532,7 @@ public function hasConstant(string $constantName): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName)); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { foreach ($this->types as $type) { if ($type->hasConstant($constantName)->yes()) { diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 43814c3643e..5353a102903 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -6,9 +6,9 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; -use PHPStan\Reflection\Dummy\DummyConstantReflection; +use PHPStan\Reflection\Dummy\DummyClassConstantReflection; use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -423,9 +423,9 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createYes(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { - return new DummyConstantReflection($constantName); + return new DummyClassConstantReflection($constantName); } public function isCloneable(): TrinaryLogic diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 50266db06bf..21f58c0c297 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -5,8 +5,8 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -171,7 +171,7 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 29ae4f752ce..f52c5ea8deb 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -5,8 +5,8 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -107,7 +107,7 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 30f8be46409..804147910e9 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -19,9 +19,9 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Callables\FunctionCallableVariant; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; @@ -791,7 +791,7 @@ public function hasConstant(string $constantName): TrinaryLogic ); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { $class = $this->getClassReflection(); if ($class === null) { diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 9c720b5e52a..7d73571c4f8 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -5,9 +5,9 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; @@ -309,7 +309,7 @@ public function hasConstant(string $constantName): TrinaryLogic return $this->getStaticObjectType()->hasConstant($constantName); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { return $this->getStaticObjectType()->getConstant($constantName); } diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 1a269cb65fc..2e426b2e66c 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -5,8 +5,8 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -165,7 +165,7 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index f5d2b1dce2c..34785fb3f38 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -3,8 +3,8 @@ namespace PHPStan\Type\Traits; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -142,7 +142,7 @@ public function hasConstant(string $constantName): TrinaryLogic return $this->resolve()->hasConstant($constantName); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { return $this->resolve()->getConstant($constantName); } diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php index cc13c23a994..ff50c721d31 100644 --- a/src/Type/Traits/MaybeObjectTypeTrait.php +++ b/src/Type/Traits/MaybeObjectTypeTrait.php @@ -2,9 +2,9 @@ namespace PHPStan\Type\Traits; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; -use PHPStan\Reflection\Dummy\DummyConstantReflection; +use PHPStan\Reflection\Dummy\DummyClassConstantReflection; use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -97,9 +97,9 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { - return new DummyConstantReflection($constantName); + return new DummyClassConstantReflection($constantName); } public function isCloneable(): TrinaryLogic diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php index 048ef5fb333..d16b86c9b1f 100644 --- a/src/Type/Traits/NonObjectTypeTrait.php +++ b/src/Type/Traits/NonObjectTypeTrait.php @@ -2,8 +2,8 @@ namespace PHPStan\Type\Traits; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -76,7 +76,7 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createNo(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { throw new ShouldNotHappenException(); } diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 174a6a2509c..5a98ade9c41 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -3,9 +3,9 @@ namespace PHPStan\Type\Traits; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; -use PHPStan\Reflection\Dummy\DummyConstantReflection; +use PHPStan\Reflection\Dummy\DummyClassConstantReflection; use PHPStan\Reflection\Dummy\DummyMethodReflection; use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -108,9 +108,9 @@ public function hasConstant(string $constantName): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { - return new DummyConstantReflection($constantName); + return new DummyClassConstantReflection($constantName); } public function getConstantStrings(): array diff --git a/src/Type/Type.php b/src/Type/Type.php index 21b15c221b3..398bc0d4f2e 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -5,9 +5,9 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\Callables\CallableParametersAcceptor; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -99,7 +99,7 @@ public function canAccessConstants(): TrinaryLogic; public function hasConstant(string $constantName): TrinaryLogic; - public function getConstant(string $constantName): ConstantReflection; + public function getConstant(string $constantName): ClassConstantReflection; public function isIterable(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 704bd421b2c..4e38c385668 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -8,8 +8,8 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; -use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -504,11 +504,11 @@ public function hasConstant(string $constantName): TrinaryLogic ); } - public function getConstant(string $constantName): ConstantReflection + public function getConstant(string $constantName): ClassConstantReflection { return $this->getInternal( static fn (Type $type): TrinaryLogic => $type->hasConstant($constantName), - static fn (Type $type): ConstantReflection => $type->getConstant($constantName), + static fn (Type $type): ClassConstantReflection => $type->getConstant($constantName), ); } diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index ca38d7ec38c..6ded7325fb5 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -128,7 +128,7 @@ public function hasConstant(string $constantName): \PHPStan\TrinaryLogic // TODO: Implement hasConstant() method. } - public function getConstant(string $constantName): \PHPStan\Reflection\ConstantReflection + public function getConstant(string $constantName): \PHPStan\Reflection\ClassConstantReflection { // TODO: Implement getConstant() method. } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php index 24bde42de8d..9ef9924063c 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\DeadCode; -use PHPStan\Reflection\ConstantReflection; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtension; use PHPStan\Rules\Constants\DirectAlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\Rule; @@ -22,7 +22,7 @@ protected function getRule(): Rule new DirectAlwaysUsedClassConstantsExtensionProvider([ new class() implements AlwaysUsedClassConstantsExtension { - public function isAlwaysUsed(ConstantReflection $constant): bool + public function isAlwaysUsed(ClassConstantReflection $constant): bool { return $constant->getDeclaringClass()->getName() === TestExtension::class && $constant->getName() === 'USED'; From 778af2ed74ba59bfb2a69fd5b45821ccdb1107c9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 10:28:58 +0200 Subject: [PATCH 0571/3097] More interfaces that are not supposed to be implemented in userland --- src/Rules/Api/BcUncoveredInterface.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Rules/Api/BcUncoveredInterface.php b/src/Rules/Api/BcUncoveredInterface.php index 5b508c2e075..80a48f9808c 100644 --- a/src/Rules/Api/BcUncoveredInterface.php +++ b/src/Rules/Api/BcUncoveredInterface.php @@ -5,6 +5,9 @@ use PHPStan\Analyser\Scope; use PHPStan\Command\Output; use PHPStan\Reflection\Callables\CallableParametersAcceptor; +use PHPStan\Reflection\ClassConstantReflection; +use PHPStan\Reflection\ClassMemberReflection; +use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; @@ -41,6 +44,9 @@ final class BcUncoveredInterface RuleError::class, TipRuleError::class, Output::class, + ClassMemberReflection::class, + ConstantReflection::class, + ClassConstantReflection::class, ]; } From cb6ab5544a016c52f931fc390bcdf9c627819d8f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 10:39:28 +0200 Subject: [PATCH 0572/3097] Even more interfaces that are not supposed to be implemented --- src/Rules/Api/BcUncoveredInterface.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Rules/Api/BcUncoveredInterface.php b/src/Rules/Api/BcUncoveredInterface.php index 80a48f9808c..ccaf001a14b 100644 --- a/src/Rules/Api/BcUncoveredInterface.php +++ b/src/Rules/Api/BcUncoveredInterface.php @@ -4,13 +4,20 @@ use PHPStan\Analyser\Scope; use PHPStan\Command\Output; +use PHPStan\Command\OutputStyle; +use PHPStan\DependencyInjection\Container; +use PHPStan\Node\ReturnStatementsNode; +use PHPStan\Node\VirtualNode; +use PHPStan\PhpDoc\Tag\TypedTag; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\ClassConstantReflection; +use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; @@ -21,13 +28,21 @@ use PHPStan\Rules\NonIgnorableRuleError; use PHPStan\Rules\RuleError; use PHPStan\Rules\TipRuleError; +use PHPStan\Type\CompoundType; +use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; +use PHPStan\Type\TypeWithClassName; final class BcUncoveredInterface { public const CLASSES = [ Type::class, + CompoundType::class, + TemplateType::class, + TypedTag::class, + TypeWithClassName::class, + VirtualNode::class, ReflectionProvider::class, Scope::class, FunctionReflection::class, @@ -47,6 +62,11 @@ final class BcUncoveredInterface ClassMemberReflection::class, ConstantReflection::class, ClassConstantReflection::class, + ClassMemberAccessAnswerer::class, + NamespaceAnswerer::class, + Container::class, + OutputStyle::class, + ReturnStatementsNode::class, ]; } From 5eacc66a6eee59bf87748c9f0b1bb9c52f816724 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 10:45:59 +0200 Subject: [PATCH 0573/3097] [BCB] From `*WithPhpDocs` to `Extended*` --- UPGRADING.md | 1 + build/PHPStan/Build/FinalClassRule.php | 4 +- src/Analyser/MutatingScope.php | 6 +- src/Analyser/NodeScopeResolver.php | 18 +++--- src/Analyser/TypeSpecifier.php | 10 ++-- src/Dependency/DependencyResolver.php | 8 +-- .../AnnotationMethodReflection.php | 10 ++-- .../AnnotationsMethodParameterReflection.php | 4 +- .../Callables/FunctionCallableVariant.php | 14 ++--- .../Dummy/ChangedTypeMethodReflection.php | 8 +-- .../Dummy/DummyConstructorReflection.php | 8 +-- .../Dummy/DummyMethodReflection.php | 4 +- ...hp => ExtendedCallableFunctionVariant.php} | 4 +- ...hpDocs.php => ExtendedFunctionVariant.php} | 8 +-- src/Reflection/ExtendedMethodReflection.php | 6 +- ...cs.php => ExtendedParameterReflection.php} | 2 +- ...ocs.php => ExtendedParametersAcceptor.php} | 4 +- src/Reflection/FunctionReflection.php | 6 +- .../GenericParametersAcceptorResolver.php | 10 ++-- ... => ExtendedNativeParameterReflection.php} | 4 +- .../Native/NativeFunctionReflection.php | 8 +-- .../Native/NativeMethodReflection.php | 8 +-- src/Reflection/ParametersAcceptorSelector.php | 58 +++++++++---------- .../Php/ClosureCallMethodReflection.php | 12 ++-- .../Php/EnumCasesMethodReflection.php | 8 +-- src/Reflection/Php/ExitFunctionReflection.php | 12 ++-- ...PhpDocs.php => ExtendedDummyParameter.php} | 4 +- .../Php/PhpClassReflectionExtension.php | 10 ++-- .../PhpFunctionFromParserNodeReflection.php | 18 +++--- src/Reflection/Php/PhpFunctionReflection.php | 16 ++--- src/Reflection/Php/PhpMethodReflection.php | 16 ++--- .../PhpParameterFromParserNodeReflection.php | 4 +- src/Reflection/Php/PhpParameterReflection.php | 4 +- src/Reflection/ResolvedFunctionVariant.php | 2 +- .../ResolvedFunctionVariantWithOriginal.php | 10 ++-- src/Reflection/ResolvedMethodReflection.php | 8 +-- .../NativeFunctionReflectionProvider.php | 10 ++-- src/Reflection/TrivialParametersAcceptor.php | 2 +- ...ackUnresolvedMethodPrototypeReflection.php | 12 ++-- ...ypeUnresolvedMethodPrototypeReflection.php | 12 ++-- .../Type/IntersectionTypeMethodReflection.php | 8 +-- .../Type/UnionTypeMethodReflection.php | 4 +- .../WrappedExtendedMethodReflection.php | 10 ++-- src/Rules/Api/BcUncoveredInterface.php | 8 +-- src/Rules/FunctionCallParametersCheck.php | 4 +- src/Rules/FunctionDefinitionCheck.php | 10 ++-- src/Rules/Generics/VarianceCheck.php | 4 +- src/Rules/Methods/MethodSignatureRule.php | 12 ++-- src/Rules/Methods/OverridingMethodRule.php | 4 +- .../ConditionalReturnTypeRuleHelper.php | 4 +- src/Rules/Pure/FunctionPurityCheck.php | 4 +- .../TooWideParameterOutTypeCheck.php | 6 +- .../ParameterOutExecutionEndTypeRule.php | 4 +- ...FromCallableDynamicReturnTypeExtension.php | 4 +- .../ReflectionProviderGoldenTest.php | 2 +- 55 files changed, 231 insertions(+), 230 deletions(-) rename src/Reflection/{CallableFunctionVariantWithPhpDocs.php => ExtendedCallableFunctionVariant.php} (90%) rename src/Reflection/{FunctionVariantWithPhpDocs.php => ExtendedFunctionVariant.php} (77%) rename src/Reflection/{ParameterReflectionWithPhpDocs.php => ExtendedParameterReflection.php} (84%) rename src/Reflection/{ParametersAcceptorWithPhpDocs.php => ExtendedParametersAcceptor.php} (75%) rename src/Reflection/Native/{NativeParameterWithPhpDocsReflection.php => ExtendedNativeParameterReflection.php} (90%) rename src/Reflection/Php/{DummyParameterWithPhpDocs.php => ExtendedDummyParameter.php} (86%) diff --git a/UPGRADING.md b/UPGRADING.md index dc6ac842e1a..7859c5f2675 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -298,3 +298,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Interface `ConstantReflection` renamed to `ClassConstantReflection` * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` * Interface `GlobalConstantReflection` renamed to `ConstantReflection` +* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index 5918003a719..a4758648c6d 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -7,7 +7,7 @@ use PHPStan\File\FileHelper; use PHPStan\Node\InClassNode; use PHPStan\Reflection\FunctionVariant; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Rules\Rule; @@ -51,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array // exceptions if (in_array($classReflection->getName(), [ FunctionVariant::class, - FunctionVariantWithPhpDocs::class, + ExtendedFunctionVariant::class, DummyParameter::class, PhpFunctionFromParserNodeReflection::class, ], true)) { diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5d11b826c65..ae1c02dca85 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -57,6 +57,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Dummy\DummyConstructorReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprContext; @@ -66,7 +67,6 @@ use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; @@ -2503,7 +2503,7 @@ private function createFirstClassCallable( foreach ($variants as $variant) { $returnType = $variant->getReturnType(); - if ($variant instanceof ParametersAcceptorWithPhpDocs) { + if ($variant instanceof ExtendedParametersAcceptor) { $returnType = $this->nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType; } @@ -2560,7 +2560,7 @@ private function createFirstClassCallable( $variant->isVariadic(), $variant->getTemplateTypeMap(), $variant->getResolvedTemplateTypeMap(), - $variant instanceof ParametersAcceptorWithPhpDocs ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), $templateTags, $throwPoints, $impurePoints, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 74f2c58c71e..ec807de6fad 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -130,16 +130,16 @@ use PHPStan\Reflection\Callables\SimpleThrowPoint; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodReflection; @@ -2649,7 +2649,7 @@ static function (): void { TemplateTypeHelper::resolveTemplateTypes( $selfOutType, $parametersAcceptor->getResolvedTemplateTypeMap(), - $parametersAcceptor instanceof ParametersAcceptorWithPhpDocs ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), TemplateTypeVariance::createCovariant(), ), $scope->getNativeType($expr->var), @@ -4545,7 +4545,7 @@ private function processArgs( $assignByReference = $parameters[$i]->passedByReference()->createsNewVariable(); $parameterType = $parameters[$i]->getType(); - if ($parameters[$i] instanceof ParameterReflectionWithPhpDocs) { + if ($parameters[$i] instanceof ExtendedParameterReflection) { $parameterNativeType = $parameters[$i]->getNativeType(); } $parameter = $parameters[$i]; @@ -4554,7 +4554,7 @@ private function processArgs( $assignByReference = $lastParameter->passedByReference()->createsNewVariable(); $parameterType = $lastParameter->getType(); - if ($lastParameter instanceof ParameterReflectionWithPhpDocs) { + if ($lastParameter instanceof ExtendedParameterReflection) { $parameterNativeType = $lastParameter->getNativeType(); } $parameter = $lastParameter; @@ -4591,7 +4591,7 @@ private function processArgs( $scopeToPass = $closureBindScope; } - if ($parameter instanceof ParameterReflectionWithPhpDocs) { + if ($parameter instanceof ExtendedParameterReflection) { $parameterCallImmediately = $parameter->isImmediatelyInvokedCallable(); if ($parameterCallImmediately->maybe()) { $callCallbackImmediately = $calleeReflection instanceof FunctionReflection; @@ -4605,7 +4605,7 @@ private function processArgs( $restoreThisScope = null; if ( $closureBindScope === null - && $parameter instanceof ParameterReflectionWithPhpDocs + && $parameter instanceof ExtendedParameterReflection && $parameter->getClosureThisType() !== null && !$arg->value->static ) { @@ -4658,7 +4658,7 @@ private function processArgs( } elseif ($arg->value instanceof Expr\ArrowFunction) { if ( $closureBindScope === null - && $parameter instanceof ParameterReflectionWithPhpDocs + && $parameter instanceof ExtendedParameterReflection && $parameter->getClosureThisType() !== null && !$arg->value->static ) { @@ -4734,7 +4734,7 @@ private function processArgs( if ($currentParameter !== null) { $assignByReference = $currentParameter->passedByReference()->createsNewVariable(); if ($assignByReference) { - if ($currentParameter instanceof ParameterReflectionWithPhpDocs && $currentParameter->getOutType() !== null) { + if ($currentParameter instanceof ExtendedParameterReflection && $currentParameter->getOutType() !== null) { $byRefType = $currentParameter->getOutType(); } elseif ( $calleeReflection instanceof MethodReflection diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5affae3a6f7..1006f336338 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -24,9 +24,9 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ResolvedFunctionVariant; use PHPStan\Rules\Arrays\AllowedArrayKeysTypes; @@ -485,7 +485,7 @@ public function specifyTypesInCondition( $asserts = $assertions->mapTypes(static fn (Type $type) => TemplateTypeHelper::resolveTemplateTypes( $type, $parametersAcceptor->getResolvedTemplateTypeMap(), - $parametersAcceptor instanceof ParametersAcceptorWithPhpDocs ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), TemplateTypeVariance::createInvariant(), )); $specifiedTypes = $this->specifyTypesFromAsserts($context, $expr, $asserts, $parametersAcceptor, $scope); @@ -533,7 +533,7 @@ public function specifyTypesInCondition( $asserts = $assertions->mapTypes(static fn (Type $type) => TemplateTypeHelper::resolveTemplateTypes( $type, $parametersAcceptor->getResolvedTemplateTypeMap(), - $parametersAcceptor instanceof ParametersAcceptorWithPhpDocs ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), TemplateTypeVariance::createInvariant(), )); $specifiedTypes = $this->specifyTypesFromAsserts($context, $expr, $asserts, $parametersAcceptor, $scope); @@ -586,7 +586,7 @@ public function specifyTypesInCondition( $asserts = $assertions->mapTypes(static fn (Type $type) => TemplateTypeHelper::resolveTemplateTypes( $type, $parametersAcceptor->getResolvedTemplateTypeMap(), - $parametersAcceptor instanceof ParametersAcceptorWithPhpDocs ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), TemplateTypeVariance::createInvariant(), )); $specifiedTypes = $this->specifyTypesFromAsserts($context, $expr, $asserts, $parametersAcceptor, $scope); @@ -935,7 +935,7 @@ public function specifyTypesInCondition( $asserts = $asserts->mapTypes(static fn (Type $type) => TemplateTypeHelper::resolveTemplateTypes( $type, $parametersAcceptor->getResolvedTemplateTypeMap(), - $parametersAcceptor instanceof ParametersAcceptorWithPhpDocs ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), TemplateTypeVariance::createInvariant(), )); diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 12635a0913d..9fde7ef34f1 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -16,9 +16,9 @@ use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InFunctionNode; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ClosureType; use PHPStan\Type\FileTypeMapper; @@ -171,7 +171,7 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies } foreach ($variant->getParameters() as $parameter) { - if (!$parameter instanceof ParameterReflectionWithPhpDocs) { + if (!$parameter instanceof ExtendedParameterReflection) { continue; } if ($parameter->getOutType() !== null) { @@ -615,7 +615,7 @@ private function getFunctionReflection(Node\Name $nameNode, ?Scope $scope): Func * @param array $dependenciesReflections */ private function extractFromParametersAcceptor( - ParametersAcceptorWithPhpDocs $parametersAcceptor, + ExtendedParametersAcceptor $parametersAcceptor, array &$dependenciesReflections, ): void { diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 6b2ff2afdb6..847a444eb57 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -5,9 +5,9 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -17,7 +17,7 @@ final class AnnotationMethodReflection implements ExtendedMethodReflection { - /** @var FunctionVariantWithPhpDocs[]|null */ + /** @var ExtendedFunctionVariant[]|null */ private ?array $variants = null; /** @@ -70,7 +70,7 @@ public function getVariants(): array { if ($this->variants === null) { $this->variants = [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( $this->templateTypeMap, null, $this->parameters, @@ -84,7 +84,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index cb86e0fec45..51bddcaabe2 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -2,13 +2,13 @@ namespace PHPStan\Reflection\Annotations; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; -final class AnnotationsMethodParameterReflection implements ParameterReflectionWithPhpDocs +final class AnnotationsMethodParameterReflection implements ExtendedParameterReflection { public function __construct(private string $name, private Type $type, private PassedByReference $passedByReference, private bool $isOptional, private bool $isVariadic, private ?Type $defaultValue) diff --git a/src/Reflection/Callables/FunctionCallableVariant.php b/src/Reflection/Callables/FunctionCallableVariant.php index 82eb478fc3f..66bd629a3e2 100644 --- a/src/Reflection/Callables/FunctionCallableVariant.php +++ b/src/Reflection/Callables/FunctionCallableVariant.php @@ -3,9 +3,9 @@ namespace PHPStan\Reflection\Callables; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVarianceMap; @@ -16,7 +16,7 @@ use function array_map; use function count; -final class FunctionCallableVariant implements CallableParametersAcceptor, ParametersAcceptorWithPhpDocs +final class FunctionCallableVariant implements CallableParametersAcceptor, ExtendedParametersAcceptor { /** @var SimpleThrowPoint[]|null */ @@ -27,18 +27,18 @@ final class FunctionCallableVariant implements CallableParametersAcceptor, Param public function __construct( private FunctionReflection|ExtendedMethodReflection $function, - private ParametersAcceptorWithPhpDocs $variant, + private ExtendedParametersAcceptor $variant, ) { } /** - * @param ParametersAcceptorWithPhpDocs[] $variants + * @param ExtendedParametersAcceptor[] $variants * @return self[] */ public static function createFromVariants(FunctionReflection|ExtendedMethodReflection $function, array $variants): array { - return array_map(static fn (ParametersAcceptorWithPhpDocs $variant) => new self($function, $variant), $variants); + return array_map(static fn (ExtendedParametersAcceptor $variant) => new self($function, $variant), $variants); } public function getTemplateTypeMap(): TemplateTypeMap @@ -52,7 +52,7 @@ public function getResolvedTemplateTypeMap(): TemplateTypeMap } /** - * @return array + * @return array */ public function getParameters(): array { diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 3dc1cd56b0d..bc0a557157a 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -6,7 +6,7 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -17,8 +17,8 @@ final class ChangedTypeMethodReflection implements ExtendedMethodReflection { /** - * @param ParametersAcceptorWithPhpDocs[] $variants - * @param ParametersAcceptorWithPhpDocs[]|null $namedArgumentsVariants + * @param ExtendedParametersAcceptor[] $variants + * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants */ public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants) { @@ -64,7 +64,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { $variants = $this->getVariants(); if (count($variants) !== 1) { diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 36b143ed12c..e3dff92c38b 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -5,9 +5,9 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -54,7 +54,7 @@ public function getPrototype(): ClassMemberReflection public function getVariants(): array { return [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), null, [], @@ -67,7 +67,7 @@ public function getVariants(): array ]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index c6157c17dbd..c02b5ea3705 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -6,7 +6,7 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; @@ -59,7 +59,7 @@ public function getVariants(): array ]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/CallableFunctionVariantWithPhpDocs.php b/src/Reflection/ExtendedCallableFunctionVariant.php similarity index 90% rename from src/Reflection/CallableFunctionVariantWithPhpDocs.php rename to src/Reflection/ExtendedCallableFunctionVariant.php index fd6c62afd53..5b67d210cc6 100644 --- a/src/Reflection/CallableFunctionVariantWithPhpDocs.php +++ b/src/Reflection/ExtendedCallableFunctionVariant.php @@ -11,11 +11,11 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -final class CallableFunctionVariantWithPhpDocs extends FunctionVariantWithPhpDocs implements CallableParametersAcceptor +final class ExtendedCallableFunctionVariant extends ExtendedFunctionVariant implements CallableParametersAcceptor { /** - * @param array $parameters + * @param array $parameters * @param SimpleThrowPoint[] $throwPoints * @param SimpleImpurePoint[] $impurePoints * @param InvalidateExprNode[] $invalidateExpressions diff --git a/src/Reflection/FunctionVariantWithPhpDocs.php b/src/Reflection/ExtendedFunctionVariant.php similarity index 77% rename from src/Reflection/FunctionVariantWithPhpDocs.php rename to src/Reflection/ExtendedFunctionVariant.php index 0b047b08b55..33c8e72c004 100644 --- a/src/Reflection/FunctionVariantWithPhpDocs.php +++ b/src/Reflection/ExtendedFunctionVariant.php @@ -9,12 +9,12 @@ /** * @api */ -class FunctionVariantWithPhpDocs extends FunctionVariant implements ParametersAcceptorWithPhpDocs +class ExtendedFunctionVariant extends FunctionVariant implements ExtendedParametersAcceptor { /** + * @param array $parameters * @api - * @param array $parameters */ public function __construct( TemplateTypeMap $templateTypeMap, @@ -38,11 +38,11 @@ public function __construct( } /** - * @return array + * @return array */ public function getParameters(): array { - /** @var array $parameters */ + /** @var array $parameters */ $parameters = parent::getParameters(); return $parameters; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 43f27903f96..e8a65b00b6d 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -23,17 +23,17 @@ interface ExtendedMethodReflection extends MethodReflection { /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getVariants(): array; /** * @internal */ - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; + public function getOnlyVariant(): ExtendedParametersAcceptor; /** - * @return ParametersAcceptorWithPhpDocs[]|null + * @return ExtendedParametersAcceptor[]|null */ public function getNamedArgumentsVariants(): ?array; diff --git a/src/Reflection/ParameterReflectionWithPhpDocs.php b/src/Reflection/ExtendedParameterReflection.php similarity index 84% rename from src/Reflection/ParameterReflectionWithPhpDocs.php rename to src/Reflection/ExtendedParameterReflection.php index 943338a493a..db8df05ab8f 100644 --- a/src/Reflection/ParameterReflectionWithPhpDocs.php +++ b/src/Reflection/ExtendedParameterReflection.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; /** @api */ -interface ParameterReflectionWithPhpDocs extends ParameterReflection +interface ExtendedParameterReflection extends ParameterReflection { public function getPhpDocType(): Type; diff --git a/src/Reflection/ParametersAcceptorWithPhpDocs.php b/src/Reflection/ExtendedParametersAcceptor.php similarity index 75% rename from src/Reflection/ParametersAcceptorWithPhpDocs.php rename to src/Reflection/ExtendedParametersAcceptor.php index f8ae03e4777..002a8a930de 100644 --- a/src/Reflection/ParametersAcceptorWithPhpDocs.php +++ b/src/Reflection/ExtendedParametersAcceptor.php @@ -6,11 +6,11 @@ use PHPStan\Type\Type; /** @api */ -interface ParametersAcceptorWithPhpDocs extends ParametersAcceptor +interface ExtendedParametersAcceptor extends ParametersAcceptor { /** - * @return array + * @return array */ public function getParameters(): array; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 09232a4d8a1..e6770e08a52 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -14,17 +14,17 @@ public function getName(): string; public function getFileName(): ?string; /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getVariants(): array; /** * @internal */ - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs; + public function getOnlyVariant(): ExtendedParametersAcceptor; /** - * @return ParametersAcceptorWithPhpDocs[]|null + * @return ExtendedParametersAcceptor[]|null */ public function getNamedArgumentsVariants(): ?array; diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 5aa65587de9..e680908c32f 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -3,7 +3,7 @@ namespace PHPStan\Reflection; use PHPStan\Reflection\Callables\CallableParametersAcceptor; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\TrinaryLogic; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\ErrorType; @@ -25,7 +25,7 @@ final class GenericParametersAcceptorResolver * @api * @param array $argTypes */ - public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ParametersAcceptorWithPhpDocs + public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ExtendedParametersAcceptor { $typeMap = TemplateTypeMap::createEmpty(); $passedArgs = []; @@ -87,11 +87,11 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc $originalParametersAcceptor = $parametersAcceptor; - if (!$parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { - $parametersAcceptor = new FunctionVariantWithPhpDocs( + if (!$parametersAcceptor instanceof ExtendedParametersAcceptor) { + $parametersAcceptor = new ExtendedFunctionVariant( $parametersAcceptor->getTemplateTypeMap(), $parametersAcceptor->getResolvedTemplateTypeMap(), - array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => new DummyParameterWithPhpDocs( + array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( $parameter->getName(), $parameter->getType(), $parameter->isOptional(), diff --git a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php b/src/Reflection/Native/ExtendedNativeParameterReflection.php similarity index 90% rename from src/Reflection/Native/NativeParameterWithPhpDocsReflection.php rename to src/Reflection/Native/ExtendedNativeParameterReflection.php index 64aa5930674..7e1388bf5a5 100644 --- a/src/Reflection/Native/NativeParameterWithPhpDocsReflection.php +++ b/src/Reflection/Native/ExtendedNativeParameterReflection.php @@ -2,12 +2,12 @@ namespace PHPStan\Reflection\Native; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class NativeParameterWithPhpDocsReflection implements ParameterReflectionWithPhpDocs +final class ExtendedNativeParameterReflection implements ExtendedParameterReflection { public function __construct( diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 529bd5fbbfe..852392d7505 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -3,8 +3,8 @@ namespace PHPStan\Reflection\Native; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -18,8 +18,8 @@ final class NativeFunctionReflection implements FunctionReflection private TrinaryLogic $returnsByReference; /** - * @param ParametersAcceptorWithPhpDocs[] $variants - * @param ParametersAcceptorWithPhpDocs[]|null $namedArgumentsVariants + * @param ExtendedParametersAcceptor[] $variants + * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants */ public function __construct( private string $name, @@ -53,7 +53,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { $variants = $this->getVariants(); if (count($variants) !== 1) { diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 1671c55aab1..b167f1223f9 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -7,8 +7,8 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodPrototypeReflection; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -22,8 +22,8 @@ final class NativeMethodReflection implements ExtendedMethodReflection { /** - * @param ParametersAcceptorWithPhpDocs[] $variants - * @param ParametersAcceptorWithPhpDocs[]|null $namedArgumentsVariants + * @param ExtendedParametersAcceptor[] $variants + * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -111,7 +111,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { $variants = $this->getVariants(); if (count($variants) !== 1) { diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 93db8e9834f..57609ab0c76 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -17,7 +17,7 @@ use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\Php\DummyParameter; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -116,7 +116,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -146,7 +146,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -196,7 +196,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -227,7 +227,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -269,7 +269,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -312,7 +312,7 @@ public static function selectFromArgs( $parameters, $acceptor->isVariadic(), $acceptor->getReturnType(), - $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), ), ]; } @@ -389,7 +389,7 @@ private static function hasAcceptorTemplateOrLateResolvableType(ParametersAccept foreach ($acceptor->getParameters() as $parameter) { if ( - $parameter instanceof ParameterReflectionWithPhpDocs + $parameter instanceof ExtendedParameterReflection && $parameter->getOutType() !== null && self::hasTemplateOrLateResolvableType($parameter->getOutType()) ) { @@ -397,7 +397,7 @@ private static function hasAcceptorTemplateOrLateResolvableType(ParametersAccept } if ( - $parameter instanceof ParameterReflectionWithPhpDocs + $parameter instanceof ExtendedParameterReflection && $parameter->getClosureThisType() !== null && self::hasTemplateOrLateResolvableType($parameter->getClosureThisType()) ) { @@ -541,7 +541,7 @@ public static function selectFromTypes( /** * @param ParametersAcceptor[] $acceptors */ - public static function combineAcceptors(array $acceptors): ParametersAcceptorWithPhpDocs + public static function combineAcceptors(array $acceptors): ExtendedParametersAcceptor { if (count($acceptors) === 0) { throw new ShouldNotHappenException( @@ -586,7 +586,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit foreach ($acceptors as $acceptor) { $returnTypes[] = $acceptor->getReturnType(); - if ($acceptor instanceof ParametersAcceptorWithPhpDocs) { + if ($acceptor instanceof ExtendedParametersAcceptor) { $phpDocReturnTypes[] = $acceptor->getPhpDocReturnType(); $nativeReturnTypes[] = $acceptor->getNativeReturnType(); } @@ -603,18 +603,18 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit foreach ($acceptor->getParameters() as $i => $parameter) { if (!isset($parameters[$i])) { - $parameters[$i] = new DummyParameterWithPhpDocs( + $parameters[$i] = new ExtendedDummyParameter( $parameter->getName(), $parameter->getType(), $i + 1 > $minimumNumberOfParameters, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue(), - $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter->getNativeType() : new MixedType(), - $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter->getPhpDocType() : new MixedType(), - $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter->getOutType() : null, - $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(), - $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter->getClosureThisType() : null, + $parameter instanceof ExtendedParameterReflection ? $parameter->getNativeType() : new MixedType(), + $parameter instanceof ExtendedParameterReflection ? $parameter->getPhpDocType() : new MixedType(), + $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null, + $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(), + $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null, ); continue; } @@ -634,7 +634,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $outType = $parameters[$i]->getOutType(); $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable(); $closureThisType = $parameters[$i]->getClosureThisType(); - if ($parameter instanceof ParameterReflectionWithPhpDocs) { + if ($parameter instanceof ExtendedParameterReflection) { $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType()); $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType()); @@ -659,7 +659,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $closureThisType = null; } - $parameters[$i] = new DummyParameterWithPhpDocs( + $parameters[$i] = new ExtendedDummyParameter( $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(), $type, $i + 1 > $minimumNumberOfParameters, @@ -685,7 +685,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit $nativeReturnType = $nativeReturnTypes === [] ? null : TypeCombinator::union(...$nativeReturnTypes); if ($callableOccurred) { - return new CallableFunctionVariantWithPhpDocs( + return new ExtendedCallableFunctionVariant( TemplateTypeMap::createEmpty(), null, $parameters, @@ -703,7 +703,7 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit ); } - return new FunctionVariantWithPhpDocs( + return new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), null, $parameters, @@ -714,17 +714,17 @@ public static function combineAcceptors(array $acceptors): ParametersAcceptorWit ); } - private static function wrapAcceptor(ParametersAcceptor $acceptor): ParametersAcceptorWithPhpDocs + private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedParametersAcceptor { - if ($acceptor instanceof ParametersAcceptorWithPhpDocs) { + if ($acceptor instanceof ExtendedParametersAcceptor) { return $acceptor; } if ($acceptor instanceof CallableParametersAcceptor) { - return new CallableFunctionVariantWithPhpDocs( + return new ExtendedCallableFunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => self::wrapParameter($parameter), $acceptor->getParameters()), + array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor->getReturnType(), @@ -739,10 +739,10 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ParametersAc ); } - return new FunctionVariantWithPhpDocs( + return new ExtendedFunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => self::wrapParameter($parameter), $acceptor->getParameters()), + array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor->getReturnType(), @@ -751,9 +751,9 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ParametersAc ); } - private static function wrapParameter(ParameterReflection $parameter): ParameterReflectionWithPhpDocs + private static function wrapParameter(ParameterReflection $parameter): ExtendedParameterReflection { - return $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter : new DummyParameterWithPhpDocs( + return $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter( $parameter->getName(), $parameter->getType(), $parameter->isOptional(), diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index 1bb9d201c8d..e28f4cd2594 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -5,12 +5,12 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\ClosureType; @@ -81,10 +81,10 @@ public function getVariants(): array array_unshift($parameters, $newThis); return [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( $this->closureType->getTemplateTypeMap(), $this->closureType->getResolvedTemplateTypeMap(), - array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => new DummyParameterWithPhpDocs( + array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( $parameter->getName(), $parameter->getType(), $parameter->isOptional(), @@ -106,7 +106,7 @@ public function getVariants(): array ]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index bd706e7c98d..2758c04c27f 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -5,9 +5,9 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -64,7 +64,7 @@ public function getPrototype(): ClassMemberReflection public function getVariants(): array { return [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [], @@ -76,7 +76,7 @@ public function getVariants(): array ]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index 331105aceba..12eb38927d9 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -3,9 +3,9 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantIntegerType; @@ -42,11 +42,11 @@ public function getVariants(): array new IntegerType(), ]); return [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [ - new DummyParameterWithPhpDocs( + new ExtendedDummyParameter( 'status', $parameterType, true, @@ -69,13 +69,13 @@ public function getVariants(): array ]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getNamedArgumentsVariants(): array { diff --git a/src/Reflection/Php/DummyParameterWithPhpDocs.php b/src/Reflection/Php/ExtendedDummyParameter.php similarity index 86% rename from src/Reflection/Php/DummyParameterWithPhpDocs.php rename to src/Reflection/Php/ExtendedDummyParameter.php index c7d8cc141ce..91238c18b91 100644 --- a/src/Reflection/Php/DummyParameterWithPhpDocs.php +++ b/src/Reflection/Php/ExtendedDummyParameter.php @@ -2,12 +2,12 @@ namespace PHPStan\Reflection\Php; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -final class DummyParameterWithPhpDocs extends DummyParameter implements ParameterReflectionWithPhpDocs +final class ExtendedDummyParameter extends DummyParameter implements ExtendedParameterReflection { public function __construct( diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 5a3606a66f4..c42863737c0 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -21,13 +21,13 @@ use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; use PHPStan\Reflection\Native\NativeMethodReflection; -use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\Reflection\PropertiesClassReflectionExtension; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\FunctionSignature; @@ -822,7 +822,7 @@ private function createNativeMethodVariant( array $stubClosureThisParameters, array $closureThisParameters, bool $usePhpDocParameterNames, - ): FunctionVariantWithPhpDocs + ): ExtendedFunctionVariant { $parameters = []; foreach ($methodSignature->getParameters() as $parameterSignature) { @@ -860,7 +860,7 @@ private function createNativeMethodVariant( $closureThisType = $closureThisParameters[$phpDocParameterName]; } - $parameters[] = new NativeParameterWithPhpDocsReflection( + $parameters[] = new ExtendedNativeParameterReflection( $usePhpDocParameterNames ? $phpDocParameterName : $parameterSignature->getName(), @@ -884,7 +884,7 @@ private function createNativeMethodVariant( $returnType = TypehintHelper::decideType($methodSignature->getReturnType(), $phpDocReturnType); } - return new FunctionVariantWithPhpDocs( + return new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), null, $parameters, diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 227b23ddab3..dc44c0d17dc 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -8,10 +8,10 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -27,13 +27,13 @@ /** * @api */ -class PhpFunctionFromParserNodeReflection implements FunctionReflection, ParametersAcceptorWithPhpDocs +class PhpFunctionFromParserNodeReflection implements FunctionReflection, ExtendedParametersAcceptor { /** @var Function_|ClassMethod */ private Node\FunctionLike $functionLike; - /** @var FunctionVariantWithPhpDocs[]|null */ + /** @var ExtendedFunctionVariant[]|null */ private ?array $variants = null; /** @@ -94,13 +94,13 @@ public function getName(): string } /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getVariants(): array { if ($this->variants === null) { $this->variants = [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( $this->getTemplateTypeMap(), $this->getResolvedTemplateTypeMap(), $this->getParameters(), @@ -115,7 +115,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this; } @@ -136,7 +136,7 @@ public function getResolvedTemplateTypeMap(): TemplateTypeMap } /** - * @return array + * @return array */ public function getParameters(): array { diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 6aba725f1cd..4669701c793 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -10,12 +10,12 @@ use PHPStan\Parser\FunctionCallStatementFinder; use PHPStan\Parser\Parser; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -32,7 +32,7 @@ final class PhpFunctionReflection implements FunctionReflection { - /** @var FunctionVariantWithPhpDocs[]|null */ + /** @var ExtendedFunctionVariant[]|null */ private ?array $variants = null; /** @@ -86,13 +86,13 @@ public function getFileName(): ?string } /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getVariants(): array { if ($this->variants === null) { $this->variants = [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( $this->templateTypeMap, null, $this->getParameters(), @@ -107,7 +107,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } @@ -118,7 +118,7 @@ public function getNamedArgumentsVariants(): ?array } /** - * @return ParameterReflectionWithPhpDocs[] + * @return ExtendedParameterReflection[] */ private function getParameters(): array { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index d71fce1a4c4..afa4f56a467 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -15,13 +15,13 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodPrototypeReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; @@ -59,7 +59,7 @@ final class PhpMethodReflection implements ExtendedMethodReflection private ?Type $nativeReturnType = null; - /** @var FunctionVariantWithPhpDocs[]|null */ + /** @var ExtendedFunctionVariant[]|null */ private ?array $variants = null; /** @@ -191,13 +191,13 @@ private function getMethodNameWithCorrectCase(string $lowercaseMethodName, strin } /** - * @return ParametersAcceptorWithPhpDocs[] + * @return ExtendedParametersAcceptor[] */ public function getVariants(): array { if ($this->variants === null) { $this->variants = [ - new FunctionVariantWithPhpDocs( + new ExtendedFunctionVariant( $this->templateTypeMap, null, $this->getParameters(), @@ -212,7 +212,7 @@ public function getVariants(): array return $this->variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } @@ -223,7 +223,7 @@ public function getNamedArgumentsVariants(): ?array } /** - * @return ParameterReflectionWithPhpDocs[] + * @return ExtendedParameterReflection[] */ private function getParameters(): array { diff --git a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php index 61d1852c7d8..8ebb272bfd4 100644 --- a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php @@ -2,7 +2,7 @@ namespace PHPStan\Reflection\Php; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; @@ -10,7 +10,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; -final class PhpParameterFromParserNodeReflection implements ParameterReflectionWithPhpDocs +final class PhpParameterFromParserNodeReflection implements ExtendedParameterReflection { private ?Type $type = null; diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index 548d9bde47e..40b28e9ff6e 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -4,9 +4,9 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; @@ -14,7 +14,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; -final class PhpParameterReflection implements ParameterReflectionWithPhpDocs +final class PhpParameterReflection implements ExtendedParameterReflection { private ?Type $type = null; diff --git a/src/Reflection/ResolvedFunctionVariant.php b/src/Reflection/ResolvedFunctionVariant.php index a6139853fb1..5b5cb6b4e65 100644 --- a/src/Reflection/ResolvedFunctionVariant.php +++ b/src/Reflection/ResolvedFunctionVariant.php @@ -4,7 +4,7 @@ use PHPStan\Type\Type; -interface ResolvedFunctionVariant extends ParametersAcceptorWithPhpDocs +interface ResolvedFunctionVariant extends ExtendedParametersAcceptor { public function getOriginalParametersAcceptor(): ParametersAcceptor; diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index ab5b182105f..4dda7b8685d 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -2,7 +2,7 @@ namespace PHPStan\Reflection; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\GenericObjectType; @@ -21,7 +21,7 @@ final class ResolvedFunctionVariantWithOriginal implements ResolvedFunctionVariant { - /** @var ParameterReflectionWithPhpDocs[]|null */ + /** @var ExtendedParameterReflection[]|null */ private ?array $parameters = null; private ?Type $returnTypeWithUnresolvableTemplateTypes = null; @@ -36,7 +36,7 @@ final class ResolvedFunctionVariantWithOriginal implements ResolvedFunctionVaria * @param array $passedArgs */ public function __construct( - private ParametersAcceptorWithPhpDocs $parametersAcceptor, + private ExtendedParametersAcceptor $parametersAcceptor, private TemplateTypeMap $resolvedTemplateTypeMap, private TemplateTypeVarianceMap $callSiteVarianceMap, private array $passedArgs, @@ -70,7 +70,7 @@ public function getParameters(): array if ($parameters === null) { $parameters = array_map( - function (ParameterReflectionWithPhpDocs $param): ParameterReflectionWithPhpDocs { + function (ExtendedParameterReflection $param): ExtendedParameterReflection { $paramType = TypeUtils::resolveLateResolvableTypes( TemplateTypeHelper::resolveTemplateTypes( $this->resolveConditionalTypesForParameter($param->getType()), @@ -107,7 +107,7 @@ function (ParameterReflectionWithPhpDocs $param): ParameterReflectionWithPhpDocs ); } - return new DummyParameterWithPhpDocs( + return new ExtendedDummyParameter( $param->getName(), $paramType, $param->isOptional(), diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 1ce0c0dc715..b04803c13c0 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -14,10 +14,10 @@ final class ResolvedMethodReflection implements ExtendedMethodReflection { - /** @var ParametersAcceptorWithPhpDocs[]|null */ + /** @var ExtendedParametersAcceptor[]|null */ private ?array $variants = null; - /** @var ParametersAcceptorWithPhpDocs[]|null */ + /** @var ExtendedParametersAcceptor[]|null */ private ?array $namedArgumentVariants = null; private ?Assertions $asserts = null; @@ -52,7 +52,7 @@ public function getVariants(): array return $this->variants = $this->resolveVariants($this->reflection->getVariants()); } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } @@ -73,7 +73,7 @@ public function getNamedArgumentsVariants(): ?array } /** - * @param ParametersAcceptorWithPhpDocs[] $variants + * @param ExtendedParametersAcceptor[] $variants * @return ResolvedFunctionVariant[] */ private function resolveVariants(array $variants): array diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 32a484c3c93..2a94fc4da58 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -9,9 +9,9 @@ use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\Assertions; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; use PHPStan\Reflection\Native\NativeFunctionReflection; -use PHPStan\Reflection\Native\NativeParameterWithPhpDocsReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -91,10 +91,10 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $variantsByType = ['positional' => []]; foreach ($functionSignaturesResult as $signatureType => $functionSignatures) { foreach ($functionSignatures ?? [] as $functionSignature) { - $variantsByType[$signatureType][] = new FunctionVariantWithPhpDocs( + $variantsByType[$signatureType][] = new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), null, - array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc): NativeParameterWithPhpDocsReflection { + array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc): ExtendedNativeParameterReflection { $type = $parameterSignature->getType(); $phpDocType = null; @@ -112,7 +112,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef } } - return new NativeParameterWithPhpDocsReflection( + return new ExtendedNativeParameterReflection( $parameterSignature->getName(), $parameterSignature->isOptional(), TypehintHelper::decideType($type, $phpDocType), diff --git a/src/Reflection/TrivialParametersAcceptor.php b/src/Reflection/TrivialParametersAcceptor.php index 05a4888c277..ea6b2781454 100644 --- a/src/Reflection/TrivialParametersAcceptor.php +++ b/src/Reflection/TrivialParametersAcceptor.php @@ -14,7 +14,7 @@ /** * @api */ -final class TrivialParametersAcceptor implements ParametersAcceptorWithPhpDocs, CallableParametersAcceptor +final class TrivialParametersAcceptor implements ExtendedParametersAcceptor, CallableParametersAcceptor { /** @api */ diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index 5fd19b31058..eaca01ec4ca 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -4,11 +4,11 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Dummy\ChangedTypeMethodReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\Type; use function array_map; @@ -82,11 +82,11 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variantFn = fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( + $variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), array_map( - fn (ParameterReflectionWithPhpDocs $parameter): ParameterReflectionWithPhpDocs => new DummyParameterWithPhpDocs( + fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( $parameter->getName(), $this->transformStaticType($parameter->getType()), $parameter->isOptional(), diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 607a08a37f4..25f676b5ae2 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -4,11 +4,11 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Dummy\ChangedTypeMethodReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\StaticType; use PHPStan\Type\Type; @@ -77,11 +77,11 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variantFn = fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( + $variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), array_map( - fn (ParameterReflectionWithPhpDocs $parameter): ParameterReflectionWithPhpDocs => new DummyParameterWithPhpDocs( + fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( $parameter->getName(), $this->transformStaticType($parameter->getType()), $parameter->isOptional(), diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index d27576767f2..c19986d71cd 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -5,11 +5,11 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -83,7 +83,7 @@ public function getVariants(): array $phpDocReturnType = TypeCombinator::intersect(...array_map(static fn (MethodReflection $method): Type => TypeCombinator::intersect(...array_map(static fn (ParametersAcceptor $acceptor): Type => $acceptor->getPhpDocReturnType(), $method->getVariants())), $this->methods)); $nativeReturnType = TypeCombinator::intersect(...array_map(static fn (MethodReflection $method): Type => TypeCombinator::intersect(...array_map(static fn (ParametersAcceptor $acceptor): Type => $acceptor->getNativeReturnType(), $method->getVariants())), $this->methods)); - return array_map(static fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( + return array_map(static fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), $acceptor->getParameters(), @@ -95,7 +95,7 @@ public function getVariants(): array ), $this->methods[0]->getVariants()); } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { $variants = $this->getVariants(); if (count($variants) !== 1) { diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 140ce0bd2e1..167493c0b88 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -6,9 +6,9 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -83,7 +83,7 @@ public function getVariants(): array return [ParametersAcceptorSelector::combineAcceptors($variants)]; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index 2f348095ff6..c14e51656ee 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -2,7 +2,7 @@ namespace PHPStan\Reflection; -use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; +use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\MixedType; @@ -55,15 +55,15 @@ public function getVariants(): array { $variants = []; foreach ($this->method->getVariants() as $variant) { - if ($variant instanceof ParametersAcceptorWithPhpDocs) { + if ($variant instanceof ExtendedParametersAcceptor) { $variants[] = $variant; continue; } - $variants[] = new FunctionVariantWithPhpDocs( + $variants[] = new ExtendedFunctionVariant( $variant->getTemplateTypeMap(), $variant->getResolvedTemplateTypeMap(), - array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => $parameter instanceof ParameterReflectionWithPhpDocs ? $parameter : new DummyParameterWithPhpDocs( + array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter( $parameter->getName(), $parameter->getType(), $parameter->isOptional(), @@ -87,7 +87,7 @@ public function getVariants(): array return $variants; } - public function getOnlyVariant(): ParametersAcceptorWithPhpDocs + public function getOnlyVariant(): ExtendedParametersAcceptor { return $this->getVariants()[0]; } diff --git a/src/Rules/Api/BcUncoveredInterface.php b/src/Rules/Api/BcUncoveredInterface.php index ccaf001a14b..3bf9c0c61db 100644 --- a/src/Rules/Api/BcUncoveredInterface.php +++ b/src/Rules/Api/BcUncoveredInterface.php @@ -15,11 +15,11 @@ use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\NamespaceAnswerer; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\IdentifierRuleError; @@ -48,8 +48,8 @@ final class BcUncoveredInterface FunctionReflection::class, ExtendedMethodReflection::class, ExtendedPropertyReflection::class, - ParametersAcceptorWithPhpDocs::class, - ParameterReflectionWithPhpDocs::class, + ExtendedParametersAcceptor::class, + ExtendedParameterReflection::class, CallableParametersAcceptor::class, FileRuleError::class, IdentifierRuleError::class, diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index aa8b9f356ae..365ab626a1f 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -7,8 +7,8 @@ use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ParameterReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ResolvedFunctionVariant; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -342,7 +342,7 @@ public function check( } if ( - $parameter instanceof ParameterReflectionWithPhpDocs + $parameter instanceof ExtendedParameterReflection && $parameter->getClosureThisType() !== null && ($argumentValue instanceof Expr\Closure || $argumentValue instanceof Expr\ArrowFunction) && $argumentValue->static diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 8f91b45ec97..6874582743c 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -18,10 +18,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\Printer\NodeTypePrinter; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ParameterReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; @@ -368,7 +368,7 @@ private function checkParametersAcceptor( return $parameterNode; }; - if ($parameter instanceof ParameterReflectionWithPhpDocs) { + if ($parameter instanceof ExtendedParameterReflection) { $parameterVar = $parameterNodeCallback()->var; if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { throw new ShouldNotHappenException(); @@ -630,7 +630,7 @@ private function getParameterNode( */ private function getParameterReferencedClasses(ParameterReflection $parameter): array { - if (!$parameter instanceof ParameterReflectionWithPhpDocs) { + if (!$parameter instanceof ExtendedParameterReflection) { return $parameter->getType()->getReferencedClasses(); } @@ -658,7 +658,7 @@ private function getParameterReferencedClasses(ParameterReflection $parameter): */ private function getReturnTypeReferencedClasses(ParametersAcceptor $parametersAcceptor): array { - if (!$parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) { + if (!$parametersAcceptor instanceof ExtendedParametersAcceptor) { return $parametersAcceptor->getReturnType()->getReferencedClasses(); } diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index 95170eecb49..d01dbf75a37 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Generics; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Generic\TemplateType; @@ -18,7 +18,7 @@ final class VarianceCheck * @return list */ public function checkParametersAcceptor( - ParametersAcceptorWithPhpDocs $parametersAcceptor, + ExtendedParametersAcceptor $parametersAcceptor, string $parameterTypeMessage, string $parameterOutTypeMessage, string $returnTypeMessage, diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index bac3f273ad6..c3c04089ce7 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -7,8 +7,8 @@ use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -196,8 +196,8 @@ private function collectParentMethods(string $methodName, ClassReflection $class */ private function checkReturnTypeCompatibility( ClassReflection $declaringClass, - ParametersAcceptorWithPhpDocs $currentVariant, - ParametersAcceptorWithPhpDocs $parentVariant, + ExtendedParametersAcceptor $currentVariant, + ExtendedParametersAcceptor $parentVariant, ): array { $returnType = TypehintHelper::decideType( @@ -226,8 +226,8 @@ private function checkReturnTypeCompatibility( } /** - * @param ParameterReflectionWithPhpDocs[] $parameters - * @param ParameterReflectionWithPhpDocs[] $parentParameters + * @param ExtendedParameterReflection[] $parameters + * @param ExtendedParameterReflection[] $parentParameters * @return array */ private function checkParameterTypeCompatibility( diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 0a78f8d3820..38c588f9db2 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -7,8 +7,8 @@ use PHPStan\Node\InClassMethodNode; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; -use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\Php\PhpClassReflectionExtension; @@ -230,7 +230,7 @@ public function processNode(Node $node, Scope $scope): array $messages = array_merge($messages, $this->methodParameterComparisonHelper->compare($prototype, $prototypeDeclaringClass, $method, false)); - if (!$prototypeVariant instanceof FunctionVariantWithPhpDocs) { + if (!$prototypeVariant instanceof ExtendedFunctionVariant) { return $this->addErrors($messages, $node, $scope); } diff --git a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php index eee8ad3281e..f48a8abc7a5 100644 --- a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php +++ b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\PhpDoc; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ConditionalType; @@ -23,7 +23,7 @@ final class ConditionalReturnTypeRuleHelper /** * @return list */ - public function check(ParametersAcceptorWithPhpDocs $acceptor): array + public function check(ExtendedParametersAcceptor $acceptor): array { $conditionalTypes = []; $parametersByName = []; diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index e70d2eb2927..13d0c1fc93e 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -8,8 +8,8 @@ use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\ThrowPoint; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Rules\Functions\CallToFunctionStatementWithoutSideEffectsRule; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -25,7 +25,7 @@ final class FunctionPurityCheck /** * @param 'Function'|'Method' $identifier - * @param ParameterReflectionWithPhpDocs[] $parameters + * @param ExtendedParameterReflection[] $parameters * @param ImpurePoint[] $impurePoints * @param ThrowPoint[] $throwPoints * @param Stmt[] $statements diff --git a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php index ceeb0712389..e891b65fb6f 100644 --- a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php @@ -6,7 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\ReturnStatement; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\TypeUtils; @@ -20,7 +20,7 @@ final class TooWideParameterOutTypeCheck /** * @param list $executionEnds * @param list $returnStatements - * @param ParameterReflectionWithPhpDocs[] $parameters + * @param ExtendedParameterReflection[] $parameters * @return list */ public function check( @@ -74,7 +74,7 @@ public function check( private function processSingleParameter( Scope $scope, string $functionDescription, - ParameterReflectionWithPhpDocs $parameter, + ExtendedParameterReflection $parameter, ): array { $isParamOutType = true; diff --git a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php index 4c380a67c36..d8337c97a0e 100644 --- a/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php +++ b/src/Rules/Variables/ParameterOutExecutionEndTypeRule.php @@ -7,8 +7,8 @@ use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParameterReflectionWithPhpDocs; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -78,7 +78,7 @@ public function processNode(Node $node, Scope $scope): array private function processSingleParameter( Scope $scope, FunctionReflection|ExtendedMethodReflection $inFunction, - ParameterReflectionWithPhpDocs $parameter, + ExtendedParameterReflection $parameter, ): array { $outType = $parameter->getOutType(); diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index d392b20500e..d579157160c 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -5,8 +5,8 @@ use Closure; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Type\ClosureType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ErrorType; @@ -47,7 +47,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $variant->isVariadic(), $variant->getTemplateTypeMap(), $variant->getResolvedTemplateTypeMap(), - $variant instanceof ParametersAcceptorWithPhpDocs ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), [], $variant->getThrowPoints(), $variant->getImpurePoints(), diff --git a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php index bfafb5996e3..870d185d59b 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php @@ -364,7 +364,7 @@ private static function generateFunctionMethodBaseDescription($reflection): stri return $result; } - /** @param ParametersAcceptorWithPhpDocs[] $variants */ + /** @param ExtendedParametersAcceptor[] $variants */ private static function generateVariantsDescription(string $name, array $variants, bool $isNamedArguments): string { $variantCount = count($variants); From 9e7f39ed4ca63dc37941295a627958d517c6c8b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 11:12:53 +0200 Subject: [PATCH 0574/3097] [BCB] `ClassPropertyNode::getNativeType()` return type changed from AST node to Type --- UPGRADING.md | 1 + src/Analyser/NodeScopeResolver.php | 7 ++- src/Dependency/DependencyResolver.php | 6 +- src/Node/ClassPropertyNode.php | 17 +++--- .../IncompatiblePropertyPhpDocTypeRule.php | 55 ++++++++++--------- .../Properties/OverridingPropertyRule.php | 9 ++- src/Rules/Types/InvalidTypesInUnionRule.php | 4 +- 7 files changed, 51 insertions(+), 48 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 7859c5f2675..5f34052a8c6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -299,3 +299,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` * Interface `GlobalConstantReflection` renamed to `ConstantReflection` * Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` +* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ec807de6fad..dfb1b6c1f4d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -170,6 +170,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; @@ -648,7 +649,7 @@ private function processStmtNode( $nodeCallback(new ClassPropertyNode( $param->var->name, $param->flags, - $param->type, + $param->type !== null ? ParserNodeTypeToPHPStanType::resolve($param->type, $scope->getClassReflection()) : null, null, $phpDoc, $phpDocParameterTypes[$param->var->name] ?? null, @@ -899,13 +900,13 @@ private function processStmtNode( new ClassPropertyNode( $propertyName, $stmt->flags, - $stmt->type, + $stmt->type !== null ? ParserNodeTypeToPHPStanType::resolve($stmt->type, $scope->getClassReflection()) : null, $prop->default, $docComment, $phpDocType, false, false, - $prop, + $stmt, $isReadOnly, $scope->isInTrait(), $scope->getClassReflection()->isReadOnly(), diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 9fde7ef34f1..71fdb78b46b 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -22,7 +22,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ClosureType; use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\Type; use function array_merge; use function count; @@ -85,9 +84,8 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies } } } elseif ($node instanceof ClassPropertyNode) { - $nativeTypeNode = $node->getNativeType(); - if ($nativeTypeNode !== null) { - $nativeType = ParserNodeTypeToPHPStanType::resolve($nativeTypeNode, $node->getClassReflection()); + $nativeType = $node->getNativeType(); + if ($nativeType !== null) { foreach ($nativeType->getReferencedClasses() as $referencedClass) { $this->addClassToDependencies($referencedClass, $dependenciesReflections); } diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index 571f8b34ed0..3f500b62c1d 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -5,8 +5,6 @@ use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Expr; -use PhpParser\Node\Identifier; -use PhpParser\Node\Name; use PhpParser\NodeAbstract; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Type; @@ -20,13 +18,13 @@ final class ClassPropertyNode extends NodeAbstract implements VirtualNode public function __construct( private string $name, private int $flags, - private Identifier|Name|Node\ComplexType|null $type, + private ?Type $type, private ?Expr $default, private ?string $phpDoc, private ?Type $phpDocType, private bool $isPromoted, private bool $isPromotedFromTrait, - Node $originalNode, + private Node\Stmt\Property|Node\Param $originalNode, private bool $isReadonlyByPhpDoc, private bool $isDeclaredInTrait, private bool $isReadonlyClass, @@ -113,12 +111,17 @@ public function isAllowedPrivateMutation(): bool return $this->isAllowedPrivateMutation; } + public function getNativeType(): ?Type + { + return $this->type; + } + /** - * @return Identifier|Name|Node\ComplexType|null + * @return Node\Identifier|Node\Name|Node\ComplexType|null */ - public function getNativeType() + public function getNativeTypeNode() { - return $this->type; + return $this->originalNode->type; } public function getClassReflection(): ClassReflection diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index 184821b5a3f..a202f67bcdd 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -10,7 +10,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Generic\TemplateType; -use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\VerbosityLevel; use function array_merge; use function sprintf; @@ -62,33 +61,35 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.unresolvableType')->build(); } - $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $classReflection); - $isSuperType = $nativeType->isSuperTypeOf($phpDocType); - if ($isSuperType->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s for property %s::$%s with type %s is incompatible with native type %s.', - $description, - $classReflection->getDisplayName(), - $propertyName, - $phpDocType->describe(VerbosityLevel::typeOnly()), - $nativeType->describe(VerbosityLevel::typeOnly()), - ))->identifier('property.phpDocType')->build(); - - } elseif ($isSuperType->maybe()) { - $errorBuilder = RuleErrorBuilder::message(sprintf( - '%s for property %s::$%s with type %s is not subtype of native type %s.', - $description, - $classReflection->getDisplayName(), - $propertyName, - $phpDocType->describe(VerbosityLevel::typeOnly()), - $nativeType->describe(VerbosityLevel::typeOnly()), - ))->identifier('property.phpDocType'); - - if ($phpDocType instanceof TemplateType) { - $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocType->getName(), $nativeType->describe(VerbosityLevel::typeOnly()))); + $nativeType = $node->getNativeType(); + if ($nativeType !== null) { + $isSuperType = $nativeType->isSuperTypeOf($phpDocType); + if ($isSuperType->no()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s for property %s::$%s with type %s is incompatible with native type %s.', + $description, + $classReflection->getDisplayName(), + $propertyName, + $phpDocType->describe(VerbosityLevel::typeOnly()), + $nativeType->describe(VerbosityLevel::typeOnly()), + ))->identifier('property.phpDocType')->build(); + + } elseif ($isSuperType->maybe()) { + $errorBuilder = RuleErrorBuilder::message(sprintf( + '%s for property %s::$%s with type %s is not subtype of native type %s.', + $description, + $classReflection->getDisplayName(), + $propertyName, + $phpDocType->describe(VerbosityLevel::typeOnly()), + $nativeType->describe(VerbosityLevel::typeOnly()), + ))->identifier('property.phpDocType'); + + if ($phpDocType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocType->getName(), $nativeType->describe(VerbosityLevel::typeOnly()))); + } + + $messages[] = $errorBuilder->build(); } - - $messages[] = $errorBuilder->build(); } $className = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index be6a9fca4f4..5767635e27d 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -9,7 +9,6 @@ use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\VerbosityLevel; use function array_merge; use function count; @@ -104,8 +103,9 @@ public function processNode(Node $node, Scope $scope): array } $typeErrors = []; + $nativeType = $node->getNativeType(); if ($prototype->hasNativeType()) { - if ($node->getNativeType() === null) { + if ($nativeType === null) { $typeErrors[] = RuleErrorBuilder::message(sprintf( 'Property %s::$%s overriding property %s::$%s (%s) should also have native type %s.', $classReflection->getDisplayName(), @@ -116,7 +116,6 @@ public function processNode(Node $node, Scope $scope): array $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), ))->identifier('property.missingNativeType')->nonIgnorable()->build(); } else { - $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $classReflection); if (!$prototype->getNativeType()->equals($nativeType)) { $typeErrors[] = RuleErrorBuilder::message(sprintf( 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', @@ -129,12 +128,12 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.nativeType')->nonIgnorable()->build(); } } - } elseif ($node->getNativeType() !== null) { + } elseif ($nativeType !== null) { $typeErrors[] = RuleErrorBuilder::message(sprintf( 'Property %s::$%s (%s) overriding property %s::$%s should not have a native type.', $classReflection->getDisplayName(), $node->getName(), - ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $classReflection)->describe(VerbosityLevel::typeOnly()), + $nativeType->describe(VerbosityLevel::typeOnly()), $prototype->getDeclaringClass()->getDisplayName(), $node->getName(), ))->identifier('property.extraNativeType')->nonIgnorable()->build(); diff --git a/src/Rules/Types/InvalidTypesInUnionRule.php b/src/Rules/Types/InvalidTypesInUnionRule.php index ba531117608..39379b66635 100644 --- a/src/Rules/Types/InvalidTypesInUnionRule.php +++ b/src/Rules/Types/InvalidTypesInUnionRule.php @@ -69,11 +69,11 @@ private function processFunctionLikeNode(Node\FunctionLike $functionLike): array */ private function processClassPropertyNode(ClassPropertyNode $classPropertyNode): array { - if (!$classPropertyNode->getNativeType() instanceof Node\ComplexType) { + if (!$classPropertyNode->getNativeTypeNode() instanceof Node\ComplexType) { return []; } - return $this->processComplexType($classPropertyNode->getNativeType()); + return $this->processComplexType($classPropertyNode->getNativeTypeNode()); } /** From f38addda2b151b6e41a746a37659c0bbe9e2293b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 11:22:50 +0200 Subject: [PATCH 0575/3097] Identifiers in the PHP baseline as real array keys --- .../BaselinePhpErrorFormatter.php | 29 ++++++++++--------- .../BaselinePhpErrorFormatterTest.php | 12 ++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php index 8107aba19b9..fefb3175fd7 100644 --- a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php @@ -8,7 +8,6 @@ use PHPStan\File\RelativePathHelper; use function array_keys; use function count; -use function implode; use function ksort; use function preg_quote; use function sort; @@ -74,22 +73,24 @@ public function formatErrors( foreach ($fileErrorsByMessage as $message => [$count, $identifiersInKeys]) { $identifiers = array_keys($identifiersInKeys); sort($identifiers); - $identifiersComment = ''; if (count($identifiers) > 0) { - if (count($identifiers) === 1) { - $identifiersComment = "\n\t// identifier: " . $identifiers[0]; - } else { - $identifiersComment = "\n\t// identifiers: " . implode(', ', $identifiers); + foreach ($identifiers as $identifier) { + $php .= sprintf( + "\$ignoreErrors[] = [\n\t'message' => %s,\n\t'identifier' => %s,\n\t'count' => %d,\n\t'path' => __DIR__ . %s,\n];\n", + var_export(Helpers::escape('#^' . preg_quote($message, '#') . '$#'), true), + var_export(Helpers::escape($identifier), true), + var_export($count, true), + var_export(Helpers::escape($file), true), + ); } + } else { + $php .= sprintf( + "\$ignoreErrors[] = [\n\t'message' => %s,\n\t'count' => %d,\n\t'path' => __DIR__ . %s,\n];\n", + var_export(Helpers::escape('#^' . preg_quote($message, '#') . '$#'), true), + var_export($count, true), + var_export(Helpers::escape($file), true), + ); } - - $php .= sprintf( - "\$ignoreErrors[] = [%s\n\t'message' => %s,\n\t'count' => %d,\n\t'path' => __DIR__ . %s,\n];\n", - $identifiersComment, - var_export(Helpers::escape('#^' . preg_quote($message, '#') . '$#'), true), - var_export($count, true), - var_export(Helpers::escape($file), true), - ); } } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php index b49c717f228..4ba93f68057 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php @@ -80,8 +80,8 @@ public function dataFormatErrors(): iterable 'path' => __DIR__ . '/Foo.php', ]; \$ignoreErrors[] = [ - // identifier: argument.type 'message' => '#^Foo with identifier$#', + 'identifier' => 'argument.type', 'count' => 2, 'path' => __DIR__ . '/Foo.php', ]; @@ -127,14 +127,20 @@ public function dataFormatErrors(): iterable 'path' => __DIR__ . '/Foo.php', ]; \$ignoreErrors[] = [ - // identifier: argument.type 'message' => '#^Foo with another message$#', + 'identifier' => 'argument.type', 'count' => 1, 'path' => __DIR__ . '/Foo.php', ]; \$ignoreErrors[] = [ - // identifiers: argument.byRef, argument.type 'message' => '#^Foo with same message, different identifier$#', + 'identifier' => 'argument.byRef', + 'count' => 2, + 'path' => __DIR__ . '/Foo.php', +]; +\$ignoreErrors[] = [ + 'message' => '#^Foo with same message, different identifier$#', + 'identifier' => 'argument.type', 'count' => 2, 'path' => __DIR__ . '/Foo.php', ]; From 64ed7dc7207aff6d7d833f9c0449b13257c4da40 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Wed, 2 Oct 2024 09:49:18 +0200 Subject: [PATCH 0576/3097] Introduce `Scope::getMaybeDefinedVariables()` --- src/Analyser/MutatingScope.php | 21 +++++++++++++++++++++ src/Analyser/Scope.php | 5 +++++ tests/PHPStan/Analyser/ScopeTest.php | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 086a09c3c2d..d8cc9faf069 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -574,6 +574,27 @@ public function getDefinedVariables(): array return $variables; } + /** + * @api + * @return array + */ + public function getMaybeDefinedVariables(): array + { + $variables = []; + foreach ($this->expressionTypes as $exprString => $holder) { + if (!$holder->getExpr() instanceof Variable) { + continue; + } + if (!$holder->getCertainty()->maybe()) { + continue; + } + + $variables[] = substr($exprString, 1); + } + + return $variables; + } + private function isGlobalVariable(string $variableName): bool { return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true); diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 0c1682209d3..b1fe0cff972 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -67,6 +67,11 @@ public function canAnyVariableExist(): bool; */ public function getDefinedVariables(): array; + /** + * @return array + */ + public function getMaybeDefinedVariables(): array; + public function hasConstant(Name $name): bool; public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index fe0644cd307..cdad83a96f7 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -5,16 +5,21 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Name\FullyQualified; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\ObjectType; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +/** + * @covers \PHPStan\Analyser\MutatingScope + */ class ScopeTest extends PHPStanTestCase { @@ -248,4 +253,26 @@ public function testGetConstantType(): void $this->assertSame('int<1, max>', $type->describe(VerbosityLevel::precise())); } + public function testDefinedVariables(): void + { + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + $scope = $scopeFactory->create(ScopeContext::create('file.php')) + ->assignVariable('a', new ConstantStringType('a'), new StringType(), TrinaryLogic::createYes()) + ->assignVariable('b', new ConstantStringType('b'), new StringType(), TrinaryLogic::createMaybe()); + + $this->assertSame(['a'], $scope->getDefinedVariables()); + } + + public function testMaybeDefinedVariables(): void + { + /** @var ScopeFactory $scopeFactory */ + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + $scope = $scopeFactory->create(ScopeContext::create('file.php')) + ->assignVariable('a', new ConstantStringType('a'), new StringType(), TrinaryLogic::createYes()) + ->assignVariable('b', new ConstantStringType('b'), new StringType(), TrinaryLogic::createMaybe()); + + $this->assertSame(['b'], $scope->getMaybeDefinedVariables()); + } + } From 710e09c41698efb1d8d3ae31791944077dbb9cc1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 1 Oct 2024 12:08:19 +0200 Subject: [PATCH 0577/3097] Refactored `FunctionCallParametersCheck::check()` parameters --- src/Rules/AttributesCheck.php | 32 ++++++------- src/Rules/Classes/InstantiationRule.php | 32 ++++++------- src/Rules/FunctionCallParametersCheck.php | 48 ++++++++++++------- src/Rules/Functions/CallCallablesRule.php | 32 ++++++------- .../CallToFunctionParametersRule.php | 32 ++++++------- src/Rules/Functions/CallUserFuncRule.php | 10 +++- src/Rules/Methods/CallMethodsRule.php | 32 ++++++------- src/Rules/Methods/CallStaticMethodsRule.php | 32 ++++++------- 8 files changed, 128 insertions(+), 122 deletions(-) diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index e04381033e0..8633178d9fd 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -136,25 +136,23 @@ public function check( $scope, $attributeConstructor->getDeclaringClass()->isBuiltin(), new New_($attribute->name, $attribute->args, $nodeAttributes), - [ - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d required.', - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d required.', - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, at least %d required.', - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, at least %d required.', - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d-%d required.', - 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor expects %s, %s given.', - '', // constructor does not have a return type - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClassName, - 'Missing parameter $%s in call to ' . $attributeClassName . ' constructor.', - 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', - 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', - 'Attribute class ' . $attributeClassName . ' constructorinvoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'attribute', $attributeConstructor->acceptsNamedArguments(), + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, at least %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, at least %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d-%d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor expects %s, %s given.', + '', // constructor does not have a return type + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClassName, + 'Missing parameter $%s in call to ' . $attributeClassName . ' constructor.', + 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', + 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', + 'Attribute class ' . $attributeClassName . ' constructorinvoked with %s, but it\'s not allowed because of @no-named-arguments.', ); foreach ($parameterErrors as $error) { diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 8994a4754b1..604038d1fa8 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -201,25 +201,23 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ $scope, $constructorReflection->getDeclaringClass()->isBuiltin(), $node, - [ - 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d required.', - 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d required.', - 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, at least %d required.', - 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, at least %d required.', - 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d-%d required.', - 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of class ' . $classDisplayName . ' constructor expects %s, %s given.', - '', // constructor does not have a return type - 'Parameter %s of class ' . $classDisplayName . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of class ' . $classDisplayName, - 'Missing parameter $%s in call to ' . $classDisplayName . ' constructor.', - 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', - 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', - 'Parameter %s of class ' . $classDisplayName . ' constructor contains unresolvable type.', - 'Class ' . $classDisplayName . ' constructor invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'new', $constructorReflection->acceptsNamedArguments(), + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, at least %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, at least %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d-%d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of class ' . $classDisplayName . ' constructor expects %s, %s given.', + '', // constructor does not have a return type + 'Parameter %s of class ' . $classDisplayName . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of class ' . $classDisplayName, + 'Missing parameter $%s in call to ' . $classDisplayName . ' constructor.', + 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', + 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', + 'Parameter %s of class ' . $classDisplayName . ' constructor contains unresolvable type.', + 'Class ' . $classDisplayName . ' constructor invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 365ab626a1f..1ad87733657 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -53,7 +53,6 @@ public function __construct( /** * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall - * @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string, 14?: string} $messages * @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType * @return list */ @@ -62,9 +61,23 @@ public function check( Scope $scope, bool $isBuiltin, $funcCall, - array $messages, string $nodeType, TrinaryLogic $acceptsNamedArguments, + string $singleInsufficientParameterMessage, + string $pluralInsufficientParametersMessage, + string $singleInsufficientParameterInVariadicFunctionMessage, + string $pluralInsufficientParametersInVariadicFunctionMessage, + string $singleInsufficientParameterWithOptionalParametersMessage, + string $pluralInsufficientParametersWithOptionalParametersMessage, + string $wrongArgumentTypeMessage, + string $voidReturnTypeUsed, + string $parameterPassedByReferenceMessage, + string $unresolvableTemplateTypeMessage, + string $missingParameterMessage, + string $unknownParameterMessage, + string $unresolvableReturnTypeMessage, + string $unresolvableParameterTypeMessage, + string $namedArgumentMessage, ): array { $functionParametersMinCount = 0; @@ -204,7 +217,7 @@ public function check( ) { if ($functionParametersMinCount === $functionParametersMaxCount) { $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[0] : $messages[1], + $invokedParametersCount === 1 ? $singleInsufficientParameterMessage : $pluralInsufficientParametersMessage, $invokedParametersCount, $functionParametersMinCount, )) @@ -213,7 +226,7 @@ public function check( ->build(); } elseif ($functionParametersMaxCount === -1 && $invokedParametersCount < $functionParametersMinCount) { $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[2] : $messages[3], + $invokedParametersCount === 1 ? $singleInsufficientParameterInVariadicFunctionMessage : $pluralInsufficientParametersInVariadicFunctionMessage, $invokedParametersCount, $functionParametersMinCount, )) @@ -222,7 +235,7 @@ public function check( ->build(); } elseif ($functionParametersMaxCount !== -1) { $errors[] = RuleErrorBuilder::message(sprintf( - $invokedParametersCount === 1 ? $messages[4] : $messages[5], + $invokedParametersCount === 1 ? $singleInsufficientParameterWithOptionalParametersMessage : $pluralInsufficientParametersWithOptionalParametersMessage, $invokedParametersCount, $functionParametersMinCount, $functionParametersMaxCount, @@ -239,13 +252,13 @@ public function check( && !$scope->isInFirstLevelStatement() && $scope->getKeepVoidType($funcCall)->isVoid()->yes() ) { - $errors[] = RuleErrorBuilder::message($messages[7]) + $errors[] = RuleErrorBuilder::message($voidReturnTypeUsed) ->identifier(sprintf('%s.void', $nodeType)) ->line($funcCall->getStartLine()) ->build(); } - [$addedErrors, $argumentsWithParameters] = $this->processArguments($parametersAcceptor, $funcCall->getStartLine(), $isBuiltin, $arguments, $hasNamedArguments, $messages[10], $messages[11]); + [$addedErrors, $argumentsWithParameters] = $this->processArguments($parametersAcceptor, $funcCall->getStartLine(), $isBuiltin, $arguments, $hasNamedArguments, $missingParameterMessage, $unknownParameterMessage); foreach ($addedErrors as $error) { $errors[] = $error; } @@ -290,9 +303,9 @@ public function check( } } - if (!$acceptsNamedArguments->yes() && isset($messages[14])) { + if (!$acceptsNamedArguments->yes()) { if ($argumentName !== null) { - $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('named argument $%s', $argumentName))) + $errors[] = RuleErrorBuilder::message(sprintf($namedArgumentMessage, sprintf('named argument $%s', $argumentName))) ->identifier('argument.named') ->line($argumentLine) ->build(); @@ -300,7 +313,7 @@ public function check( $unpackedArrayType = $scope->getType($argumentValue); $hasStringKey = $unpackedArrayType->getIterableKeyType()->isString(); if (!$hasStringKey->no()) { - $errors[] = RuleErrorBuilder::message(sprintf($messages[14], sprintf('unpacked array with %s', $hasStringKey->yes() ? 'string key' : 'possibly string key'))) + $errors[] = RuleErrorBuilder::message(sprintf($namedArgumentMessage, sprintf('unpacked array with %s', $hasStringKey->yes() ? 'string key' : 'possibly string key'))) ->identifier('argument.named') ->line($argumentLine) ->build(); @@ -317,7 +330,7 @@ public function check( if (!$accepts->result) { $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType); $errors[] = RuleErrorBuilder::message(sprintf( - $messages[6], + $wrongArgumentTypeMessage, $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), $parameterType->describe($verbosityLevel), $argumentValueType->describe($verbosityLevel), @@ -331,12 +344,11 @@ public function check( if ( $originalParameter !== null - && isset($messages[13]) && !$this->unresolvableTypeHelper->containsUnresolvableType($originalParameter->getType()) && $this->unresolvableTypeHelper->containsUnresolvableType($parameterType) ) { $errors[] = RuleErrorBuilder::message(sprintf( - $messages[13], + $unresolvableParameterTypeMessage, $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), ))->identifier('argument.unresolvableType')->line($argumentLine)->build(); } @@ -348,7 +360,7 @@ public function check( && $argumentValue->static ) { $errors[] = RuleErrorBuilder::message(sprintf( - $messages[6], + $wrongArgumentTypeMessage, $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), 'bindable closure', 'static closure', @@ -368,7 +380,7 @@ public function check( if ($this->nullsafeCheck->containsNullSafe($argumentValue)) { $errors[] = RuleErrorBuilder::message(sprintf( - $messages[8], + $parameterPassedByReferenceMessage, $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), )) ->identifier('argument.byRef') @@ -412,7 +424,7 @@ public function check( } $errors[] = RuleErrorBuilder::message(sprintf( - $messages[8], + $parameterPassedByReferenceMessage, $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), ))->identifier('argument.byRef')->line($argumentLine)->build(); } @@ -469,7 +481,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty continue; } - $errors[] = RuleErrorBuilder::message(sprintf($messages[9], $name)) + $errors[] = RuleErrorBuilder::message(sprintf($unresolvableTemplateTypeMessage, $name)) ->identifier('argument.templateType') ->line($funcCall->getStartLine()) ->tip('See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type') @@ -481,7 +493,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty !$this->unresolvableTypeHelper->containsUnresolvableType($originalParametersAcceptor->getReturnType()) && $this->unresolvableTypeHelper->containsUnresolvableType($parametersAcceptor->getReturnType()) ) { - $errors[] = RuleErrorBuilder::message($messages[12]) + $errors[] = RuleErrorBuilder::message($unresolvableReturnTypeMessage) ->identifier(sprintf('%s.unresolvableReturnType', $nodeType)) ->line($funcCall->getStartLine()) ->build(); diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index 05cd89d0a74..15c0bfb9cad 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -118,25 +118,23 @@ public function processNode( $scope, false, $node, - [ - ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', - ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', - ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', - ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', - 'Result of ' . $callableDescription . ' (void) is used.', - 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to ' . $callableDescription, - 'Missing parameter $%s in call to ' . $callableDescription . '.', - 'Unknown parameter $%s in call to ' . $callableDescription . '.', - 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', - 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', - ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'callable', $acceptsNamedArguments, + ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', + ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', + ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', + ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', + 'Result of ' . $callableDescription . ' (void) is used.', + 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to ' . $callableDescription, + 'Missing parameter $%s in call to ' . $callableDescription . '.', + 'Unknown parameter $%s in call to ' . $callableDescription . '.', + 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', + 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', + ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ), ); } diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index d1ca216791e..39b4ee650fe 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -49,25 +49,23 @@ public function processNode(Node $node, Scope $scope): array $scope, $function->isBuiltin(), $node, - [ - 'Function ' . $functionName . ' invoked with %d parameter, %d required.', - 'Function ' . $functionName . ' invoked with %d parameters, %d required.', - 'Function ' . $functionName . ' invoked with %d parameter, at least %d required.', - 'Function ' . $functionName . ' invoked with %d parameters, at least %d required.', - 'Function ' . $functionName . ' invoked with %d parameter, %d-%d required.', - 'Function ' . $functionName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of function ' . $functionName . ' expects %s, %s given.', - 'Result of function ' . $functionName . ' (void) is used.', - 'Parameter %s of function ' . $functionName . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to function ' . $functionName, - 'Missing parameter $%s in call to function ' . $functionName . '.', - 'Unknown parameter $%s in call to function ' . $functionName . '.', - 'Return type of call to function ' . $functionName . ' contains unresolvable type.', - 'Parameter %s of function ' . $functionName . ' contains unresolvable type.', - 'Function ' . $functionName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'function', $function->acceptsNamedArguments(), + 'Function ' . $functionName . ' invoked with %d parameter, %d required.', + 'Function ' . $functionName . ' invoked with %d parameters, %d required.', + 'Function ' . $functionName . ' invoked with %d parameter, at least %d required.', + 'Function ' . $functionName . ' invoked with %d parameters, at least %d required.', + 'Function ' . $functionName . ' invoked with %d parameter, %d-%d required.', + 'Function ' . $functionName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of function ' . $functionName . ' expects %s, %s given.', + 'Result of function ' . $functionName . ' (void) is used.', + 'Parameter %s of function ' . $functionName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to function ' . $functionName, + 'Missing parameter $%s in call to function ' . $functionName . '.', + 'Unknown parameter $%s in call to function ' . $functionName . '.', + 'Return type of call to function ' . $functionName . ' contains unresolvable type.', + 'Parameter %s of function ' . $functionName . ' contains unresolvable type.', + 'Function ' . $functionName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ); } diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index c4030961cf1..2ea1fb27771 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -60,7 +60,13 @@ public function processNode(Node $node, Scope $scope): array $callableDescription = 'callable passed to call_user_func()'; - return $this->check->check($parametersAcceptor, $scope, false, $funcCall, [ + return $this->check->check( + $parametersAcceptor, + $scope, + false, + $funcCall, + 'function', + $acceptsNamedArguments, ucfirst($callableDescription) . ' invoked with %d parameter, %d required.', ucfirst($callableDescription) . ' invoked with %d parameters, %d required.', ucfirst($callableDescription) . ' invoked with %d parameter, at least %d required.', @@ -76,7 +82,7 @@ public function processNode(Node $node, Scope $scope): array 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'function', $acceptsNamedArguments); + ); } } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 4f45dbf9fdc..b0d522f439a 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -55,25 +55,23 @@ public function processNode(Node $node, Scope $scope): array $scope, $declaringClass->isBuiltin(), $node, - [ - 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', - 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of method ' . $messagesMethodName . ' expects %s, %s given.', - 'Result of method ' . $messagesMethodName . ' (void) is used.', - 'Parameter %s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to method ' . $messagesMethodName, - 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', - 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', - 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', - 'Parameter %s of method ' . $messagesMethodName . ' contains unresolvable type.', - 'Method ' . $messagesMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'method', $methodReflection->acceptsNamedArguments(), + 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameter, at least %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', + 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of method ' . $messagesMethodName . ' expects %s, %s given.', + 'Result of method ' . $messagesMethodName . ' (void) is used.', + 'Parameter %s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to method ' . $messagesMethodName, + 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', + 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', + 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', + 'Parameter %s of method ' . $messagesMethodName . ' contains unresolvable type.', + 'Method ' . $messagesMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 33612ff02cc..0f98eca2d9c 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -63,25 +63,23 @@ public function processNode(Node $node, Scope $scope): array $scope, $method->getDeclaringClass()->isBuiltin(), $node, - [ - $displayMethodName . ' invoked with %d parameter, %d required.', - $displayMethodName . ' invoked with %d parameters, %d required.', - $displayMethodName . ' invoked with %d parameter, at least %d required.', - $displayMethodName . ' invoked with %d parameters, at least %d required.', - $displayMethodName . ' invoked with %d parameter, %d-%d required.', - $displayMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $lowercasedMethodName . ' expects %s, %s given.', - 'Result of ' . $lowercasedMethodName . ' (void) is used.', - 'Parameter %s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName, - 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', - 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', - 'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.', - 'Parameter %s of ' . $lowercasedMethodName . ' contains unresolvable type.', - $displayMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', - ], 'staticMethod', $method->acceptsNamedArguments(), + $displayMethodName . ' invoked with %d parameter, %d required.', + $displayMethodName . ' invoked with %d parameters, %d required.', + $displayMethodName . ' invoked with %d parameter, at least %d required.', + $displayMethodName . ' invoked with %d parameters, at least %d required.', + $displayMethodName . ' invoked with %d parameter, %d-%d required.', + $displayMethodName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of ' . $lowercasedMethodName . ' expects %s, %s given.', + 'Result of ' . $lowercasedMethodName . ' (void) is used.', + 'Parameter %s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName, + 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', + 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', + 'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.', + 'Parameter %s of ' . $lowercasedMethodName . ' contains unresolvable type.', + $displayMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); return $errors; From e95f79b2a3fff1f749cdd6dc9eaab2d2600def1a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 2 Oct 2024 10:10:30 +0200 Subject: [PATCH 0578/3097] Level 10 - checkImplicitMixed --- changelog-2.0.md | 1 + conf/config.level10.neon | 5 ++ conf/config.levelmax.neon | 2 +- src/Testing/LevelsTestCase.php | 2 +- tests/PHPStan/Levels/data/acceptTypes-10.json | 17 +++++ tests/PHPStan/Levels/data/arrayAccess-10.json | 7 +++ .../Levels/data/arrayDimFetches-10.json | 22 +++++++ .../Levels/data/callableCalls-10-missing.json | 12 ++++ .../PHPStan/Levels/data/clone-10-missing.json | 12 ++++ tests/PHPStan/Levels/data/coalesce-10.json | 12 ++++ .../data/constantAccesses-10-missing.json | 17 +++++ .../Levels/data/constantAccesses-10.json | 62 +++++++++++++++++++ .../Levels/data/constantAccesses83-10.json | 27 ++++++++ .../Levels/data/methodCalls-10-missing.json | 52 ++++++++++++++++ .../Levels/data/object-10-missing.json | 22 +++++++ tests/PHPStan/Levels/data/object-10.json | 32 ++++++++++ .../data/propertyAccesses-10-missing.json | 32 ++++++++++ .../Levels/data/propertyAccesses-10.json | 57 +++++++++++++++++ .../Levels/data/stringOffsetAccess-10.json | 32 ++++++++++ tests/PHPStan/Levels/data/variables-10.json | 7 +++ 20 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 conf/config.level10.neon create mode 100644 tests/PHPStan/Levels/data/acceptTypes-10.json create mode 100644 tests/PHPStan/Levels/data/arrayAccess-10.json create mode 100644 tests/PHPStan/Levels/data/arrayDimFetches-10.json create mode 100644 tests/PHPStan/Levels/data/callableCalls-10-missing.json create mode 100644 tests/PHPStan/Levels/data/clone-10-missing.json create mode 100644 tests/PHPStan/Levels/data/coalesce-10.json create mode 100644 tests/PHPStan/Levels/data/constantAccesses-10-missing.json create mode 100644 tests/PHPStan/Levels/data/constantAccesses-10.json create mode 100644 tests/PHPStan/Levels/data/constantAccesses83-10.json create mode 100644 tests/PHPStan/Levels/data/methodCalls-10-missing.json create mode 100644 tests/PHPStan/Levels/data/object-10-missing.json create mode 100644 tests/PHPStan/Levels/data/object-10.json create mode 100644 tests/PHPStan/Levels/data/propertyAccesses-10-missing.json create mode 100644 tests/PHPStan/Levels/data/propertyAccesses-10.json create mode 100644 tests/PHPStan/Levels/data/stringOffsetAccess-10.json create mode 100644 tests/PHPStan/Levels/data/variables-10.json diff --git a/changelog-2.0.md b/changelog-2.0.md index 9243a742880..84559ebb675 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -5,6 +5,7 @@ When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](h Major new features 🚀 ===================== +* **Level 10** - level 9 on steroids, treats all `mixed` types strictly, not just explicit `mixed` * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 * **Validate inline PHPDoc `@var` tag** type against native type (level 2) (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) diff --git a/conf/config.level10.neon b/conf/config.level10.neon new file mode 100644 index 00000000000..5d052692c9c --- /dev/null +++ b/conf/config.level10.neon @@ -0,0 +1,5 @@ +includes: + - config.level9.neon + +parameters: + checkImplicitMixed: true diff --git a/conf/config.levelmax.neon b/conf/config.levelmax.neon index da48578fe3e..ce4c43f2f74 100644 --- a/conf/config.levelmax.neon +++ b/conf/config.levelmax.neon @@ -1,2 +1,2 @@ includes: - - config.level9.neon + - config.level10.neon diff --git a/src/Testing/LevelsTestCase.php b/src/Testing/LevelsTestCase.php index 9946e97c053..499277dc8ba 100644 --- a/src/Testing/LevelsTestCase.php +++ b/src/Testing/LevelsTestCase.php @@ -71,7 +71,7 @@ public function testLevels( putenv('__PHPSTAN_FORCE_VALIDATE_STUB_FILES=1'); - foreach (range(0, 9) as $level) { + foreach (range(0, 10) as $level) { unset($outputLines); exec(sprintf('%s %s analyse --no-progress --error-format=prettyJson --level=%d %s %s %s', escapeshellarg(PHP_BINARY), $command, $level, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : '', $this->shouldAutoloadAnalysedFile() ? sprintf('--autoload-file %s', escapeshellarg($file)) : '', escapeshellarg($file)), $outputLines); diff --git a/tests/PHPStan/Levels/data/acceptTypes-10.json b/tests/PHPStan/Levels/data/acceptTypes-10.json new file mode 100644 index 00000000000..8a1b7a3992b --- /dev/null +++ b/tests/PHPStan/Levels/data/acceptTypes-10.json @@ -0,0 +1,17 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 170, + "ignorable": true + }, + { + "message": "Parameter #1 $closure of method Levels\\AcceptTypes\\ClosureAccepts::doBar() expects Closure(Levels\\AcceptTypes\\FooInterface, int): Levels\\AcceptTypes\\FooInterface, Closure(mixed): mixed given.", + "line": 325, + "ignorable": true + }, + { + "message": "Parameter #1 $callable of method Levels\\AcceptTypes\\ClosureAccepts::doBaz() expects callable(Levels\\AcceptTypes\\FooInterface, int): Levels\\AcceptTypes\\FooInterface, Closure(mixed): mixed given.", + "line": 326, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayAccess-10.json b/tests/PHPStan/Levels/data/arrayAccess-10.json new file mode 100644 index 00000000000..9dc3ca3eb92 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayAccess-10.json @@ -0,0 +1,7 @@ +[ + { + "message": "Cannot assign offset mixed to SplObjectStorage.", + "line": 43, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-10.json b/tests/PHPStan/Levels/data/arrayDimFetches-10.json new file mode 100644 index 00000000000..900a4ee636f --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayDimFetches-10.json @@ -0,0 +1,22 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 14, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 21, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 27, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 28, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/callableCalls-10-missing.json b/tests/PHPStan/Levels/data/callableCalls-10-missing.json new file mode 100644 index 00000000000..5c7f12b38dd --- /dev/null +++ b/tests/PHPStan/Levels/data/callableCalls-10-missing.json @@ -0,0 +1,12 @@ +[ + { + "message": "Closure invoked with 0 parameters, 1 required.", + "line": 37, + "ignorable": true + }, + { + "message": "Trying to invoke int but it's not a callable.", + "line": 43, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/clone-10-missing.json b/tests/PHPStan/Levels/data/clone-10-missing.json new file mode 100644 index 00000000000..40e1203120d --- /dev/null +++ b/tests/PHPStan/Levels/data/clone-10-missing.json @@ -0,0 +1,12 @@ +[ + { + "message": "Cannot clone non-object variable $nullableInt of type int.", + "line": 34, + "ignorable": true + }, + { + "message": "Cannot clone non-object variable $nullableUnion of type int|Levels\\Cloning\\Foo.", + "line": 35, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/coalesce-10.json b/tests/PHPStan/Levels/data/coalesce-10.json new file mode 100644 index 00000000000..e74887cb133 --- /dev/null +++ b/tests/PHPStan/Levels/data/coalesce-10.json @@ -0,0 +1,12 @@ +[ + { + "message": "Cannot access property $bar on mixed.", + "line": 6, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 11, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/constantAccesses-10-missing.json b/tests/PHPStan/Levels/data/constantAccesses-10-missing.json new file mode 100644 index 00000000000..0cc5a3f5d44 --- /dev/null +++ b/tests/PHPStan/Levels/data/constantAccesses-10-missing.json @@ -0,0 +1,17 @@ +[ + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Foo::BAR_CONSTANT.", + "line": 53, + "ignorable": true + }, + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Bar|Levels\\ConstantAccesses\\Foo::BAR_CONSTANT.", + "line": 56, + "ignorable": true + }, + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Bar|Levels\\ConstantAccesses\\Foo::FOO_CONSTANT.", + "line": 55, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/constantAccesses-10.json b/tests/PHPStan/Levels/data/constantAccesses-10.json new file mode 100644 index 00000000000..cf84dbb4c66 --- /dev/null +++ b/tests/PHPStan/Levels/data/constantAccesses-10.json @@ -0,0 +1,62 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 6, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 17, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 18, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 20, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 23, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 49, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 50, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 52, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 53, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 55, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 56, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 58, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/constantAccesses83-10.json b/tests/PHPStan/Levels/data/constantAccesses83-10.json new file mode 100644 index 00000000000..7d5fcb38d37 --- /dev/null +++ b/tests/PHPStan/Levels/data/constantAccesses83-10.json @@ -0,0 +1,27 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 15, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 16, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 18, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 19, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 20, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/methodCalls-10-missing.json b/tests/PHPStan/Levels/data/methodCalls-10-missing.json new file mode 100644 index 00000000000..47cdcab7693 --- /dev/null +++ b/tests/PHPStan/Levels/data/methodCalls-10-missing.json @@ -0,0 +1,52 @@ +[ + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 53, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 56, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 59, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 162, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 166, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 170, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 59, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 60, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 170, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 171, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/object-10-missing.json b/tests/PHPStan/Levels/data/object-10-missing.json new file mode 100644 index 00000000000..4d1f2153bae --- /dev/null +++ b/tests/PHPStan/Levels/data/object-10-missing.json @@ -0,0 +1,22 @@ +[ + { + "message": "Call to an undefined method object::foo().", + "line": 25, + "ignorable": true + }, + { + "message": "Access to an undefined property object::$bar.", + "line": 26, + "ignorable": true + }, + { + "message": "Call to an undefined static method object::baz().", + "line": 28, + "ignorable": true + }, + { + "message": "Access to an undefined static property object::$dolor.", + "line": 29, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/object-10.json b/tests/PHPStan/Levels/data/object-10.json new file mode 100644 index 00000000000..57f727da24b --- /dev/null +++ b/tests/PHPStan/Levels/data/object-10.json @@ -0,0 +1,32 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 14, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 17, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 26, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 29, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 38, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 41, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json new file mode 100644 index 00000000000..1a8bc8b4b7d --- /dev/null +++ b/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json @@ -0,0 +1,32 @@ +[ + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", + "line": 61, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", + "line": 166, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", + "line": 63, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", + "line": 169, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$bar.", + "line": 170, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses-10.json b/tests/PHPStan/Levels/data/propertyAccesses-10.json new file mode 100644 index 00000000000..9581e25ad9a --- /dev/null +++ b/tests/PHPStan/Levels/data/propertyAccesses-10.json @@ -0,0 +1,57 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 14, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 18, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 32, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 36, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 95, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 186, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 187, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 188, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 198, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 199, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 200, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-10.json b/tests/PHPStan/Levels/data/stringOffsetAccess-10.json new file mode 100644 index 00000000000..cc773c1e069 --- /dev/null +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-10.json @@ -0,0 +1,32 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 13, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 16, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 23, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 27, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 31, + "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 35, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/variables-10.json b/tests/PHPStan/Levels/data/variables-10.json new file mode 100644 index 00000000000..fd397067b98 --- /dev/null +++ b/tests/PHPStan/Levels/data/variables-10.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 7, + "ignorable": true + } +] \ No newline at end of file From 04aa17f1f83c456ff688d8cfde19e69b193333af Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 2 Oct 2024 14:09:59 +0200 Subject: [PATCH 0579/3097] Added missing BC break --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index 5f34052a8c6..96579f42c7a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -300,3 +300,4 @@ Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createRefle * Interface `GlobalConstantReflection` renamed to `ConstantReflection` * Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` * `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null +* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node From 7081a966c56074f163238a8d810970706b93fad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Wed, 2 Oct 2024 14:18:46 +0200 Subject: [PATCH 0580/3097] Added missing BC break --- UPGRADING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 96579f42c7a..bf3fee1b8a1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -233,6 +233,16 @@ Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/P Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. +### List type is enabled for everyone + +Removed static methods from `AccessoryArrayListType` class: + +* `isListTypeEnabled()` +* `setListTypeEnabled()` +* `intersectWith()` + +Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`. + ### Minor backward compatibility breaks * Classes that were previously `@final` were made `final` From 3cdac94e67bf8e508b150e9ee0e3075b3105ad19 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 2 Oct 2024 21:20:49 +0200 Subject: [PATCH 0581/3097] Update composer/pcre --- composer.lock | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index f1585bc0a9f..916baf9f89c 100644 --- a/composer.lock +++ b/composer.lock @@ -148,30 +148,38 @@ }, { "name": "composer/pcre", - "version": "3.1.3", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", - "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.10", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -199,7 +207,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.3" + "source": "https://github.com/composer/pcre/tree/3.3.1" }, "funding": [ { @@ -215,7 +223,7 @@ "type": "tidelift" } ], - "time": "2024-03-19T10:26:25+00:00" + "time": "2024-08-27T18:44:43+00:00" }, { "name": "composer/semver", From 37cdfa3200a2cf1f1779324ff30790a0ab3c4b1e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 3 Oct 2024 08:27:11 +0200 Subject: [PATCH 0582/3097] UPGRADING: fix missing syntax highlighting --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index bf3fee1b8a1..474ecaf275c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -185,7 +185,7 @@ $returnType = ParametersAcceptorSelector::selectSingle($function->getVariants()) **After**: -``` +```php $returnType = $node->getFunctionReflection()->getReturnType(); ``` From 25cd191875697b3718f23799cec64df7e3f1d57d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 3 Oct 2024 10:16:59 +0200 Subject: [PATCH 0583/3097] UPGRADING: fix typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 474ecaf275c..d66361a2865 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -9,7 +9,7 @@ PHPStan now requires PHP 7.4 or newer to run. ## Upgrading guide for end users -The best way do get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** +The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). From b1d176ee45dd5050a84bd49da5477e102f28dba8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 3 Oct 2024 10:43:40 +0200 Subject: [PATCH 0584/3097] UPGRADING: fix missing backticks --- UPGRADING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d66361a2865..cc0fd98bdad 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -287,15 +287,15 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead * Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead -* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer bool -* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer int +* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool` +* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int` * Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead * `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` * Remove `FunctionReflection::isFinal()` * [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) * `additionalConfigFiles` config parameter must be a list * Remove `__set_state()` on objects that should not be serialized in cache -* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts string +* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` * Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead * Remove `tempResultCachePath` config parameter, use `resultCachePath` instead * `LevelsTestCase::dataTopics()` data provider made static @@ -309,5 +309,5 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` * Interface `GlobalConstantReflection` renamed to `ConstantReflection` * Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` -* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null +* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` * Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node From 58a5251f1b82b20e08a7d01d956bc8bf25909bc1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 08:55:54 +0200 Subject: [PATCH 0585/3097] Upgrading note --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index cc0fd98bdad..83e9631321a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -311,3 +311,4 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` * `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` * Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node + * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node From 46a2477ae79ad958a29db073fe95c7de8a96d09e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 09:00:30 +0200 Subject: [PATCH 0586/3097] Upgrading note --- UPGRADING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 83e9631321a..161a09499bc 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -309,6 +309,9 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` * Interface `GlobalConstantReflection` renamed to `ConstantReflection` * Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` + * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor` + * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection` + * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant` * `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` * Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node From 0715ab942a6d2081044890fb900401e03d05e684 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 10:47:53 +0200 Subject: [PATCH 0587/3097] Upgrading note --- UPGRADING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 161a09499bc..c1ed2430174 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -109,6 +109,10 @@ Tags without a PHP version are no longer published - `nightly`, `2`, `latest` ar ## Upgrading guide for extension developers +> [!NOTE] +> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. +> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. + ### PHPStan now uses nikic/php-parser v5 See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. From b009a445bd3d96013d02746109d6ebd777275da4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 10:49:01 +0200 Subject: [PATCH 0588/3097] Typo --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index c1ed2430174..2ab208b5da5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -111,6 +111,7 @@ Tags without a PHP version are no longer published - `nightly`, `2`, `latest` ar > [!NOTE] > Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. +> > You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. ### PHPStan now uses nikic/php-parser v5 From 70a3e075008f6d2b216f4fa58cdefad685ef048d Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Thu, 3 Oct 2024 12:37:22 +0200 Subject: [PATCH 0589/3097] Spread list usages in Reflection, Scope, Type --- src/Analyser/MutatingScope.php | 4 ++-- src/Analyser/StatementResult.php | 4 ++-- src/PhpDoc/TypeNodeResolver.php | 7 ++++--- .../AnnotationMethodReflection.php | 4 ++-- .../Callables/FunctionCallableVariant.php | 2 +- src/Reflection/ClassReflection.php | 20 +++++++++---------- .../Dummy/ChangedTypeMethodReflection.php | 4 ++-- .../ExtendedCallableFunctionVariant.php | 2 +- src/Reflection/ExtendedFunctionVariant.php | 6 +++--- src/Reflection/ExtendedMethodReflection.php | 4 ++-- src/Reflection/ExtendedParametersAcceptor.php | 2 +- src/Reflection/FunctionReflection.php | 4 ++-- src/Reflection/FunctionVariant.php | 4 ++-- src/Reflection/InaccessibleMethod.php | 3 --- src/Reflection/MethodReflection.php | 2 +- .../Native/NativeFunctionReflection.php | 4 ++-- .../Native/NativeMethodReflection.php | 4 ++-- src/Reflection/ParametersAcceptor.php | 2 +- src/Reflection/ParametersAcceptorSelector.php | 13 ++++++------ src/Reflection/Php/ExitFunctionReflection.php | 2 +- .../PhpFunctionFromParserNodeReflection.php | 7 ++----- src/Reflection/Php/PhpFunctionReflection.php | 7 ++----- src/Reflection/Php/PhpMethodReflection.php | 8 ++++---- .../ResolvedFunctionVariantWithOriginal.php | 2 +- src/Reflection/ResolvedMethodReflection.php | 6 +++--- .../SignatureMap/FunctionSignature.php | 4 ++-- .../SignatureMap/SignatureMapParser.php | 2 +- src/Type/Accessory/HasPropertyType.php | 3 --- src/Type/ArrayType.php | 3 --- src/Type/CallableType.php | 9 +++------ src/Type/ClosureType.php | 9 +++------ src/Type/FloatType.php | 3 --- src/Type/Generic/GenericObjectType.php | 3 --- src/Type/IterableType.php | 3 --- src/Type/JustNullableTypeTrait.php | 3 --- src/Type/MixedType.php | 3 --- src/Type/NeverType.php | 3 --- src/Type/NullType.php | 3 --- src/Type/ObjectType.php | 3 --- src/Type/ObjectWithoutClassType.php | 3 --- src/Type/StaticType.php | 3 --- src/Type/Type.php | 4 ++-- src/Type/TypeUtils.php | 6 +++--- src/Type/UnionType.php | 3 --- src/Type/VoidType.php | 3 --- 45 files changed, 74 insertions(+), 129 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 461ab91c4f9..b145afa082e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -552,7 +552,7 @@ public function getVariableType(string $variableName): Type /** * @api - * @return array + * @return list */ public function getDefinedVariables(): array { @@ -573,7 +573,7 @@ public function getDefinedVariables(): array /** * @api - * @return array + * @return list */ public function getMaybeDefinedVariables(): array { diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index 71f0ddc7406..dad528dc182 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -81,7 +81,7 @@ public function getExitPoints(): array /** * @param class-string|class-string $stmtClass - * @return StatementExitPoint[] + * @return list */ public function getExitPointsByType(string $stmtClass): array { @@ -115,7 +115,7 @@ public function getExitPointsByType(string $stmtClass): array } /** - * @return StatementExitPoint[] + * @return list */ public function getExitPointsForOuterLoop(): array { diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index ee7ef34786c..6dd4f821b97 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -107,6 +107,7 @@ use Traversable; use function array_key_exists; use function array_map; +use function array_values; use function count; use function explode; use function get_class; @@ -927,7 +928,7 @@ private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $ $mainType = $this->resolve($typeNode->identifier, $nameScope); $isVariadic = false; - $parameters = array_map( + $parameters = array_values(array_map( function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection { $isVariadic = $isVariadic || $parameterNode->isVariadic; $parameterName = $parameterNode->parameterName; @@ -945,7 +946,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi ); }, $typeNode->parameters, - ); + )); $returnType = $this->resolve($typeNode->returnType, $nameScope); @@ -1196,7 +1197,7 @@ private function expandIntMaskToType(Type $type): ?Type /** * @api * @param TypeNode[] $typeNodes - * @return Type[] + * @return list */ public function resolveMultiple(array $typeNodes, NameScope $nameScope): array { diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 847a444eb57..865abdbe044 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -17,11 +17,11 @@ final class AnnotationMethodReflection implements ExtendedMethodReflection { - /** @var ExtendedFunctionVariant[]|null */ + /** @var list|null */ private ?array $variants = null; /** - * @param AnnotationsMethodParameterReflection[] $parameters + * @param list $parameters */ public function __construct( private string $name, diff --git a/src/Reflection/Callables/FunctionCallableVariant.php b/src/Reflection/Callables/FunctionCallableVariant.php index 66bd629a3e2..71ea905c52d 100644 --- a/src/Reflection/Callables/FunctionCallableVariant.php +++ b/src/Reflection/Callables/FunctionCallableVariant.php @@ -52,7 +52,7 @@ public function getResolvedTemplateTypeMap(): TemplateTypeMap } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 7d966f83d5b..a557218fd57 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -130,7 +130,7 @@ final class ClassReflection private false|ResolvedPhpDocBlock $traitContextResolvedPhpDocBlock = false; - /** @var ClassReflection[]|null */ + /** @var array|null */ private ?array $cachedInterfaces = null; private ClassReflection|false|null $cachedParentClass = false; @@ -360,7 +360,7 @@ public function getClassHierarchyDistances(): array } /** - * @return ReflectionClass[] + * @return list */ private function collectTraits(ReflectionClass|ReflectionEnum $class): array { @@ -845,7 +845,7 @@ public function implementsInterface(string $className): bool } /** - * @return ClassReflection[] + * @return list */ public function getParents(): array { @@ -860,7 +860,7 @@ public function getParents(): array } /** - * @return ClassReflection[] + * @return array */ public function getInterfaces(): array { @@ -894,7 +894,7 @@ public function getInterfaces(): array } /** - * @return ClassReflection[] + * @return array */ private function collectInterfaces(ClassReflection $interface): array { @@ -910,7 +910,7 @@ private function collectInterfaces(ClassReflection $interface): array } /** - * @return ClassReflection[] + * @return array */ public function getImmediateInterfaces(): array { @@ -1102,7 +1102,7 @@ public function hasTraitUse(string $traitName): bool } /** - * @return string[] + * @return list */ private function getTraitNames(): array { @@ -1459,7 +1459,7 @@ public function varianceMapFromList(array $variances): TemplateTypeVarianceMap return new TemplateTypeVarianceMap($map); } - /** @return array */ + /** @return list */ public function typeMapToList(TemplateTypeMap $typeMap): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); @@ -1475,7 +1475,7 @@ public function typeMapToList(TemplateTypeMap $typeMap): array return $list; } - /** @return array */ + /** @return list */ public function varianceMapToList(TemplateTypeVarianceMap $varianceMap): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); @@ -1775,7 +1775,7 @@ public function getMethodTags(): array } /** - * @return array + * @return list */ public function getResolvedMixinTypes(): array { diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index bc0a557157a..3b3279596aa 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -17,8 +17,8 @@ final class ChangedTypeMethodReflection implements ExtendedMethodReflection { /** - * @param ExtendedParametersAcceptor[] $variants - * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants + * @param list $variants + * @param list|null $namedArgumentsVariants */ public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants) { diff --git a/src/Reflection/ExtendedCallableFunctionVariant.php b/src/Reflection/ExtendedCallableFunctionVariant.php index 5b67d210cc6..5e2d3a9c108 100644 --- a/src/Reflection/ExtendedCallableFunctionVariant.php +++ b/src/Reflection/ExtendedCallableFunctionVariant.php @@ -15,7 +15,7 @@ final class ExtendedCallableFunctionVariant extends ExtendedFunctionVariant impl { /** - * @param array $parameters + * @param list $parameters * @param SimpleThrowPoint[] $throwPoints * @param SimpleImpurePoint[] $impurePoints * @param InvalidateExprNode[] $invalidateExpressions diff --git a/src/Reflection/ExtendedFunctionVariant.php b/src/Reflection/ExtendedFunctionVariant.php index 33c8e72c004..e45f402bb0b 100644 --- a/src/Reflection/ExtendedFunctionVariant.php +++ b/src/Reflection/ExtendedFunctionVariant.php @@ -13,7 +13,7 @@ class ExtendedFunctionVariant extends FunctionVariant implements ExtendedParamet { /** - * @param array $parameters + * @param list $parameters * @api */ public function __construct( @@ -38,11 +38,11 @@ public function __construct( } /** - * @return array + * @return list */ public function getParameters(): array { - /** @var array $parameters */ + /** @var list $parameters */ $parameters = parent::getParameters(); return $parameters; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index e8a65b00b6d..b49a71bb1a3 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -23,7 +23,7 @@ interface ExtendedMethodReflection extends MethodReflection { /** - * @return ExtendedParametersAcceptor[] + * @return list */ public function getVariants(): array; @@ -33,7 +33,7 @@ public function getVariants(): array; public function getOnlyVariant(): ExtendedParametersAcceptor; /** - * @return ExtendedParametersAcceptor[]|null + * @return list|null */ public function getNamedArgumentsVariants(): ?array; diff --git a/src/Reflection/ExtendedParametersAcceptor.php b/src/Reflection/ExtendedParametersAcceptor.php index 002a8a930de..77fb213b494 100644 --- a/src/Reflection/ExtendedParametersAcceptor.php +++ b/src/Reflection/ExtendedParametersAcceptor.php @@ -10,7 +10,7 @@ interface ExtendedParametersAcceptor extends ParametersAcceptor { /** - * @return array + * @return list */ public function getParameters(): array; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index e6770e08a52..33b355b8446 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -14,7 +14,7 @@ public function getName(): string; public function getFileName(): ?string; /** - * @return ExtendedParametersAcceptor[] + * @return list */ public function getVariants(): array; @@ -24,7 +24,7 @@ public function getVariants(): array; public function getOnlyVariant(): ExtendedParametersAcceptor; /** - * @return ExtendedParametersAcceptor[]|null + * @return list|null */ public function getNamedArgumentsVariants(): ?array; diff --git a/src/Reflection/FunctionVariant.php b/src/Reflection/FunctionVariant.php index b6023cae163..7c69274ef00 100644 --- a/src/Reflection/FunctionVariant.php +++ b/src/Reflection/FunctionVariant.php @@ -16,7 +16,7 @@ class FunctionVariant implements ParametersAcceptor /** * @api - * @param array $parameters + * @param list $parameters */ public function __construct( private TemplateTypeMap $templateTypeMap, @@ -46,7 +46,7 @@ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Reflection/InaccessibleMethod.php b/src/Reflection/InaccessibleMethod.php index fef9716d6c0..037f4e8137a 100644 --- a/src/Reflection/InaccessibleMethod.php +++ b/src/Reflection/InaccessibleMethod.php @@ -37,9 +37,6 @@ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap return TemplateTypeVarianceMap::createEmpty(); } - /** - * @return array - */ public function getParameters(): array { return []; diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index 8d601e9471e..529a5011dd3 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -14,7 +14,7 @@ public function getName(): string; public function getPrototype(): ClassMemberReflection; /** - * @return ParametersAcceptor[] + * @return list */ public function getVariants(): array; diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 852392d7505..730f8c61e91 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -18,8 +18,8 @@ final class NativeFunctionReflection implements FunctionReflection private TrinaryLogic $returnsByReference; /** - * @param ExtendedParametersAcceptor[] $variants - * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants + * @param list $variants + * @param list|null $namedArgumentsVariants */ public function __construct( private string $name, diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index b167f1223f9..8f1e21d7c91 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -22,8 +22,8 @@ final class NativeMethodReflection implements ExtendedMethodReflection { /** - * @param ExtendedParametersAcceptor[] $variants - * @param ExtendedParametersAcceptor[]|null $namedArgumentsVariants + * @param list $variants + * @param list|null $namedArgumentsVariants */ public function __construct( private ReflectionProvider $reflectionProvider, diff --git a/src/Reflection/ParametersAcceptor.php b/src/Reflection/ParametersAcceptor.php index f4c2d4f1c01..b5fa5f1a2d7 100644 --- a/src/Reflection/ParametersAcceptor.php +++ b/src/Reflection/ParametersAcceptor.php @@ -20,7 +20,7 @@ public function getTemplateTypeMap(): TemplateTypeMap; public function getResolvedTemplateTypeMap(): TemplateTypeMap; /** - * @return array + * @return list */ public function getParameters(): array; diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 57609ab0c76..cc36c1d9512 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -44,6 +44,7 @@ use function array_map; use function array_merge; use function array_slice; +use function array_values; use function constant; use function count; use function defined; @@ -143,7 +144,7 @@ public static function selectFromArgs( new FunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - $parameters, + array_values($parameters), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), @@ -193,7 +194,7 @@ public static function selectFromArgs( new FunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - $parameters, + array_values($parameters), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), @@ -224,7 +225,7 @@ public static function selectFromArgs( new FunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - $parameters, + array_values($parameters), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), @@ -309,7 +310,7 @@ public static function selectFromArgs( new FunctionVariant( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), - $parameters, + array_values($parameters), $acceptor->isVariadic(), $acceptor->getReturnType(), $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), @@ -688,7 +689,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc return new ExtendedCallableFunctionVariant( TemplateTypeMap::createEmpty(), null, - $parameters, + array_values($parameters), $isVariadic, $returnType, $phpDocReturnType ?? $returnType, @@ -706,7 +707,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc return new ExtendedFunctionVariant( TemplateTypeMap::createEmpty(), null, - $parameters, + array_values($parameters), $isVariadic, $returnType, $phpDocReturnType ?? $returnType, diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index 12eb38927d9..76c8e7cf7a2 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -75,7 +75,7 @@ public function getOnlyVariant(): ExtendedParametersAcceptor } /** - * @return ExtendedParametersAcceptor[] + * @return list */ public function getNamedArgumentsVariants(): array { diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index dc44c0d17dc..1d157476df5 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -33,7 +33,7 @@ class PhpFunctionFromParserNodeReflection implements FunctionReflection, Extende /** @var Function_|ClassMethod */ private Node\FunctionLike $functionLike; - /** @var ExtendedFunctionVariant[]|null */ + /** @var list|null */ private ?array $variants = null; /** @@ -93,9 +93,6 @@ public function getName(): string return (string) $this->functionLike->namespacedName; } - /** - * @return ExtendedParametersAcceptor[] - */ public function getVariants(): array { if ($this->variants === null) { @@ -136,7 +133,7 @@ public function getResolvedTemplateTypeMap(): TemplateTypeMap } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 4669701c793..6209323f772 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -32,7 +32,7 @@ final class PhpFunctionReflection implements FunctionReflection { - /** @var ExtendedFunctionVariant[]|null */ + /** @var list|null */ private ?array $variants = null; /** @@ -85,9 +85,6 @@ public function getFileName(): ?string return $this->filename; } - /** - * @return ExtendedParametersAcceptor[] - */ public function getVariants(): array { if ($this->variants === null) { @@ -118,7 +115,7 @@ public function getNamedArgumentsVariants(): ?array } /** - * @return ExtendedParameterReflection[] + * @return list */ private function getParameters(): array { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index afa4f56a467..d0e008a7ed0 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -52,14 +52,14 @@ final class PhpMethodReflection implements ExtendedMethodReflection { - /** @var PhpParameterReflection[]|null */ + /** @var list|null */ private ?array $parameters = null; private ?Type $returnType = null; private ?Type $nativeReturnType = null; - /** @var ExtendedFunctionVariant[]|null */ + /** @var list|null */ private ?array $variants = null; /** @@ -191,7 +191,7 @@ private function getMethodNameWithCorrectCase(string $lowercaseMethodName, strin } /** - * @return ExtendedParametersAcceptor[] + * @return list */ public function getVariants(): array { @@ -223,7 +223,7 @@ public function getNamedArgumentsVariants(): ?array } /** - * @return ExtendedParameterReflection[] + * @return list */ private function getParameters(): array { diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index 4dda7b8685d..dcf68ca1ef6 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -21,7 +21,7 @@ final class ResolvedFunctionVariantWithOriginal implements ResolvedFunctionVariant { - /** @var ExtendedParameterReflection[]|null */ + /** @var list|null */ private ?array $parameters = null; private ?Type $returnTypeWithUnresolvableTemplateTypes = null; diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index b04803c13c0..33b70bafe48 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -14,10 +14,10 @@ final class ResolvedMethodReflection implements ExtendedMethodReflection { - /** @var ExtendedParametersAcceptor[]|null */ + /** @var list|null */ private ?array $variants = null; - /** @var ExtendedParametersAcceptor[]|null */ + /** @var list|null */ private ?array $namedArgumentVariants = null; private ?Assertions $asserts = null; @@ -74,7 +74,7 @@ public function getNamedArgumentsVariants(): ?array /** * @param ExtendedParametersAcceptor[] $variants - * @return ResolvedFunctionVariant[] + * @return list */ private function resolveVariants(array $variants): array { diff --git a/src/Reflection/SignatureMap/FunctionSignature.php b/src/Reflection/SignatureMap/FunctionSignature.php index 07886541f81..f9107d4b23b 100644 --- a/src/Reflection/SignatureMap/FunctionSignature.php +++ b/src/Reflection/SignatureMap/FunctionSignature.php @@ -8,7 +8,7 @@ final class FunctionSignature { /** - * @param array $parameters + * @param list $parameters */ public function __construct( private array $parameters, @@ -20,7 +20,7 @@ public function __construct( } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Reflection/SignatureMap/SignatureMapParser.php b/src/Reflection/SignatureMap/SignatureMapParser.php index 0652b11383b..e60cede66d5 100644 --- a/src/Reflection/SignatureMap/SignatureMapParser.php +++ b/src/Reflection/SignatureMap/SignatureMapParser.php @@ -57,7 +57,7 @@ private function getTypeFromString(string $typeString, ?string $className): Type /** * @param array $parameterMap - * @return array + * @return list */ private function getParameters(array $parameterMap): array { diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index f65b2bbe48c..759230b2cd0 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -35,9 +35,6 @@ public function __construct(private string $propertyName) { } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 97ebb9a1963..11304c9edd6 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -64,9 +64,6 @@ public function getItemType(): Type return $this->itemType; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return array_merge( diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 325a195d279..353b5622ea3 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -52,7 +52,7 @@ class CallableType implements CompoundType, CallableParametersAcceptor use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; - /** @var array */ + /** @var list */ private array $parameters; private Type $returnType; @@ -67,7 +67,7 @@ class CallableType implements CompoundType, CallableParametersAcceptor /** * @api - * @param array|null $parameters + * @param list|null $parameters * @param array $templateTags */ public function __construct( @@ -101,9 +101,6 @@ public function isPure(): TrinaryLogic return $this->isPure; } - /** - * @return string[] - */ public function getReferencedClasses(): array { $classes = []; @@ -345,7 +342,7 @@ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 46101a4bfe8..f4e50f80890 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -59,7 +59,7 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor use NonRemoveableTypeTrait; use NonGeneralizableTypeTrait; - /** @var array */ + /** @var list */ private array $parameters; private Type $returnType; @@ -81,7 +81,7 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor /** * @api - * @param array|null $parameters + * @param list|null $parameters * @param array $templateTags * @param SimpleThrowPoint[] $throwPoints * @param ?SimpleImpurePoint[] $impurePoints @@ -165,9 +165,6 @@ public function getAncestorWithClassName(string $className): ?TypeWithClassName return $this->objectType->getAncestorWithClassName($className); } - /** - * @return string[] - */ public function getReferencedClasses(): array { $classes = $this->objectType->getReferencedClasses(); @@ -481,7 +478,7 @@ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap } /** - * @return array + * @return list */ public function getParameters(): array { diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index a9bea4a0707..829f1cb8ed9 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -41,9 +41,6 @@ public function __construct() { } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index b73a7efc940..f7c18de70b7 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -91,9 +91,6 @@ public function equals(Type $type): bool return true; } - /** - * @return string[] - */ public function getReferencedClasses(): array { $classes = parent::getReferencedClasses(); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 512f88ac7a5..6d375c7e2c5 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -52,9 +52,6 @@ public function getItemType(): Type return $this->itemType; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return array_merge( diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 481e9fb3ac6..24f2974ae42 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -8,9 +8,6 @@ trait JustNullableTypeTrait { - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 5353a102903..8ffc6337716 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -61,9 +61,6 @@ public function __construct( $this->subtractedType = $subtractedType; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 21f58c0c297..5e2291a38bc 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -39,9 +39,6 @@ public function isExplicit(): bool return $this->isExplicit; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/NullType.php b/src/Type/NullType.php index b8ba6be3367..f9296f32ff8 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -36,9 +36,6 @@ public function __construct() { } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 804147910e9..c08fc0af10d 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -252,9 +252,6 @@ public function getPropertyWithoutTransformingStatic(string $propertyName, Class return $classReflection->getProperty($propertyName, $scope); } - /** - * @return string[] - */ public function getReferencedClasses(): array { return [$this->className]; diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index e9cd001bdc4..69f10e8dba0 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -34,9 +34,6 @@ public function __construct( $this->subtractedType = $subtractedType; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 7d73571c4f8..cb3a135431d 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -99,9 +99,6 @@ public function getStaticObjectType(): ObjectType return $this->staticObjectType; } - /** - * @return string[] - */ public function getReferencedClasses(): array { return $this->getStaticObjectType()->getReferencedClasses(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 398bc0d4f2e..2e0557ba0ca 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -28,7 +28,7 @@ interface Type { /** - * @return string[] + * @return list */ public function getReferencedClasses(): array; @@ -313,7 +313,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; * which the receiver type was * found. * - * @return TemplateTypeReference[] + * @return list */ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array; diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index c00c7602ecd..65268fba797 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -18,7 +18,7 @@ final class TypeUtils { /** - * @return ConstantIntegerType[] + * @return list */ public static function getConstantIntegers(Type $type): array { @@ -26,7 +26,7 @@ public static function getConstantIntegers(Type $type): array } /** - * @return IntegerRangeType[] + * @return list */ public static function getIntegerRanges(Type $type): array { @@ -34,7 +34,7 @@ public static function getIntegerRanges(Type $type): array } /** - * @return mixed[] + * @return list */ private static function map( string $typeClass, diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 4e38c385668..3c8b9ef1ff1 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -106,9 +106,6 @@ protected function getSortedTypes(): array return $this->types; } - /** - * @return string[] - */ public function getReferencedClasses(): array { $classes = []; diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index a49c642acaf..5895449145d 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -37,9 +37,6 @@ public function __construct() { } - /** - * @return string[] - */ public function getReferencedClasses(): array { return []; From 712c33e02ea2d95542cd333a493e276b22399773 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 14:46:42 +0200 Subject: [PATCH 0590/3097] Process `ClassConstFetch::$class` when it's a name --- src/Analyser/NodeScopeResolver.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c9ae8045466..f5c64cd93c7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3168,6 +3168,11 @@ static function (): void { $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); + } else { + $hasYield = false; + $throwPoints = []; + $impurePoints = []; + $nodeCallback($expr->class, $scope); } } elseif ($expr instanceof Expr\Empty_) { $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); From b38c852c7c9e1e49baa0dc8700dd13df531d0938 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 14:48:12 +0200 Subject: [PATCH 0591/3097] Process `ClassConstFetch::$name` --- src/Analyser/NodeScopeResolver.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f5c64cd93c7..92eae571e54 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3159,9 +3159,6 @@ static function (): void { $throwPoints = $result->getThrowPoints(); $impurePoints = $result->getImpurePoints(); } elseif ($expr instanceof Expr\ClassConstFetch) { - $hasYield = false; - $throwPoints = []; - $impurePoints = []; if ($expr->class instanceof Expr) { $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); @@ -3174,6 +3171,16 @@ static function (): void { $impurePoints = []; $nodeCallback($expr->class, $scope); } + + if ($expr->name instanceof Expr) { + $result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep()); + $scope = $result->getScope(); + $hasYield = $hasYield || $result->hasYield(); + $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); + } else { + $nodeCallback($expr->name, $scope); + } } elseif ($expr instanceof Expr\Empty_) { $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); $scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); From 1249a20d4c287e92e9360802be859c54c4fbf9f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 14:03:49 +0200 Subject: [PATCH 0592/3097] Report old PHP-Parser v4 class names in PHPStan-related code --- conf/config.level0.neon | 1 + src/Rules/Api/OldPhpParser4ClassRule.php | 79 +++++++++++++++++++ .../Rules/Api/OldPhpParser4ClassRuleTest.php | 29 +++++++ .../Rules/Api/data/old-php-parser-4-class.php | 32 ++++++++ 4 files changed, 141 insertions(+) create mode 100644 src/Rules/Api/OldPhpParser4ClassRule.php create mode 100644 tests/PHPStan/Rules/Api/OldPhpParser4ClassRuleTest.php create mode 100644 tests/PHPStan/Rules/Api/data/old-php-parser-4-class.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index fbad323697c..d4927a56c47 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -18,6 +18,7 @@ rules: - PHPStan\Rules\Api\ApiTraitUseRule - PHPStan\Rules\Api\GetTemplateTypeRule - PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule + - PHPStan\Rules\Api\OldPhpParser4ClassRule - PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule - PHPStan\Rules\Api\RuntimeReflectionInstantiationRule - PHPStan\Rules\Api\RuntimeReflectionFunctionRule diff --git a/src/Rules/Api/OldPhpParser4ClassRule.php b/src/Rules/Api/OldPhpParser4ClassRule.php new file mode 100644 index 00000000000..8c86e3c713c --- /dev/null +++ b/src/Rules/Api/OldPhpParser4ClassRule.php @@ -0,0 +1,79 @@ + + */ +final class OldPhpParser4ClassRule implements Rule +{ + + private const NAME_MAPPING = [ + // from https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md#renamed-nodes + 'PhpParser\Node\Scalar\LNumber' => Node\Scalar\Int_::class, + 'PhpParser\Node\Scalar\DNumber' => Node\Scalar\Float_::class, + 'PhpParser\Node\Scalar\Encapsed' => Node\Scalar\InterpolatedString::class, + 'PhpParser\Node\Scalar\EncapsedStringPart' => Node\InterpolatedStringPart::class, + 'PhpParser\Node\Expr\ArrayItem' => Node\ArrayItem::class, + 'PhpParser\Node\Expr\ClosureUse' => Node\ClosureUse::class, + 'PhpParser\Node\Stmt\DeclareDeclare' => Node\DeclareItem::class, + 'PhpParser\Node\Stmt\PropertyProperty' => Node\PropertyItem::class, + 'PhpParser\Node\Stmt\StaticVar' => Node\StaticVar::class, + 'PhpParser\Node\Stmt\UseUse' => Node\UseItem::class, + ]; + + public function getNodeType(): string + { + return Name::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $nameMapping = array_change_key_case(self::NAME_MAPPING); + $lowerName = $node->toLowerString(); + if (!array_key_exists($lowerName, $nameMapping)) { + return []; + } + + $newName = $nameMapping[$lowerName]; + + if (!$scope->isInClass()) { + return []; + } + + $classReflection = $scope->getClassReflection(); + $hasPhpStanInterface = false; + foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) { + if (!str_starts_with($interfaceName, 'PHPStan\\')) { + continue; + } + + $hasPhpStanInterface = true; + } + + if (!$hasPhpStanInterface) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Class %s not found. It has been renamed to %s in PHP-Parser v5.', + $node->toString(), + $newName, + ))->identifier('phpParser.classRenamed') + ->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Api/OldPhpParser4ClassRuleTest.php b/tests/PHPStan/Rules/Api/OldPhpParser4ClassRuleTest.php new file mode 100644 index 00000000000..23892389dd8 --- /dev/null +++ b/tests/PHPStan/Rules/Api/OldPhpParser4ClassRuleTest.php @@ -0,0 +1,29 @@ + + */ +class OldPhpParser4ClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new OldPhpParser4ClassRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/old-php-parser-4-class.php'], [ + [ + 'Class PhpParser\Node\Expr\ArrayItem not found. It has been renamed to PhpParser\Node\ArrayItem in PHP-Parser v5.', + 24, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Api/data/old-php-parser-4-class.php b/tests/PHPStan/Rules/Api/data/old-php-parser-4-class.php new file mode 100644 index 00000000000..f9f017054fc --- /dev/null +++ b/tests/PHPStan/Rules/Api/data/old-php-parser-4-class.php @@ -0,0 +1,32 @@ + Date: Thu, 3 Oct 2024 17:24:57 +0200 Subject: [PATCH 0593/3097] Fix PHP baseline count --- .../BaselinePhpErrorFormatter.php | 26 +++++++++++-------- .../BaselinePhpErrorFormatterTest.php | 4 +-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php index fefb3175fd7..65cafffb9fa 100644 --- a/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php @@ -6,11 +6,9 @@ use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; use PHPStan\File\RelativePathHelper; -use function array_keys; use function count; use function ksort; use function preg_quote; -use function sort; use function sprintf; use function var_export; use const SORT_STRING; @@ -53,33 +51,39 @@ public function formatErrors( $fileErrorsByMessage = []; foreach ($errors as $error) { $errorMessage = $error->getMessage(); + $identifier = $error->getIdentifier(); if (!isset($fileErrorsByMessage[$errorMessage])) { $fileErrorsByMessage[$errorMessage] = [ 1, - $error->getIdentifier() !== null ? [$error->getIdentifier() => true] : [], + $identifier !== null ? [$identifier => 1] : [], ]; continue; } $fileErrorsByMessage[$errorMessage][0]++; - if ($error->getIdentifier() === null) { + if ($identifier === null) { continue; } - $fileErrorsByMessage[$errorMessage][1][$error->getIdentifier()] = true; + + if (!isset($fileErrorsByMessage[$errorMessage][1][$identifier])) { + $fileErrorsByMessage[$errorMessage][1][$identifier] = 1; + continue; + } + + $fileErrorsByMessage[$errorMessage][1][$identifier]++; } ksort($fileErrorsByMessage, SORT_STRING); - foreach ($fileErrorsByMessage as $message => [$count, $identifiersInKeys]) { - $identifiers = array_keys($identifiersInKeys); - sort($identifiers); + foreach ($fileErrorsByMessage as $message => [$totalCount, $identifiers]) { + ksort($identifiers, SORT_STRING); if (count($identifiers) > 0) { - foreach ($identifiers as $identifier) { + foreach ($identifiers as $identifier => $identifierCount) { $php .= sprintf( "\$ignoreErrors[] = [\n\t'message' => %s,\n\t'identifier' => %s,\n\t'count' => %d,\n\t'path' => __DIR__ . %s,\n];\n", var_export(Helpers::escape('#^' . preg_quote($message, '#') . '$#'), true), var_export(Helpers::escape($identifier), true), - var_export($count, true), + var_export($identifierCount, true), var_export(Helpers::escape($file), true), ); } @@ -87,7 +91,7 @@ public function formatErrors( $php .= sprintf( "\$ignoreErrors[] = [\n\t'message' => %s,\n\t'count' => %d,\n\t'path' => __DIR__ . %s,\n];\n", var_export(Helpers::escape('#^' . preg_quote($message, '#') . '$#'), true), - var_export($count, true), + var_export($totalCount, true), var_export(Helpers::escape($file), true), ); } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php index 4ba93f68057..e9590e94024 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselinePhpErrorFormatterTest.php @@ -135,13 +135,13 @@ public function dataFormatErrors(): iterable \$ignoreErrors[] = [ 'message' => '#^Foo with same message, different identifier$#', 'identifier' => 'argument.byRef', - 'count' => 2, + 'count' => 1, 'path' => __DIR__ . '/Foo.php', ]; \$ignoreErrors[] = [ 'message' => '#^Foo with same message, different identifier$#', 'identifier' => 'argument.type', - 'count' => 2, + 'count' => 1, 'path' => __DIR__ . '/Foo.php', ]; From c8b7ea9e8f51c8bbc38dfa6b04f9a0172f5cfea0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 3 Oct 2024 17:11:05 +0200 Subject: [PATCH 0594/3097] Neon baseline - add identifier key --- phpstan-baseline.neon | 322 ++++++++++++++++++ .../BaselineNeonErrorFormatter.php | 59 +++- .../BaselineNeonErrorFormatterTest.php | 133 ++++++++ 3 files changed, 499 insertions(+), 15 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b1010cff7b6..22970c52018 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2,1610 +2,1932 @@ parameters: ignoreErrors: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php - message: "#^Method PHPStan\\\\Analyser\\\\AnalyserResultFinalizer\\:\\:finalize\\(\\) throws checked exception Throwable but it's missing from the PHPDoc @throws tag\\.$#" + identifier: missingType.checkedException count: 1 path: src/Analyser/AnalyserResultFinalizer.php - message: "#^Cannot assign offset 'realCount' to array\\|string\\.$#" + identifier: offsetAssign.dimType count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - message: "#^Casting to string something that's already string\\.$#" + identifier: cast.useless count: 3 path: src/Analyser/MutatingScope.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Analyser/MutatingScope.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/MutatingScope.php - message: "#^Only numeric types are allowed in pre\\-increment, float\\|int\\|string\\|null given\\.$#" + identifier: preInc.nonNumeric count: 1 path: src/Analyser/MutatingScope.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/NodeScopeResolver.php - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + identifier: argument.type count: 1 path: src/Analyser/NodeScopeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Analyser/TypeSpecifier.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 5 path: src/Analyser/TypeSpecifier.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/TypeSpecifier.php - message: "#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\\\Collectors\\\\Collector\\:\\:processNode\\(\\)\\.$#" + identifier: generics.variance count: 1 path: src/Collectors/Collector.php - message: "#^Method PHPStan\\\\Collectors\\\\Registry\\:\\:__construct\\(\\) has parameter \\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector but does not specify its types\\: TNodeType, TValue$#" + identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$cache with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - message: "#^Anonymous function has an unused use \\$container\\.$#" + identifier: closure.unusedUse count: 1 path: src/Command/CommandHelper.php - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + identifier: argument.type count: 1 path: src/Command/CommandHelper.php - message: "#^Static property PHPStan\\\\Command\\\\CommandHelper\\:\\:\\$reservedMemory is never read, only written\\.$#" + identifier: property.onlyWritten count: 1 path: src/Command/CommandHelper.php - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - message: "#^Variable method call on Nette\\\\Schema\\\\Elements\\\\AnyOf\\|Nette\\\\Schema\\\\Elements\\\\Structure\\|Nette\\\\Schema\\\\Elements\\\\Type\\.$#" + identifier: method.dynamicName count: 1 path: src/DependencyInjection/ContainerFactory.php - message: "#^Variable static method call on Nette\\\\Schema\\\\Expect\\.$#" + identifier: staticMethod.dynamicName count: 1 path: src/DependencyInjection/ContainerFactory.php - message: "#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\\\DI\\\\Config\\\\Helpers\\.$#" + identifier: classConstant.deprecatedClass count: 1 path: src/DependencyInjection/NeonAdapter.php - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + identifier: argument.type count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" + identifier: method.dynamicName count: 2 path: src/PhpDoc/PhpDocBlock.php - message: "#^Variable static method call on PHPStan\\\\PhpDoc\\\\PhpDocBlock\\.$#" + identifier: staticMethod.dynamicName count: 1 path: src/PhpDoc/PhpDocBlock.php - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV…' will always evaluate to true\\.$#" + identifier: function.alreadyNarrowedType count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getSelfOutTypeTagVa…' will always evaluate to true\\.$#" + identifier: function.alreadyNarrowedType count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - message: "#^Method PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock\\:\\:getNameScope\\(\\) should return PHPStan\\\\Analyser\\\\NameScope but returns PHPStan\\\\Analyser\\\\NameScope\\|null\\.$#" + identifier: return.type count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/PhpDoc/TypeNodeResolver.php - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\Identifier\\\\Exception\\\\InvalidIdentifierName is never thrown in the try block\\.$#" + identifier: catch.neverThrown count: 3 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\NodeCompiler\\\\Exception\\\\UnableToCompileNode is never thrown in the try block\\.$#" + identifier: catch.neverThrown count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + identifier: argument.type count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - message: "#^Method PHPStan\\\\Reflection\\\\BetterReflection\\\\SourceLocator\\\\FileReadTrapStreamWrapper\\:\\:invokeWithRealFileStreamWrapper\\(\\) has parameter \\$cb with no signature specified for callable\\.$#" + identifier: missingType.callable count: 1 path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + identifier: argument.type count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + identifier: argument.type count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Reflection/ClassReflection.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Reflection/ClassReflection.php - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#" + identifier: return.type count: 1 path: src/Reflection/ClassReflection.php - message: "#^Binary operation \"&\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Binary operation \"\\*\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Binary operation \"\\+\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Binary operation \"\\-\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Binary operation \"\\|\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 22 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 10 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + identifier: varTag.nativeType count: 1 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + identifier: varTag.type count: 4 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^PHPDoc tag @var with type float\\|int\\|null is not subtype of type int\\|null\\.$#" + identifier: varTag.type count: 6 path: src/Reflection/InitializerExprTypeResolver.php - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + identifier: phpstanApi.constructor count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Classes/RequireExtendsRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Classes/RequireImplementsRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 6 path: src/Rules/Comparison/BooleanAndConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/BooleanNotConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ConstantLooseComparisonRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/DoWhileLoopConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ElseIfConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/IfConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Rules/Comparison/LogicalXorConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Rules/Comparison/MatchExpressionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/TernaryOperatorConstantConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/DirectRegistry.php - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/DirectRegistry.php - message: "#^Method PHPStan\\\\Rules\\\\DirectRegistry\\:\\:__construct\\(\\) has parameter \\$rules with generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Generics/GenericAncestorsCheck.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Generics/TemplateTypeCheck.php - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/LazyRegistry.php - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/LazyRegistry.php - message: "#^Method PHPStan\\\\Rules\\\\LazyRegistry\\:\\:getRulesFromContainer\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/RequireExtendsCheck.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/VarTagTypeRuleHelper.php - message: "#^Access to an undefined property T of PHPStan\\\\Rules\\\\RuleError\\:\\:\\$tip\\.$#" + identifier: property.notFound count: 2 path: src/Rules/RuleErrorBuilder.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/RuleLevelHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/UnusedFunctionParametersCheck.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Variables/CompactVariablesRule.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Variables/CompactVariablesRule.php - message: "#^Anonymous function has an unused use \\$container\\.$#" + identifier: closure.unusedUse count: 1 path: src/Testing/PHPStanTestCase.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Testing/TypeInferenceTestCase.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryArrayListType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryLiteralStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasMethodType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasOffsetType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Accessory/HasOffsetValueType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Accessory/HasOffsetValueType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasOffsetValueType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasPropertyType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/NonEmptyArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/OversizedArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/ArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/ArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/ArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/BooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/BooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/CallableType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/CallableType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ClosureType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 7 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType\\|PHPStan\\\\Type\\\\Constant\\\\ConstantStringType but it's error\\-prone and dangerous\\.$#" + identifier: phpstanApi.varTagAssumption count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + identifier: varTag.nativeType count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + identifier: varTag.type count: 1 path: src/Type/Constant/ConstantArrayTypeBuilder.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantBooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantBooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Constant/ConstantBooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantFloatType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantFloatType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantIntegerType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantIntegerType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - message: "#^PHPDoc tag @var with type int\\|string is not subtype of type string\\.$#" + identifier: varTag.type count: 1 path: src/Type/Constant/ConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/OversizedArrayBuilder.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Enum/EnumCaseObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/ExponentiateHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/FileTypeMapper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/FloatType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericClassStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Generic/GenericClassStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Generic/GenericClassStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericClassStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/GenericClassStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/GenericObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/GenericObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBenevolentUnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBooleanType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantArrayType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantIntegerType.php - message: "#^Method PHPStan\\\\Type\\\\Generic\\\\TemplateConstantIntegerType\\:\\:toPhpDocNode\\(\\) should return PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\ConstTypeNode but returns PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\IdentifierTypeNode\\.$#" + identifier: return.type count: 1 path: src/Type/Generic/TemplateConstantIntegerType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateFloatType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateGenericObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntegerType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntersectionType.php - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" + identifier: instanceof.alwaysFalse count: 2 path: src/Type/Generic/TemplateIntersectionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateKeyOfType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateMixedType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectShapeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectWithoutClassType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateStrictMixedType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateStringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateUnionType.php - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" + identifier: instanceof.alwaysFalse count: 2 path: src/Type/Generic/TemplateUnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntegerRangeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/IntegerRangeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntegerRangeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/IntegerType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/IntersectionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntersectionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/IntersectionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/IntersectionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/IterableType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/IterableType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/NullType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/NullType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectShapeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/ObjectShapeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectShapeType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 6 path: src/Type/ObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/ObjectType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/ObjectWithoutClassType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/ObjectWithoutClassType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 16 path: src/Type/Php/BcMathStringOrNullReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DefineConstantTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DefinedConstantTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DsMapDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/IsAFunctionTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MethodExistsTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/RangeFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php - message: "#^Cannot access offset int\\<0, max\\> on \\(float\\|int\\)\\.$#" + identifier: offsetAccess.nonOffsetAccessible count: 2 path: src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/StrRepeatFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/StrlenFunctionReturnTypeExtension.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/StaticType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/StaticType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/StringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/StringType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 5 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 14 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 5 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 8 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - message: "#^Instanceof between PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType and PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType will always evaluate to true\\.$#" + identifier: instanceof.alwaysTrue count: 1 path: src/Type/TypeCombinator.php - message: "#^Result of \\|\\| is always true\\.$#" + identifier: booleanOr.alwaysTrue count: 1 path: src/Type/TypeCombinator.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeUtils.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypeUtils.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypehintHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypehintHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypehintHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/UnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 1 path: src/Type/UnionType.php - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#" + identifier: phpstanApi.varTagAssumption count: 1 path: src/Type/UnionType.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + identifier: phpstanApi.instanceofType count: 3 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 4 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\VoidType is error\\-prone and deprecated\\. Use Type\\:\\:isVoid\\(\\) instead\\.$#" + identifier: phpstanApi.instanceofType count: 2 path: src/Type/VoidType.php - message: "#^Unreachable statement \\- code above always terminates\\.$#" + identifier: deadCode.unreachable count: 1 path: tests/PHPStan/Analyser/AnalyserTest.php - message: "#^Class PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - message: "#^Method PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - message: "#^Class PHPStan\\\\Analyser\\\\EvaluationOrderTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - message: "#^Method PHPStan\\\\Analyser\\\\EvaluationOrderTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - message: "#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\\.$#" + identifier: constant.notFound count: 1 path: tests/PHPStan/Command/AnalyseCommandTest.php - message: "#^Class PHPStan\\\\Node\\\\FileNodeTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Node/FileNodeTest.php - message: "#^Method PHPStan\\\\Node\\\\FileNodeTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + identifier: missingType.generics count: 1 path: tests/PHPStan/Node/FileNodeTest.php - message: "#^PHPDoc tag @var with type string is not subtype of type class\\-string\\.$#" + identifier: varTag.type count: 1 path: tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + identifier: phpstanApi.constructor count: 1 path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + identifier: phpstanApi.constructor count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" + identifier: phpstanApi.varTagAssumption count: 1 path: tests/PHPStan/Type/IterableTypeTest.php diff --git a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php index b6dd1e2c2f2..ac02e1f9e10 100644 --- a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php @@ -9,6 +9,7 @@ use PHPStan\Command\Output; use PHPStan\File\RelativePathHelper; use PHPStan\ShouldNotHappenException; +use function count; use function ksort; use function preg_quote; use function substr; @@ -37,29 +38,57 @@ public function formatErrors( if (!$fileSpecificError->canBeIgnored()) { continue; } - $fileErrors[$this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = $fileSpecificError->getMessage(); + $fileErrors[$this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = $fileSpecificError; } ksort($fileErrors, SORT_STRING); $errorsToOutput = []; - foreach ($fileErrors as $file => $errorMessages) { - $fileErrorsCounts = []; - foreach ($errorMessages as $errorMessage) { - if (!isset($fileErrorsCounts[$errorMessage])) { - $fileErrorsCounts[$errorMessage] = 1; + foreach ($fileErrors as $file => $errors) { + $fileErrorsByMessage = []; + foreach ($errors as $error) { + $errorMessage = $error->getMessage(); + $identifier = $error->getIdentifier(); + if (!isset($fileErrorsByMessage[$errorMessage])) { + $fileErrorsByMessage[$errorMessage] = [ + 1, + $identifier !== null ? [$identifier => 1] : [], + ]; continue; } - $fileErrorsCounts[$errorMessage]++; + $fileErrorsByMessage[$errorMessage][0]++; + + if ($identifier === null) { + continue; + } + + if (!isset($fileErrorsByMessage[$errorMessage][1][$identifier])) { + $fileErrorsByMessage[$errorMessage][1][$identifier] = 1; + continue; + } + + $fileErrorsByMessage[$errorMessage][1][$identifier]++; } - ksort($fileErrorsCounts, SORT_STRING); - - foreach ($fileErrorsCounts as $message => $count) { - $errorsToOutput[] = [ - 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), - 'count' => $count, - 'path' => Helpers::escape($file), - ]; + ksort($fileErrorsByMessage, SORT_STRING); + + foreach ($fileErrorsByMessage as $message => [$totalCount, $identifiers]) { + ksort($identifiers, SORT_STRING); + if (count($identifiers) > 0) { + foreach ($identifiers as $identifier => $identifierCount) { + $errorsToOutput[] = [ + 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), + 'identifier' => $identifier, + 'count' => $identifierCount, + 'path' => Helpers::escape($file), + ]; + } + } else { + $errorsToOutput[] = [ + 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), + 'count' => $totalCount, + 'path' => Helpers::escape($file), + ]; + } } } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php index ac972f04a97..ace5a21c5b1 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php @@ -448,4 +448,137 @@ public function testEndOfFileNewlines( Assert::assertNotSame("\n", substr($content, -($expectedNewlinesCount + 1), 1)); } + public function dataFormatErrorsWithIdentifiers(): iterable + { + yield [ + [ + new Error( + 'Foo', + __DIR__ . '/Foo.php', + 5, + ), + new Error( + 'Foo', + __DIR__ . '/Foo.php', + 5, + ), + (new Error( + 'Foo with identifier', + __DIR__ . '/Foo.php', + 5, + ))->withIdentifier('argument.type'), + (new Error( + 'Foo with identifier', + __DIR__ . '/Foo.php', + 6, + ))->withIdentifier('argument.type'), + ], + [ + 'parameters' => [ + 'ignoreErrors' => [ + [ + 'message' => '#^Foo$#', + 'count' => 2, + 'path' => 'Foo.php', + ], + [ + 'message' => '#^Foo with identifier$#', + 'identifier' => 'argument.type', + 'count' => 2, + 'path' => 'Foo.php', + ], + ], + ], + ], + ]; + + yield [ + [ + new Error( + 'Foo', + __DIR__ . '/Foo.php', + 5, + ), + new Error( + 'Foo', + __DIR__ . '/Foo.php', + 5, + ), + (new Error( + 'Foo with same message, different identifier', + __DIR__ . '/Foo.php', + 5, + ))->withIdentifier('argument.type'), + (new Error( + 'Foo with same message, different identifier', + __DIR__ . '/Foo.php', + 6, + ))->withIdentifier('argument.byRef'), + (new Error( + 'Foo with another message', + __DIR__ . '/Foo.php', + 5, + ))->withIdentifier('argument.type'), + ], + [ + 'parameters' => [ + 'ignoreErrors' => [ + [ + 'message' => '#^Foo$#', + 'count' => 2, + 'path' => 'Foo.php', + ], + [ + 'message' => '#^Foo with another message$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => 'Foo.php', + ], + [ + 'message' => '#^Foo with same message, different identifier$#', + 'identifier' => 'argument.byRef', + 'count' => 1, + 'path' => 'Foo.php', + ], + [ + 'message' => '#^Foo with same message, different identifier$#', + 'identifier' => 'argument.type', + 'count' => 1, + 'path' => 'Foo.php', + ], + ], + ], + ], + ]; + } + + /** + * @dataProvider dataFormatErrorsWithIdentifiers + * @param list $errors + * @param mixed[] $expectedOutput + */ + public function testFormatErrorsWithIdentifiers(array $errors, array $expectedOutput): void + { + $formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(__DIR__)); + $formatter->formatErrors( + new AnalysisResult( + $errors, + [], + [], + [], + [], + false, + null, + true, + 0, + true, + [], + ), + $this->getOutput(), + '', + ); + + $this->assertSame($expectedOutput, Neon::decode($this->getOutputContent())); + } + } From c0a430c3189610cb3e62b38d097ac751fb7cd3d5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 09:23:06 +0200 Subject: [PATCH 0595/3097] Upgrading note --- UPGRADING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 2ab208b5da5..af436a4ec19 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -36,6 +36,12 @@ After changing your `composer.json`, run `composer update 'phpstan/*' -W`. It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. +### Noteworthy changes to code analysis + +* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) +* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) +* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. + ### Removed option `checkMissingIterableValueType` It's strongly recommended to add the missing array typehints. From f1853a8df867368015d81d5d571a394f04afd922 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 09:26:44 +0200 Subject: [PATCH 0596/3097] Upgrading note --- UPGRADING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING.md b/UPGRADING.md index af436a4ec19..d23ee8f530f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -40,6 +40,7 @@ It's up to you whether you go through the new reported errors or if you just put * [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) +* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type) * **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. ### Removed option `checkMissingIterableValueType` From d598545fe96c5e6ab26e36e2ee6afc5760126ec3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 09:46:51 +0200 Subject: [PATCH 0597/3097] Move upgrading notes --- UPGRADING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index d23ee8f530f..68769233aa5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -113,6 +113,9 @@ Tags without a PHP version are no longer published - `nightly`, `2`, `latest` ar * Removed unused config parameter `memoryLimitFile` * Removed unused feature toggle `disableRuntimeReflectionProvider` * Removed unused config parameter `staticReflectionClassNamePatterns` +* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead +* `additionalConfigFiles` config parameter must be a list ## Upgrading guide for extension developers @@ -305,11 +308,8 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` * Remove `FunctionReflection::isFinal()` * [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) -* `additionalConfigFiles` config parameter must be a list * Remove `__set_state()` on objects that should not be serialized in cache * Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` -* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead -* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead * `LevelsTestCase::dataTopics()` data provider made static * `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint * Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) From bf19914cac1682d0eab8bf65a874ba368522311c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 09:51:05 +0200 Subject: [PATCH 0598/3097] Added missing rules to StubValidator --- src/PhpDoc/StubValidator.php | 20 ++++++++++++++++++++ stubs/ReflectionClass.stub | 5 ----- stubs/ext-ds.stub | 10 ---------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index a0539b7168b..39ebcf09b34 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -47,6 +47,8 @@ use PHPStan\Rules\Generics\ClassAncestorsRule; use PHPStan\Rules\Generics\ClassTemplateTypeRule; use PHPStan\Rules\Generics\CrossCheckInterfacesHelper; +use PHPStan\Rules\Generics\EnumAncestorsRule; +use PHPStan\Rules\Generics\EnumTemplateTypeRule; use PHPStan\Rules\Generics\FunctionSignatureVarianceRule; use PHPStan\Rules\Generics\FunctionTemplateTypeRule; use PHPStan\Rules\Generics\GenericAncestorsCheck; @@ -58,8 +60,10 @@ use PHPStan\Rules\Generics\MethodTagTemplateTypeRule; use PHPStan\Rules\Generics\MethodTagTemplateTypeTraitRule; use PHPStan\Rules\Generics\MethodTemplateTypeRule; +use PHPStan\Rules\Generics\PropertyVarianceRule; use PHPStan\Rules\Generics\TemplateTypeCheck; use PHPStan\Rules\Generics\TraitTemplateTypeRule; +use PHPStan\Rules\Generics\UsedTraitsRule; use PHPStan\Rules\Generics\VarianceCheck; use PHPStan\Rules\Methods\ExistingClassesInTypehintsRule; use PHPStan\Rules\Methods\MethodParameterComparisonHelper; @@ -69,6 +73,10 @@ use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule; use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; +use PHPStan\Rules\PhpDoc\AssertRuleHelper; +use PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper; +use PHPStan\Rules\PhpDoc\FunctionAssertRule; +use PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule; use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; use PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule; @@ -78,6 +86,8 @@ use PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule; use PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule; use PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule; +use PHPStan\Rules\PhpDoc\MethodAssertRule; +use PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Properties\ExistingClassesInPropertiesRule; use PHPStan\Rules\Properties\MissingPropertyTypehintRule; @@ -186,6 +196,8 @@ private function getRuleRegistry(Container $container): RuleRegistry $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); $reflector = $container->getService('stubReflector'); $relativePathHelper = $container->getService('simpleRelativePathHelper'); + $assertRuleHelper = $container->getByType(AssertRuleHelper::class); + $conditionalReturnTypeRuleHelper = $container->getByType(ConditionalReturnTypeRuleHelper::class); $rules = [ // level 0 @@ -237,6 +249,14 @@ private function getRuleRegistry(Container $container): RuleRegistry new PropertyTagRule($propertyTagCheck), new PropertyTagTraitRule($propertyTagCheck, $reflectionProvider), new PropertyTagTraitUseRule($propertyTagCheck), + new EnumAncestorsRule($genericAncestorsCheck, $crossCheckInterfacesHelper), + new EnumTemplateTypeRule(), + new PropertyVarianceRule($varianceCheck), + new UsedTraitsRule($fileTypeMapper, $genericAncestorsCheck), + new FunctionAssertRule($assertRuleHelper), + new MethodAssertRule($assertRuleHelper), + new FunctionConditionalReturnTypeRule($conditionalReturnTypeRuleHelper), + new MethodConditionalReturnTypeRule($conditionalReturnTypeRuleHelper), // level 6 new MissingFunctionParameterTypehintRule($missingTypehintCheck), diff --git a/stubs/ReflectionClass.stub b/stubs/ReflectionClass.stub index f47d5d89a17..3f6ce4bddfd 100644 --- a/stubs/ReflectionClass.stub +++ b/stubs/ReflectionClass.stub @@ -6,11 +6,6 @@ class ReflectionClass { - /** - * @var class-string - */ - public $name; - /** * @param T|class-string $argument * @throws ReflectionException diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index ba72a0d5840..f0b45a47b75 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -353,16 +353,6 @@ final class Map implements Collection, ArrayAccess */ final class Pair implements JsonSerializable { - /** - * @var TKey - */ - public $key; - - /** - * @var TValue - */ - public $value; - /** * @param TKey $key * @param TValue $value From ce3c89360b10a7927b6c95f722fb4608ec08282a Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 27 Sep 2024 12:42:28 +0200 Subject: [PATCH 0599/3097] Report precise offsets in errors Fixes phpstan/phpstan#11760 --- src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php | 5 +++-- src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php | 3 ++- .../Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 8f78c9023bb..5b81f82f1a3 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -14,6 +14,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use function count; use function sprintf; final class NonexistentOffsetInArrayDimFetchCheck @@ -55,7 +56,7 @@ public function check( if ($type->hasOffsetValueType($dimType)->no()) { return [ - RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) + RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(count($dimType->getConstantStrings()) > 0 ? VerbosityLevel::precise() : VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) ->identifier('offsetAccess.notFound') ->build(), ]; @@ -104,7 +105,7 @@ public function check( if ($report) { return [ - RuleErrorBuilder::message(sprintf('Offset %s might not exist on %s.', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) + RuleErrorBuilder::message(sprintf('Offset %s might not exist on %s.', $dimType->describe(count($dimType->getConstantStrings()) > 0 ? VerbosityLevel::precise() : VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))) ->identifier('offsetAccess.notFound') ->build(), ]; diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index 3cb1b933286..d3ef021189d 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -12,6 +12,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; +use function count; use function sprintf; /** @@ -74,7 +75,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf( 'Cannot access offset %s on %s.', - $dimType->describe(VerbosityLevel::value()), + $dimType->describe(count($dimType->getConstantStrings()) > 0 ? VerbosityLevel::precise() : VerbosityLevel::value()), $isOffsetAccessibleType->describe(VerbosityLevel::value()), ))->identifier('offsetAccess.nonOffsetAccessible')->build(), ]; diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 3862059b021..b45c1200891 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -162,7 +162,7 @@ public function testRule(): void 443, ], [ - 'Offset \'feature_pretty…\' might not exist on array{version: non-falsy-string, commit: string|null, pretty_version: string|null, feature_version: non-falsy-string, feature_pretty_version?: string|null}.', + 'Offset \'feature_pretty_version\' might not exist on array{version: non-falsy-string, commit: string|null, pretty_version: string|null, feature_version: non-falsy-string, feature_pretty_version?: string|null}.', 504, ], [ From dbe8b7445e4c30cb64db242916f66c471bdd3d92 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 4 Oct 2024 10:18:57 +0200 Subject: [PATCH 0600/3097] TypeInferenceTestCase: allow asserting array offset certainty --- src/Rules/Debug/FileAssertRule.php | 20 ++++++------- src/Testing/TypeInferenceTestCase.php | 17 ++++++----- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 2 +- .../assert-variable-certainty-on-array.php | 28 +++++++++++++++++++ .../Rules/Debug/FileAssertRuleTest.php | 8 ++++-- .../PHPStan/Rules/Debug/data/file-asserts.php | 19 +++++++++++++ .../Testing/TypeInferenceTestCaseTest.php | 11 ++++++++ .../assert-certainty-variable-or-offset.php | 15 ++++++++++ 9 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/assert-variable-certainty-on-array.php create mode 100644 tests/PHPStan/Testing/data/assert-certainty-variable-or-offset.php diff --git a/src/Rules/Debug/FileAssertRule.php b/src/Rules/Debug/FileAssertRule.php index 3f8e6a62ee5..769f37bd1cc 100644 --- a/src/Rules/Debug/FileAssertRule.php +++ b/src/Rules/Debug/FileAssertRule.php @@ -171,15 +171,14 @@ private function processAssertVariableCertainty(array $args, Scope $scope): arra // @phpstan-ignore staticMethod.dynamicName $expectedCertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); $variable = $args[1]->value; - if (!$variable instanceof Node\Expr\Variable) { - return [ - RuleErrorBuilder::message('Invalid assertVariableCertainty call.') - ->nonIgnorable() - ->identifier('phpstan.unknownExpectation') - ->build(), - ]; - } - if (!is_string($variable->name)) { + if ($variable instanceof Node\Expr\Variable && is_string($variable->name)) { + $actualCertaintyValue = $scope->hasVariableType($variable->name); + $variableDescription = sprintf('variable $%s', $variable->name); + } elseif ($variable instanceof Node\Expr\ArrayDimFetch && $variable->dim !== null) { + $offset = $scope->getType($variable->dim); + $actualCertaintyValue = $scope->getType($variable->var)->hasOffsetValueType($offset); + $variableDescription = sprintf('offset %s', $offset->describe(VerbosityLevel::precise())); + } else { return [ RuleErrorBuilder::message('Invalid assertVariableCertainty call.') ->nonIgnorable() @@ -188,13 +187,12 @@ private function processAssertVariableCertainty(array $args, Scope $scope): arra ]; } - $actualCertaintyValue = $scope->hasVariableType($variable->name); if ($expectedCertaintyValue->equals($actualCertaintyValue)) { return []; } return [ - RuleErrorBuilder::message(sprintf('Expected variable certainty %s, actual: %s', $expectedCertaintyValue->describe(), $actualCertaintyValue->describe())) + RuleErrorBuilder::message(sprintf('Expected %s certainty %s, actual: %s', $variableDescription, $expectedCertaintyValue->describe(), $actualCertaintyValue->describe())) ->nonIgnorable() ->identifier('phpstan.variable') ->build(), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index a80e510da35..2b8d8e855cd 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -135,7 +135,7 @@ public function assertFileAsserts( $variableName = $args[2]; $this->assertTrue( $expectedCertainty->equals($actualCertainty), - sprintf('Expected %s, actual certainty of variable $%s is %s in %s on line %d.', $expectedCertainty->describe(), $variableName, $actualCertainty->describe(), $file, $args[3]), + sprintf('Expected %s, actual certainty of %s is %s in %s on line %d.', $expectedCertainty->describe(), $variableName, $actualCertainty->describe(), $file, $args[3]), ); } } @@ -216,15 +216,18 @@ public static function gatherAssertTypes(string $file): array // @phpstan-ignore staticMethod.dynamicName $expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); $variable = $node->getArgs()[1]->value; - if (!$variable instanceof Node\Expr\Variable) { - self::fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); - } - if (!is_string($variable->name)) { + if ($variable instanceof Node\Expr\Variable && is_string($variable->name)) { + $actualCertaintyValue = $scope->hasVariableType($variable->name); + $variableDescription = sprintf('variable $%s', $variable->name); + } elseif ($variable instanceof Node\Expr\ArrayDimFetch && $variable->dim !== null) { + $offset = $scope->getType($variable->dim); + $actualCertaintyValue = $scope->getType($variable->var)->hasOffsetValueType($offset); + $variableDescription = sprintf('offset %s', $offset->describe(VerbosityLevel::precise())); + } else { self::fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); } - $actualCertaintyValue = $scope->hasVariableType($variable->name); - $assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variable->name, $node->getStartLine()]; + $assert = ['variableCertainty', $file, $expectedertaintyValue, $actualCertaintyValue, $variableDescription, $node->getStartLine()]; } else { $correctFunction = null; diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 430b0e02f80..aea1961e324 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -906,7 +906,7 @@ private function assertVariables( $this->assertTrue( $expectedCertainty->equals($certainty), sprintf( - 'Certainty of variable $%s is %s, expected %s', + 'Certainty of %s is %s, expected %s', $variableName, $certainty->describe(), $expectedCertainty->describe(), diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2816b5ce526..e0998e52525 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -255,7 +255,7 @@ public function testFile(string $file): void $variableName = $args[2]; if ($expectedCertainty->equals($actualCertainty) !== true) { - $failures[] = sprintf("Certainty of variable \$%s on line %d:\nExpected: %s\nActual: %s\n", $variableName, $args[3], $expectedCertainty->describe(), $actualCertainty->describe()); + $failures[] = sprintf("Certainty of %s on line %d:\nExpected: %s\nActual: %s\n", $variableName, $args[3], $expectedCertainty->describe(), $actualCertainty->describe()); } } } diff --git a/tests/PHPStan/Analyser/nsrt/assert-variable-certainty-on-array.php b/tests/PHPStan/Analyser/nsrt/assert-variable-certainty-on-array.php new file mode 100644 index 00000000000..813518812a5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/assert-variable-certainty-on-array.php @@ -0,0 +1,28 @@ +gatherAssertTypes($filePath); } + public function testVariableOrOffsetDescription(): void + { + $filePath = __DIR__ . '/data/assert-certainty-variable-or-offset.php'; + + [$variableAssert, $offsetAssert] = array_values($this->gatherAssertTypes($filePath)); + + $this->assertSame('variable $context', $variableAssert[4]); + $this->assertSame("offset 'email'", $offsetAssert[4]); + } + } diff --git a/tests/PHPStan/Testing/data/assert-certainty-variable-or-offset.php b/tests/PHPStan/Testing/data/assert-certainty-variable-or-offset.php new file mode 100644 index 00000000000..b06391db457 --- /dev/null +++ b/tests/PHPStan/Testing/data/assert-certainty-variable-or-offset.php @@ -0,0 +1,15 @@ + Date: Fri, 4 Oct 2024 09:59:49 +0200 Subject: [PATCH 0601/3097] Bleeding edge - added absent type checks to AssertRuleHelper --- conf/config.neon | 4 + src/Rules/PhpDoc/AssertRuleHelper.php | 160 ++++++++++++++++-- src/Rules/PhpDoc/FunctionAssertRule.php | 2 +- src/Rules/PhpDoc/MethodAssertRule.php | 2 +- .../Rules/PhpDoc/FunctionAssertRuleTest.php | 18 +- .../Rules/PhpDoc/MethodAssertRuleTest.php | 79 ++++++++- .../Rules/PhpDoc/data/method-assert.php | 133 +++++++++++++++ 7 files changed, 376 insertions(+), 22 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 67351dc4dad..aa096cc686e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1083,6 +1083,10 @@ services: - class: PHPStan\Rules\PhpDoc\AssertRuleHelper + arguments: + checkMissingTypehints: %checkMissingTypehints% + checkClassCaseSensitivity: %checkClassCaseSensitivity% + absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\PhpDoc\UnresolvableTypeHelper diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 40551504faa..073d131922c 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -2,39 +2,63 @@ namespace PHPStan\Rules\PhpDoc; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PHPStan\Node\Expr\TypeExpr; +use PHPStan\PhpDoc\Tag\AssertTag; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ErrorType; use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function array_key_exists; +use function array_merge; +use function implode; use function sprintf; use function substr; final class AssertRuleHelper { - public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + public function __construct( + private InitializerExprTypeResolver $initializerExprTypeResolver, + private ReflectionProvider $reflectionProvider, + private UnresolvableTypeHelper $unresolvableTypeHelper, + private ClassNameCheck $classCheck, + private MissingTypehintCheck $missingTypehintCheck, + private GenericObjectTypeCheck $genericObjectTypeCheck, + private bool $absentTypeChecks, + private bool $checkClassCaseSensitivity, + private bool $checkMissingTypehints, + ) { } /** * @return list */ - public function check(ExtendedMethodReflection|FunctionReflection $reflection, ParametersAcceptor $acceptor): array + public function check( + Function_|ClassMethod $node, + ExtendedMethodReflection|FunctionReflection $reflection, + ParametersAcceptor $acceptor, + ): array { $parametersByName = []; foreach ($acceptor->getParameters() as $parameter) { $parametersByName[$parameter->getName()] = $parameter->getType(); } - if ($reflection instanceof ExtendedMethodReflection) { + if ($reflection instanceof ExtendedMethodReflection && !$reflection->isStatic()) { $class = $reflection->getDeclaringClass(); $parametersByName['this'] = new ObjectType($class->getName(), null, $class); } @@ -57,38 +81,138 @@ public function check(ExtendedMethodReflection|FunctionReflection $reflection, P $assertedExpr = $assert->getParameter()->getExpr(new TypeExpr($parametersByName[$parameterName])); $assertedExprType = $this->initializerExprTypeResolver->getType($assertedExpr, $context); + $assertedExprString = $assert->getParameter()->describe(); if ($assertedExprType instanceof ErrorType) { + if ($this->absentTypeChecks) { + $errors[] = RuleErrorBuilder::message(sprintf('Assert references unknown %s.', $assertedExprString)) + ->identifier('assert.unknownExpr') + ->build(); + } continue; } $assertedType = $assert->getType(); + $tagName = [ + AssertTag::NULL => '@phpstan-assert', + AssertTag::IF_TRUE => '@phpstan-assert-if-true', + AssertTag::IF_FALSE => '@phpstan-assert-if-false', + ][$assert->getIf()]; + + if ($this->absentTypeChecks) { + if ($this->unresolvableTypeHelper->containsUnresolvableType($assertedType)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for %s contains unresolvable type.', + $tagName, + $assertedExprString, + ))->identifier('assert.unresolvableType')->build(); + continue; + } + } + $isSuperType = $assertedType->isSuperTypeOf($assertedExprType); - if ($isSuperType->maybe()) { + if (!$isSuperType->maybe()) { + if ($assert->isNegated() ? $isSuperType->yes() : $isSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Asserted %stype %s for %s with type %s can never happen.', + $assert->isNegated() ? 'negated ' : '', + $assertedType->describe(VerbosityLevel::precise()), + $assertedExprString, + $assertedExprType->describe(VerbosityLevel::precise()), + ))->identifier('assert.impossibleType')->build(); + } elseif ($assert->isNegated() ? $isSuperType->no() : $isSuperType->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Asserted %stype %s for %s with type %s does not narrow down the type.', + $assert->isNegated() ? 'negated ' : '', + $assertedType->describe(VerbosityLevel::precise()), + $assertedExprString, + $assertedExprType->describe(VerbosityLevel::precise()), + ))->identifier('assert.alreadyNarrowedType')->build(); + } + } + + if (!$this->absentTypeChecks) { continue; } - $assertedExprString = $assert->getParameter()->describe(); + foreach ($assertedType->getReferencedClasses() as $class) { + if (!$this->reflectionProvider->hasClass($class)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for %s contains unknown class %s.', + $tagName, + $assertedExprString, + $class, + ))->identifier('class.notFound')->build(); + continue; + } + + $classReflection = $this->reflectionProvider->getClass($class); + if ($classReflection->isTrait()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for %s contains invalid type %s.', + $tagName, + $assertedExprString, + $class, + ))->identifier('assert.trait')->build(); + continue; + } + + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $assertedType, + sprintf('PHPDoc tag %s for %s contains generic type %%s but %%s %%s is not generic.', $tagName, $assertedExprString), + sprintf('Generic type %%s in PHPDoc tag %s for %s does not specify all template types of %%s %%s: %%s', $tagName, $assertedExprString), + sprintf('Generic type %%s in PHPDoc tag %s for %s specifies %%d template types, but %%s %%s supports only %%d: %%s', $tagName, $assertedExprString), + sprintf('Type %%s in generic type %%s in PHPDoc tag %s for %s is not subtype of template type %%s of %%s %%s.', $tagName, $assertedExprString), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for %s is in conflict with %%s template type %%s of %%s %%s.', $tagName, $assertedExprString), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag %s for %s is redundant, template type %%s of %%s %%s has the same variance.', $tagName, $assertedExprString), + )); + + if (!$this->checkMissingTypehints) { + continue; + } - if ($assert->isNegated() ? $isSuperType->yes() : $isSuperType->no()) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($assertedType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); $errors[] = RuleErrorBuilder::message(sprintf( - 'Asserted %stype %s for %s with type %s can never happen.', - $assert->isNegated() ? 'negated ' : '', - $assertedType->describe(VerbosityLevel::precise()), + 'PHPDoc tag %s for %s has no value type specified in iterable type %s.', + $tagName, $assertedExprString, - $assertedExprType->describe(VerbosityLevel::precise()), - ))->identifier('assert.impossibleType')->build(); - } elseif ($assert->isNegated() ? $isSuperType->no() : $isSuperType->yes()) { + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($assertedType) as [$innerName, $genericTypeNames]) { $errors[] = RuleErrorBuilder::message(sprintf( - 'Asserted %stype %s for %s with type %s does not narrow down the type.', - $assert->isNegated() ? 'negated ' : '', - $assertedType->describe(VerbosityLevel::precise()), + 'PHPDoc tag %s for %s contains generic %s but does not specify its types: %s', + $tagName, $assertedExprString, - $assertedExprType->describe(VerbosityLevel::precise()), - ))->identifier('assert.alreadyNarrowedType')->build(); + $innerName, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); } - } + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($assertedType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for %s has no signature specified for %s.', + $tagName, + $assertedExprString, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } return $errors; } diff --git a/src/Rules/PhpDoc/FunctionAssertRule.php b/src/Rules/PhpDoc/FunctionAssertRule.php index 481d99f1655..893192e2500 100644 --- a/src/Rules/PhpDoc/FunctionAssertRule.php +++ b/src/Rules/PhpDoc/FunctionAssertRule.php @@ -31,7 +31,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->helper->check($function, $variants[0]); + return $this->helper->check($node->getOriginalNode(), $function, $variants[0]); } } diff --git a/src/Rules/PhpDoc/MethodAssertRule.php b/src/Rules/PhpDoc/MethodAssertRule.php index d8be08408fc..6694ad3e421 100644 --- a/src/Rules/PhpDoc/MethodAssertRule.php +++ b/src/Rules/PhpDoc/MethodAssertRule.php @@ -31,7 +31,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->helper->check($method, $variants[0]); + return $this->helper->check($node->getOriginalNode(), $method, $variants[0]); } } diff --git a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php index 3bfd8ae7a6e..7c63ae9d67b 100644 --- a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php @@ -3,6 +3,11 @@ namespace PHPStan\Rules\PhpDoc; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -15,7 +20,18 @@ class FunctionAssertRuleTest extends RuleTestCase protected function getRule(): Rule { $initializerExprTypeResolver = self::getContainer()->getByType(InitializerExprTypeResolver::class); - return new FunctionAssertRule(new AssertRuleHelper($initializerExprTypeResolver)); + $reflectionProvider = $this->createReflectionProvider(); + return new FunctionAssertRule(new AssertRuleHelper( + $initializerExprTypeResolver, + $reflectionProvider, + new UnresolvableTypeHelper(), + new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), + new MissingTypehintCheck(true, true, true, true, []), + new GenericObjectTypeCheck(), + true, + true, + true, + )); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php index 932a09c299a..84474ea49ae 100644 --- a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php @@ -3,6 +3,11 @@ namespace PHPStan\Rules\PhpDoc; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\ClassForbiddenNameCheck; +use PHPStan\Rules\ClassNameCheck; +use PHPStan\Rules\Generics\GenericObjectTypeCheck; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -15,7 +20,18 @@ class MethodAssertRuleTest extends RuleTestCase protected function getRule(): Rule { $initializerExprTypeResolver = self::getContainer()->getByType(InitializerExprTypeResolver::class); - return new MethodAssertRule(new AssertRuleHelper($initializerExprTypeResolver)); + $reflectionProvider = $this->createReflectionProvider(); + return new MethodAssertRule(new AssertRuleHelper( + $initializerExprTypeResolver, + $reflectionProvider, + new UnresolvableTypeHelper(), + new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), + new MissingTypehintCheck(true, true, true, true, []), + new GenericObjectTypeCheck(), + true, + true, + true, + )); } public function testRule(): void @@ -54,6 +70,67 @@ public function testRule(): void 'Asserted negated type string for $i with type int does not narrow down the type.', 72, ], + [ + 'PHPDoc tag @phpstan-assert for $this->fooProp contains unresolvable type.', + 94, + ], + [ + 'PHPDoc tag @phpstan-assert-if-true for $a contains unresolvable type.', + 94, + ], + [ + 'PHPDoc tag @phpstan-assert for $a contains unknown class MethodAssert\Nonexistent.', + 105, + ], + [ + 'PHPDoc tag @phpstan-assert for $b contains invalid type MethodAssert\FooTrait.', + 105, + ], + [ + 'Class MethodAssert\Foo referenced with incorrect case: MethodAssert\fOO.', + 105, + ], + [ + 'Assert references unknown $this->barProp.', + 105, + ], + [ + 'Assert references unknown parameter $this.', + 113, + ], + [ + 'PHPDoc tag @phpstan-assert for $m contains generic type Exception but class Exception is not generic.', + 131, + ], + [ + 'Generic type MethodAssert\FooBar in PHPDoc tag @phpstan-assert for $m does not specify all template types of class MethodAssert\FooBar: T, TT', + 138, + ], + [ + 'Type mixed in generic type MethodAssert\FooBar in PHPDoc tag @phpstan-assert for $m is not subtype of template type T of int of class MethodAssert\FooBar.', + 138, + ], + [ + 'Generic type MethodAssert\FooBar in PHPDoc tag @phpstan-assert for $m does not specify all template types of class MethodAssert\FooBar: T, TT', + 145, + ], + [ + 'Generic type MethodAssert\FooBar in PHPDoc tag @phpstan-assert for $m specifies 3 template types, but class MethodAssert\FooBar supports only 2: T, TT', + 152, + ], + [ + 'PHPDoc tag @phpstan-assert for $m has no value type specified in iterable type array.', + 194, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'PHPDoc tag @phpstan-assert for $m contains generic class MethodAssert\FooBar but does not specify its types: T, TT', + 202, + ], + [ + 'PHPDoc tag @phpstan-assert for $m has no signature specified for callable.', + 210, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/method-assert.php b/tests/PHPStan/Rules/PhpDoc/data/method-assert.php index fa603eb85fb..ad07ee066dd 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/method-assert.php +++ b/tests/PHPStan/Rules/PhpDoc/data/method-assert.php @@ -80,3 +80,136 @@ public function negate2(int $i): void { } } + +class AbsentTypeChecks +{ + + /** @var string|null */ + public $fooProp; + + /** + * @phpstan-assert-if-true string&int $a + * @phpstan-assert string&int $this->fooProp + */ + public function doFoo($a): bool + { + + } + + /** + * @phpstan-assert Nonexistent $a + * @phpstan-assert FooTrait $b + * @phpstan-assert fOO $c + * @phpstan-assert Foo $this->barProp + */ + public function doBar($a, $b, $c): bool + { + + } + + /** + * @phpstan-assert !null $this->fooProp + */ + public static function doBaz(): void + { + + } + +} + +trait FooTrait +{ + +} + +class InvalidGenerics +{ + + /** + * @phpstan-assert \Exception $m + */ + function invalidPhpstanAssertGeneric($m) { + + } + + /** + * @phpstan-assert FooBar $m + */ + function invalidPhpstanAssertWrongGenericParams($m) { + + } + + /** + * @phpstan-assert FooBar $m + */ + function invalidPhpstanAssertNotAllGenericParams($m) { + + } + + /** + * @phpstan-assert FooBar $m + */ + function invalidPhpstanAssertMoreGenericParams($m) { + + } + +} + + +/** + * @template T of int + * @template TT of string + */ +class FooBar { + /** + * @param-out T $s + */ + function genericClassFoo(mixed &$s): void + { + } + + /** + * @template S of self + * @param-out S $s + */ + function genericSelf(mixed &$s): void + { + } + + /** + * @template S of static + * @param-out S $s + */ + function genericStatic(mixed &$s): void + { + } +} + +class MissingTypes +{ + + /** + * @phpstan-assert array $m + */ + public function doFoo($m): void + { + + } + + /** + * @phpstan-assert FooBar $m + */ + public function doBar($m): void + { + + } + + /** + * @phpstan-assert callable $m + */ + public function doBaz($m): void + { + + } + +} From 8f55339fa525386da6e4182156d342951e14b990 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 11:04:04 +0200 Subject: [PATCH 0602/3097] Fix build --- conf/config.neon | 1 - src/Rules/PhpDoc/AssertRuleHelper.php | 29 +++++++------------ .../Rules/PhpDoc/FunctionAssertRuleTest.php | 3 +- .../Rules/PhpDoc/MethodAssertRuleTest.php | 3 +- 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 7b84bf2705a..6a8f4d68970 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -975,7 +975,6 @@ services: arguments: checkMissingTypehints: %checkMissingTypehints% checkClassCaseSensitivity: %checkClassCaseSensitivity% - absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\PhpDoc\UnresolvableTypeHelper diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 073d131922c..091da8aa2a3 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -37,7 +37,6 @@ public function __construct( private ClassNameCheck $classCheck, private MissingTypehintCheck $missingTypehintCheck, private GenericObjectTypeCheck $genericObjectTypeCheck, - private bool $absentTypeChecks, private bool $checkClassCaseSensitivity, private bool $checkMissingTypehints, ) @@ -83,11 +82,9 @@ public function check( $assertedExprType = $this->initializerExprTypeResolver->getType($assertedExpr, $context); $assertedExprString = $assert->getParameter()->describe(); if ($assertedExprType instanceof ErrorType) { - if ($this->absentTypeChecks) { - $errors[] = RuleErrorBuilder::message(sprintf('Assert references unknown %s.', $assertedExprString)) - ->identifier('assert.unknownExpr') - ->build(); - } + $errors[] = RuleErrorBuilder::message(sprintf('Assert references unknown %s.', $assertedExprString)) + ->identifier('assert.unknownExpr') + ->build(); continue; } @@ -99,15 +96,13 @@ public function check( AssertTag::IF_FALSE => '@phpstan-assert-if-false', ][$assert->getIf()]; - if ($this->absentTypeChecks) { - if ($this->unresolvableTypeHelper->containsUnresolvableType($assertedType)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for %s contains unresolvable type.', - $tagName, - $assertedExprString, - ))->identifier('assert.unresolvableType')->build(); - continue; - } + if ($this->unresolvableTypeHelper->containsUnresolvableType($assertedType)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for %s contains unresolvable type.', + $tagName, + $assertedExprString, + ))->identifier('assert.unresolvableType')->build(); + continue; } $isSuperType = $assertedType->isSuperTypeOf($assertedExprType); @@ -131,10 +126,6 @@ public function check( } } - if (!$this->absentTypeChecks) { - continue; - } - foreach ($assertedType->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php index 7c63ae9d67b..72c49cc2863 100644 --- a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php @@ -26,11 +26,10 @@ protected function getRule(): Rule $reflectionProvider, new UnresolvableTypeHelper(), new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, []), new GenericObjectTypeCheck(), true, true, - true, )); } diff --git a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php index 84474ea49ae..c038a01891d 100644 --- a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php @@ -26,11 +26,10 @@ protected function getRule(): Rule $reflectionProvider, new UnresolvableTypeHelper(), new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), - new MissingTypehintCheck(true, true, true, true, []), + new MissingTypehintCheck(true, []), new GenericObjectTypeCheck(), true, true, - true, )); } From efcf36898f0bb8eb5d8ab7268637b9c5678b2a6e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 11:08:43 +0200 Subject: [PATCH 0603/3097] Update changelog --- UPGRADING.md | 2 -- changelog-2.0.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 68769233aa5..57adbd10121 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,3 @@ -This document is a work in progress. - Upgrading from PHPStan 1.x to 2.0 ================================= diff --git a/changelog-2.0.md b/changelog-2.0.md index 84559ebb675..2a7a46631a8 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -1,5 +1,3 @@ -This document is a work in progress. - When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases) + a separate [UPGRADING](./UPGRADING.md) document. Major new features 🚀 From 40c4505f00fc2dfad604c238497aa98b03db7fa8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 11:43:24 +0200 Subject: [PATCH 0604/3097] Finish changelog --- changelog-2.0.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/changelog-2.0.md b/changelog-2.0.md index 2a7a46631a8..f0abba38dea 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -19,6 +19,7 @@ Major new features 🚀 * Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) * Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule * Because "always true" is always reported, these are no longer needed +* New option: `polluteScopeWithBlock` (defaults to `true`, `false` in `phpstan-strict-rules`) (https://github.com/phpstan/phpstan-src/commit/946cf180c960930c2c42075d0f28ff9090507272) * Checking truthiness of `@phpstan-pure` above functions and methods * Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 @@ -76,6 +77,11 @@ Major new features 🚀 Improvements 🔧 ===================== +* TableErrorFormatter - always output identifiers (https://github.com/phpstan/phpstan-src/commit/fc66c24113e9fe88c3155703224eb03768846fdd) +* Config option `exceptions.check.tooWideThrowType` made true by default (https://github.com/phpstan/phpstan-src/commit/1b1da3e2ce3acf10dde03d9656638cda4f7389a4) +* Use `implicitThrows` to only look for explicit throw points in too-wide `@throws` rules when set to `false` (https://github.com/phpstan/phpstan-src/commit/a0e688c1d1e4c5e82f989b26485eb9162f47aa97) +* Rules about tooWideThrowType moved to level 4 (https://github.com/phpstan/phpstan-src/commit/d7798d7f2c47f426efe91c566e6cafd5a4e2410c) +* Both .php and .neon baselines now include error identifiers (https://github.com/phpstan/phpstan-src/commit/f38addda2b151b6e41a746a37659c0bbe9e2293b, https://github.com/phpstan/phpstan-src/commit/c8b7ea9e8f51c8bbc38dfa6b04f9a0172f5cfea0) * PHPDoc parser: Require whitespace before description with limited start tokens (https://github.com/phpstan/phpdoc-parser/pull/128), https://github.com/phpstan/phpdoc-parser/issues/125, thanks @rvanvelzen! * Unescape strings in PHPDoc parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) * PHPDoc parser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! @@ -118,6 +124,14 @@ Improvements 🔧 * Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) * InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) * CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) +* ContainerFactory - always check duplicate files (https://github.com/phpstan/phpstan-src/commit/939a715a0636ed05752659dbe7646c1f1a574765) +* Display parent class name for anonymous class like native PHP does ([#3362](https://github.com/phpstan/phpstan-src/pull/3362)), thanks @mvorisek! +* Always report static property fetch in `isset()`, not just on PHP 8.2+ ([#3476](https://github.com/phpstan/phpstan-src/pull/3476)), thanks @ondrejmirtes! +* Revert "Dumb down parameter types in some recently added stubs" (https://github.com/phpstan/phpstan-src/commit/950a491485c46068074ca3f4f6dc5b970d41465a) +* Do not apply heuristics of `Collection<...>|Foo[]` being resolved to Collection of Foo (https://github.com/phpstan/phpstan-src/commit/fff8f095988a66f298aa4037fe8e6ba98266063c) +* Collected PHP errors cannot be ignored (https://github.com/phpstan/phpstan-src/commit/1d3f4313955dc6fa5c6ce60fa58afe765964e5b0) +* Added missing rules to StubValidator (https://github.com/phpstan/phpstan-src/commit/bf19914cac1682d0eab8bf65a874ba368522311c) +* Report precise offsets in errors ([#3504](https://github.com/phpstan/phpstan-src/pull/3504)), thanks @ruudk! Bugfixes 🐛 @@ -150,5 +164,31 @@ Function signature fixes 🤖 * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3!* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! + Internals 🔍 ===================== + +* Tool to make optional parameters required across the codebase (https://github.com/phpstan/phpstan-src/commit/7e366e08f96e2e4095b3f02b5487e8f9531f37bf) +* A few more MutatingScope method parameters made required (https://github.com/phpstan/phpstan-src/commit/2c4c0cde75e637ac323e81def57d4a2ace952429) +* CommandHelper::begin() parameters made required (https://github.com/phpstan/phpstan-src/commit/f17cf9ec43111cb29dd50d620fb6259c0ab0d373) +* MethodTag - constructor parameter `$templateTags` is required (https://github.com/phpstan/phpstan-src/commit/5b58f83e6d8b5044d742caed9729d00178c4a9de) +* InitializerExprTypeResolver - constructor parameter `$usePathConstantsAsConstantString` made required (https://github.com/phpstan/phpstan-src/commit/f88d9ba7f56ef6c3b783aee1c909a3422c0ef3c3) +* `PhpMethodReflectionFactory::create()` - all parameters are required (https://github.com/phpstan/phpstan-src/commit/8bfbf8f254a68e4f1b15419eb950ea677fc2916e) +* FunctionCallParametersCheck - parameters `$nodeType` and `$acceptsNamedArguments` made required (https://github.com/phpstan/phpstan-src/commit/493752737c32eb878de4dfb91817761b952348e4) +* MethodParameterComparisonHelper - parameter `$ignorable` of `compare()` method made required (https://github.com/phpstan/phpstan-src/commit/f85a500288b0b8ef9a19d405c0e3d99ab57ce797) +* Parameter `$dateTimeClass` of DateTimeModifyReturnTypeExtension constructor made required (https://github.com/phpstan/phpstan-src/commit/a8cd423e842deaa7d924580665207a4b1a373115) +* NativeFunctionReflection construct parameters made required (https://github.com/phpstan/phpstan-src/commit/64ff598cd42268d2178d02efd208afe637060978) +* Cover AccessoryArrayListType constructor with BC promise (https://github.com/phpstan/phpstan-src/commit/51de9032c6e98bff2d6eb0e5b7295720ec0276b9) +* Add `PhpVersion` parameter to various `Type` methods ([#3478](https://github.com/phpstan/phpstan-src/pull/3478)), thanks @VincentLanglet! +* Move ContainerDynamicReturnTypeExtension to build/PHPStan (https://github.com/phpstan/phpstan-src/commit/5651bec661582b2d62de1b4ae9d5f27e69e3c524) +* Renamed NewOptimizedDirectorySourceLocator to OptimizedDirectorySourceLocator (https://github.com/phpstan/phpstan-src/commit/db02a30ca11c7b9839c30e0321ed403dd14f6c73) +* Remove unneded abstraction (https://github.com/phpstan/phpstan-src/commit/f302c9069274afa63ec1b4f313ca72340699e9d8) +* Introduce native return types thanks to PHP 7.4 return type covariance (https://github.com/phpstan/phpstan-src/commit/392f090066bfc9946b4ad524ffecf3d420c23114) +* ReadWritePropertiesExtension - use ExtendedPropertyReflection in parameter type (https://github.com/phpstan/phpstan-src/commit/f0a629685de2202687b9f92bd0e1a516daf2443e) +* Declare more precise `getClass()` return types in extension interfaces ([#1754](https://github.com/phpstan/phpstan-src/pull/1754)), thanks @staabm! +* (https://github.com/phpstan/phpstan-src/commit/38cb5a315e5573231d8695df343c8ee87a8c3b2e) +* HasOffsetType - put constructor parameter type natively (https://github.com/phpstan/phpstan-src/commit/b5accb3f6bbcffc8a44934539b88903e09b6a174) +* Printer is covered by BC promise (https://github.com/phpstan/phpstan-src/commit/b0858332efc7aa2f2fde7544a2a821ba81bde13b) +* More interfaces that are not supposed to be implemented in userland (https://github.com/phpstan/phpstan-src/commit/778af2ed74ba59bfb2a69fd5b45821ccdb1107c9, https://github.com/phpstan/phpstan-src/commit/cb6ab5544a016c52f931fc390bcdf9c627819d8f) +* Refactored `FunctionCallParametersCheck::check()` parameters (https://github.com/phpstan/phpstan-src/commit/710e09c41698efb1d8d3ae31791944077dbb9cc1) +* Spread list usages in Reflection, Scope, Type ([#3530](https://github.com/phpstan/phpstan-src/pull/3530)), thanks @janedbal! From 8051007f39905883f6ec36f9d64ad673bbaed355 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:23:43 +0200 Subject: [PATCH 0605/3097] Fix test --- tests/PHPStan/Analyser/nsrt/assert-docblock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index b6391d651ab..8451a48ebd3 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -39,7 +39,7 @@ function validateStringOrIntArray(array $arr) : bool { * @param mixed[] $arr * @phpstan-assert-if-true =string[] $arr * @phpstan-assert-if-false =int[] $arr - * @phpstan-assert-if-false =non-empty-array $arr + * @phpstan-assert-if-false =non-empty-array $arr */ function validateStringOrNonEmptyIntArray(array $arr) : bool { return false; From 4c5e44f846c42d213cffd429a11a223a39e0e2ce Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 4 Oct 2024 12:30:17 +0200 Subject: [PATCH 0606/3097] functionMap: a bit more precise get_defined_constants --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 80c57788d62..f11a8f3ff03 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3303,7 +3303,7 @@ 'get_declared_classes' => ['list'], 'get_declared_interfaces' => ['list'], 'get_declared_traits' => ['list'], -'get_defined_constants' => ['array', 'categorize='=>'bool'], +'get_defined_constants' => ['array', 'categorize='=>'bool'], 'get_defined_functions' => ['array{internal:non-empty-list,user:list}', 'exclude_disabled='=>'bool'], 'get_defined_vars' => ['array'], 'get_extension_funcs' => ['list|false', 'extension_name'=>'string'], From 87f948a20d724e2a35c58e0b3f6bc5f655203d1d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:23:43 +0200 Subject: [PATCH 0607/3097] Fix test --- tests/PHPStan/Analyser/nsrt/assert-docblock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index 1b094a1cd7b..8923430fd35 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -39,7 +39,7 @@ function validateStringOrIntArray(array $arr) : bool { * @param mixed[] $arr * @phpstan-assert-if-true =string[] $arr * @phpstan-assert-if-false =int[] $arr - * @phpstan-assert-if-false =non-empty-array $arr + * @phpstan-assert-if-false =non-empty-array $arr */ function validateStringOrNonEmptyIntArray(array $arr) : bool { return false; From 58b33972621d3faf25ac6c7b82b8a6703349b496 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:26:54 +0200 Subject: [PATCH 0608/3097] Fix build --- build/enum-adapter-errors.neon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon index eaa39f1d5e0..8acded5318c 100644 --- a/build/enum-adapter-errors.neon +++ b/build/enum-adapter-errors.neon @@ -140,6 +140,11 @@ parameters: count: 1 path: ../src/Reflection/ClassReflection.php + - + message: "#^PHPDoc tag @phpstan\-assert\-if\-true for \$this\-\>reflection contains unknown class PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionEnum\.$#" + count: 1 + path: ../src/Reflection/ClassReflection.php + - message: "#^Method PHPStan\\\\Reflection\\\\Php\\\\BuiltinMethodReflection\\:\\:getDeclaringClass\\(\\) has invalid return type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" count: 1 From 2b19dcfbc9b97ebdbfa3493fc032ceaf187e6a16 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:28:08 +0200 Subject: [PATCH 0609/3097] Cleanup --- build/baseline-lt-7.3.neon | 2 - build/enum-adapter-errors.neon | 151 --------------------------- build/ignore-by-php-version.neon.php | 9 -- phpstan-baseline.neon | 15 +++ 4 files changed, 15 insertions(+), 162 deletions(-) delete mode 100644 build/baseline-lt-7.3.neon delete mode 100644 build/enum-adapter-errors.neon diff --git a/build/baseline-lt-7.3.neon b/build/baseline-lt-7.3.neon deleted file mode 100644 index aab49911587..00000000000 --- a/build/baseline-lt-7.3.neon +++ /dev/null @@ -1,2 +0,0 @@ -parameters: - ignoreErrors: [] diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon deleted file mode 100644 index 8acded5318c..00000000000 --- a/build/enum-adapter-errors.neon +++ /dev/null @@ -1,151 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Call to method getAttributes\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getBackingType\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getValueExpression\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnumBackedCase\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getCase\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getCases\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getConstructor\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getDocComment\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getFileName\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getName\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getParentClass\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 4 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method getTraits\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method hasCase\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method implementsInterface\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isAbstract\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isBacked\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isEnum\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isFinal\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isInterface\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isInternal\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isReadOnly\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isSubclassOf\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Call to method isTrait\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum not found\\.$#" - count: 4 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnumBackedCase not found\\.$#" - count: 2 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getNativeReflection\\(\\) has invalid return type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Parameter \\$class of method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:collectTraits\\(\\) has invalid type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Parameter \\$reflection of method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:__construct\\(\\) has invalid type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Property PHPStan\\\\Reflection\\\\ClassReflection\\:\\:\\$reflection has unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum as its type\\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^PHPDoc tag @phpstan\-assert\-if\-true for \$this\-\>reflection contains unknown class PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionEnum\.$#" - count: 1 - path: ../src/Reflection/ClassReflection.php - - - - message: "#^Method PHPStan\\\\Reflection\\\\Php\\\\BuiltinMethodReflection\\:\\:getDeclaringClass\\(\\) has invalid return type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: ../src/Reflection/Php/BuiltinMethodReflection.php diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 62de0070e5a..2c808909bca 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -1,11 +1,6 @@ = 80000) { $includes[] = __DIR__ . '/baseline-8.0.neon'; } @@ -20,10 +15,6 @@ $includes[] = __DIR__ . '/ignore-gte-php7.4-errors.neon'; } -if (PHP_VERSION_ID < 70400) { - $includes[] = __DIR__ . '/enum-adapter-errors.neon'; -} - if (PHP_VERSION_ID < 80000) { $includes[] = __DIR__ . '/more-enum-adapter-errors.neon'; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 22970c52018..63e2058a4c9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -690,6 +690,21 @@ parameters: count: 1 path: src/Rules/Variables/CompactVariablesRule.php + - + message: """ + #^Call to deprecated method assertFileNotExists\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\: + https\\://github\\.com/sebastianbergmann/phpunit/issues/4077$# + """ + identifier: staticMethod.deprecated + count: 1 + path: src/Testing/LevelsTestCase.php + + - + message: "#^Call to function method_exists\\(\\) with 'PHPUnit\\\\\\\\Framework\\\\\\\\TestCase' and 'assertFileDoesNotEx…' will always evaluate to true\\.$#" + identifier: function.alreadyNarrowedType + count: 1 + path: src/Testing/LevelsTestCase.php + - message: "#^Anonymous function has an unused use \\$container\\.$#" identifier: closure.unusedUse From 0519ceda8608b9449546c67a2e3da408de0e4ce7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 13:32:39 +0200 Subject: [PATCH 0610/3097] Fix --- build/enum-adapter-errors.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/enum-adapter-errors.neon b/build/enum-adapter-errors.neon index 8acded5318c..d78223e40cc 100644 --- a/build/enum-adapter-errors.neon +++ b/build/enum-adapter-errors.neon @@ -141,7 +141,7 @@ parameters: path: ../src/Reflection/ClassReflection.php - - message: "#^PHPDoc tag @phpstan\-assert\-if\-true for \$this\-\>reflection contains unknown class PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionEnum\.$#" + message: '#^PHPDoc tag @phpstan\-assert\-if\-true for \$this\-\>reflection contains unknown class PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionEnum\.$#' count: 1 path: ../src/Reflection/ClassReflection.php From 1326a795309000aab4fe929eeb10143d709a39aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 4 Oct 2024 14:03:38 +0200 Subject: [PATCH 0611/3097] Fix ClassPropertyNode line --- src/Analyser/NodeScopeResolver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 65af41abc9a..66da20eec99 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -896,6 +896,8 @@ private function processStmtNode( } elseif (isset($varTags[$propertyName])) { $phpDocType = $varTags[$propertyName]->getType(); } + $propStmt = clone $stmt; + $propStmt->setAttributes($prop->getAttributes()); $nodeCallback( new ClassPropertyNode( $propertyName, @@ -906,7 +908,7 @@ private function processStmtNode( $phpDocType, false, false, - $stmt, + $propStmt, $isReadOnly, $scope->isInTrait(), $scope->getClassReflection()->isReadOnly(), From 6cf6fff04b29119d9757fd9a231e4589083a20a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 5 Oct 2024 09:51:04 +0200 Subject: [PATCH 0612/3097] Update nette/neon --- composer.json | 6 +---- composer.lock | 18 +++++++------- patches/NeonParser.patch | 11 --------- patches/NetteNeonStringNode.patch | 40 ------------------------------- 4 files changed, 10 insertions(+), 65 deletions(-) delete mode 100644 patches/NeonParser.patch delete mode 100644 patches/NetteNeonStringNode.patch diff --git a/composer.json b/composer.json index 6b268bb1463..1b3a8bedf29 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "jetbrains/phpstorm-stubs": "dev-master#56f6b9e55f5885e651553843a1aaf9ec9c586c04", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", - "nette/neon": "3.3.3", + "nette/neon": "3.3.4", "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", @@ -117,10 +117,6 @@ ], "nette/di": [ "patches/DependencyChecker.patch" - ], - "nette/neon": [ - "patches/NetteNeonStringNode.patch", - "patches/NeonParser.patch" ] } }, diff --git a/composer.lock b/composer.lock index 2a0883c5005..660c3ad5ec1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9485ba4e0af44d8602eb360c34f92b8d", + "content-hash": "eb6f30bebe27d08d2b3b9e2c729ccf20", "packages": [ { "name": "clue/ndjson-react", @@ -1706,21 +1706,21 @@ }, { "name": "nette/neon", - "version": "v3.3.3", + "version": "v3.3.4", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95" + "reference": "bb88bf3a54dd21bf4dbddb5cd525d7b0c61b7cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/22e384da162fab42961d48eb06c06d3ad0c11b95", - "reference": "22e384da162fab42961d48eb06c06d3ad0c11b95", + "url": "https://api.github.com/repos/nette/neon/zipball/bb88bf3a54dd21bf4dbddb5cd525d7b0c61b7cda", + "reference": "bb88bf3a54dd21bf4dbddb5cd525d7b0c61b7cda", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=7.1" + "php": "7.1 - 8.4" }, "require-dev": { "nette/tester": "^2.0", @@ -1768,9 +1768,9 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.3.3" + "source": "https://github.com/nette/neon/tree/v3.3.4" }, - "time": "2022-03-10T02:04:26+00:00" + "time": "2024-10-04T22:17:24+00:00" }, { "name": "nette/php-generator", @@ -6453,7 +6453,7 @@ "php": "^8.1", "composer-runtime-api": "^2.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.99" }, diff --git a/patches/NeonParser.patch b/patches/NeonParser.patch deleted file mode 100644 index 9ff7a2b3dea..00000000000 --- a/patches/NeonParser.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- src/Neon/Parser.php 2022-03-10 03:04:26 -+++ src/Neon/Parser.php 2024-08-26 21:57:02 -@@ -236,7 +236,7 @@ - } - - -- private function injectPos(Node $node, int $start = null, int $end = null): Node -+ private function injectPos(Node $node, ?int $start = null, ?int $end = null): Node - { - $node->startTokenPos = $start ?? $this->tokens->getPos(); - $node->startLine = $this->posToLine[$node->startTokenPos]; diff --git a/patches/NetteNeonStringNode.patch b/patches/NetteNeonStringNode.patch deleted file mode 100644 index ff7332693fa..00000000000 --- a/patches/NetteNeonStringNode.patch +++ /dev/null @@ -1,40 +0,0 @@ ---- src/Neon/Node/StringNode.php 2022-03-10 03:04:26 -+++ src/Neon/Node/StringNode.php 2024-08-26 14:53:45 -@@ -79,27 +79,18 @@ - - public function toString(): string - { -- if (strpos($this->value, "\n") === false) { -- return "'" . str_replace("'", "''", $this->value) . "'"; -+ $res = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); -+ if ($res === false) { -+ throw new Nette\Neon\Exception('Invalid UTF-8 sequence: ' . $this->value); -+ } - -- } elseif (preg_match('~\n[\t ]+\'{3}~', $this->value)) { -- $s = json_encode($this->value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); -- $s = preg_replace_callback( -- '#[^\\\\]|\\\\(.)#s', -- function ($m) { -- return ['n' => "\n", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; -- }, -- substr($s, 1, -1) -- ); -- $s = str_replace('"""', '""\"', $s); -- $delim = '"""'; -- -- } else { -- $s = $this->value; -- $delim = "'''"; -+ if (strpos($this->value, "\n") !== false) { -+ $res = preg_replace_callback('#[^\\\\]|\\\\(.)#s', function ($m) { -+ return ['n' => "\n\t", 't' => "\t", '"' => '"'][$m[1] ?? ''] ?? $m[0]; -+ }, $res); -+ $res = '"""' . "\n\t" . substr($res, 1, -1) . "\n" . '"""'; - } - -- $s = preg_replace('#^(?=.)#m', "\t", $s); -- return $delim . "\n" . $s . "\n" . $delim; -+ return $res; - } - } From bc81fe968607d7a773d0982617624956f79bb67f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 5 Oct 2024 09:54:43 +0200 Subject: [PATCH 0613/3097] Regenerate baseline --- phpstan-baseline.neon | 654 +++++++++++++++++++++--------------------- 1 file changed, 327 insertions(+), 327 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 63e2058a4c9..370a77c8319 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,1948 +1,1948 @@ parameters: ignoreErrors: - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: build/PHPStan/Build/ContainerDynamicReturnTypeExtension.php - - message: "#^Method PHPStan\\\\Analyser\\\\AnalyserResultFinalizer\\:\\:finalize\\(\\) throws checked exception Throwable but it's missing from the PHPDoc @throws tag\\.$#" + message: '#^Method PHPStan\\Analyser\\AnalyserResultFinalizer\:\:finalize\(\) throws checked exception Throwable but it''s missing from the PHPDoc @throws tag\.$#' identifier: missingType.checkedException count: 1 path: src/Analyser/AnalyserResultFinalizer.php - - message: "#^Cannot assign offset 'realCount' to array\\|string\\.$#" + message: '#^Cannot assign offset ''realCount'' to array\|string\.$#' identifier: offsetAssign.dimType count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php - - message: "#^Casting to string something that's already string\\.$#" + message: '#^Casting to string something that''s already string\.$#' identifier: cast.useless count: 3 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/MutatingScope.php - - message: "#^Only numeric types are allowed in pre\\-increment, float\\|int\\|string\\|null given\\.$#" + message: '#^Only numeric types are allowed in pre\-increment, float\|int\|string\|null given\.$#' identifier: preInc.nonNumeric count: 1 path: src/Analyser/MutatingScope.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/NodeScopeResolver.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' identifier: argument.type count: 1 path: src/Analyser/NodeScopeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Analyser/TypeSpecifier.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 5 path: src/Analyser/TypeSpecifier.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Analyser/TypeSpecifier.php - - message: "#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\\\Collectors\\\\Collector\\:\\:processNode\\(\\)\\.$#" + message: '#^Template type TNodeType is declared as covariant, but occurs in contravariant position in parameter node of method PHPStan\\Collectors\\Collector\:\:processNode\(\)\.$#' identifier: generics.variance count: 1 path: src/Collectors/Collector.php - - message: "#^Method PHPStan\\\\Collectors\\\\Registry\\:\\:__construct\\(\\) has parameter \\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector but does not specify its types\\: TNodeType, TValue$#" + message: '#^Method PHPStan\\Collectors\\Registry\:\:__construct\(\) has parameter \$collectors with generic interface PHPStan\\Collectors\\Collector but does not specify its types\: TNodeType, TValue$#' identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$cache with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + message: '#^Property PHPStan\\Collectors\\Registry\:\:\$cache with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - - message: "#^Property PHPStan\\\\Collectors\\\\Registry\\:\\:\\$collectors with generic interface PHPStan\\\\Collectors\\\\Collector does not specify its types\\: TNodeType, TValue$#" + message: '#^Property PHPStan\\Collectors\\Registry\:\:\$collectors with generic interface PHPStan\\Collectors\\Collector does not specify its types\: TNodeType, TValue$#' identifier: missingType.generics count: 1 path: src/Collectors/Registry.php - - message: "#^Anonymous function has an unused use \\$container\\.$#" + message: '#^Anonymous function has an unused use \$container\.$#' identifier: closure.unusedUse count: 1 path: src/Command/CommandHelper.php - - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' identifier: argument.type count: 1 path: src/Command/CommandHelper.php - - message: "#^Static property PHPStan\\\\Command\\\\CommandHelper\\:\\:\\$reservedMemory is never read, only written\\.$#" + message: '#^Static property PHPStan\\Command\\CommandHelper\:\:\$reservedMemory is never read, only written\.$#' identifier: property.onlyWritten count: 1 path: src/Command/CommandHelper.php - - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#1 \\$headers \\(array\\\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$headers \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\StyleInterface\\:\\:table\\(\\)$#" + message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Parameter \\#2 \\$rows \\(array\\\\>\\) of method PHPStan\\\\Command\\\\ErrorsConsoleStyle\\:\\:table\\(\\) should be contravariant with parameter \\$rows \\(array\\) of method Symfony\\\\Component\\\\Console\\\\Style\\\\SymfonyStyle\\:\\:table\\(\\)$#" + message: '#^Parameter \#2 \$rows \(array\\>\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$rows \(array\) of method Symfony\\Component\\Console\\Style\\SymfonyStyle\:\:table\(\)$#' identifier: method.childParameterType count: 1 path: src/Command/ErrorsConsoleStyle.php - - message: "#^Variable method call on Nette\\\\Schema\\\\Elements\\\\AnyOf\\|Nette\\\\Schema\\\\Elements\\\\Structure\\|Nette\\\\Schema\\\\Elements\\\\Type\\.$#" + message: '#^Variable method call on Nette\\Schema\\Elements\\AnyOf\|Nette\\Schema\\Elements\\Structure\|Nette\\Schema\\Elements\\Type\.$#' identifier: method.dynamicName count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: "#^Variable static method call on Nette\\\\Schema\\\\Expect\\.$#" + message: '#^Variable static method call on Nette\\Schema\\Expect\.$#' identifier: staticMethod.dynamicName count: 1 path: src/DependencyInjection/ContainerFactory.php - - message: "#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\\\DI\\\\Config\\\\Helpers\\.$#" + message: '#^Fetching class constant PREVENT_MERGING of deprecated class Nette\\DI\\Config\\Helpers\.$#' identifier: classConstant.deprecatedClass count: 1 path: src/DependencyInjection/NeonAdapter.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: "#^Parameter \\#1 \\$path of function dirname expects string, string\\|false given\\.$#" + message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' identifier: argument.type count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php - - message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" + message: '#^Variable method call on PHPStan\\Reflection\\ClassReflection\.$#' identifier: method.dynamicName count: 2 path: src/PhpDoc/PhpDocBlock.php - - message: "#^Variable static method call on PHPStan\\\\PhpDoc\\\\PhpDocBlock\\.$#" + message: '#^Variable static method call on PHPStan\\PhpDoc\\PhpDocBlock\.$#' identifier: staticMethod.dynamicName count: 1 path: src/PhpDoc/PhpDocBlock.php - - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV…' will always evaluate to true\\.$#" + message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getParamOutTypeTagV…'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getSelfOutTypeTagVa…' will always evaluate to true\\.$#" + message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getSelfOutTypeTagVa…'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType count: 1 path: src/PhpDoc/PhpDocNodeResolver.php - - message: "#^Method PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock\\:\\:getNameScope\\(\\) should return PHPStan\\\\Analyser\\\\NameScope but returns PHPStan\\\\Analyser\\\\NameScope\\|null\\.$#" + message: '#^Method PHPStan\\PhpDoc\\ResolvedPhpDocBlock\:\:getNameScope\(\) should return PHPStan\\Analyser\\NameScope but returns PHPStan\\Analyser\\NameScope\|null\.$#' identifier: return.type count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/PhpDoc/TypeNodeResolver.php - - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\Identifier\\\\Exception\\\\InvalidIdentifierName is never thrown in the try block\\.$#" + message: '#^Dead catch \- PHPStan\\BetterReflection\\Identifier\\Exception\\InvalidIdentifierName is never thrown in the try block\.$#' identifier: catch.neverThrown count: 3 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: "#^Dead catch \\- PHPStan\\\\BetterReflection\\\\NodeCompiler\\\\Exception\\\\UnableToCompileNode is never thrown in the try block\\.$#" + message: '#^Dead catch \- PHPStan\\BetterReflection\\NodeCompiler\\Exception\\UnableToCompileNode is never thrown in the try block\.$#' identifier: catch.neverThrown count: 1 path: src/Reflection/BetterReflection/BetterReflectionProvider.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' identifier: argument.type count: 1 path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php - - message: "#^Method PHPStan\\\\Reflection\\\\BetterReflection\\\\SourceLocator\\\\FileReadTrapStreamWrapper\\:\\:invokeWithRealFileStreamWrapper\\(\\) has parameter \\$cb with no signature specified for callable\\.$#" + message: '#^Method PHPStan\\Reflection\\BetterReflection\\SourceLocator\\FileReadTrapStreamWrapper\:\:invokeWithRealFileStreamWrapper\(\) has parameter \$cb with no signature specified for callable\.$#' identifier: missingType.callable count: 1 path: src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\ClassLike\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_ given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\ClassLike\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Function_ given\.$#' identifier: argument.type count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#" + message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' identifier: argument.type count: 2 path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php - - message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Reflection/ClassReflection.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Reflection/ClassReflection.php - - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#" + message: '#^Method PHPStan\\Reflection\\ClassReflection\:\:getCacheKey\(\) should return string but returns string\|null\.$#' identifier: return.type count: 1 path: src/Reflection/ClassReflection.php - - message: "#^Binary operation \"&\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "&" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\*\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\*" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\+\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\+" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\-\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\-" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\^\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\^" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Binary operation \"\\|\" between bool\\|float\\|int\\|string\\|null and bool\\|float\\|int\\|string\\|null results in an error\\.$#" + message: '#^Binary operation "\|" between bool\|float\|int\|string\|null and bool\|float\|int\|string\|null results in an error\.$#' identifier: binaryOp.invalid count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 22 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 10 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' identifier: varTag.nativeType count: 1 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' identifier: varTag.type count: 4 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^PHPDoc tag @var with type float\\|int\\|null is not subtype of type int\\|null\\.$#" + message: '#^PHPDoc tag @var with type float\|int\|null is not subtype of type int\|null\.$#' identifier: varTag.type count: 6 path: src/Reflection/InitializerExprTypeResolver.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.constructor count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Classes/RequireExtendsRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Classes/RequireImplementsRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 6 path: src/Rules/Comparison/BooleanAndConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/BooleanNotConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ConstantLooseComparisonRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/DoWhileLoopConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ElseIfConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/IfConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Rules/Comparison/LogicalXorConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Rules/Comparison/MatchExpressionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Comparison/TernaryOperatorConstantConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php - - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Method PHPStan\\\\Rules\\\\DirectRegistry\\:\\:__construct\\(\\) has parameter \\$rules with generic interface PHPStan\\\\Rules\\\\Rule but does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Rules\\DirectRegistry\:\:__construct\(\) has parameter \$rules with generic interface PHPStan\\Rules\\Rule but does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\DirectRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\DirectRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/DirectRegistry.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Generics/GenericAncestorsCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Generics/TemplateTypeCheck.php - - message: "#^Function class_implements\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_implements\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Function class_parents\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#" + message: '#^Function class_parents\(\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' identifier: phpstanApi.runtimeReflection count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Method PHPStan\\\\Rules\\\\LazyRegistry\\:\\:getRulesFromContainer\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Rules\\LazyRegistry\:\:getRulesFromContainer\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$cache with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$cache with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Property PHPStan\\\\Rules\\\\LazyRegistry\\:\\:\\$rules with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Property PHPStan\\Rules\\LazyRegistry\:\:\$rules with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: src/Rules/LazyRegistry.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/MethodParameterComparisonHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/RequireExtendsCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/PhpDoc/VarTagTypeRuleHelper.php - - message: "#^Access to an undefined property T of PHPStan\\\\Rules\\\\RuleError\\:\\:\\$tip\\.$#" + message: '#^Access to an undefined property T of PHPStan\\Rules\\RuleError\:\:\$tip\.$#' identifier: property.notFound count: 2 path: src/Rules/RuleErrorBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/RuleLevelHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/UnusedFunctionParametersCheck.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Rules/Variables/CompactVariablesRule.php - - message: """ - #^Call to deprecated method assertFileNotExists\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\: - https\\://github\\.com/sebastianbergmann/phpunit/issues/4077$# - """ + message: ''' + #^Call to deprecated method assertFileNotExists\(\) of class PHPUnit\\Framework\\Assert\: + https\://github\.com/sebastianbergmann/phpunit/issues/4077$# + ''' identifier: staticMethod.deprecated count: 1 path: src/Testing/LevelsTestCase.php - - message: "#^Call to function method_exists\\(\\) with 'PHPUnit\\\\\\\\Framework\\\\\\\\TestCase' and 'assertFileDoesNotEx…' will always evaluate to true\\.$#" + message: '#^Call to function method_exists\(\) with ''PHPUnit\\\\Framework\\\\TestCase'' and ''assertFileDoesNotEx…'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType count: 1 path: src/Testing/LevelsTestCase.php - - message: "#^Anonymous function has an unused use \\$container\\.$#" + message: '#^Anonymous function has an unused use \$container\.$#' identifier: closure.unusedUse count: 1 path: src/Testing/PHPStanTestCase.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Testing/TypeInferenceTestCase.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryArrayListType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryLiteralStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryLowercaseStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonEmptyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNonFalsyStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasMethodType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasOffsetType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasOffsetValueType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasPropertyType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/NonEmptyArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/OversizedArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/BooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/BooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/CallableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/CallableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ClosureType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 7 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType\\|PHPStan\\\\Type\\\\Constant\\\\ConstantStringType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\Constant\\ConstantIntegerType\|PHPStan\\Type\\Constant\\ConstantStringType but it''s error\-prone and dangerous\.$#' identifier: phpstanApi.varTagAssumption count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of native type int\.$#' identifier: varTag.nativeType count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^PHPDoc tag @var with type float\\|int is not subtype of type int\\.$#" + message: '#^PHPDoc tag @var with type float\|int is not subtype of type int\.$#' identifier: varTag.type count: 1 path: src/Type/Constant/ConstantArrayTypeBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Constant/ConstantBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^PHPDoc tag @var with type int\\|string is not subtype of type string\\.$#" + message: '#^PHPDoc tag @var with type int\|string is not subtype of type string\.$#' identifier: varTag.type count: 1 path: src/Type/Constant/ConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Constant/OversizedArrayBuilder.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Enum/EnumCaseObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/ExponentiateHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/FileTypeMapper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/FloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/GenericClassStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/GenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBenevolentUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateBooleanType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantArrayType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: "#^Method PHPStan\\\\Type\\\\Generic\\\\TemplateConstantIntegerType\\:\\:toPhpDocNode\\(\\) should return PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\ConstTypeNode but returns PHPStan\\\\PhpDocParser\\\\Ast\\\\Type\\\\IdentifierTypeNode\\.$#" + message: '#^Method PHPStan\\Type\\Generic\\TemplateConstantIntegerType\:\:toPhpDocNode\(\) should return PHPStan\\PhpDocParser\\Ast\\Type\\ConstTypeNode but returns PHPStan\\PhpDocParser\\Ast\\Type\\IdentifierTypeNode\.$#' identifier: return.type count: 1 path: src/Type/Generic/TemplateConstantIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateConstantStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateFloatType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateGenericObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\IntersectionType will always evaluate to false\.$#' identifier: instanceof.alwaysFalse count: 2 path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateKeyOfType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateMixedType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Generic/TemplateStrictMixedType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Generic/TemplateTypeFactory.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/Generic/TemplateUnionType.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Type and PHPStan\\Type\\UnionType will always evaluate to false\.$#' identifier: instanceof.alwaysFalse count: 2 path: src/Type/Generic/TemplateUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntegerRangeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/IntegerType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/IntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/IterableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/IterableType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/NullType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/NullType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectShapeType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Enum\\\\EnumCaseObjectType is error\\-prone and deprecated\\. Use Type\\:\\:getEnumCases\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Enum\\EnumCaseObjectType is error\-prone and deprecated\. Use Type\:\:getEnumCases\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericObjectType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 6 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/ObjectType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/ObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/ObjectWithoutClassType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 16 path: src/Type/Php/BcMathStringOrNullReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/CompactFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DefineConstantTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DefinedConstantTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\TypeWithClassName is error\-prone and deprecated\. Use Type\:\:getObjectClassNames\(\) or Type\:\:getObjectClassReflections\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/DsMapDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/FilterFunctionReturnTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/IsAFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/MethodExistsTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/Php/RangeFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php - - message: "#^Cannot access offset int\\<0, max\\> on \\(float\\|int\\)\\.$#" + message: '#^Cannot access offset int\<0, max\> on \(float\|int\)\.$#' identifier: offsetAccess.nonOffsetAccessible count: 2 path: src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/StrRepeatFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/Php/StrlenFunctionReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/StaticType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectWithoutClassType is error\-prone and deprecated\. Use Type\:\:isObject\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/StaticType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/StringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/StringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 5 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 14 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 5 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\FloatType is error\\-prone and deprecated\\. Use Type\\:\\:isFloat\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\FloatType is error\-prone and deprecated\. Use Type\:\:isFloat\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 8 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ObjectShapeType is error\-prone and deprecated\. Use Type\:\:isObject\(\) and Type\:\:hasProperty\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\StringType is error\\-prone and deprecated\\. Use Type\\:\\:isString\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\StringType is error\-prone and deprecated\. Use Type\:\:isString\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypeCombinator.php - - message: "#^Instanceof between PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType and PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType will always evaluate to true\\.$#" + message: '#^Instanceof between PHPStan\\Type\\Constant\\ConstantIntegerType and PHPStan\\Type\\Constant\\ConstantIntegerType will always evaluate to true\.$#' identifier: instanceof.alwaysTrue count: 1 path: src/Type/TypeCombinator.php - - message: "#^Result of \\|\\| is always true\\.$#" + message: '#^Result of \|\| is always true\.$#' identifier: booleanOr.alwaysTrue count: 1 path: src/Type/TypeCombinator.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/TypeUtils.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypeUtils.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/TypehintHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Generic\\\\GenericClassStringType is error\\-prone and deprecated\\. Use Type\\:\\:isClassStringType\\(\\) and Type\\:\\:getClassStringObjectType\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericClassStringType is error\-prone and deprecated\. Use Type\:\:isClassStringType\(\) and Type\:\:getClassStringObjectType\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 1 path: src/Type/UnionType.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Type is always PHPStan\\Type\\BooleanType but it''s error\-prone and dangerous\.$#' identifier: phpstanApi.varTagAssumption count: 1 path: src/Type/UnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' identifier: phpstanApi.instanceofType count: 3 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\CallableType is error\\-prone and deprecated\\. Use Type\\:\\:isCallable\\(\\) and Type\\:\\:getCallableParametersAcceptors\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 4 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntegerType is error\\-prone and deprecated\\. Use Type\\:\\:isInteger\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntegerType is error\-prone and deprecated\. Use Type\:\:isInteger\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\NullType is error\\-prone and deprecated\\. Use Type\\:\\:isNull\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\NullType is error\-prone and deprecated\. Use Type\:\:isNull\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/UnionTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\VoidType is error\\-prone and deprecated\\. Use Type\\:\\:isVoid\\(\\) instead\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\VoidType is error\-prone and deprecated\. Use Type\:\:isVoid\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Type/VoidType.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" + message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable count: 1 path: tests/PHPStan/Analyser/AnalyserTest.php - - message: "#^Class PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Analyser\\AnonymousClassNameRuleTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: "#^Method PHPStan\\\\Analyser\\\\AnonymousClassNameRuleTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Analyser\\AnonymousClassNameRuleTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php - - message: "#^Class PHPStan\\\\Analyser\\\\EvaluationOrderTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Analyser\\EvaluationOrderTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: "#^Method PHPStan\\\\Analyser\\\\EvaluationOrderTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Analyser\\EvaluationOrderTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: "#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\\.$#" + message: '#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\.$#' identifier: constant.notFound count: 1 path: tests/PHPStan/Command/AnalyseCommandTest.php - - message: "#^Class PHPStan\\\\Node\\\\FileNodeTest extends generic class PHPStan\\\\Testing\\\\RuleTestCase but does not specify its types\\: TRule$#" + message: '#^Class PHPStan\\Node\\FileNodeTest extends generic class PHPStan\\Testing\\RuleTestCase but does not specify its types\: TRule$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: "#^Method PHPStan\\\\Node\\\\FileNodeTest\\:\\:getRule\\(\\) return type with generic interface PHPStan\\\\Rules\\\\Rule does not specify its types\\: TNodeType$#" + message: '#^Method PHPStan\\Node\\FileNodeTest\:\:getRule\(\) return type with generic interface PHPStan\\Rules\\Rule does not specify its types\: TNodeType$#' identifier: missingType.generics count: 1 path: tests/PHPStan/Node/FileNodeTest.php - - message: "#^PHPDoc tag @var with type string is not subtype of type class\\-string\\.$#" + message: '#^PHPDoc tag @var with type string is not subtype of type class\-string\.$#' identifier: varTag.type count: 1 path: tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.constructor count: 1 path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php - - message: "#^Creating new PHPStan\\\\Php8StubsMap is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.constructor count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - - message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" + message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#' identifier: phpstanApi.varTagAssumption count: 1 path: tests/PHPStan/Type/IterableTypeTest.php From 0210d458d7e2dfa2d800d2a3558667fd0c061002 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 5 Oct 2024 13:07:48 +0200 Subject: [PATCH 0614/3097] Fix `ConstantArrayType::isSuperTypeOf()` --- src/Type/Constant/ConstantArrayType.php | 2 +- .../WrongVariableNameInVarTagRuleTest.php | 4 -- tests/PHPStan/Type/ArrayTypeTest.php | 11 ++++++ .../Type/Constant/ConstantArrayTypeTest.php | 37 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index de722864897..46adbf9a73a 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -405,7 +405,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } - return $result->and($isKeySuperType, $this->getIterableValueType()->isSuperTypeOf($type->getIterableKeyType())); + return $result->and($isKeySuperType, $this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())); } if ($type instanceof CompoundType) { diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index f0c8cd0025f..155c0d6ea49 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -140,10 +140,6 @@ public function testRule(): void 'PHPDoc tag @var above a function has no effect.', 313, ], - [ - "PHPDoc tag @var with type array is not subtype of native type array{: 'empty', 1: '1'}.", - 324, - ], ]); } diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index 98332b70316..e701997c6cb 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -71,6 +71,17 @@ public function dataIsSuperTypeOf(): array new IntersectionType([new ArrayType(new IntegerType(), new StringType()), new OversizedArrayType()]), TrinaryLogic::createYes(), ], + [ + new ArrayType(new StringType(), new MixedType()), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new UnionType([new IntegerType(), new NullType()]), + ]), + TrinaryLogic::createYes(), + ], ]; } diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index f2ee31c44f2..b047b86a691 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -18,6 +18,7 @@ use PHPStan\Type\IterableType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -654,6 +655,42 @@ public function dataIsSuperTypeOf(): iterable ], [1], [0]), TrinaryLogic::createMaybe(), ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new UnionType([new IntegerType(), new NullType()]), + ]), + new ArrayType(new StringType(), new MixedType()), + TrinaryLogic::createMaybe(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new IntegerType(), + new UnionType([new IntegerType(), new NullType()]), + ]), + new ArrayType(new StringType(), new StringType()), + TrinaryLogic::createNo(), + ]; + + yield [ + new ConstantArrayType([ + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ], [ + new IntegerType(), + new UnionType([new IntegerType(), new NullType()]), + ]), + new ArrayType(new StringType(), new MixedType()), + TrinaryLogic::createNo(), + ]; } /** From e2654b7dc87951e1481bc46d8f1f7ff587e3f484 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 6 Oct 2024 12:45:11 +0200 Subject: [PATCH 0615/3097] Fix duplicate errors in AssertRuleHelper --- src/Rules/MissingTypehintCheck.php | 6 +++++- .../Rules/PhpDoc/FunctionAssertRuleTest.php | 5 +++++ .../PHPStan/Rules/PhpDoc/data/function-assert.php | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index d6c55e62ec0..75dd681fb1d 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -85,7 +85,11 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array if ($iterableValue instanceof MixedType && !$iterableValue->isExplicitMixed()) { $iterablesWithMissingValueTypehint[] = $type; } - if ($type instanceof IntersectionType && !$type->isList()->yes()) { + if ($type instanceof IntersectionType) { + if ($type->isList()->yes()) { + return $traverse($type->getIterableValueType()); + } + return $type; } } diff --git a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php index 7c63ae9d67b..ec7035775d9 100644 --- a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php @@ -70,6 +70,11 @@ public function testRule(): void 'Asserted negated type string for $i with type int does not narrow down the type.', 70, ], + [ + 'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type array.', + 88, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/function-assert.php b/tests/PHPStan/Rules/PhpDoc/data/function-assert.php index e1b25eb6c07..d35c1b3b5b9 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/function-assert.php +++ b/tests/PHPStan/Rules/PhpDoc/data/function-assert.php @@ -76,3 +76,18 @@ function negate1(int $i): void function negate2(int $i): void { } + +/** + * @param array $array + * @param string $message + * + * @phpstan-impure + * + * @psalm-assert list $array + */ +function isList($array, $message = ''): void +{ + if (!array_is_list($array)) { + + } +} From f680629bc92e4dd5d7acd3bc60c9539fb047452b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 6 Oct 2024 14:24:35 +0200 Subject: [PATCH 0616/3097] IntersectionType - always describe list as list --- phpstan-baseline.neon | 12 +++ src/Type/IntersectionType.php | 33 +++++++++ .../Analyser/AnalyserIntegrationTest.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-4117.php | 2 +- .../nsrt/constant-array-optional-set.php | 6 +- tests/PHPStan/Analyser/nsrt/count-maybe.php | 30 ++++---- .../Arrays/IterableInForeachRuleTest.php | 2 +- ...plodeParameterCastableToStringRuleTest.php | 2 +- .../ParameterCastableToStringRuleTest.php | 2 +- .../Rules/Functions/ReturnTypeRuleTest.php | 2 +- .../SortParameterCastableToStringRuleTest.php | 30 ++++---- .../Rules/Methods/ReturnTypeRuleTest.php | 4 +- .../Rules/PhpDoc/FunctionAssertRuleTest.php | 2 +- tests/PHPStan/Type/IntersectionTypeTest.php | 73 +++++++++++++++++++ 14 files changed, 161 insertions(+), 43 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 370a77c8319..246118a8d5e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1311,6 +1311,12 @@ parameters: count: 3 path: src/Type/IntersectionType.php + - + message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/IntersectionType.php + - message: '#^Doing instanceof PHPStan\\Type\\BooleanType is error\-prone and deprecated\. Use Type\:\:isBoolean\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1323,6 +1329,12 @@ parameters: count: 2 path: src/Type/IntersectionType.php + - + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Type/IntersectionType.php + - message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index c6c6d6b6449..f3784184bad 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -27,6 +27,7 @@ use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\NonEmptyArrayType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; @@ -45,8 +46,10 @@ use function md5; use function sprintf; use function str_starts_with; +use function strcasecmp; use function strlen; use function substr; +use function usort; /** @api */ class IntersectionType implements CompoundType @@ -292,13 +295,43 @@ public function describe(VerbosityLevel $level): string return $level->handle( function () use ($level): string { $typeNames = []; + $isList = $this->isList()->yes(); + $valueType = null; foreach ($this->getSortedTypes() as $type) { + if ($isList) { + if ($type instanceof ArrayType || $type instanceof ConstantArrayType) { + $valueType = $type->getIterableValueType(); + continue; + } + if ($type instanceof NonEmptyArrayType) { + continue; + } + } if ($type instanceof AccessoryType) { continue; } $typeNames[] = $type->generalize(GeneralizePrecision::lessSpecific())->describe($level); } + if ($isList) { + $isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed(); + $innerType = ''; + if ($valueType !== null && !$isMixedValueType) { + $innerType = sprintf('<%s>', $valueType->describe($level)); + } + + $typeNames[] = 'list' . $innerType; + } + + usort($typeNames, static function ($a, $b) { + $cmp = strcasecmp($a, $b); + if ($cmp !== 0) { + return $cmp; + } + + return $a <=> $b; + }); + return implode('&', $typeNames); }, fn (): string => $this->describeItself($level, true), diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 19b27830932..52791b8b95e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -843,7 +843,7 @@ public function testUnresolvableParameter(): void { $errors = $this->runAnalyse(__DIR__ . '/data/unresolvable-parameter.php'); $this->assertCount(3, $errors); - $this->assertSame('Parameter #2 $array of function array_map expects array, array|false given.', $errors[0]->getMessage()); + $this->assertSame('Parameter #2 $array of function array_map expects array, list|false given.', $errors[0]->getMessage()); $this->assertSame(18, $errors[0]->getLine()); $this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[1]->getMessage()); $this->assertSame(30, $errors[1]->getLine()); @@ -892,7 +892,7 @@ public function testBug7554(): void $errors = $this->runAnalyse(__DIR__ . '/data/bug-7554.php'); $this->assertCount(2, $errors); - $this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, array>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage()); + $this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, list|string>>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage()); $this->assertSame(26, $errors[0]->getLine()); $this->assertSame('Cannot access offset int<1, max> on list}>|false.', $errors[1]->getMessage()); diff --git a/tests/PHPStan/Analyser/nsrt/bug-4117.php b/tests/PHPStan/Analyser/nsrt/bug-4117.php index f1f2f60f409..510df695f12 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4117.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4117.php @@ -34,7 +34,7 @@ public function broken(int $key) if ($item) { assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item); } else { - assertType("(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|null", $item); + assertType("(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|null", $item); } assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item); diff --git a/tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php b/tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php index 6faaa574c1a..fe3512a45b3 100644 --- a/tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php +++ b/tests/PHPStan/Analyser/nsrt/constant-array-optional-set.php @@ -60,13 +60,13 @@ class Bar */ public function doFoo($nextAutoIndexes) { - assertType('non-empty-list|int', $nextAutoIndexes); + assertType('int|non-empty-list', $nextAutoIndexes); if (is_int($nextAutoIndexes)) { assertType('int', $nextAutoIndexes); } else { assertType('non-empty-list', $nextAutoIndexes); } - assertType('non-empty-list|int', $nextAutoIndexes); + assertType('int|non-empty-list', $nextAutoIndexes); } /** @@ -75,7 +75,7 @@ public function doFoo($nextAutoIndexes) */ public function doBar($nextAutoIndexes) { - assertType('non-empty-list|int', $nextAutoIndexes); + assertType('int|non-empty-list', $nextAutoIndexes); if (is_int($nextAutoIndexes)) { $nextAutoIndexes = [$nextAutoIndexes]; assertType('array{int}', $nextAutoIndexes); diff --git a/tests/PHPStan/Analyser/nsrt/count-maybe.php b/tests/PHPStan/Analyser/nsrt/count-maybe.php index 255c936d221..4be30d9f49f 100644 --- a/tests/PHPStan/Analyser/nsrt/count-maybe.php +++ b/tests/PHPStan/Analyser/nsrt/count-maybe.php @@ -86,9 +86,9 @@ function doFoo4($maybeCountable, int $mode): void if (count($maybeCountable, $mode) > 0) { assertType('non-empty-list', $maybeCountable); } else { - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } /** @@ -100,9 +100,9 @@ function doFoo5($maybeCountable, $maybeMode): void if (count($maybeCountable, $maybeMode) > 0) { assertType('non-empty-list', $maybeCountable); } else { - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } /** @@ -113,9 +113,9 @@ function doFoo6($maybeCountable, float $invalidMode): void if (count($maybeCountable, $invalidMode) > 0) { assertType('non-empty-list', $maybeCountable); } else { - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } - assertType('list|float', $maybeCountable); + assertType('float|list', $maybeCountable); } /** @@ -124,11 +124,11 @@ function doFoo6($maybeCountable, float $invalidMode): void function doFoo7($maybeCountable, int $mode): void { if (count($maybeCountable, $mode) > 0) { - assertType('non-empty-list|Countable', $maybeCountable); + assertType('Countable|non-empty-list', $maybeCountable); } else { - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } /** @@ -138,11 +138,11 @@ function doFoo7($maybeCountable, int $mode): void function doFoo8($maybeCountable, $maybeMode): void { if (count($maybeCountable, $maybeMode) > 0) { - assertType('non-empty-list|Countable', $maybeCountable); + assertType('Countable|non-empty-list', $maybeCountable); } else { - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } /** @@ -151,11 +151,11 @@ function doFoo8($maybeCountable, $maybeMode): void function doFoo9($maybeCountable, float $invalidMode): void { if (count($maybeCountable, $invalidMode) > 0) { - assertType('non-empty-list|Countable', $maybeCountable); + assertType('Countable|non-empty-list', $maybeCountable); } else { - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } - assertType('list|Countable|float', $maybeCountable); + assertType('Countable|float|list', $maybeCountable); } function doFooBar1(array $countable, int $mode): void diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index c83001b6a8f..43c6020744b 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -32,7 +32,7 @@ public function testCheckWithMaybes(): void 10, ], [ - 'Argument of an invalid type array|false supplied for foreach, only iterables are supported.', + 'Argument of an invalid type list|false supplied for foreach, only iterables are supported.', 19, ], [ diff --git a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php index e5003028bc1..8111e9a9657 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php @@ -64,7 +64,7 @@ public function testImplode(): void { $this->analyse([__DIR__ . '/data/implode.php'], [ [ - 'Parameter #2 $array of function implode expects array, array|string> given.', + 'Parameter #2 $array of function implode expects array, array|string> given.', 9, ], [ diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php index e577240cb8d..83b0f405678 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php @@ -154,7 +154,7 @@ public function testBug3946(): void { $this->analyse([__DIR__ . '/data/bug-3946.php'], [ [ - 'Parameter #1 $keys of function array_combine expects an array of values castable to string, array|Bug3946\stdClass|float|int|string> given.', + 'Parameter #1 $keys of function array_combine expects an array of values castable to string, array|string> given.', 8, ], ]); diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index bd993f3bca6..14d161de9a9 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -173,7 +173,7 @@ public function testListWithNullablesChecked(): void $this->checkNullables = true; $this->analyse([__DIR__ . '/data/return-list-nullables.php'], [ [ - 'Function ReturnListNullables\doFoo() should return array|null but returns array.', + 'Function ReturnListNullables\doFoo() should return array|null but returns list.', 16, ], ]); diff --git a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php index f0350b51791..8c0105a424e 100644 --- a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php @@ -26,7 +26,7 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions.php'], $this->hackParameterNames([ [ - 'Parameter #1 $array of function array_unique expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function array_unique expects an array of values castable to string, array> given.', 16, ], [ @@ -38,27 +38,27 @@ public function testRule(): void 20, ], [ - 'Parameter #1 $array of function rsort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function rsort expects an array of values castable to string, list> given.', 21, ], [ - 'Parameter #1 $array of function asort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function asort expects an array of values castable to string, list> given.', 22, ], [ - 'Parameter #1 $array of function arsort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function arsort expects an array of values castable to string, array> given.', 23, ], [ - 'Parameter #1 $array of function sort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function sort expects an array of values castable to string, array> given.', 25, ], [ - 'Parameter #1 $array of function rsort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function rsort expects an array of values castable to string, list> given.', 26, ], [ - 'Parameter #1 $array of function asort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function asort expects an array of values castable to string, list> given.', 27, ], [ @@ -70,11 +70,11 @@ public function testRule(): void 32, ], [ - 'Parameter #1 $array of function sort expects an array of values castable to string, array> given.', + 'Parameter #1 $array of function sort expects an array of values castable to string, array> given.', 33, ], [ - 'Parameter #1 $array of function sort expects an array of values castable to string and float, array> given.', + 'Parameter #1 $array of function sort expects an array of values castable to string and float, list> given.', 34, ], ])); @@ -88,7 +88,7 @@ public function testNamedArguments(): void $this->analyse([__DIR__ . '/data/sort-param-castable-to-string-functions-named-args.php'], [ [ - 'Parameter $array of function array_unique expects an array of values castable to string, array> given.', + 'Parameter $array of function array_unique expects an array of values castable to string, array> given.', 7, ], [ @@ -96,15 +96,15 @@ public function testNamedArguments(): void 9, ], [ - 'Parameter $array of function rsort expects an array of values castable to string, array> given.', + 'Parameter $array of function rsort expects an array of values castable to string, list> given.', 10, ], [ - 'Parameter $array of function asort expects an array of values castable to string, array> given.', + 'Parameter $array of function asort expects an array of values castable to string, list> given.', 11, ], [ - 'Parameter $array of function arsort expects an array of values castable to string, array> given.', + 'Parameter $array of function arsort expects an array of values castable to string, array> given.', 12, ], ]); @@ -126,11 +126,11 @@ public function testEnum(): void 14, ], [ - 'Parameter #1 $array of function rsort expects an array of values castable to string, array given.', + 'Parameter #1 $array of function rsort expects an array of values castable to string, list given.', 15, ], [ - 'Parameter #1 $array of function asort expects an array of values castable to string, array given.', + 'Parameter #1 $array of function asort expects an array of values castable to string, list given.', 16, ], [ diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 150b170ca32..4c19971d5e8 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -237,7 +237,7 @@ public function testReturnTypeRule(): void 759, ], [ - 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', + 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', 817, ], [ @@ -245,7 +245,7 @@ public function testReturnTypeRule(): void 840, ], [ - 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', + 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', 860, ], [ diff --git a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php index 5af9a8aae4c..8a691952879 100644 --- a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php @@ -70,7 +70,7 @@ public function testRule(): void 70, ], [ - 'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type array.', + 'PHPDoc tag @phpstan-assert for $array has no value type specified in iterable type list.', 88, 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', ], diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index c5dbfc07ab0..7648a80ae90 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -7,6 +7,7 @@ use ObjectTypeEnums\FooEnum; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -418,6 +419,78 @@ public function dataDescribe(): iterable VerbosityLevel::precise(), 'lowercase-string', ]; + + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-list', + ]; + + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new IntegerType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new IntegerType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-list', + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array', + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new IntegerType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new IntegerType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array', + ]; } /** From 069da3f0c6b7078620ee65fd322c593fcfa42e28 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 6 Oct 2024 16:15:48 +0200 Subject: [PATCH 0617/3097] TypeNodeResolver - more specific array key type for lists --- src/PhpDoc/TypeNodeResolver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 6dd4f821b97..f617b076930 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -420,10 +420,10 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new NonAcceptingNeverType(); case 'list': - return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType()), new AccessoryArrayListType()); + return TypeCombinator::intersect(new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), new AccessoryArrayListType()); case 'non-empty-list': return TypeCombinator::intersect( - new ArrayType(new IntegerType(), new MixedType()), + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), new NonEmptyArrayType(), new AccessoryArrayListType(), ); @@ -666,7 +666,7 @@ static function (string $variance): TemplateTypeVariance { return $arrayType; } elseif (in_array($mainTypeName, ['list', 'non-empty-list'], true)) { if (count($genericTypes) === 1) { // list - $listType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $genericTypes[0]), new AccessoryArrayListType()); + $listType = TypeCombinator::intersect(new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $genericTypes[0]), new AccessoryArrayListType()); if ($mainTypeName === 'non-empty-list') { return TypeCombinator::intersect($listType, new NonEmptyArrayType()); } From 45a52ce93c2232e0b48d92580e183e8d1a5fc642 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 30 Sep 2024 10:12:59 +0200 Subject: [PATCH 0618/3097] Simplify preserveKeys TrinaryLogic creation in 2 extensions --- src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php | 3 +-- src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php index d17122bbecc..84ca5046dea 100644 --- a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php @@ -44,9 +44,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $preserveKeysType = isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : new ConstantBooleanType(false); - $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType); - return $arrayType->chunkArray($lengthType, $preserveKeys); + return $arrayType->chunkArray($lengthType, $preserveKeysType->isTrue()); } } diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 1696d39d48f..825f7d6c1a8 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -36,9 +36,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new ConstantBooleanType(false); - $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType); - return $type->reverseArray($preserveKeys); + return $type->reverseArray($preserveKeysType->isTrue()); } } From 7f19560de245fddb28acee86bb9b62056063be01 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 6 Oct 2024 16:20:57 +0200 Subject: [PATCH 0619/3097] Add `Type::sliceArray()` (#3514) --- phpstan-baseline.neon | 2 +- src/Type/Accessory/AccessoryArrayListType.php | 13 ++ src/Type/Accessory/HasOffsetType.php | 16 ++ src/Type/Accessory/HasOffsetValueType.php | 16 ++ src/Type/Accessory/NonEmptyArrayType.php | 12 ++ src/Type/Accessory/OversizedArrayType.php | 5 + src/Type/ArrayType.php | 5 + src/Type/Constant/ConstantArrayType.php | 175 ++++++++++-------- src/Type/IntersectionType.php | 5 + src/Type/MixedType.php | 9 + src/Type/NeverType.php | 5 + .../ArraySliceFunctionReturnTypeExtension.php | 58 ++---- src/Type/StaticType.php | 5 + src/Type/Traits/LateResolvableTypeTrait.php | 5 + src/Type/Traits/MaybeArrayTypeTrait.php | 5 + src/Type/Traits/NonArrayTypeTrait.php | 5 + src/Type/Type.php | 2 + src/Type/UnionType.php | 5 + tests/PHPStan/Analyser/nsrt/array-slice.php | 13 ++ tests/PHPStan/Analyser/nsrt/bug-10721.php | 6 +- 20 files changed, 243 insertions(+), 124 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3f40a5c43ca..197663fec6b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -794,7 +794,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 8 + count: 10 path: src/Type/Constant/ConstantArrayType.php - diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index d32cc8882c8..fb50126b590 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -247,6 +247,19 @@ public function shuffleArray(): Type return $this; } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ($preserveKeys->no()) { + return $this; + } + + if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes()) { + return $this; + } + + return new MixedType(); + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 2194a10cef7..bcd5a931224 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -12,6 +12,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; @@ -25,6 +26,7 @@ use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -208,6 +210,20 @@ public function shuffleArray(): Type return new NonEmptyArrayType(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ( + $this->offsetType->isSuperTypeOf($offsetType)->yes() + && ($lengthType->isNull()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) + ) { + return $preserveKeys->yes() + ? TypeCombinator::intersect($this, new NonEmptyArrayType()) + : new NonEmptyArrayType(); + } + + return new MixedType(); + } + public function isIterableAtLeastOnce(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 853645c91a1..651d81560ab 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -14,6 +14,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\ErrorType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; @@ -27,6 +28,7 @@ use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -264,6 +266,20 @@ public function shuffleArray(): Type return new NonEmptyArrayType(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ( + $this->offsetType->isSuperTypeOf($offsetType)->yes() + && ($lengthType->isNull()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) + ) { + return $preserveKeys->yes() + ? TypeCombinator::intersect($this, new NonEmptyArrayType()) + : new NonEmptyArrayType(); + } + + return new MixedType(); + } + public function isIterableAtLeastOnce(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 293e1f111f2..084cc28d3cb 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -224,6 +224,18 @@ public function shuffleArray(): Type return $this; } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ( + (new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() + && ($lengthType->isNull()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) + ) { + return $this; + } + + return new MixedType(); + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index fa64240e897..7bb8e5213b1 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -220,6 +220,11 @@ public function shuffleArray(): Type return $this; } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 4543e36b771..c1d163d97c1 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -591,6 +591,11 @@ public function shuffleArray(): Type return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->itemType)); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this; + } + public function isCallable(): TrinaryLogic { return TrinaryLogic::createMaybe()->and($this->itemType->isString()); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 27d874deab8..259952b41b5 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -31,6 +31,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\ConstantType; +use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\ErrorType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\TemplateTypeMap; @@ -39,6 +40,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; @@ -812,7 +814,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type $keyTypesCount = count($this->keyTypes); for ($i = 0; $i < $keyTypesCount; $i += $length) { - $chunk = $this->slice($i, $length, true); + $chunk = $this->sliceArray(new ConstantIntegerType($i), new ConstantIntegerType($length), TrinaryLogic::createYes()); $builder->setOffsetValueType(null, $preserveKeys->yes() ? $chunk : $chunk->getValuesArray()); } @@ -882,7 +884,7 @@ public function popArray(): Type return $this->removeLastElements(1); } - private function reverseConstantArray(TrinaryLogic $preserveKeys): self + public function reverseArray(TrinaryLogic $preserveKeys): Type { $keyTypesReversed = array_reverse($this->keyTypes, true); $keyTypes = array_values($keyTypesReversed); @@ -894,11 +896,6 @@ private function reverseConstantArray(TrinaryLogic $preserveKeys): self return $preserveKeys->yes() ? $reversed : $reversed->reindex(); } - public function reverseArray(TrinaryLogic $preserveKeys): Type - { - return $this->reverseConstantArray($preserveKeys); - } - public function searchArray(Type $needleType): Type { $matches = []; @@ -957,6 +954,85 @@ public function shuffleArray(): Type return $generalizedArray; } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + $keyTypesCount = count($this->keyTypes); + if ($keyTypesCount === 0) { + return $this; + } + + $offset = $offsetType instanceof ConstantIntegerType ? $offsetType->getValue() : 0; + $length = $lengthType instanceof ConstantIntegerType ? $lengthType->getValue() : $keyTypesCount; + + if ($length < 0) { + // Negative lengths prevent access to the most right n elements + return $this->removeLastElements($length * -1) + ->sliceArray($offsetType, new NullType(), $preserveKeys); + } + + if ($keyTypesCount + $offset <= 0) { + // A negative offset cannot reach left outside the array + $offset = 0; + } + + if ($offset < 0) { + /* + * Transforms the problem with the negative offset in one with a positive offset using array reversion. + * The reason is belows handling of optional keys which works only from left to right. + * + * e.g. + * array{a: 0, b: 1, c: 2, d: 3, e: 4} + * with offset -4 and length 2 (which would be sliced to array{b: 1, c: 2}) + * + * is transformed via reversion to + * + * array{e: 4, d: 3, c: 2, b: 1, a: 0} + * with offset 2 and length 2 (which will be sliced to array{c: 2, b: 1} and then reversed again) + */ + $offset *= -1; + $reversedLength = min($length, $offset); + $reversedOffset = $offset - $reversedLength; + return $this->reverseArray(TrinaryLogic::createYes()) + ->sliceArray(new ConstantIntegerType($reversedOffset), new ConstantIntegerType($reversedLength), $preserveKeys) + ->reverseArray(TrinaryLogic::createYes()); + } + + if ($offset > 0) { + return $this->removeFirstElements($offset, false) + ->sliceArray(new ConstantIntegerType(0), $lengthType, $preserveKeys); + } + + $builder = ConstantArrayTypeBuilder::createEmpty(); + + $nonOptionalElementsCount = 0; + $hasOptional = false; + for ($i = 0; $nonOptionalElementsCount < $length && $i < $keyTypesCount; $i++) { + $isOptional = $this->isOptionalKey($i); + if (!$isOptional) { + $nonOptionalElementsCount++; + } else { + $hasOptional = true; + } + + $isLastElement = $nonOptionalElementsCount >= $length || $i + 1 >= $keyTypesCount; + if ($isLastElement && $length < $keyTypesCount && $hasOptional) { + // If the slice is not full yet, but has at least one optional key + // the last non-optional element is going to be optional. + // Otherwise, it would not fit into the slice if previous non-optional keys are there. + $isOptional = true; + } + + $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); + } + + $slice = $builder->getArray(); + if (!$slice instanceof self) { + throw new ShouldNotHappenException(); + } + + return $preserveKeys->yes() ? $slice : $slice->reindex(); + } + public function isIterableAtLeastOnce(): TrinaryLogic { $keysCount = count($this->keyTypes); @@ -1147,87 +1223,30 @@ private function removeFirstElements(int $length, bool $reindex = true): self return $array; } + /** @deprecated Use sliceArray() instead */ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): self { - $keyTypesCount = count($this->keyTypes); - if ($keyTypesCount === 0) { - return $this; - } - - $limit ??= $keyTypesCount; - if ($limit < 0) { - // Negative limits prevent access to the most right n elements - return $this->removeLastElements($limit * -1) - ->slice($offset, null, $preserveKeys); - } - - if ($keyTypesCount + $offset <= 0) { - // A negative offset cannot reach left outside the array - $offset = 0; - } - - if ($offset < 0) { - /* - * Transforms the problem with the negative offset in one with a positive offset using array reversion. - * The reason is belows handling of optional keys which works only from left to right. - * - * e.g. - * array{a: 0, b: 1, c: 2, d: 3, e: 4} - * with offset -4 and limit 2 (which would be sliced to array{b: 1, c: 2}) - * - * is transformed via reversion to - * - * array{e: 4, d: 3, c: 2, b: 1, a: 0} - * with offset 2 and limit 2 (which will be sliced to array{c: 2, b: 1} and then reversed again) - */ - $offset *= -1; - $reversedLimit = min($limit, $offset); - $reversedOffset = $offset - $reversedLimit; - return $this->reverseConstantArray(TrinaryLogic::createYes()) - ->slice($reversedOffset, $reversedLimit, $preserveKeys) - ->reverseConstantArray(TrinaryLogic::createYes()); - } - - if ($offset > 0) { - return $this->removeFirstElements($offset, false) - ->slice(0, $limit, $preserveKeys); - } - - $builder = ConstantArrayTypeBuilder::createEmpty(); - - $nonOptionalElementsCount = 0; - $hasOptional = false; - for ($i = 0; $nonOptionalElementsCount < $limit && $i < $keyTypesCount; $i++) { - $isOptional = $this->isOptionalKey($i); - if (!$isOptional) { - $nonOptionalElementsCount++; - } else { - $hasOptional = true; - } - - $isLastElement = $nonOptionalElementsCount >= $limit || $i + 1 >= $keyTypesCount; - if ($isLastElement && $limit < $keyTypesCount && $hasOptional) { - // If the slice is not full yet, but has at least one optional key - // the last non-optional element is going to be optional. - // Otherwise, it would not fit into the slice if previous non-optional keys are there. - $isOptional = true; - } - - $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); - } - - $slice = $builder->getArray(); - if (!$slice instanceof self) { + $array = $this->sliceArray( + ConstantTypeHelper::getTypeFromValue($offset), + ConstantTypeHelper::getTypeFromValue($limit), + TrinaryLogic::createFromBoolean($preserveKeys), + ); + if (!$array instanceof self) { throw new ShouldNotHappenException(); } - return $preserveKeys ? $slice : $slice->reindex(); + return $array; } /** @deprecated Use reverseArray() instead */ public function reverse(bool $preserveKeys = false): self { - return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys)); + $array = $this->reverseArray(TrinaryLogic::createFromBoolean($preserveKeys)); + if (!$array instanceof self) { + throw new ShouldNotHappenException(); + } + + return $array; } /** diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 22a4d811e4a..bb3eaab0e7d 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -780,6 +780,11 @@ public function shuffleArray(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->shuffleArray()); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys)); + } + public function getEnumCases(): array { $compare = []; diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index f72e8f3d760..777a47b94d0 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -274,6 +274,15 @@ public function shuffleArray(): Type return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed))); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + if ($this->isArray()->no()) { + return new ErrorType(); + } + + return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); + } + public function isCallable(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 1523562cabe..f9e2288e287 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -341,6 +341,11 @@ public function shuffleArray(): Type return new NeverType(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new NeverType(); + } + public function isCallable(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php index 156a8c1a463..75f6a14b639 100644 --- a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php @@ -4,19 +4,22 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; final class ArraySliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'array_slice'; @@ -25,49 +28,20 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { $args = $functionCall->getArgs(); - if (count($args) < 1) { + if (count($args) < 2) { return null; } - $valueType = $scope->getType($args[0]->value); - if (!$valueType->isArray()->yes()) { - return null; + $arrayType = $scope->getType($args[0]->value); + if ($arrayType->isArray()->no()) { + return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - $offsetType = isset($args[1]) ? $scope->getType($args[1]->value) : null; - $limitType = isset($args[2]) ? $scope->getType($args[2]->value) : null; - - $constantArrays = $valueType->getConstantArrays(); - if (count($constantArrays) > 0) { - $preserveKeysType = isset($args[3]) ? $scope->getType($args[3]->value) : null; - - $offset = $offsetType instanceof ConstantIntegerType ? $offsetType->getValue() : 0; - $limit = $limitType instanceof ConstantIntegerType ? $limitType->getValue() : null; - $preserveKeys = $preserveKeysType !== null && $preserveKeysType->isTrue()->yes(); - - $results = []; - foreach ($constantArrays as $constantArray) { - $results[] = $constantArray->slice($offset, $limit, $preserveKeys); - } - - return TypeCombinator::union(...$results); - } - - if ($valueType->isIterableAtLeastOnce()->yes()) { - $optionalOffsetsType = TypeCombinator::union($valueType, new ConstantArrayType([], [])); - - $zero = new ConstantIntegerType(0); - if ( - ($offsetType === null || $zero->isSuperTypeOf($offsetType)->yes()) - && ($limitType === null || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($limitType)->yes()) - ) { - return TypeCombinator::intersect($optionalOffsetsType, new NonEmptyArrayType()); - } - - return $optionalOffsetsType; - } + $offsetType = $scope->getType($args[1]->value); + $lengthType = isset($args[2]) ? $scope->getType($args[2]->value) : new NullType(); + $preserveKeysType = isset($args[3]) ? $scope->getType($args[3]->value) : new ConstantBooleanType(false); - return $valueType; + return $arrayType->sliceArray($offsetType, $lengthType, $preserveKeysType->isTrue()); } } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index e0d29249510..0be91db587c 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -460,6 +460,11 @@ public function shuffleArray(): Type return $this->getStaticObjectType()->shuffleArray(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->getStaticObjectType()->sliceArray($offsetType, $lengthType, $preserveKeys); + } + public function isCallable(): TrinaryLogic { return $this->getStaticObjectType()->isCallable(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index fb03a4d1702..3a90652de9f 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -307,6 +307,11 @@ public function shuffleArray(): Type return $this->resolve()->shuffleArray(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->resolve()->sliceArray($offsetType, $lengthType, $preserveKeys); + } + public function isCallable(): TrinaryLogic { return $this->resolve()->isCallable(); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index a68b3577225..afafc917084 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -94,4 +94,9 @@ public function shuffleArray(): Type return new ErrorType(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + } diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index d6f332d2f23..1d1b9482427 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -94,4 +94,9 @@ public function shuffleArray(): Type return new ErrorType(); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return new ErrorType(); + } + } diff --git a/src/Type/Type.php b/src/Type/Type.php index 398529994e1..b0ed8a7788b 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -169,6 +169,8 @@ public function shiftArray(): Type; public function shuffleArray(): Type; + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type; + /** * @return list */ diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 08b8d2c186d..0a3479d9a82 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -751,6 +751,11 @@ public function shuffleArray(): Type return $this->unionTypes(static fn (Type $type): Type => $type->shuffleArray()); } + public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys)); + } + public function getEnumCases(): array { return $this->pickFromTypes( diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index b28c660786a..87fa61e36f0 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -89,4 +89,17 @@ public function constantArraysWithOptionalKeys(array $arr): void assertType('array{a: 0}', array_slice($arr, -3, 1)); } + + public function offsets(array $arr): void + { + if (array_key_exists(1, $arr)) { + assertType('non-empty-array', array_slice($arr, 1, null, false)); + assertType('hasOffset(1)&non-empty-array', array_slice($arr, 1, null, true)); + } + if (array_key_exists(1, $arr) && $arr[1] === 'foo') { + assertType('non-empty-array', array_slice($arr, 1, null, false)); + assertType("hasOffsetValue(1, 'foo')&non-empty-array", array_slice($arr, 1, null, true)); + } + } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10721.php b/tests/PHPStan/Analyser/nsrt/bug-10721.php index 67acc5964dd..cf7ce492722 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10721.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10721.php @@ -68,10 +68,10 @@ public function listVariants(): void assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 3)); // could be non-empty-array assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 3)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 3, true)); + assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, -1, 3, true)); assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 3, true)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 3, true)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 3, true)); + assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, 1, 3, true)); // could be non-empty-array + assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, 2, 3, true)); assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 3, false)); assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 3, false)); From 331072415b4483f344fda178b087e819ada6cc62 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Oct 2024 14:14:20 +0200 Subject: [PATCH 0620/3097] token_name() returns non-empty-string --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f11a8f3ff03..4ed11564454 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -12606,7 +12606,7 @@ 'timezone_version_get' => ['string'], 'tmpfile' => ['__benevolent'], 'token_get_all' => ['list', 'source'=>'string', 'flags='=>'int'], -'token_name' => ['string', 'type'=>'int'], +'token_name' => ['non-empty-string', 'type'=>'int'], 'TokyoTyrant::__construct' => ['void', 'host='=>'string', 'port='=>'int', 'options='=>'array'], 'TokyoTyrant::add' => ['int|float', 'key'=>'string', 'increment'=>'float', 'type='=>'int'], 'TokyoTyrant::connect' => ['TokyoTyrant', 'host'=>'string', 'port='=>'int', 'options='=>'array'], From c6db9a920c35493e09458236f18a267a7548fec3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Oct 2024 16:22:33 +0200 Subject: [PATCH 0621/3097] Fix unused private property is not sometimes detected --- .../DeadCode/UnusedPrivateMethodRule.php | 11 +++++++++- .../DeadCode/UnusedPrivatePropertyRule.php | 11 +++++++++- .../DeadCode/UnusedPrivateMethodRuleTest.php | 10 ++++++++++ .../UnusedPrivatePropertyRuleTest.php | 15 ++++++++++++++ .../PHPStan/Rules/DeadCode/data/bug-11802.php | 20 +++++++++++++++++++ .../Rules/DeadCode/data/bug-11802b.php | 19 ++++++++++++++++++ 6 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-11802.php create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-11802b.php diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index 52ec29d01aa..e5b561af65d 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -84,7 +84,16 @@ public function processNode(Node $node, Scope $scope): array $methodNameType = $callScope->getType($methodCallNode->name); $strings = $methodNameType->getConstantStrings(); if (count($strings) === 0) { - return []; + // handle subtractions of a dynamic method call + foreach ($methods as $lowerMethodName => $method) { + if ((new ConstantStringType($method->getNode()->name->toString()))->isSuperTypeOf($methodNameType)->no()) { + continue; + } + + unset($methods[$lowerMethodName]); + } + + continue; } $methodNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $strings); diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index f3373d4c107..b41185b5c96 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -120,7 +120,16 @@ public function processNode(Node $node, Scope $scope): array $propertyNameType = $usage->getScope()->getType($fetch->name); $strings = $propertyNameType->getConstantStrings(); if (count($strings) === 0) { - return []; + // handle subtractions of a dynamic property fetch + foreach ($properties as $propertyName => $data) { + if ((new ConstantStringType($propertyName))->isSuperTypeOf($propertyNameType)->no()) { + continue; + } + + unset($properties[$propertyName]); + } + + continue; } $propertyNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $strings); diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index 246237f97f3..ca57318a599 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -133,4 +133,14 @@ public function testBug9765(): void $this->analyse([__DIR__ . '/data/bug-9765.php'], []); } + public function testBug11802(): void + { + $this->analyse([__DIR__ . '/data/bug-11802b.php'], [ + [ + 'Method Bug11802b\HelloWorld::doBar() is unused.', + 10, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 9e97e8bc4a1..b4d2f4de0b9 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -336,4 +336,19 @@ public function testBug7251(): void $this->analyse([__DIR__ . '/data/bug-7251.php'], []); } + public function testBug11802(): void + { + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-11802.php'], [ + [ + 'Property Bug11802\HelloWorld::$isFinal is never read, only written.', + 8, + $tip, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11802.php b/tests/PHPStan/Rules/DeadCode/data/bug-11802.php new file mode 100644 index 00000000000..a97152f385a --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11802.php @@ -0,0 +1,20 @@ += 8.0 + +namespace Bug11802; + +class HelloWorld +{ + public function __construct( + private bool $isFinal, + private bool $used + ) + { + } + + public function doFoo(HelloWorld $x, $y): void + { + if ($y !== 'isFinal') { + $s = $x->{$y}; + } + } +} diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php b/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php new file mode 100644 index 00000000000..68bc97f2ac5 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11802b.php @@ -0,0 +1,19 @@ += 8.0 + +namespace Bug11802b; + +class HelloWorld +{ + public function __construct( + ) {} + + private function doBar():void {} + + private function doFooBar():void {} + + public function doFoo(HelloWorld $x, $y): void { + if ($y !== 'doBar') { + $s = $x->$y(); + } + } +} From 3078f1a175d2245aab64f77c8bc5f9fe9cc884f5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 3 Oct 2024 10:20:22 +0200 Subject: [PATCH 0622/3097] curl_multi_getcontent() can return null --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 4ed11564454..33e19aee345 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1497,7 +1497,7 @@ 'curl_multi_close' => ['void', 'mh'=>'resource'], 'curl_multi_errno' => ['int', 'mh'=>'resource'], 'curl_multi_exec' => ['int', 'mh'=>'resource', '&w_still_running'=>'int'], -'curl_multi_getcontent' => ['string', 'ch'=>'resource'], +'curl_multi_getcontent' => ['string|null', 'ch'=>'resource'], 'curl_multi_info_read' => ['array|false', 'mh'=>'resource', '&w_msgs_in_queue='=>'int'], 'curl_multi_init' => ['resource'], 'curl_multi_remove_handle' => ['int', 'mh'=>'resource', 'ch'=>'resource'], From e53d7eed00cee7e651f0698e7e2003ee9a2b76d1 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 1 Oct 2024 18:38:32 +0200 Subject: [PATCH 0623/3097] Set normalized in BenevolentUnionType --- src/Type/BenevolentUnionType.php | 4 ++-- src/Type/TypeCombinator.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index dc1245ad45a..bb4a47761bc 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -14,9 +14,9 @@ class BenevolentUnionType extends UnionType * @api * @param Type[] $types */ - public function __construct(array $types) + public function __construct(array $types, bool $normalized = false) { - parent::__construct($types); + parent::__construct($types, $normalized); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 745c1c25276..1db0ce23a26 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -363,7 +363,7 @@ public static function union(Type ...$types): Type return $benevolentUnionObject->withTypes($types); } - return new BenevolentUnionType($types); + return new BenevolentUnionType($types, true); } } From d3bc3e3d0b7afc17efb4d5a1e5f5fde3887e22e8 Mon Sep 17 00:00:00 2001 From: Danny van der Sluijs Date: Sun, 6 Oct 2024 16:28:09 +0200 Subject: [PATCH 0624/3097] Update functionMap.php for SplFileInfo::getPathInfo return type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 33e19aee345..5ea25339854 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11556,7 +11556,7 @@ 'SplFileInfo::getMTime' => ['__benevolent'], 'SplFileInfo::getOwner' => ['__benevolent'], 'SplFileInfo::getPath' => ['string'], -'SplFileInfo::getPathInfo' => ['SplFileInfo', 'class_name='=>'string'], +'SplFileInfo::getPathInfo' => ['__benevolent', 'class_name='=>'string'], 'SplFileInfo::getPathname' => ['string'], 'SplFileInfo::getPerms' => ['__benevolent'], 'SplFileInfo::getRealPath' => ['__benevolent'], From a9ec51269d0181c38ea176d5d1d83ec62041808b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 6 Oct 2024 16:28:41 +0200 Subject: [PATCH 0625/3097] Fix a few regex class parsing usecases --- resources/RegexGrammar.pp | 19 ++- src/Type/Regex/RegexGroupParser.php | 5 +- .../Analyser/nsrt/preg_match_shapes.php | 121 ++++++++++++++++++ 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/resources/RegexGrammar.pp b/resources/RegexGrammar.pp index b8bea027d3f..ba174feb290 100644 --- a/resources/RegexGrammar.pp +++ b/resources/RegexGrammar.pp @@ -42,14 +42,16 @@ // // Character classes. +// tokens suffixed with "fc_" are the same as without such suffix but followed by "class:_class" +%token negative_class_fc_ \[\^(?=\]) -> class_fc +%token class_fc_ \[(?=\]) -> class_fc +%token class_fc:_class \] -> class %token negative_class_ \[\^ -> class %token class_ \[ -> class %token class:posix_class \[:\^?[a-z]+:\] %token class:class_ \[ -%token class:_class_literal (?<=[^\\]\[|[^\\]\[\^)\] %token class:_class \] -> default %token class:range \- -%token class:escaped_end_class \\\] // taken over from literals but class:character has \b support on top (backspace in character classes) %token class:character \\([aefnrtb]|c[\x00-\x7f]) %token class:dynamic_character \\([0-7]{3}|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}) @@ -58,7 +60,8 @@ // Internal options. // See https://www.regular-expressions.info/refmodifiers.html -%token internal_option \(\?([imsxnJUX^]|xx)?-?([imsxnJUX^]|xx)\) +// and https://www.php.net/manual/en/regexp.reference.internal-options.php +%token internal_option \(\?[imsxnJUX^]*-?[imsxnJUX^]+\) // Lookahead and lookbehind assertions. %token lookahead_ \(\?= @@ -88,7 +91,7 @@ %token nc:_named_capturing > -> default %token nc:capturing_name .+?(?=(?) %token non_capturing_ \(\?: -%token non_capturing_internal_option \(\?([imsxnJUX^]|xx)?-?([imsxnJUX^]|xx): +%token non_capturing_internal_option \(\?[imsxnJUX^]*-?[imsxnJUX^]+: %token non_capturing_reset_ \(\?\| %token atomic_group_ \(\?> %token capturing_ \( @@ -177,10 +180,14 @@ #class: ( - ::negative_class_:: #negativeclass + ::negative_class_fc_:: #negativeclass + <_class> + | ::class_fc_:: + <_class> + | ::negative_class_:: #negativeclass | ::class_:: ) - ( | <_class_literal> )? ( | | range() | literal() | )* ? + ? ( | | range() ? | literal() )* ? ::_class:: #range: diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 9780b2c69a9..ec944bb630e 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -525,11 +525,11 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap if ( in_array($token, [ - 'literal', 'escaped_end_class', + 'literal', // literal "-" in front/back of a character class like '[-a-z]' or '[abc-]', not forming a range 'range', // literal "[" or "]" inside character classes '[[]' or '[]]' - 'class_', '_class_literal', + 'class_', '_class', ], true) ) { if (str_contains($patternModifiers, 'x') && trim($value) === '') { @@ -544,7 +544,6 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap if ( $appendLiterals - && in_array($token, ['literal', 'range', 'class_', '_class_literal'], true) && $onlyLiterals !== null && (!in_array($value, ['.'], true) || $isEscaped || $inCharacterClass) ) { diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index f92af453fba..0a6b883e4cc 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -467,6 +467,9 @@ function bug11323(string $s): void { if (preg_match('{([-\p{L}[\]*|\x03\a\b+?{}(?:)-]+[^[:digit:]?{}a-z0-9#-k]+)(a-z)}', $s, $matches)) { assertType("array{string, non-falsy-string, 'a-z'}", $matches); } + if (preg_match('{(\d+)(?i)insensitive((?xs-i)case SENSITIVE here.+and dot matches new lines)}', $s, $matches)) { + assertType('array{string, numeric-string, non-falsy-string}', $matches); + } if (preg_match('{(\d+)(?i)insensitive((?x-i)case SENSITIVE here(?i:insensitive non-capturing group))}', $s, $matches)) { assertType('array{string, numeric-string, non-falsy-string}', $matches); } @@ -778,3 +781,121 @@ function testLtrimDelimiter (string $string): void { assertType("array{string, 'x'}", $matches); } } + +function testUnescapeBackslash (string $string): void { + if (preg_match(<<<'EOD' + ~(\[)~ + EOD, $string, $matches)) { + assertType("array{string, '['}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\d)~ + EOD, $string, $matches)) { + assertType("array{string, numeric-string}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\\d)~ + EOD, $string, $matches)) { + assertType("array{string, '\\\d'}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\\\d)~ + EOD, $string, $matches)) { + assertType("array{string, non-falsy-string}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\\\\d)~ + EOD, $string, $matches)) { + assertType("array{string, '\\\\\\\d'}", $matches); + } +} + +function testEscapedDelimiter (string $string): void { + if (preg_match(<<<'EOD' + /(\/)/ + EOD, $string, $matches)) { + assertType("array{string, '/'}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\~)~ + EOD, $string, $matches)) { + assertType("array{string, '~'}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\[2])~ + EOD, $string, $matches)) { + assertType("array{string, '[2]'}", $matches); + } + + if (preg_match(<<<'EOD' + [(\[2\])] + EOD, $string, $matches)) { + assertType("array{string, '[2]'}", $matches); + } + + if (preg_match(<<<'EOD' + ~(\{2})~ + EOD, $string, $matches)) { + assertType("array{string, '{2}'}", $matches); + } + + if (preg_match(<<<'EOD' + {(\{2\})} + EOD, $string, $matches)) { + assertType("array{string, '{2}'}", $matches); + } + + if (preg_match(<<<'EOD' + ~([a\]])~ + EOD, $string, $matches)) { + assertType("array{string, ']'|'a'}", $matches); + } + + if (preg_match(<<<'EOD' + ~([a[])~ + EOD, $string, $matches)) { + assertType("array{string, '['|'a'}", $matches); + } + + if (preg_match(<<<'EOD' + ~([a\]b])~ + EOD, $string, $matches)) { + assertType("array{string, ']'|'a'|'b'}", $matches); + } + + if (preg_match(<<<'EOD' + ~([a[b])~ + EOD, $string, $matches)) { + assertType("array{string, '['|'a'|'b'}", $matches); + } + + if (preg_match(<<<'EOD' + ~([a\[b])~ + EOD, $string, $matches)) { + assertType("array{string, '['|'a'|'b'}", $matches); + } + + if (preg_match(<<<'EOD' + [([a\[b])] + EOD, $string, $matches)) { + assertType("array{string, '['|'a'|'b'}", $matches); + } + + if (preg_match(<<<'EOD' + {(x\\\{)|(y\\\\\})} + EOD, $string, $matches)) { + assertType("array{string, '', 'y\\\\\\\}'}|array{string, 'x\\\{'}", $matches); + } +} + +function bugUnescapedDashAfterRange (string $string): void { + if (preg_match('/([0-1-y])/', $string, $matches)) { + assertType("array{string, non-empty-string}", $matches); + } +} From 83ba5972466d0b9bdfa4592996ccf381de625b1f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Oct 2024 16:29:22 +0200 Subject: [PATCH 0626/3097] Refactor RegexGroupParser for more immutability and less pass-by-ref --- src/Type/Regex/RegexAstWalkResult.php | 105 ++++++++++++++++++++++++++ src/Type/Regex/RegexGroupParser.php | 50 +++++------- 2 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 src/Type/Regex/RegexAstWalkResult.php diff --git a/src/Type/Regex/RegexAstWalkResult.php b/src/Type/Regex/RegexAstWalkResult.php new file mode 100644 index 00000000000..32e017a254f --- /dev/null +++ b/src/Type/Regex/RegexAstWalkResult.php @@ -0,0 +1,105 @@ + $capturingGroups + * @param list $markVerbs + */ + public function __construct( + private int $alternationId, + private int $captureGroupId, + private array $capturingGroups, + private array $markVerbs, + ) + { + } + + public static function createEmpty(): self + { + return new self( + -1, + // use different start-index for groups to make it easier to distinguish groupids from other ids + 100, + [], + [], + ); + } + + public function nextAlternationId(): self + { + return new self( + $this->alternationId + 1, + $this->captureGroupId, + $this->capturingGroups, + $this->markVerbs, + ); + } + + public function nextCaptureGroupId(): self + { + return new self( + $this->alternationId, + $this->captureGroupId + 1, + $this->capturingGroups, + $this->markVerbs, + ); + } + + public function addCapturingGroup(RegexCapturingGroup $group): self + { + $capturingGroups = $this->capturingGroups; + $capturingGroups[$group->getId()] = $group; + + return new self( + $this->alternationId, + $this->captureGroupId, + $capturingGroups, + $this->markVerbs, + ); + } + + public function markVerb(string $markVerb): self + { + $verbs = $this->markVerbs; + $verbs[] = $markVerb; + + return new self( + $this->alternationId, + $this->captureGroupId, + $this->capturingGroups, + $verbs, + ); + } + + public function getAlternationId(): int + { + return $this->alternationId; + } + + public function getCaptureGroupId(): int + { + return $this->captureGroupId; + } + + /** + * @return array + */ + public function getCapturingGroups(): array + { + return $this->capturingGroups; + } + + /** + * @return list + */ + public function getMarkVerbs(): array + { + return $this->markVerbs; + } + +} diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index ec944bb630e..0e61d50e9f3 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -73,51 +73,39 @@ public function parseGroups(string $regex): ?array $captureOnlyNamed = str_contains($modifiers, 'n'); } - $capturingGroups = []; - $alternationId = -1; - $captureGroupId = 100; - $markVerbs = []; - $this->walkRegexAst( + $astWalkResult = $this->walkRegexAst( $ast, null, - $alternationId, 0, false, null, - $captureGroupId, - $capturingGroups, - $markVerbs, $captureOnlyNamed, false, $modifiers, + RegexAstWalkResult::createEmpty(), ); - return [$capturingGroups, $markVerbs]; + return [$astWalkResult->getCapturingGroups(), $astWalkResult->getMarkVerbs()]; } - /** - * @param array $capturingGroups - * @param list $markVerbs - */ private function walkRegexAst( TreeNode $ast, ?RegexAlternation $alternation, - int &$alternationId, int $combinationIndex, bool $inOptionalQuantification, RegexCapturingGroup|RegexNonCapturingGroup|null $parentGroup, - int &$captureGroupId, - array &$capturingGroups, - array &$markVerbs, bool $captureOnlyNamed, bool $repeatedMoreThanOnce, string $patternModifiers, - ): void + RegexAstWalkResult $astWalkResult, + ): RegexAstWalkResult { $group = null; if ($ast->getId() === '#capturing') { + $astWalkResult = $astWalkResult->nextCaptureGroupId(); + $group = new RegexCapturingGroup( - $captureGroupId++, + $astWalkResult->getCaptureGroupId(), null, $alternation, $inOptionalQuantification, @@ -130,9 +118,11 @@ private function walkRegexAst( ); $parentGroup = $group; } elseif ($ast->getId() === '#namedcapturing') { + $astWalkResult = $astWalkResult->nextCaptureGroupId(); + $name = $ast->getChild(0)->getValueValue(); $group = new RegexCapturingGroup( - $captureGroupId++, + $astWalkResult->getCaptureGroupId(), $name, $alternation, $inOptionalQuantification, @@ -176,20 +166,19 @@ private function walkRegexAst( } if ($ast->getId() === '#alternation') { - $alternationId++; - $alternation = new RegexAlternation($alternationId, count($ast->getChildren())); + $astWalkResult = $astWalkResult->nextAlternationId(); + $alternation = new RegexAlternation($astWalkResult->getAlternationId(), count($ast->getChildren())); } if ($ast->getId() === '#mark') { - $markVerbs[] = $ast->getChild(0)->getValueValue(); - return; + return $astWalkResult->markVerb($ast->getChild(0)->getValueValue()); } if ( $group instanceof RegexCapturingGroup && (!$captureOnlyNamed || $group->isNamed()) ) { - $capturingGroups[$group->getId()] = $group; + $astWalkResult = $astWalkResult->addCapturingGroup($group); if ($alternation !== null) { $alternation->pushGroup($combinationIndex, $group); @@ -197,19 +186,16 @@ private function walkRegexAst( } foreach ($ast->getChildren() as $child) { - $this->walkRegexAst( + $astWalkResult = $this->walkRegexAst( $child, $alternation, - $alternationId, $combinationIndex, $inOptionalQuantification, $parentGroup, - $captureGroupId, - $capturingGroups, - $markVerbs, $captureOnlyNamed, $repeatedMoreThanOnce, $patternModifiers, + $astWalkResult, ); if ($ast->getId() !== '#alternation') { @@ -218,6 +204,8 @@ private function walkRegexAst( $combinationIndex++; } + + return $astWalkResult; } private function allowConstantTypes( From 328b6adf3b5f0d3986554d2c162d464f468094f7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 27 Sep 2024 23:22:43 +0200 Subject: [PATCH 0627/3097] Int::toString is lowercase --- src/Type/IntegerRangeType.php | 3 ++ src/Type/IntegerType.php | 2 + .../Analyser/nsrt/array-key-exists.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-10863.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-11129.php | 52 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-11716.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4587.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-7387.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-8568.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8635.php | 2 +- .../Analyser/nsrt/cast-to-numeric-string.php | 28 +++++----- tests/PHPStan/Analyser/nsrt/filter-var.php | 2 +- tests/PHPStan/Analyser/nsrt/generics.php | 6 +-- tests/PHPStan/Analyser/nsrt/key-exists.php | 8 +-- .../PHPStan/Analyser/nsrt/range-to-string.php | 2 +- .../nsrt/set-type-type-specifying.php | 2 +- tests/PHPStan/Analyser/nsrt/strval.php | 4 +- .../PHPStan/Rules/DeadCode/data/bug-8620.php | 2 +- .../PHPStan/Rules/Generics/data/bug-6301.php | 2 +- 19 files changed, 74 insertions(+), 69 deletions(-) diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 8a9414fbeed..3db816aecaa 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -474,6 +475,7 @@ public function toString(): Type if ($isZero->no()) { return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), new AccessoryNonFalsyStringType(), ]); @@ -481,6 +483,7 @@ public function toString(): Type return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index f91a646c9dd..354067f0a97 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -6,6 +6,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -81,6 +82,7 @@ public function toString(): Type { return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/tests/PHPStan/Analyser/nsrt/array-key-exists.php b/tests/PHPStan/Analyser/nsrt/array-key-exists.php index ed6f552d155..3ae615b2d9a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/array-key-exists.php @@ -50,16 +50,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (array_key_exists($key2, $a)) { - assertType('numeric-string', $key2); + assertType('lowercase-string&numeric-string', $key2); } if (array_key_exists($key3, $a)) { - assertType('int|numeric-string', $key3); + assertType('int|(lowercase-string&numeric-string)', $key3); } if (array_key_exists($key4, $a)) { - assertType('(int|numeric-string)', $key4); + assertType('(int|(lowercase-string&numeric-string))', $key4); } if (array_key_exists($key5, $a)) { - assertType('int|numeric-string', $key5); + assertType('int|(lowercase-string&numeric-string)', $key5); } if (array_key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10863.php b/tests/PHPStan/Analyser/nsrt/bug-10863.php index ecad48736ec..275bc3774e8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10863.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10863.php @@ -12,7 +12,7 @@ class Foo */ public function doFoo($b): void { - assertType('non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string', '@' . $b); } /** @@ -20,7 +20,7 @@ public function doFoo($b): void */ public function doFoo2($b): void { - assertType('non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string', '@' . $b); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11129.php b/tests/PHPStan/Analyser/nsrt/bug-11129.php index 8637ad7c4a1..1f60b4089fb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11129.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11129.php @@ -21,14 +21,14 @@ public function foo( $maybeNegativeConstStrings, $maybeNonNumericConstStrings, $maybeFloatConstStrings, bool $bool, float $float ): void { - assertType('non-falsy-string', '0'.$i); - assertType('non-falsy-string&numeric-string', $i.'0'); + assertType('lowercase-string&non-falsy-string', '0'.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.'0'); - assertType('non-falsy-string&numeric-string', '0'.$positiveInt); - assertType('non-falsy-string&numeric-string', $positiveInt.'0'); + assertType('lowercase-string&non-falsy-string&numeric-string', '0'.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.'0'); - assertType('non-falsy-string', '0'.$negativeInt); - assertType('non-falsy-string&numeric-string', $negativeInt.'0'); + assertType('lowercase-string&non-falsy-string', '0'.$negativeInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.'0'); assertType("'00'|'01'|'02'", '0'.$positiveConstStrings); assertType( "'00'|'10'|'20'", $positiveConstStrings.'0'); @@ -39,29 +39,29 @@ public function foo( assertType("'00'|'01'|'0a'", '0'.$maybeNonNumericConstStrings); assertType("'00'|'10'|'a0'", $maybeNonNumericConstStrings.'0'); - assertType('non-falsy-string&numeric-string', $i.$positiveConstStrings); - assertType( 'non-falsy-string', $positiveConstStrings.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.$positiveConstStrings); + assertType('lowercase-string&non-falsy-string', $positiveConstStrings.$i); - assertType('non-falsy-string', $i.$maybeNegativeConstStrings); - assertType('non-falsy-string', $maybeNegativeConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$i); - assertType('non-falsy-string', $i.$maybeNonNumericConstStrings); - assertType('non-falsy-string', $maybeNonNumericConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeNonNumericConstStrings); + assertType('lowercase-string&non-falsy-string', $maybeNonNumericConstStrings.$i); - assertType('non-falsy-string', $i.$maybeFloatConstStrings); // could be 'non-falsy-string&numeric-string' - assertType('non-falsy-string', $maybeFloatConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' + assertType('lowercase-string&non-falsy-string', $maybeFloatConstStrings.$i); - assertType('non-empty-string&numeric-string', $i.$bool); - assertType('non-empty-string', $bool.$i); - assertType('non-falsy-string&numeric-string', $positiveInt.$bool); - assertType('non-falsy-string&numeric-string', $bool.$positiveInt); - assertType('non-falsy-string&numeric-string', $negativeInt.$bool); - assertType('non-falsy-string', $bool.$negativeInt); + assertType('lowercase-string&non-empty-string&numeric-string', $i.$bool); + assertType('lowercase-string&non-empty-string', $bool.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.$bool); + assertType('lowercase-string&non-falsy-string&numeric-string', $bool.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.$bool); + assertType('lowercase-string&non-falsy-string', $bool.$negativeInt); - assertType('non-falsy-string', $i.$i); - assertType('non-falsy-string', $negativeInt.$negativeInt); - assertType('non-falsy-string', $maybeNegativeConstStrings.$negativeInt); - assertType('non-falsy-string', $negativeInt.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string', $i.$i); + assertType('lowercase-string&non-falsy-string', $negativeInt.$negativeInt); + assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$negativeInt); + assertType('lowercase-string&non-falsy-string', $negativeInt.$maybeNegativeConstStrings); // https://3v4l.org/BCS2K assertType('non-falsy-string', $float.$float); @@ -75,9 +75,9 @@ public function foo( // https://3v4l.org/Ia4r0 $scientificFloatAsString = '3e4'; assertType('non-falsy-string', $numericString.$scientificFloatAsString); - assertType('non-falsy-string', $i.$scientificFloatAsString); + assertType('lowercase-string&non-falsy-string', $i.$scientificFloatAsString); assertType('non-falsy-string', $scientificFloatAsString.$numericString); - assertType('non-falsy-string', $scientificFloatAsString.$i); + assertType('lowercase-string&non-falsy-string', $scientificFloatAsString.$i); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index e637669483c..83c10f32258 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -75,7 +75,7 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyed assertType('int', $i); if (isset($intKeyedArr[$s])) { - assertType("numeric-string", $s); + assertType("lowercase-string&numeric-string", $s); } else { assertType('string', $s); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4587.php b/tests/PHPStan/Analyser/nsrt/bug-4587.php index 644b9d7b797..623fea93af1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4587.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4587.php @@ -27,11 +27,11 @@ public function b(): void $type = array_map(static function (array $result): array { assertType('array{a: int}', $result); $result['a'] = (string) $result['a']; - assertType('array{a: numeric-string}', $result); + assertType('array{a: lowercase-string&numeric-string}', $result); return $result; }, $results); - assertType('list', $type); + assertType('list', $type); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index cfb1a97642e..814513ac975 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -29,7 +29,7 @@ public function inputTypes(int $i, float $f, string $s, int $intRange) { public function specifiers(int $i) { // https://3v4l.org/fmVIg - assertType('numeric-string', sprintf('%14s', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14s', $i)); assertType('numeric-string', sprintf('%d', $i)); @@ -59,9 +59,9 @@ public function specifiers(int $i) { */ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) { // https://3v4l.org/vVL0c - assertType('numeric-string', sprintf('%2$6s', $mixed, $i)); - assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); - assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); + assertType('lowercase-string&numeric-string', sprintf('%2$6s', $mixed, $i)); + assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); + assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange)); // https://3v4l.org/1ECIq diff --git a/tests/PHPStan/Analyser/nsrt/bug-8568.php b/tests/PHPStan/Analyser/nsrt/bug-8568.php index 71db7a6c98a..9236447acf6 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8568.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8568.php @@ -8,7 +8,7 @@ class HelloWorld { public function sayHello(): void { - assertType('non-falsy-string', 'a' . $this->get()); + assertType('lowercase-string&non-falsy-string', 'a' . $this->get()); } public function get(): ?int diff --git a/tests/PHPStan/Analyser/nsrt/bug-8635.php b/tests/PHPStan/Analyser/nsrt/bug-8635.php index 18952545792..fe49aa8a2db 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8635.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8635.php @@ -8,6 +8,6 @@ class HelloWorld { public function EchoInt(int $value): void { - assertType('numeric-string', "$value"); + assertType('lowercase-string&numeric-string', "$value"); } } diff --git a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php index c7d6fcb84c1..7c13500af25 100644 --- a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php @@ -13,13 +13,13 @@ * @param 1 $constantInt */ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('numeric-string', (string)$a); + assertType('lowercase-string&numeric-string', (string)$a); assertType('numeric-string', (string)$b); assertType('numeric-string', (string)$numeric); assertType('numeric-string', (string)$numeric2); assertType('numeric-string', (string)$number); - assertType('non-falsy-string&numeric-string', (string)$positive); - assertType('non-falsy-string&numeric-string', (string)$negative); + assertType('lowercase-string&non-falsy-string&numeric-string', (string)$positive); + assertType('lowercase-string&non-falsy-string&numeric-string', (string)$negative); assertType("'1'", (string)$constantInt); } @@ -32,28 +32,28 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param 1 $constantInt */ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('numeric-string', '' . $a); + assertType('lowercase-string&numeric-string', '' . $a); assertType('numeric-string', '' . $b); assertType('numeric-string', '' . $numeric); assertType('numeric-string', '' . $numeric2); assertType('numeric-string', '' . $number); - assertType('non-falsy-string&numeric-string', '' . $positive); - assertType('non-falsy-string&numeric-string', '' . $negative); + assertType('lowercase-string&non-falsy-string&numeric-string', '' . $positive); + assertType('lowercase-string&non-falsy-string&numeric-string', '' . $negative); assertType("'1'", '' . $constantInt); - assertType('numeric-string', $a . ''); + assertType('lowercase-string&numeric-string', $a . ''); assertType('numeric-string', $b . ''); assertType('numeric-string', $numeric . ''); assertType('numeric-string', $numeric2 . ''); assertType('numeric-string', $number . ''); - assertType('non-falsy-string&numeric-string', $positive . ''); - assertType('non-falsy-string&numeric-string', $negative . ''); + assertType('lowercase-string&non-falsy-string&numeric-string', $positive . ''); + assertType('lowercase-string&non-falsy-string&numeric-string', $negative . ''); assertType("'1'", $constantInt . ''); } function concatAssignEmptyString(int $i, float $f) { $i .= ''; - assertType('numeric-string', $i); + assertType('lowercase-string&numeric-string', $i); $s = ''; $s .= $f; @@ -66,13 +66,13 @@ function concatAssignEmptyString(int $i, float $f) { */ function integerRangeToString($positive, $negative) { - assertType('numeric-string', (string) $positive); - assertType('numeric-string', (string) $negative); + assertType('lowercase-string&numeric-string', (string) $positive); + assertType('lowercase-string&numeric-string', (string) $negative); if ($positive !== 0) { - assertType('non-falsy-string&numeric-string', (string) $positive); + assertType('lowercase-string&non-falsy-string&numeric-string', (string) $positive); } if ($negative !== 0) { - assertType('non-falsy-string&numeric-string', (string) $negative); + assertType('lowercase-string&non-falsy-string&numeric-string', (string) $negative); } } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index e726c77d75c..28580c448c4 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -158,7 +158,7 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType("'17'", filter_var(17.0)); assertType("'17.1'", filter_var(17.1)); assertType("'1.0E-50'", filter_var(1e-50)); - assertType('numeric-string', filter_var($int)); + assertType('lowercase-string&numeric-string', filter_var($int)); assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index bcd7ecf6160..873ad66b6d5 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -147,10 +147,10 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('Closure(int): numeric-string', function (int $a): string { + assertType('Closure(int): (lowercase-string&numeric-string)', function (int $a): string { return (string)$a; }); - assertType('array', f($arrayOfInt, function (int $a): string { + assertType('array', f($arrayOfInt, function (int $a): string { return (string)$a; })); assertType('Closure(mixed): string', function ($a): string { @@ -224,7 +224,7 @@ function testArrayMap(array $listOfIntegers) return (string) $int; }, $listOfIntegers); - assertType('array', $strings); + assertType('array', $strings); } /** diff --git a/tests/PHPStan/Analyser/nsrt/key-exists.php b/tests/PHPStan/Analyser/nsrt/key-exists.php index 11c2ed6a2a3..3c85802e739 100644 --- a/tests/PHPStan/Analyser/nsrt/key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/key-exists.php @@ -49,16 +49,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (key_exists($key2, $a)) { - assertType('numeric-string', $key2); + assertType('lowercase-string&numeric-string', $key2); } if (key_exists($key3, $a)) { - assertType('int|numeric-string', $key3); + assertType('int|(lowercase-string&numeric-string)', $key3); } if (key_exists($key4, $a)) { - assertType('(int|numeric-string)', $key4); + assertType('(int|(lowercase-string&numeric-string))', $key4); } if (key_exists($key5, $a)) { - assertType('int|numeric-string', $key5); + assertType('int|(lowercase-string&numeric-string)', $key5); } if (key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/range-to-string.php b/tests/PHPStan/Analyser/nsrt/range-to-string.php index 494c135d95b..2d70eb4babc 100644 --- a/tests/PHPStan/Analyser/nsrt/range-to-string.php +++ b/tests/PHPStan/Analyser/nsrt/range-to-string.php @@ -17,6 +17,6 @@ public function sayHello($i, $ii, $maxlong, $toolong): void assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i); assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii); assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong); - assertType("numeric-string", (string) $toolong); + assertType("lowercase-string&numeric-string", (string) $toolong); } } diff --git a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php index 11869c0623f..1ab4badbe56 100644 --- a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php @@ -11,7 +11,7 @@ function doString(string $s, int $i, float $f, array $a, object $o) assertType('string', $s); settype($i, 'string'); - assertType('numeric-string', $i); + assertType('lowercase-string&numeric-string', $i); settype($f, 'string'); assertType('numeric-string', $f); diff --git a/tests/PHPStan/Analyser/nsrt/strval.php b/tests/PHPStan/Analyser/nsrt/strval.php index 870aafd6b8b..a6c43978936 100644 --- a/tests/PHPStan/Analyser/nsrt/strval.php +++ b/tests/PHPStan/Analyser/nsrt/strval.php @@ -17,9 +17,9 @@ function strvalTest(string $string, string $class): void assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); assertType('\'42\'', strval(42)); - assertType('numeric-string', strval(rand())); + assertType('lowercase-string&numeric-string', strval(rand())); assertType('numeric-string', strval(rand() * 0.1)); - assertType('numeric-string', strval(strval(rand()))); + assertType('lowercase-string&numeric-string', strval(strval(rand()))); assertType('class-string', strval($class)); assertType('string', strval(new \Exception())); assertType('*ERROR*', strval(new \stdClass())); diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php index cad6d811c29..44bc78cd456 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php @@ -9,7 +9,7 @@ class HelloWorld public function nullCoalesceAndConcatenation (?int $a = null): int { $key = ($a ?? "x") . "-"; - assertType('non-falsy-string', $key); + assertType('lowercase-string&non-falsy-string', $key); if ($key === "x-") { return 0; } return 1; diff --git a/tests/PHPStan/Rules/Generics/data/bug-6301.php b/tests/PHPStan/Rules/Generics/data/bug-6301.php index 9811b632531..73dff935f20 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-6301.php +++ b/tests/PHPStan/Rules/Generics/data/bug-6301.php @@ -22,7 +22,7 @@ public function str($s) * @param literal-string $literalString */ public function foo(int $i, $nonEmpty, $numericString, $literalString):void { - assertType('numeric-string', $this->str((string) $i)); + assertType('lowercase-string&numeric-string', $this->str((string) $i)); assertType('non-empty-string', $this->str($nonEmpty)); assertType('numeric-string', $this->str($numericString)); assertType('literal-string', $this->str($literalString)); From 5ac7a1c71d2aa283e4295a42845fedafe6100896 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Sun, 6 Oct 2024 16:33:25 +0200 Subject: [PATCH 0628/3097] Update return type of `spl_autoload_functions` on PHP8.0+ --- build/baseline-8.0.neon | 2 +- build/spl-autoload-functions-php-8.neon | 2 +- build/spl-autoload-functions-pre-php-7.neon | 5 ----- resources/functionMap_php80delta.php | 1 + src/Command/CommandHelper.php | 4 ++-- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index 3e0d07184d1..95dfa6cf8af 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -1,7 +1,7 @@ parameters: ignoreErrors: - - message: "#^Strict comparison using \\=\\=\\= between array and false will always evaluate to false\\.$#" + message: "#^Strict comparison using \\=\\=\\= between list and false will always evaluate to false\\.$#" count: 1 path: ../src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php diff --git a/build/spl-autoload-functions-php-8.neon b/build/spl-autoload-functions-php-8.neon index b534d6c94f2..36693218894 100644 --- a/build/spl-autoload-functions-php-8.neon +++ b/build/spl-autoload-functions-php-8.neon @@ -1,6 +1,6 @@ parameters: ignoreErrors: - - message: "#^PHPDoc tag @var with type array\\\\|false is not subtype of native type array\\.$#" + message: "#^PHPDoc tag @var with type list\\\\|false is not subtype of native type list\\\\.$#" count: 2 path: ../src/Command/CommandHelper.php diff --git a/build/spl-autoload-functions-pre-php-7.neon b/build/spl-autoload-functions-pre-php-7.neon index abafcfbf083..42cd820e71d 100644 --- a/build/spl-autoload-functions-pre-php-7.neon +++ b/build/spl-autoload-functions-pre-php-7.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^PHPDoc tag @var with type array\\\\|false is not subtype of native type list\\\\|false\\.$#" - count: 2 - path: ../src/Command/CommandHelper.php - - message: '#^Parameter \#1 \$array \(list\) of array_values is already a list, call has no effect\.$#' path: ../src/Type/TypeCombinator.php diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 1b234d2280d..11c65e5258d 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -98,6 +98,7 @@ 'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], 'socket_select' => ['int|false', '&w_read'=>'Socket[]|null', '&w_write'=>'Socket[]|null', '&w_except'=>'Socket[]|null', 'seconds'=>'int|null', 'microseconds='=>'int'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], + 'spl_autoload_functions' => ['list'], 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'str_split' => ['non-empty-list', 'str'=>'string', 'split_length='=>'positive-int'], 'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 55926064a2a..b0219c9fac6 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -164,7 +164,7 @@ public static function begin( $currentWorkingDirectoryFileHelper = new FileHelper($currentWorkingDirectory); $currentWorkingDirectory = $currentWorkingDirectoryFileHelper->getWorkingDirectory(); - /** @var array|false $autoloadFunctionsBefore */ + /** @var list|false $autoloadFunctionsBefore */ $autoloadFunctionsBefore = spl_autoload_functions(); if ($autoloadFile !== null) { @@ -459,7 +459,7 @@ public static function begin( self::executeBootstrapFile($bootstrapFileFromArray, $container, $errorOutput, $debugEnabled); } - /** @var array|false $autoloadFunctionsAfter */ + /** @var list|false $autoloadFunctionsAfter */ $autoloadFunctionsAfter = spl_autoload_functions(); if ($autoloadFunctionsBefore !== false && $autoloadFunctionsAfter !== false) { From 133c60e766fd8874254e1889c01cc474f8558d4d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 6 Oct 2024 16:35:18 +0200 Subject: [PATCH 0629/3097] Handle lowercase string in sprintf --- ...intfFunctionDynamicReturnTypeExtension.php | 46 +++++-- tests/PHPStan/Analyser/nsrt/bug-11201.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7387.php | 6 +- .../PHPStan/Analyser/nsrt/dynamic-sprintf.php | 2 +- .../nsrt/lowercase-string-sprintf.php | 116 ++++++++++++++++++ 5 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/lowercase-string-sprintf.php diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 7bb7e2da26f..dc30f2afdd7 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -8,9 +8,11 @@ use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -60,6 +62,13 @@ public function getTypeFromFunctionCall( $formatType = $scope->getType($args[0]->value); $formatStrings = $formatType->getConstantStrings(); + $isLowercase = $formatType->isLowercaseString()->yes() && $this->allValuesSatisfies( + $functionReflection, + $scope, + $args, + static fn (Type $type): bool => $type->toString()->isLowercaseString()->yes() + ); + $singlePlaceholderEarlyReturn = null; $allPatternsNonEmpty = count($formatStrings) !== 0; $allPatternsNonFalsy = count($formatStrings) !== 0; @@ -130,10 +139,10 @@ public function getTypeFromFunctionCall( $singlePlaceholderEarlyReturn = $checkArgType->toString(); } elseif ($matches['specifier'] !== 's') { - $singlePlaceholderEarlyReturn = new IntersectionType([ - new StringType(), + $singlePlaceholderEarlyReturn = $this->getStringReturnType( new AccessoryNumericStringType(), - ]); + $isLowercase, + ); } continue; @@ -148,10 +157,7 @@ public function getTypeFromFunctionCall( } if ($allPatternsNonFalsy) { - return new IntersectionType([ - new StringType(), - new AccessoryNonFalsyStringType(), - ]); + return $this->getStringReturnType(new AccessoryNonFalsyStringType(), $isLowercase); } $isNonEmpty = $allPatternsNonEmpty; @@ -165,13 +171,10 @@ public function getTypeFromFunctionCall( } if ($isNonEmpty) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + return $this->getStringReturnType(new AccessoryNonEmptyStringType(), $isLowercase); } - return new StringType(); + return $this->getStringReturnType(null, $isLowercase); } /** @@ -347,4 +350,23 @@ private function getConstantType(array $args, FunctionReflection $functionReflec return TypeCombinator::union(...$returnTypes); } + private function getStringReturnType(?AccessoryType $accessoryType, bool $isLowercase): Type + { + $accessoryTypes = []; + if ($accessoryType !== null) { + $accessoryTypes[] = $accessoryType; + } + if ($isLowercase) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } + + if (count($accessoryTypes) === 0) { + return new StringType(); + } + + $accessoryTypes[] = new StringType(); + + return new IntersectionType($accessoryTypes); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11201.php b/tests/PHPStan/Analyser/nsrt/bug-11201.php index 74a41fa2355..202e5b1700a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11201.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11201.php @@ -53,4 +53,4 @@ function returnsBool(): bool { assertType("' 1'", $s); $s = sprintf('%20s', returnsBool()); -assertType("non-falsy-string", $s); +assertType("lowercase-string&non-falsy-string", $s); diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index 814513ac975..ea27c480042 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -107,11 +107,11 @@ public function escapedPercent(int $i) { public function vsprintf(array $array) { - assertType('numeric-string', vsprintf("%4d", explode('-', '1988-8-1'))); + assertType('lowercase-string&numeric-string', vsprintf("%4d", explode('-', '1988-8-1'))); assertType('numeric-string', vsprintf("%4d", $array)); - assertType('numeric-string', vsprintf("%4d", ['123'])); + assertType('lowercase-string&numeric-string', vsprintf("%4d", ['123'])); assertType('\'123\'', vsprintf("%s", ['123'])); // too many arguments.. php silently allows it - assertType('numeric-string', vsprintf("%4d", ['123', '456'])); + assertType('lowercase-string&numeric-string', vsprintf("%4d", ['123', '456'])); } } diff --git a/tests/PHPStan/Analyser/nsrt/dynamic-sprintf.php b/tests/PHPStan/Analyser/nsrt/dynamic-sprintf.php index ec25be47cd3..3555613fe04 100644 --- a/tests/PHPStan/Analyser/nsrt/dynamic-sprintf.php +++ b/tests/PHPStan/Analyser/nsrt/dynamic-sprintf.php @@ -33,7 +33,7 @@ public function integerRange(int $a, string $b): void */ public function tooBigRange(int $a, string $b): void { - assertType("non-falsy-string", sprintf('%d %s', $a, $b)); + assertType("lowercase-string&non-falsy-string", sprintf('%d %s', $a, $b)); } } diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-sprintf.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-sprintf.php new file mode 100644 index 00000000000..db7127a15c6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/lowercase-string-sprintf.php @@ -0,0 +1,116 @@ + Date: Sun, 6 Oct 2024 16:37:41 +0200 Subject: [PATCH 0630/3097] Fix preg_match_all with PREG_SET_ORDER does not see capture group as optional --- src/Type/Php/RegexArrayShapeMatcher.php | 15 ++++++++++++++- .../Analyser/nsrt/preg_match_all_shapes.php | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 63e44b3ea26..64bdf2d6055 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -417,7 +417,20 @@ private function isGroupOptional(RegexCapturingGroup $captureGroup, TrinaryLogic private function createGroupValueType(RegexCapturingGroup $captureGroup, TrinaryLogic $wasMatched, int $flags, bool $isTrailingOptional, bool $isLastGroup, bool $matchesAll): Type { if ($matchesAll) { - if (!$this->containsSetOrder($flags) && !$this->containsUnmatchedAsNull($flags, $matchesAll) && $captureGroup->isOptional()) { + if ( + ( + !$this->containsSetOrder($flags) + && !$this->containsUnmatchedAsNull($flags, $matchesAll) + && $captureGroup->isOptional() + ) + || + ( + $this->containsSetOrder($flags) + && !$this->containsUnmatchedAsNull($flags, $matchesAll) + && $captureGroup->isOptional() + && !$isTrailingOptional + ) + ) { $groupValueType = $this->getValueType( TypeCombinator::union($captureGroup->getType(), new ConstantStringType('')), $flags, diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php index c415fca42f3..7ed783a8e97 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php @@ -175,3 +175,12 @@ function doFoobarNull(string $s): void { } } } + +function bug11661(): void { + preg_match_all('/(ERR)?(.+)/', 'abc', $results, PREG_SET_ORDER); + assertType("list", $results); + + preg_match_all('/(ERR)?.+/', 'abc', $results, PREG_SET_ORDER); + assertType("list", $results); + +} From 70448ad1c9a25c57d0ad9e57141a1a6ac4788437 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 6 Oct 2024 16:39:58 +0200 Subject: [PATCH 0631/3097] Fix test --- tests/PHPStan/Analyser/nsrt/bug-7387.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index ea27c480042..4f28696fb99 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -31,22 +31,22 @@ public function specifiers(int $i) { // https://3v4l.org/fmVIg assertType('lowercase-string&numeric-string', sprintf('%14s', $i)); - assertType('numeric-string', sprintf('%d', $i)); + assertType('lowercase-string&numeric-string', sprintf('%d', $i)); - assertType('numeric-string', sprintf('%14b', $i)); - assertType('non-falsy-string', sprintf('%14c', $i)); // binary string - assertType('numeric-string', sprintf('%14d', $i)); - assertType('numeric-string', sprintf('%14e', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14b', $i)); + assertType('lowercase-string&non-falsy-string', sprintf('%14c', $i)); // binary string + assertType('lowercase-string&numeric-string', sprintf('%14d', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14e', $i)); assertType('numeric-string', sprintf('%14E', $i)); - assertType('numeric-string', sprintf('%14f', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14f', $i)); assertType('numeric-string', sprintf('%14F', $i)); - assertType('numeric-string', sprintf('%14g', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14g', $i)); assertType('numeric-string', sprintf('%14G', $i)); - assertType('numeric-string', sprintf('%14h', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14h', $i)); assertType('numeric-string', sprintf('%14H', $i)); - assertType('numeric-string', sprintf('%14o', $i)); - assertType('numeric-string', sprintf('%14u', $i)); - assertType('numeric-string', sprintf('%14x', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14o', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14u', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14x', $i)); assertType('numeric-string', sprintf('%14X', $i)); } @@ -102,7 +102,7 @@ public function invalidPositionalArgFormat($mixed, string $s) { public function escapedPercent(int $i) { // https://3v4l.org/2m50L - assertType('non-falsy-string', sprintf("%%d", $i)); + assertType('lowercase-string&non-falsy-string', sprintf("%%d", $i)); } public function vsprintf(array $array) From 7717291e9e6b7783825c01aaa7b2330fa03979e3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 6 Oct 2024 16:45:43 +0200 Subject: [PATCH 0632/3097] [BCB] Remove `ConstantArrayType::slice()` --- UPGRADING.md | 1 + phpstan-baseline.neon | 2 +- src/Type/Constant/ConstantArrayType.php | 29 +------------------------ 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 57adbd10121..6cfb483e2af 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -289,6 +289,7 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead * Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead * Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead +* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead * Made `TypeUtils` thinner by removing methods: * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 31b15792edf..246118a8d5e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -882,7 +882,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 9 + count: 7 path: src/Type/Constant/ConstantArrayType.php - diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 8fa3cd05ff2..ab0e41edc3c 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -30,7 +30,6 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\ErrorType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\TemplateTypeMap; @@ -39,10 +38,10 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Traits\ArrayTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; -use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; @@ -1183,32 +1182,6 @@ private function removeFirstElements(int $length, bool $reindex = true): self return $array; } - /** @deprecated Use sliceArray() instead */ - public function slice(int $offset, ?int $limit, bool $preserveKeys = false): self - { - $array = $this->sliceArray( - ConstantTypeHelper::getTypeFromValue($offset), - ConstantTypeHelper::getTypeFromValue($limit), - TrinaryLogic::createFromBoolean($preserveKeys), - ); - if (!$array instanceof self) { - throw new ShouldNotHappenException(); - } - - return $array; - } - - /** @deprecated Use reverseArray() instead */ - public function reverse(bool $preserveKeys = false): self - { - $array = $this->reverseArray(TrinaryLogic::createFromBoolean($preserveKeys)); - if (!$array instanceof self) { - throw new ShouldNotHappenException(); - } - - return $array; - } - private function reindex(): self { $keyTypes = []; From eb6a95a9230d9c40e738c92edfef4f89e56678d2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 6 Oct 2024 16:50:46 +0200 Subject: [PATCH 0633/3097] =?UTF-8?q?Fix=20false-positive=20with=20preg=5F?= =?UTF-8?q?match():=20Strict=20comparison=20using=20=3D=3D=3D=20between=20?= =?UTF-8?q?''=20and=20non-falsy-string=E2=80=A6ween=20''=20and=20non-falsy?= =?UTF-8?q?-string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Type/Regex/RegexGroupParser.php | 16 +++++++------- .../Analyser/nsrt/preg_match_shapes.php | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 0e61d50e9f3..dba7fcfe4e6 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -367,6 +367,7 @@ private function walkGroupAst( if ( $ast->getId() === '#concatenation' && count($children) > 0 + && !$walkResult->isInOptionalQuantification() ) { $meaningfulTokens = 0; foreach ($children as $child) { @@ -400,13 +401,14 @@ private function walkGroupAst( if ($min === 0) { $walkResult = $walkResult->inOptionalQuantification(true); } - if ($min >= 1) { - $walkResult = $walkResult - ->nonEmpty(TrinaryLogic::createYes()) - ->inOptionalQuantification(false); - } - if ($min >= 2 && !$inAlternation) { - $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); + + if (!$walkResult->isInOptionalQuantification()) { + if ($min >= 1) { + $walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes()); + } + if ($min >= 2 && !$inAlternation) { + $walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes()); + } } $walkResult = $walkResult->onlyLiterals(null); diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 0a6b883e4cc..701dc1f049a 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -894,8 +894,27 @@ function testEscapedDelimiter (string $string): void { } } -function bugUnescapedDashAfterRange (string $string): void { +function bugUnescapedDashAfterRange (string $string): void +{ if (preg_match('/([0-1-y])/', $string, $matches)) { assertType("array{string, non-empty-string}", $matches); } } + +function bug11744(string $string): void +{ + if (!preg_match('~^((/[a-z]+)?)~', $string, $matches)) { + return; + } + assertType('array{0: string, 1: string, 2?: non-falsy-string}', $matches); + + if (!preg_match('~^((/[a-z]+)?.*)~', $string, $matches)) { + return; + } + assertType('array{0: string, 1: string, 2?: non-falsy-string}', $matches); + + if (!preg_match('~^((/[a-z]+)?.+)~', $string, $matches)) { + return; + } + assertType('array{0: string, 1: non-empty-string, 2?: non-falsy-string}', $matches); +} From 5b43d5004648ded12d5a635db9bb82c56efc2c11 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 08:53:57 +0200 Subject: [PATCH 0634/3097] Fix typo --- src/Type/Php/FilterFunctionReturnTypeHelper.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index 17ba5ade7b8..cebc1577596 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -397,13 +397,13 @@ private function getOptions(Type $flagsType, int $filterValue): array $optionNames = array_merge(['default'], $this->getFilterTypeOptions()[$filterValue] ?? []); foreach ($optionNames as $optionName) { - $optionaNameType = new ConstantStringType($optionName); - if (!$optionsType->hasOffsetValueType($optionaNameType)->yes()) { + $optionalNameType = new ConstantStringType($optionName); + if (!$optionsType->hasOffsetValueType($optionalNameType)->yes()) { $options[$optionName] = null; continue; } - $options[$optionName] = $optionsType->getOffsetValueType($optionaNameType); + $options[$optionName] = $optionsType->getOffsetValueType($optionalNameType); } return $options; From e82354030f842ce12679956410f4762ae6b6b407 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 01:25:35 +0000 Subject: [PATCH 0635/3097] chore(deps): update crate-ci/typos action to v1.25.0 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 10b814e08a0..4fdafdcbd92 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.24.6" + uses: "crate-ci/typos@v1.25.0" with: files: "README.md src/" From bb19be5bf0878d17d302a89cdb4e749c764a65d5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 3 Oct 2024 20:52:40 +0200 Subject: [PATCH 0636/3097] Remove $isFinal dead-code in PhpFunctionReflection --- src/Reflection/BetterReflection/BetterReflectionProvider.php | 3 --- src/Reflection/FunctionReflectionFactory.php | 1 - src/Reflection/Php/PhpFunctionReflection.php | 1 - 3 files changed, 5 deletions(-) diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 7542e3ccdf6..72f918f62bc 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -304,7 +304,6 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $deprecatedTag = null; $isDeprecated = false; $isInternal = false; - $isFinal = false; $isPure = null; $asserts = Assertions::createEmpty(); $acceptsNamedArguments = true; @@ -327,7 +326,6 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); $isDeprecated = $resolvedPhpDoc->isDeprecated(); $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); $isPure = $resolvedPhpDoc->isPure(); $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); if ($resolvedPhpDoc->hasPhpDocString()) { @@ -348,7 +346,6 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, $isDeprecated, $isInternal, - $isFinal, $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null, $isPure, $asserts, diff --git a/src/Reflection/FunctionReflectionFactory.php b/src/Reflection/FunctionReflectionFactory.php index 4333954ff32..405eea46b11 100644 --- a/src/Reflection/FunctionReflectionFactory.php +++ b/src/Reflection/FunctionReflectionFactory.php @@ -25,7 +25,6 @@ public function create( ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal, - bool $isFinal, ?string $filename, ?bool $isPure, Assertions $asserts, diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 6209323f772..e28f19f8792 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -54,7 +54,6 @@ public function __construct( private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, - private bool $isFinal, private ?string $filename, private ?bool $isPure, private Assertions $asserts, From 544101bd8370ca434eb26b925e2a26f639491ac2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 08:59:17 +0200 Subject: [PATCH 0637/3097] Fix stubs --- stubs/ReflectionClass.stub | 6 ++++++ stubs/ext-ds.stub | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/stubs/ReflectionClass.stub b/stubs/ReflectionClass.stub index 3f6ce4bddfd..889ab78f1b6 100644 --- a/stubs/ReflectionClass.stub +++ b/stubs/ReflectionClass.stub @@ -6,6 +6,12 @@ class ReflectionClass { + /** + * @readonly + * @var class-string + */ + public $name; + /** * @param T|class-string $argument * @throws ReflectionException diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index f0b45a47b75..e05628d9685 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -348,11 +348,21 @@ final class Map implements Collection, ArrayAccess } /** - * @template-covariant TKey - * @template-covariant TValue + * @template TKey + * @template TValue */ final class Pair implements JsonSerializable { + /** + * @var TKey + */ + public $key; + + /** + * @var TValue + */ + public $value; + /** * @param TKey $key * @param TValue $value From 5639793808d535aff4c2c34fccc4e204ee4e8ea5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 09:34:19 +0200 Subject: [PATCH 0638/3097] Revert "Int::toString is lowercase" This reverts commit 328b6adf3b5f0d3986554d2c162d464f468094f7. --- src/Type/IntegerRangeType.php | 3 -- src/Type/IntegerType.php | 2 - .../Analyser/nsrt/array-key-exists.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-10863.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-11129.php | 52 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-11716.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4587.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-7387.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-8568.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8635.php | 2 +- .../Analyser/nsrt/cast-to-numeric-string.php | 28 +++++----- tests/PHPStan/Analyser/nsrt/filter-var.php | 2 +- tests/PHPStan/Analyser/nsrt/generics.php | 6 +-- tests/PHPStan/Analyser/nsrt/key-exists.php | 8 +-- .../PHPStan/Analyser/nsrt/range-to-string.php | 2 +- .../nsrt/set-type-type-specifying.php | 2 +- tests/PHPStan/Analyser/nsrt/strval.php | 4 +- .../PHPStan/Rules/DeadCode/data/bug-8620.php | 2 +- .../PHPStan/Rules/Generics/data/bug-6301.php | 2 +- 19 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 3db816aecaa..8a9414fbeed 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -10,7 +10,6 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; -use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -475,7 +474,6 @@ public function toString(): Type if ($isZero->no()) { return new IntersectionType([ new StringType(), - new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), new AccessoryNonFalsyStringType(), ]); @@ -483,7 +481,6 @@ public function toString(): Type return new IntersectionType([ new StringType(), - new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 354067f0a97..f91a646c9dd 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -6,7 +6,6 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; -use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -82,7 +81,6 @@ public function toString(): Type { return new IntersectionType([ new StringType(), - new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/tests/PHPStan/Analyser/nsrt/array-key-exists.php b/tests/PHPStan/Analyser/nsrt/array-key-exists.php index 3ae615b2d9a..ed6f552d155 100644 --- a/tests/PHPStan/Analyser/nsrt/array-key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/array-key-exists.php @@ -50,16 +50,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (array_key_exists($key2, $a)) { - assertType('lowercase-string&numeric-string', $key2); + assertType('numeric-string', $key2); } if (array_key_exists($key3, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key3); + assertType('int|numeric-string', $key3); } if (array_key_exists($key4, $a)) { - assertType('(int|(lowercase-string&numeric-string))', $key4); + assertType('(int|numeric-string)', $key4); } if (array_key_exists($key5, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key5); + assertType('int|numeric-string', $key5); } if (array_key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10863.php b/tests/PHPStan/Analyser/nsrt/bug-10863.php index 275bc3774e8..ecad48736ec 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10863.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10863.php @@ -12,7 +12,7 @@ class Foo */ public function doFoo($b): void { - assertType('lowercase-string&non-falsy-string', '@' . $b); + assertType('non-falsy-string', '@' . $b); } /** @@ -20,7 +20,7 @@ public function doFoo($b): void */ public function doFoo2($b): void { - assertType('lowercase-string&non-falsy-string', '@' . $b); + assertType('non-falsy-string', '@' . $b); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11129.php b/tests/PHPStan/Analyser/nsrt/bug-11129.php index 1f60b4089fb..8637ad7c4a1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11129.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11129.php @@ -21,14 +21,14 @@ public function foo( $maybeNegativeConstStrings, $maybeNonNumericConstStrings, $maybeFloatConstStrings, bool $bool, float $float ): void { - assertType('lowercase-string&non-falsy-string', '0'.$i); - assertType('lowercase-string&non-falsy-string&numeric-string', $i.'0'); + assertType('non-falsy-string', '0'.$i); + assertType('non-falsy-string&numeric-string', $i.'0'); - assertType('lowercase-string&non-falsy-string&numeric-string', '0'.$positiveInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.'0'); + assertType('non-falsy-string&numeric-string', '0'.$positiveInt); + assertType('non-falsy-string&numeric-string', $positiveInt.'0'); - assertType('lowercase-string&non-falsy-string', '0'.$negativeInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.'0'); + assertType('non-falsy-string', '0'.$negativeInt); + assertType('non-falsy-string&numeric-string', $negativeInt.'0'); assertType("'00'|'01'|'02'", '0'.$positiveConstStrings); assertType( "'00'|'10'|'20'", $positiveConstStrings.'0'); @@ -39,29 +39,29 @@ public function foo( assertType("'00'|'01'|'0a'", '0'.$maybeNonNumericConstStrings); assertType("'00'|'10'|'a0'", $maybeNonNumericConstStrings.'0'); - assertType('lowercase-string&non-falsy-string&numeric-string', $i.$positiveConstStrings); - assertType('lowercase-string&non-falsy-string', $positiveConstStrings.$i); + assertType('non-falsy-string&numeric-string', $i.$positiveConstStrings); + assertType( 'non-falsy-string', $positiveConstStrings.$i); - assertType('lowercase-string&non-falsy-string', $i.$maybeNegativeConstStrings); - assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$i); + assertType('non-falsy-string', $i.$maybeNegativeConstStrings); + assertType('non-falsy-string', $maybeNegativeConstStrings.$i); - assertType('lowercase-string&non-falsy-string', $i.$maybeNonNumericConstStrings); - assertType('lowercase-string&non-falsy-string', $maybeNonNumericConstStrings.$i); + assertType('non-falsy-string', $i.$maybeNonNumericConstStrings); + assertType('non-falsy-string', $maybeNonNumericConstStrings.$i); - assertType('lowercase-string&non-falsy-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' - assertType('lowercase-string&non-falsy-string', $maybeFloatConstStrings.$i); + assertType('non-falsy-string', $i.$maybeFloatConstStrings); // could be 'non-falsy-string&numeric-string' + assertType('non-falsy-string', $maybeFloatConstStrings.$i); - assertType('lowercase-string&non-empty-string&numeric-string', $i.$bool); - assertType('lowercase-string&non-empty-string', $bool.$i); - assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.$bool); - assertType('lowercase-string&non-falsy-string&numeric-string', $bool.$positiveInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.$bool); - assertType('lowercase-string&non-falsy-string', $bool.$negativeInt); + assertType('non-empty-string&numeric-string', $i.$bool); + assertType('non-empty-string', $bool.$i); + assertType('non-falsy-string&numeric-string', $positiveInt.$bool); + assertType('non-falsy-string&numeric-string', $bool.$positiveInt); + assertType('non-falsy-string&numeric-string', $negativeInt.$bool); + assertType('non-falsy-string', $bool.$negativeInt); - assertType('lowercase-string&non-falsy-string', $i.$i); - assertType('lowercase-string&non-falsy-string', $negativeInt.$negativeInt); - assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$negativeInt); - assertType('lowercase-string&non-falsy-string', $negativeInt.$maybeNegativeConstStrings); + assertType('non-falsy-string', $i.$i); + assertType('non-falsy-string', $negativeInt.$negativeInt); + assertType('non-falsy-string', $maybeNegativeConstStrings.$negativeInt); + assertType('non-falsy-string', $negativeInt.$maybeNegativeConstStrings); // https://3v4l.org/BCS2K assertType('non-falsy-string', $float.$float); @@ -75,9 +75,9 @@ public function foo( // https://3v4l.org/Ia4r0 $scientificFloatAsString = '3e4'; assertType('non-falsy-string', $numericString.$scientificFloatAsString); - assertType('lowercase-string&non-falsy-string', $i.$scientificFloatAsString); + assertType('non-falsy-string', $i.$scientificFloatAsString); assertType('non-falsy-string', $scientificFloatAsString.$numericString); - assertType('lowercase-string&non-falsy-string', $scientificFloatAsString.$i); + assertType('non-falsy-string', $scientificFloatAsString.$i); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index 83c10f32258..e637669483c 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -75,7 +75,7 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyed assertType('int', $i); if (isset($intKeyedArr[$s])) { - assertType("lowercase-string&numeric-string", $s); + assertType("numeric-string", $s); } else { assertType('string', $s); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4587.php b/tests/PHPStan/Analyser/nsrt/bug-4587.php index 623fea93af1..644b9d7b797 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4587.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4587.php @@ -27,11 +27,11 @@ public function b(): void $type = array_map(static function (array $result): array { assertType('array{a: int}', $result); $result['a'] = (string) $result['a']; - assertType('array{a: lowercase-string&numeric-string}', $result); + assertType('array{a: numeric-string}', $result); return $result; }, $results); - assertType('list', $type); + assertType('list', $type); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index 4f28696fb99..e1929a795a4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -29,7 +29,7 @@ public function inputTypes(int $i, float $f, string $s, int $intRange) { public function specifiers(int $i) { // https://3v4l.org/fmVIg - assertType('lowercase-string&numeric-string', sprintf('%14s', $i)); + assertType('numeric-string', sprintf('%14s', $i)); assertType('lowercase-string&numeric-string', sprintf('%d', $i)); @@ -59,9 +59,9 @@ public function specifiers(int $i) { */ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) { // https://3v4l.org/vVL0c - assertType('lowercase-string&numeric-string', sprintf('%2$6s', $mixed, $i)); - assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); - assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); + assertType('numeric-string', sprintf('%2$6s', $mixed, $i)); + assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); + assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange)); // https://3v4l.org/1ECIq diff --git a/tests/PHPStan/Analyser/nsrt/bug-8568.php b/tests/PHPStan/Analyser/nsrt/bug-8568.php index 9236447acf6..71db7a6c98a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8568.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8568.php @@ -8,7 +8,7 @@ class HelloWorld { public function sayHello(): void { - assertType('lowercase-string&non-falsy-string', 'a' . $this->get()); + assertType('non-falsy-string', 'a' . $this->get()); } public function get(): ?int diff --git a/tests/PHPStan/Analyser/nsrt/bug-8635.php b/tests/PHPStan/Analyser/nsrt/bug-8635.php index fe49aa8a2db..18952545792 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8635.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8635.php @@ -8,6 +8,6 @@ class HelloWorld { public function EchoInt(int $value): void { - assertType('lowercase-string&numeric-string', "$value"); + assertType('numeric-string', "$value"); } } diff --git a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php index 7c13500af25..c7d6fcb84c1 100644 --- a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php @@ -13,13 +13,13 @@ * @param 1 $constantInt */ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('lowercase-string&numeric-string', (string)$a); + assertType('numeric-string', (string)$a); assertType('numeric-string', (string)$b); assertType('numeric-string', (string)$numeric); assertType('numeric-string', (string)$numeric2); assertType('numeric-string', (string)$number); - assertType('lowercase-string&non-falsy-string&numeric-string', (string)$positive); - assertType('lowercase-string&non-falsy-string&numeric-string', (string)$negative); + assertType('non-falsy-string&numeric-string', (string)$positive); + assertType('non-falsy-string&numeric-string', (string)$negative); assertType("'1'", (string)$constantInt); } @@ -32,28 +32,28 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param 1 $constantInt */ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('lowercase-string&numeric-string', '' . $a); + assertType('numeric-string', '' . $a); assertType('numeric-string', '' . $b); assertType('numeric-string', '' . $numeric); assertType('numeric-string', '' . $numeric2); assertType('numeric-string', '' . $number); - assertType('lowercase-string&non-falsy-string&numeric-string', '' . $positive); - assertType('lowercase-string&non-falsy-string&numeric-string', '' . $negative); + assertType('non-falsy-string&numeric-string', '' . $positive); + assertType('non-falsy-string&numeric-string', '' . $negative); assertType("'1'", '' . $constantInt); - assertType('lowercase-string&numeric-string', $a . ''); + assertType('numeric-string', $a . ''); assertType('numeric-string', $b . ''); assertType('numeric-string', $numeric . ''); assertType('numeric-string', $numeric2 . ''); assertType('numeric-string', $number . ''); - assertType('lowercase-string&non-falsy-string&numeric-string', $positive . ''); - assertType('lowercase-string&non-falsy-string&numeric-string', $negative . ''); + assertType('non-falsy-string&numeric-string', $positive . ''); + assertType('non-falsy-string&numeric-string', $negative . ''); assertType("'1'", $constantInt . ''); } function concatAssignEmptyString(int $i, float $f) { $i .= ''; - assertType('lowercase-string&numeric-string', $i); + assertType('numeric-string', $i); $s = ''; $s .= $f; @@ -66,13 +66,13 @@ function concatAssignEmptyString(int $i, float $f) { */ function integerRangeToString($positive, $negative) { - assertType('lowercase-string&numeric-string', (string) $positive); - assertType('lowercase-string&numeric-string', (string) $negative); + assertType('numeric-string', (string) $positive); + assertType('numeric-string', (string) $negative); if ($positive !== 0) { - assertType('lowercase-string&non-falsy-string&numeric-string', (string) $positive); + assertType('non-falsy-string&numeric-string', (string) $positive); } if ($negative !== 0) { - assertType('lowercase-string&non-falsy-string&numeric-string', (string) $negative); + assertType('non-falsy-string&numeric-string', (string) $negative); } } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index 28580c448c4..e726c77d75c 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -158,7 +158,7 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType("'17'", filter_var(17.0)); assertType("'17.1'", filter_var(17.1)); assertType("'1.0E-50'", filter_var(1e-50)); - assertType('lowercase-string&numeric-string', filter_var($int)); + assertType('numeric-string', filter_var($int)); assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index 873ad66b6d5..bcd7ecf6160 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -147,10 +147,10 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('Closure(int): (lowercase-string&numeric-string)', function (int $a): string { + assertType('Closure(int): numeric-string', function (int $a): string { return (string)$a; }); - assertType('array', f($arrayOfInt, function (int $a): string { + assertType('array', f($arrayOfInt, function (int $a): string { return (string)$a; })); assertType('Closure(mixed): string', function ($a): string { @@ -224,7 +224,7 @@ function testArrayMap(array $listOfIntegers) return (string) $int; }, $listOfIntegers); - assertType('array', $strings); + assertType('array', $strings); } /** diff --git a/tests/PHPStan/Analyser/nsrt/key-exists.php b/tests/PHPStan/Analyser/nsrt/key-exists.php index 3c85802e739..11c2ed6a2a3 100644 --- a/tests/PHPStan/Analyser/nsrt/key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/key-exists.php @@ -49,16 +49,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (key_exists($key2, $a)) { - assertType('lowercase-string&numeric-string', $key2); + assertType('numeric-string', $key2); } if (key_exists($key3, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key3); + assertType('int|numeric-string', $key3); } if (key_exists($key4, $a)) { - assertType('(int|(lowercase-string&numeric-string))', $key4); + assertType('(int|numeric-string)', $key4); } if (key_exists($key5, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key5); + assertType('int|numeric-string', $key5); } if (key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/range-to-string.php b/tests/PHPStan/Analyser/nsrt/range-to-string.php index 2d70eb4babc..494c135d95b 100644 --- a/tests/PHPStan/Analyser/nsrt/range-to-string.php +++ b/tests/PHPStan/Analyser/nsrt/range-to-string.php @@ -17,6 +17,6 @@ public function sayHello($i, $ii, $maxlong, $toolong): void assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i); assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii); assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong); - assertType("lowercase-string&numeric-string", (string) $toolong); + assertType("numeric-string", (string) $toolong); } } diff --git a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php index 1ab4badbe56..11869c0623f 100644 --- a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php @@ -11,7 +11,7 @@ function doString(string $s, int $i, float $f, array $a, object $o) assertType('string', $s); settype($i, 'string'); - assertType('lowercase-string&numeric-string', $i); + assertType('numeric-string', $i); settype($f, 'string'); assertType('numeric-string', $f); diff --git a/tests/PHPStan/Analyser/nsrt/strval.php b/tests/PHPStan/Analyser/nsrt/strval.php index a6c43978936..870aafd6b8b 100644 --- a/tests/PHPStan/Analyser/nsrt/strval.php +++ b/tests/PHPStan/Analyser/nsrt/strval.php @@ -17,9 +17,9 @@ function strvalTest(string $string, string $class): void assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); assertType('\'42\'', strval(42)); - assertType('lowercase-string&numeric-string', strval(rand())); + assertType('numeric-string', strval(rand())); assertType('numeric-string', strval(rand() * 0.1)); - assertType('lowercase-string&numeric-string', strval(strval(rand()))); + assertType('numeric-string', strval(strval(rand()))); assertType('class-string', strval($class)); assertType('string', strval(new \Exception())); assertType('*ERROR*', strval(new \stdClass())); diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php index 44bc78cd456..cad6d811c29 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php @@ -9,7 +9,7 @@ class HelloWorld public function nullCoalesceAndConcatenation (?int $a = null): int { $key = ($a ?? "x") . "-"; - assertType('lowercase-string&non-falsy-string', $key); + assertType('non-falsy-string', $key); if ($key === "x-") { return 0; } return 1; diff --git a/tests/PHPStan/Rules/Generics/data/bug-6301.php b/tests/PHPStan/Rules/Generics/data/bug-6301.php index 73dff935f20..9811b632531 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-6301.php +++ b/tests/PHPStan/Rules/Generics/data/bug-6301.php @@ -22,7 +22,7 @@ public function str($s) * @param literal-string $literalString */ public function foo(int $i, $nonEmpty, $numericString, $literalString):void { - assertType('lowercase-string&numeric-string', $this->str((string) $i)); + assertType('numeric-string', $this->str((string) $i)); assertType('non-empty-string', $this->str($nonEmpty)); assertType('numeric-string', $this->str($numericString)); assertType('literal-string', $this->str($literalString)); From 8e9a34cfda001f21bbcef57728c875d735697e81 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 4 Oct 2024 10:58:01 +0200 Subject: [PATCH 0639/3097] Introduce isSuperTypeOfWithReason --- src/Type/Accessory/AccessoryArrayListType.php | 27 ++- .../Accessory/AccessoryLiteralStringType.php | 25 ++- .../AccessoryLowercaseStringType.php | 25 ++- .../Accessory/AccessoryNonEmptyStringType.php | 27 ++- .../Accessory/AccessoryNonFalsyStringType.php | 27 ++- .../Accessory/AccessoryNumericStringType.php | 25 ++- src/Type/Accessory/HasMethodType.php | 25 ++- src/Type/Accessory/HasOffsetType.php | 27 ++- src/Type/Accessory/HasOffsetValueType.php | 43 +++-- src/Type/Accessory/HasPropertyType.php | 23 ++- src/Type/Accessory/NonEmptyArrayType.php | 27 ++- src/Type/Accessory/OversizedArrayType.php | 27 ++- src/Type/ArrayType.php | 13 +- src/Type/CallableType.php | 30 ++-- src/Type/CallableTypeHelper.php | 22 +-- src/Type/ClassStringType.php | 9 +- src/Type/ClosureType.php | 17 +- src/Type/CompoundType.php | 9 + src/Type/ConditionalType.php | 9 +- src/Type/ConditionalTypeForParameter.php | 9 +- src/Type/Constant/ConstantArrayType.php | 32 ++-- src/Type/Constant/ConstantIntegerType.php | 18 +- src/Type/Constant/ConstantStringType.php | 26 +-- src/Type/Enum/EnumCaseObjectType.php | 18 +- src/Type/FloatType.php | 11 +- src/Type/Generic/GenericClassStringType.php | 22 ++- src/Type/Generic/GenericObjectType.php | 24 ++- src/Type/Generic/TemplateMixedType.php | 2 +- src/Type/Generic/TemplateStrictMixedType.php | 2 +- src/Type/Generic/TemplateType.php | 4 +- src/Type/Generic/TemplateTypeTrait.php | 31 ++-- src/Type/Generic/TemplateTypeVariance.php | 22 +-- src/Type/IntegerRangeType.php | 41 +++-- src/Type/IntersectionType.php | 22 ++- src/Type/IsSuperTypeOfResult.php | 159 ++++++++++++++++++ src/Type/IterableType.php | 34 ++-- src/Type/JustNullableTypeTrait.php | 11 +- src/Type/MixedType.php | 32 ++-- src/Type/NeverType.php | 18 +- src/Type/NonAcceptingNeverType.php | 11 +- src/Type/NullType.php | 11 +- src/Type/ObjectShapeType.php | 27 +-- src/Type/ObjectType.php | 47 +++--- src/Type/ObjectWithoutClassType.php | 23 ++- src/Type/StaticType.php | 17 +- src/Type/StrictMixedType.php | 18 +- ...gAlwaysAcceptingObjectWithToStringType.php | 15 +- src/Type/ThisType.php | 11 +- src/Type/Traits/ConstantScalarTypeTrait.php | 14 +- src/Type/Traits/LateResolvableTypeTrait.php | 23 ++- src/Type/Type.php | 9 + src/Type/UnionType.php | 18 +- src/Type/VoidType.php | 11 +- 53 files changed, 880 insertions(+), 350 deletions(-) create mode 100644 src/Type/IsSuperTypeOfResult.php diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index fb50126b590..491af5b813d 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -93,28 +94,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isList()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isList()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isList()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isList()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -124,7 +133,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 110fbea58c7..700063570e1 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; @@ -85,26 +86,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isLiteralString(); + return new IsSuperTypeOfResult($type->isLiteralString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isLiteralString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isLiteralString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -114,7 +125,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 195a807dbcf..fe57034af12 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -81,26 +82,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isLowercaseString(); + return new IsSuperTypeOfResult($type->isLowercaseString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isLowercaseString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -110,7 +121,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index b911c21fbbe..6587e85d35e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -18,6 +18,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -83,30 +84,40 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->isNonFalsyString()->yes()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNonEmptyString(); + return new IsSuperTypeOfResult($type->isNonEmptyString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isNonEmptyString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNonEmptyString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -116,7 +127,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 5703420e9f8..2a562644754 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -17,6 +17,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -83,30 +84,40 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNonFalsyString(); + return new IsSuperTypeOfResult($type->isNonFalsyString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof AccessoryNonEmptyStringType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $otherType->isNonFalsyString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNonFalsyString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -116,7 +127,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9cb274782d5..1ab8d31eff3 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -18,6 +18,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\StringType; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; @@ -82,26 +83,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isNumericString(); + return new IsSuperTypeOfResult($type->isNumericString(), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isNumericString() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isNumericString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -119,7 +130,7 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): return AcceptsResult::createYes(); } - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 98503a13a8d..1ced070eb52 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -15,6 +15,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\StringType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; @@ -77,26 +78,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): TrinaryLogic { - return $type->hasMethod($this->methodName); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return new IsSuperTypeOfResult($type->hasMethod($this->methodName), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } - return $limit->and($otherType->hasMethod($this->methodName)); + return $limit->and(new IsSuperTypeOfResult($otherType->hasMethod($this->methodName), [])); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -106,7 +117,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index bcd5a931224..f1ccf3c2a20 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\MaybeArrayTypeTrait; @@ -95,23 +96,33 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)); + return new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isOffsetAccessible() - ->and($otherType->hasOffsetValueType($this->offsetType)) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -121,7 +132,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 651d81560ab..600fa6ca636 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -16,6 +16,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\MaybeArrayTypeTrait; @@ -91,34 +92,44 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return new AcceptsResult( - $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)) - ->and($this->valueType->accepts($type->getOffsetValueType($this->offsetType), $strictTypes)), - [], - ); + $result = new AcceptsResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); + + return $result->and($this->valueType->acceptsWithReason($type->getOffsetValueType($this->offsetType), $strictTypes)); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $type->isOffsetAccessible() - ->and($type->hasOffsetValueType($this->offsetType)) - ->and($this->valueType->isSuperTypeOf($type->getOffsetValueType($this->offsetType))); + + $result = new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($this->valueType->isSuperTypeOfWithReason($type->getOffsetValueType($this->offsetType))); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isOffsetAccessible() - ->and($otherType->hasOffsetValueType($this->offsetType)) - ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOf($this->valueType)) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); + + return $result + ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOfWithReason($this->valueType)) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -128,7 +139,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index f5084471356..372bcf70f10 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -11,6 +11,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -79,22 +80,32 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): TrinaryLogic { - return $type->hasProperty($this->propertyName); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return new IsSuperTypeOfResult($type->hasProperty($this->propertyName), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } - return $limit->and($otherType->hasProperty($this->propertyName)); + return $limit->and(new IsSuperTypeOfResult($otherType->hasProperty($this->propertyName), [])); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -104,7 +115,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 084cc28d3cb..c822edcc7ae 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -90,28 +91,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isIterableAtLeastOnce()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isIterableAtLeastOnce()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isIterableAtLeastOnce()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isIterableAtLeastOnce()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -121,7 +130,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 7bb8e5213b1..4d39431f7b2 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -14,6 +14,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -86,28 +87,36 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isArray() - ->and($type->isOversizedArray()); + return new IsSuperTypeOfResult($type->isArray()->and($type->isOversizedArray()), []); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isArray() - ->and($otherType->isOversizedArray()) - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isOversizedArray()), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -117,7 +126,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index c1d163d97c1..01ca997eeea 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -128,17 +128,22 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getItemType()->isSuperTypeOf($type->getItemType()) - ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); + return $this->getItemType()->isSuperTypeOfWithReason($type->getItemType()) + ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 82b3645a273..d9bbea57e6e 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -140,21 +140,26 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType && !$type instanceof self) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult { - $isCallable = new AcceptsResult($type->isCallable(), []); + $isCallable = new IsSuperTypeOfResult($type->isCallable(), []); if ($isCallable->no()) { return $isCallable; } @@ -171,7 +176,7 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep $typePure = $typePure->and($variant->isPure()); } - return $isCallable->and(new AcceptsResult($typePure, [])); + return $isCallable->and(new IsSuperTypeOfResult($typePure, [])); } return $isCallable; @@ -195,13 +200,18 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return $otherType->isCallable() - ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + return (new IsSuperTypeOfResult($otherType->isCallable(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -211,7 +221,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 29f416d3aa4..aa28e59fff3 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -16,7 +16,7 @@ public static function isParametersAcceptorSuperTypeOf( CallableParametersAcceptor $ours, CallableParametersAcceptor $theirs, bool $treatMixedAsAny, - ): AcceptsResult + ): IsSuperTypeOfResult { $theirParameters = $theirs->getParameters(); $ourParameters = $ours->getParameters(); @@ -40,7 +40,7 @@ public static function isParametersAcceptorSuperTypeOf( } } - $result = AcceptsResult::createYes(); + $result = IsSuperTypeOfResult::createYes(); foreach ($theirParameters as $i => $theirParameter) { $parameterDescription = $theirParameter->getName() === '' ? sprintf('#%d', $i + 1) : sprintf('#%d $%s', $i + 1, $theirParameter->getName()); if (!isset($ourParameters[$i])) { @@ -48,7 +48,7 @@ public static function isParametersAcceptorSuperTypeOf( continue; } - $accepts = new AcceptsResult(TrinaryLogic::createNo(), [ + $accepts = new IsSuperTypeOfResult(TrinaryLogic::createNo(), [ sprintf( 'Parameter %s of passed callable is required but accepting callable does not have that parameter. It will be called without it.', $parameterDescription, @@ -62,7 +62,7 @@ public static function isParametersAcceptorSuperTypeOf( $ourParameterType = $ourParameter->getType(); if ($ourParameter->isOptional() && !$theirParameter->isOptional()) { - $accepts = new AcceptsResult(TrinaryLogic::createNo(), [ + $accepts = new IsSuperTypeOfResult(TrinaryLogic::createNo(), [ sprintf( 'Parameter %s of passed callable is required but the parameter of accepting callable is optional. It might be called without it.', $parameterDescription, @@ -73,13 +73,14 @@ public static function isParametersAcceptorSuperTypeOf( if ($treatMixedAsAny) { $isSuperType = $theirParameter->getType()->acceptsWithReason($ourParameterType, true); + $isSuperType = new IsSuperTypeOfResult($isSuperType->result, $isSuperType->reasons); } else { - $isSuperType = new AcceptsResult($theirParameter->getType()->isSuperTypeOf($ourParameterType), []); + $isSuperType = $theirParameter->getType()->isSuperTypeOfWithReason($ourParameterType); } if ($isSuperType->maybe()) { $verbosity = VerbosityLevel::getRecommendedLevelByType($theirParameter->getType(), $ourParameterType); - $isSuperType = new AcceptsResult($isSuperType->result, array_merge($isSuperType->reasons, [ + $isSuperType = new IsSuperTypeOfResult($isSuperType->result, array_merge($isSuperType->reasons, [ sprintf( 'Type %s of parameter %s of passed callable needs to be same or wider than parameter type %s of accepting callable.', $theirParameter->getType()->describe($verbosity), @@ -93,21 +94,22 @@ public static function isParametersAcceptorSuperTypeOf( } if (!$treatMixedAsAny && $theirParameterCount < $ourParameterCount) { - $result = $result->and(AcceptsResult::createMaybe()); + $result = $result->and(IsSuperTypeOfResult::createMaybe()); } $theirReturnType = $theirs->getReturnType(); if ($treatMixedAsAny) { $isReturnTypeSuperType = $ours->getReturnType()->acceptsWithReason($theirReturnType, true); + $isReturnTypeSuperType = new IsSuperTypeOfResult($isReturnTypeSuperType->result, $isReturnTypeSuperType->reasons); } else { - $isReturnTypeSuperType = new AcceptsResult($ours->getReturnType()->isSuperTypeOf($theirReturnType), []); + $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOfWithReason($theirReturnType); } $pure = $ours->isPure(); if ($pure->yes()) { - $result = $result->and(new AcceptsResult($theirs->isPure(), [])); + $result = $result->and(new IsSuperTypeOfResult($theirs->isPure(), [])); } elseif ($pure->no()) { - $result = $result->and(new AcceptsResult($theirs->isPure()->negate(), [])); + $result = $result->and(new IsSuperTypeOfResult($theirs->isPure()->negate(), [])); } return $result->and($isReturnTypeSuperType); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index eaca9d4572e..2f58e27d15e 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -36,12 +36,17 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isClassStringType(); + return new IsSuperTypeOfResult($type->isClassStringType(), []); } public function isString(): TrinaryLogic diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index e55847aae03..bbe6d5a73ef 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -196,19 +196,24 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $this->objectType->acceptsWithReason($type, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult { if ($type instanceof self) { return CallableTypeHelper::isParametersAcceptorSuperTypeOf( @@ -219,10 +224,10 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): Accep } if ($type->getObjectClassNames() === [Closure::class]) { - return AcceptsResult::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return new AcceptsResult($this->objectType->isSuperTypeOf($type), []); + return $this->objectType->isSuperTypeOfWithReason($type); } public function equals(Type $type): bool diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index ea9b2f4660b..b3080ceb914 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -10,6 +10,15 @@ interface CompoundType extends Type public function isSubTypeOf(Type $otherType): TrinaryLogic; + /** + * This is like isSubTypeOf() but gives reasons + * why the type was not/might not be accepted in some non-intuitive scenarios. + * + * In PHPStan 2.0 this method will be removed and the return type of isSubTypeOf() + * will change to IsSuperTypeOfResult. + */ + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult; + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic; public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult; diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index aa5d8af0a61..f69d3633f76 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -62,10 +62,15 @@ public function isNegated(): bool } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOf($type->if) - ->and($this->else->isSuperTypeOf($type->else)); + return $this->if->isSuperTypeOfWithReason($type->if) + ->and($this->else->isSuperTypeOfWithReason($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index 57c2fe5d8d2..72d7f0f5a2b 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -76,10 +76,15 @@ public function toConditional(Type $subject): Type } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOf($type->if) - ->and($this->else->isSuperTypeOf($type->else)); + return $this->if->isSuperTypeOfWithReason($type->if) + ->and($this->else->isSuperTypeOfWithReason($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 259952b41b5..6838aa336b3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -38,6 +38,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -363,10 +364,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { if (count($this->keyTypes) === 0) { - return $type->isIterableAtLeastOnce()->negate(); + return new IsSuperTypeOfResult($type->isIterableAtLeastOnce()->negate(), []); } $results = []; @@ -374,44 +380,44 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $hasOffset = $type->hasOffsetValueType($keyType); if ($hasOffset->no()) { if (!$this->isOptionalKey($i)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } - $results[] = TrinaryLogic::createYes(); + $results[] = IsSuperTypeOfResult::createYes(); continue; } elseif ($hasOffset->maybe() && !$this->isOptionalKey($i)) { - $results[] = TrinaryLogic::createMaybe(); + $results[] = IsSuperTypeOfResult::createMaybe(); } - $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); + $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOfWithReason($type->getOffsetValueType($keyType)); if ($isValueSuperType->no()) { - return TrinaryLogic::createNo(); + return $isValueSuperType; } $results[] = $isValueSuperType; } - return TrinaryLogic::createYes()->and(...$results); + return IsSuperTypeOfResult::createYes()->and(...$results); } if ($type instanceof ArrayType) { - $result = TrinaryLogic::createMaybe(); + $result = IsSuperTypeOfResult::createMaybe(); if (count($this->keyTypes) === 0) { return $result; } - $isKeySuperType = $this->getKeyType()->isSuperTypeOf($type->getKeyType()); + $isKeySuperType = $this->getKeyType()->isSuperTypeOfWithReason($type->getKeyType()); if ($isKeySuperType->no()) { - return TrinaryLogic::createNo(); + return $isKeySuperType; } - return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOf($type->getItemType())); + return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOfWithReason($type->getItemType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 9226acacc24..603281d734c 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -11,6 +11,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; @@ -38,30 +39,35 @@ public function getValue(): int } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + return $this->value === $type->value ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createNo(); } if ($type instanceof IntegerRangeType) { $min = $type->getMin(); $max = $type->getMax(); if (($min === null || $min <= $this->value) && ($max === null || $this->value <= $max)) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 382911e69dc..6350d17cefd 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -33,6 +33,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -147,11 +148,16 @@ private function export(string $value): string } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof GenericClassStringType) { $genericType = $type->getGenericType(); if ($genericType instanceof MixedType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($genericType instanceof StaticType) { $genericType = $genericType->getStaticObjectType(); @@ -164,34 +170,34 @@ public function isSuperTypeOf(Type $type): TrinaryLogic // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); + $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); } // Explicitly handle the uncertainty for Yes & Maybe. if ($isSuperType->yes()) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($type instanceof ClassStringType) { - return $this->isClassStringType()->yes() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + return $this->isClassStringType()->yes() ? IsSuperTypeOfResult::createMaybe() : IsSuperTypeOfResult::createNo(); } if ($type instanceof self) { - return $this->value === $type->value ? TrinaryLogic::createYes() : TrinaryLogic::createNo(); + return $this->value === $type->value ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createNo(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function isCallable(): TrinaryLogic diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 42d8f4afc22..cf91e6e2d9c 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -16,6 +16,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\GeneralizePrecision; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectType; use PHPStan\Type\SubtractableType; use PHPStan\Type\Type; @@ -65,34 +66,39 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSuperTypeOf($type), []); + return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createFromBoolean( + return IsSuperTypeOfResult::createFromBoolean( $this->enumCaseName === $type->enumCaseName && $this->getClassName() === $type->getClassName(), ); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ( $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); if ($isSuperType->yes()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } } $parent = new parent($this->getClassName(), $this->getSubtractedType(), $this->getClassReflection()); - return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe()); + return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); } public function subtract(Type $type): Type diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 0ffa96e002c..bd5b2a9082f 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -83,16 +83,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index fd6ecd5f03d..893c13d2c57 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -12,6 +12,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; @@ -86,15 +87,20 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof ConstantStringType) { $genericType = $this->type; if ($genericType instanceof MixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($genericType instanceof StaticType) { @@ -108,23 +114,23 @@ public function isSuperTypeOf(Type $type): TrinaryLogic // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); } else { - $isSuperType = $genericType->isSuperTypeOf($objectType); + $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); } if (!$type->isClassStringType()->yes()) { - $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); + $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); } return $isSuperType; } elseif ($type instanceof self) { - return $this->type->isSuperTypeOf($type->type); + return $this->type->isSuperTypeOfWithReason($type->type); } elseif ($type instanceof StringType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function traverse(callable $cb): Type diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 775134aab6b..24e4aa8f9fe 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -18,6 +18,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; @@ -129,21 +130,26 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult return $type->isAcceptedWithReasonBy($this, $strictTypes); } - return $this->isSuperTypeOfInternal($type, true); + return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $this->isSuperTypeOfInternal($type, false)->result; + return $this->isSuperTypeOfInternal($type, false); } - private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): AcceptsResult + private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSuperTypeOfResult { - $nakedSuperTypeOf = new AcceptsResult(parent::isSuperTypeOf($type), []); + $nakedSuperTypeOf = parent::isSuperTypeOfWithReason($type); if ($nakedSuperTypeOf->no()) { return $nakedSuperTypeOf; } @@ -161,11 +167,11 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Accept return $nakedSuperTypeOf; } - return $nakedSuperTypeOf->and(AcceptsResult::createMaybe()); + return $nakedSuperTypeOf->and(IsSuperTypeOfResult::createMaybe()); } if (count($this->types) !== count($ancestor->types)) { - return AcceptsResult::createNo(); + return IsSuperTypeOfResult::createNo(); } $classReflection = $this->getClassReflection(); @@ -197,14 +203,14 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Accept $results[] = $templateType->isValidVarianceWithReason($this->types[$i], $ancestor->types[$i]); } - $results[] = AcceptsResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); + $results[] = IsSuperTypeOfResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); } if (count($results) === 0) { return $nakedSuperTypeOf; } - $result = AcceptsResult::createYes(); + $result = IsSuperTypeOfResult::createYes(); foreach ($results as $innerResult) { $result = $result->and($innerResult); } diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 3363818673a..132cb20d6bf 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -47,7 +47,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); + $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index 6ae56cc2284..071475e2154 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -45,7 +45,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } } diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 7661078ca1b..7398fe836f9 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -3,8 +3,8 @@ namespace PHPStan\Type\Generic; use PHPStan\TrinaryLogic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Type; /** @api */ @@ -24,7 +24,7 @@ public function isArgument(): bool; public function isValidVariance(Type $a, Type $b): TrinaryLogic; - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult; + public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult; public function getVariance(): TemplateTypeVariance; diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 66fd32a2fd3..b344be36905 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -9,6 +9,7 @@ use PHPStan\Type\AcceptsResult; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\SubtractableType; @@ -98,7 +99,7 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason($a, $b)->result; } - public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult + public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult { return $this->variance->isValidVarianceWithReason($this, $a, $b); } @@ -206,20 +207,30 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof TemplateType || $type instanceof IntersectionType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $this->getBound()->isSuperTypeOf($type) - ->and(TrinaryLogic::createMaybe()); + return $this->getBound()->isSuperTypeOfWithReason($type) + ->and(IsSuperTypeOfResult::createMaybe()); } public function isSubTypeOf(Type $type): TrinaryLogic + { + return $this->isSubTypeOfWithReason($type)->result; + } + + public function isSubTypeOfWithReason(Type $type): IsSuperTypeOfResult { /** @var TBound $bound */ $bound = $this->getBound(); @@ -229,19 +240,19 @@ public function isSubTypeOf(Type $type): TrinaryLogic && !$type instanceof TemplateType && ($type instanceof UnionType || $type instanceof IntersectionType) ) { - return $type->isSuperTypeOf($this); + return $type->isSuperTypeOfWithReason($this); } if (!$type instanceof TemplateType) { - return $type->isSuperTypeOf($this->getBound()); + return $type->isSuperTypeOfWithReason($this->getBound()); } if ($this->getScope()->equals($type->getScope()) && $this->getName() === $type->getName()) { - return $type->getBound()->isSuperTypeOf($this->getBound()); + return $type->getBound()->isSuperTypeOfWithReason($this->getBound()); } - return $type->getBound()->isSuperTypeOf($this->getBound()) - ->and(TrinaryLogic::createMaybe()); + return $type->getBound()->isSuperTypeOfWithReason($this->getBound()) + ->and(IsSuperTypeOfResult::createMaybe()); } public function toArrayKey(): Type diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 786c61302c6..c48a457b19b 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -5,8 +5,8 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -134,30 +134,30 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason(null, $a, $b)->result; } - public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): AcceptsResult + public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult { if ($b instanceof NeverType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof MixedType && !$a instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof BenevolentUnionType) { if (!$a->isSuperTypeOf($b)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof BenevolentUnionType) { if (!$b->isSuperTypeOf($a)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof MixedType && !$b instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($this->invariant()) { @@ -177,19 +177,19 @@ public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, } } - return new AcceptsResult(TrinaryLogic::createFromBoolean($result), $reasons); + return new IsSuperTypeOfResult(TrinaryLogic::createFromBoolean($result), $reasons); } if ($this->covariant()) { - return new AcceptsResult($a->isSuperTypeOf($b), []); + return $a->isSuperTypeOfWithReason($b); } if ($this->contravariant()) { - return new AcceptsResult($b->isSuperTypeOf($a), []); + return $b->isSuperTypeOfWithReason($a); } if ($this->bivariant()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } throw new ShouldNotHappenException(); diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 8a9414fbeed..1b914804dcd 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -15,6 +15,7 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use function array_filter; +use function array_map; use function assert; use function ceil; use function count; @@ -209,7 +210,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof parent) { - return new AcceptsResult($this->isSuperTypeOf($type), []); + return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); } if ($type instanceof CompoundType) { @@ -220,6 +221,11 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self || $type instanceof ConstantIntegerType) { if ($type instanceof self) { @@ -231,48 +237,53 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if (self::isDisjoint($this->min, $this->max, $typeMin, $typeMax)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ( ($this->min === null || $typeMin !== null && $this->min <= $typeMin) && ($this->max === null || $typeMax !== null && $this->max >= $typeMax) ) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof parent) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } if ($otherType instanceof UnionType) { - return $this->isSubTypeOfUnion($otherType); + return $this->isSubTypeOfUnionWithReason($otherType); } if ($otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } - private function isSubTypeOfUnion(UnionType $otherType): TrinaryLogic + private function isSubTypeOfUnionWithReason(UnionType $otherType): IsSuperTypeOfResult { if ($this->min !== null && $this->max !== null) { $matchingConstantIntegers = array_filter( @@ -281,11 +292,11 @@ private function isSubTypeOfUnion(UnionType $otherType): TrinaryLogic ); if (count($matchingConstantIntegers) === ($this->max - $this->min + 1)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } } - return TrinaryLogic::createNo()->lazyOr($otherType->getTypes(), fn (Type $innerType) => $this->isSubTypeOf($innerType)); + return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOfWithReason($innerType), $otherType->getTypes())); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -295,7 +306,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index bb3eaab0e7d..140e45a8edd 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -221,28 +221,38 @@ public function acceptsWithReason(Type $otherType, bool $strictTypes): AcceptsRe } public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($otherType)->result; + } + + public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType && $this->equals($otherType)) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createYes()->lazyAnd($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); + return IsSuperTypeOfResult::createYes()->and(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if (($otherType instanceof self || $otherType instanceof UnionType) && !$otherType instanceof TemplateType) { - return $otherType->isSuperTypeOf($this); + return $otherType->isSuperTypeOfWithReason($this); } - $result = TrinaryLogic::lazyMaxMin($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); + $result = IsSuperTypeOfResult::maxMin(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); if ($this->isOversizedArray()->yes()) { if (!$result->no()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } } diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php new file mode 100644 index 00000000000..30e9fe6acc6 --- /dev/null +++ b/src/Type/IsSuperTypeOfResult.php @@ -0,0 +1,159 @@ + $reasons + */ + public function __construct( + public readonly TrinaryLogic $result, + public readonly array $reasons, + ) + { + } + + public function yes(): bool + { + return $this->result->yes(); + } + + public function maybe(): bool + { + return $this->result->maybe(); + } + + public function no(): bool + { + return $this->result->no(); + } + + public static function createYes(): self + { + return new self(TrinaryLogic::createYes(), []); + } + + /** + * @param list $reasons + */ + public static function createNo(array $reasons = []): self + { + return new self(TrinaryLogic::createNo(), $reasons); + } + + public static function createMaybe(): self + { + return new self(TrinaryLogic::createMaybe(), []); + } + + public static function createFromBoolean(bool $value): self + { + return new self(TrinaryLogic::createFromBoolean($value), []); + } + + public function toAcceptsResult(): AcceptsResult + { + return new AcceptsResult($this->result, $this->reasons); + } + + public function and(self ...$others): self + { + $results = []; + $reasons = []; + foreach ($others as $other) { + $results[] = $other->result; + $reasons[] = $other->reasons; + } + + return new self( + $this->result->and(...$results), + array_values(array_unique(array_merge($this->reasons, ...$reasons))), + ); + } + + public function or(self ...$others): self + { + $results = []; + $reasons = []; + foreach ($others as $other) { + $results[] = $other->result; + $reasons[] = $other->reasons; + } + + return new self( + $this->result->or(...$results), + array_values(array_unique(array_merge($this->reasons, ...$reasons))), + ); + } + + /** + * @param callable(string): string $cb + */ + public function decorateReasons(callable $cb): self + { + $reasons = []; + foreach ($this->reasons as $reason) { + $reasons[] = $cb($reason); + } + + return new self($this->result, $reasons); + } + + public static function extremeIdentity(self ...$operands): self + { + if ($operands === []) { + throw new ShouldNotHappenException(); + } + + $result = TrinaryLogic::extremeIdentity(...array_map(static fn (self $result) => $result->result, $operands)); + + return new self($result, self::mergeReasons($operands)); + } + + public static function maxMin(self ...$operands): self + { + if ($operands === []) { + throw new ShouldNotHappenException(); + } + + $result = TrinaryLogic::maxMin(...array_map(static fn (self $result) => $result->result, $operands)); + + return new self($result, self::mergeReasons($operands)); + } + + public function negate(): self + { + return new self($this->result->negate(), $this->reasons); + } + + /** + * @param array $operands + * + * @return list + */ + private static function mergeReasons(array $operands): array + { + $reasons = []; + foreach ($operands as $operand) { + foreach ($operand->reasons as $reason) { + $reasons[] = $reason; + } + } + + return $reasons; + } + +} diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 6ef8ff5ee3e..662d2e1b0cc 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -101,14 +101,19 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return $type->isIterable() - ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())) - ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); + return (new IsSuperTypeOfResult($type->isIterable(), [])) + ->and($this->getIterableValueType()->isSuperTypeOfWithReason($type->getIterableValueType())) + ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); } public function isSuperTypeOfMixed(Type $type): TrinaryLogic @@ -140,9 +145,14 @@ private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOf(new UnionType([ + return $otherType->isSuperTypeOfWithReason(new UnionType([ new ArrayType($this->keyType, $this->itemType), new IntersectionType([ new ObjectType(Traversable::class), @@ -152,19 +162,19 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic } if ($otherType instanceof self) { - $limit = TrinaryLogic::createYes(); + $limit = IsSuperTypeOfResult::createYes(); } else { - $limit = TrinaryLogic::createMaybe(); + $limit = IsSuperTypeOfResult::createMaybe(); } if ($otherType->isConstantArray()->yes() && $otherType->isIterableAtLeastOnce()->no()) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } return $limit->and( - $otherType->isIterable(), - $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), - $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType), + new IsSuperTypeOfResult($otherType->isIterable(), []), + $otherType->getIterableValueType()->isSuperTypeOfWithReason($this->itemType), + $otherType->getIterableKeyType()->isSuperTypeOfWithReason($this->keyType), ); } @@ -175,7 +185,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 7f48131262a..df6bee28b81 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -45,16 +45,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 777a47b94d0..20117d68b32 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -137,24 +137,29 @@ public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($this->subtractedType === null || $type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof self) { if ($type->subtractedType === null) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); + return $isSuperType; } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return $this->subtractedType->isSuperTypeOf($type)->negate(); + return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type @@ -326,19 +331,24 @@ public function equals(Type $type): bool } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($otherType); + $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($otherType); if ($isSuperType->yes()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -348,7 +358,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = new AcceptsResult($this->isSuperTypeOf($acceptingType), []); + $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index f9e2288e287..45f9cb05f3b 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -83,12 +83,17 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool @@ -98,7 +103,12 @@ public function equals(Type $type): bool public function isSubTypeOf(Type $otherType): TrinaryLogic { - return TrinaryLogic::createYes(); + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::createYes(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -108,7 +118,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return new AcceptsResult($this->isSubTypeOf($acceptingType), []); + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/NonAcceptingNeverType.php b/src/Type/NonAcceptingNeverType.php index 3eaddf53cb3..861165cdf2b 100644 --- a/src/Type/NonAcceptingNeverType.php +++ b/src/Type/NonAcceptingNeverType.php @@ -15,15 +15,20 @@ public function __construct() } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult diff --git a/src/Type/NullType.php b/src/Type/NullType.php index ab50c2040e2..7b60d5d3cb9 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -91,16 +91,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index a00324259a5..af1e4d67d4d 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -238,13 +238,18 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); @@ -257,13 +262,13 @@ public function isSuperTypeOf(Type $type): TrinaryLogic continue; } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - $result = TrinaryLogic::createYes(); + $result = IsSuperTypeOfResult::createYes(); $scope = new OutOfClassScope(); foreach ($this->properties as $propertyName => $propertyType) { - $hasProperty = $type->hasProperty($propertyName); + $hasProperty = new IsSuperTypeOfResult($type->hasProperty($propertyName), []); if ($hasProperty->no()) { if (in_array($propertyName, $this->optionalProperties, true)) { continue; @@ -271,7 +276,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $hasProperty; } if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) { - $hasProperty = TrinaryLogic::createYes(); + $hasProperty = IsSuperTypeOfResult::createYes(); } $result = $result->and($hasProperty); @@ -283,26 +288,26 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if (!$otherProperty->isPublic()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($otherProperty->isStatic()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if (!$otherProperty->isReadable()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $otherPropertyType = $otherProperty->getReadableType(); - $isSuperType = $propertyType->isSuperTypeOf($otherPropertyType); + $isSuperType = $propertyType->isSuperTypeOfWithReason($otherPropertyType); if ($isSuperType->no()) { return $isSuperType; } $result = $result->and($isSuperType); } - return $result->and($type->isObject()); + return $result->and(new IsSuperTypeOfResult($type->isObject(), [])); } public function equals(Type $type): bool diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 31a9613e918..3e8ac1371f2 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -71,7 +71,7 @@ class ObjectType implements TypeWithClassName, SubtractableType private ?Type $subtractedType; - /** @var array> */ + /** @var array> */ private static array $superTypes = []; private ?self $cachedParent = null; @@ -314,10 +314,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { $thatClassNames = $type->getObjectClassNames(); if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $thisDescription = $this->describeCache(); @@ -333,31 +338,31 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if ($type instanceof CompoundType) { - return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); + return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOfWithReason($this); } if ($type instanceof ClosureType) { - return self::$superTypes[$thisDescription][$description] = $this->isInstanceOf(Closure::class); + return self::$superTypes[$thisDescription][$description] = new IsSuperTypeOfResult($this->isInstanceOf(Closure::class), []); } if ($type instanceof ObjectWithoutClassType) { if ($type->getSubtractedType() !== null) { $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } } - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - $transformResult = static fn (TrinaryLogic $result) => $result; + $transformResult = static fn (IsSuperTypeOfResult $result) => $result; if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOf($type); + $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($type); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } if ($isSuperType->maybe()) { - $transformResult = static fn (TrinaryLogic $result) => $result->and(TrinaryLogic::createMaybe()); + $transformResult = static fn (IsSuperTypeOfResult $result) => $result->and(IsSuperTypeOfResult::createMaybe()); } } @@ -365,9 +370,9 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); if ($isSuperType->yes()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } } @@ -377,43 +382,43 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if ($thatClassNames[0] === $thisClassName) { - return $transformResult(TrinaryLogic::createYes()); + return $transformResult(IsSuperTypeOfResult::createYes()); } $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); $thisClassReflection = $this->getClassReflection(); if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } $thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]); if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($thisClassReflection->getName() === $thatClassReflection->getName()) { - return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes()); + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } if ($thatClassReflection->isSubclassOf($thisClassName)) { - return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes()); + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } if ($thisClassReflection->isSubclassOf($thatClassNames[0])) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 1144acd2f83..52dd0e7a21f 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -69,38 +69,43 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } if ($type instanceof self) { if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->subtractedType !== null) { - $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); if ($isSuperType->yes()) { - return TrinaryLogic::createYes(); + return $isSuperType; } } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof ObjectShapeType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->getObjectClassNames() === []) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } if ($this->subtractedType === null) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return $this->subtractedType->isSuperTypeOf($type)->negate(); + return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); } public function equals(Type $type): bool diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 0be91db587c..9cf4da4dc8d 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -152,17 +152,22 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOf($type); + return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); } if ($type instanceof ObjectWithoutClassType) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof ObjectType) { - $result = $this->getStaticObjectType()->isSuperTypeOf($type); + $result = $this->getStaticObjectType()->isSuperTypeOfWithReason($type); if ($result->yes()) { $classReflection = $type->getClassReflection(); if ($classReflection !== null && $classReflection->isFinal()) { @@ -170,14 +175,14 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } } - return $result->and(TrinaryLogic::createMaybe()); + return $result->and(IsSuperTypeOfResult::createMaybe()); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 00a91412337..13b0fee7ded 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -80,19 +80,29 @@ public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): public function isSuperTypeOf(Type $type): TrinaryLogic { - return TrinaryLogic::createYes(); + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::createYes(); } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($otherType instanceof MixedType && !$otherType instanceof TemplateMixedType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } public function equals(Type $type): bool diff --git a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php index 0130d7e0948..24a88696cb2 100644 --- a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php +++ b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php @@ -9,25 +9,30 @@ class StringAlwaysAcceptingObjectWithToStringType extends StringType { public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } $thatClassNames = $type->getObjectClassNames(); if ($thatClassNames === []) { - return parent::isSuperTypeOf($type); + return parent::isSuperTypeOfWithReason($type); } - $result = TrinaryLogic::createNo(); + $result = IsSuperTypeOfResult::createNo(); $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); foreach ($thatClassNames as $thatClassName) { if (!$reflectionProvider->hasClass($thatClassName)) { - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } $typeClass = $reflectionProvider->getClass($thatClassName); - $result = $result->or(TrinaryLogic::createFromBoolean($typeClass->hasNativeMethod('__toString'))); + $result = $result->or(IsSuperTypeOfResult::createFromBoolean($typeClass->hasNativeMethod('__toString'))); } return $result; diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 963f98c1918..3eb3eb845a4 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -35,18 +35,23 @@ public function describe(VerbosityLevel $level): string } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOf($type); + return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } $parent = new parent($this->getClassReflection(), $this->getSubtractedType()); - return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe()); + return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); } public function changeSubtractedType(?Type $subtractedType): Type diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 527d10b68a1..4c8c6a72945 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -10,6 +10,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\ConstantScalarType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LooseComparisonHelper; use PHPStan\Type\Type; @@ -35,20 +36,25 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createFromBoolean($this->equals($type)); + return IsSuperTypeOfResult::createFromBoolean($this->equals($type)); } if ($type instanceof parent) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 3a90652de9f..76855da1f48 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -14,6 +14,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LateResolvableType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -59,24 +60,29 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { return $this->isSuperTypeOfDefault($type); } - private function isSuperTypeOfDefault(Type $type): TrinaryLogic + private function isSuperTypeOfDefault(Type $type): IsSuperTypeOfResult { if ($type instanceof NeverType) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof LateResolvableType) { $type = $type->resolve(); } - $isSuperType = $this->resolve()->isSuperTypeOf($type); + $isSuperType = $this->resolve()->isSuperTypeOfWithReason($type); if (!$this->isResolvable()) { - $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); + $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); } return $isSuperType; @@ -523,14 +529,19 @@ public function tryRemove(Type $typeToRemove): ?Type } public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isSubTypeOf($otherType); + return $result->isSubTypeOfWithReason($otherType); } - return $otherType->isSuperTypeOf($result); + return $otherType->isSuperTypeOfWithReason($result); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/Type.php b/src/Type/Type.php index b0ed8a7788b..6c5064f4eae 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -78,6 +78,15 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult; public function isSuperTypeOf(Type $type): TrinaryLogic; + /** + * This is like isSuperTypeOf() but gives reasons + * why the type was not/might not be accepted in some non-intuitive scenarios. + * + * In PHPStan 2.0 this method will be removed and the return type of isSuperTypeOf() + * will change to IsSuperTypeOfResult. + */ + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult; + public function equals(Type $type): bool; public function describe(VerbosityLevel $level): string; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 0a3479d9a82..6b828625b6b 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -205,6 +205,11 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($otherType)->result; + } + + public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult { if ( ($otherType instanceof self && !$otherType instanceof TemplateUnionType) @@ -214,16 +219,16 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic || $otherType instanceof ConditionalTypeForParameter || $otherType instanceof IntegerRangeType ) { - return $otherType->isSubTypeOf($this); + return $otherType->isSubTypeOfWithReason($this); } - $result = TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType)); + $result = IsSuperTypeOfResult::createNo()->or(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); if ($result->yes()) { return $result; } if ($otherType instanceof TemplateUnionType) { - return $result->or($otherType->isSubTypeOf($this)); + return $result->or($otherType->isSubTypeOfWithReason($this)); } return $result; @@ -231,7 +236,12 @@ public function isSuperTypeOf(Type $otherType): TrinaryLogic public function isSubTypeOf(Type $otherType): TrinaryLogic { - return TrinaryLogic::lazyExtremeIdentity($this->getTypes(), static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType)); + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + { + return IsSuperTypeOfResult::extremeIdentity(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index add4ffca45a..4353c6efdd3 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -70,16 +70,21 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult } public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOf($this); + return $type->isSubTypeOfWithReason($this); } - return TrinaryLogic::createNo(); + return IsSuperTypeOfResult::createNo(); } public function equals(Type $type): bool From 254d9a575665bdfd9093130b3ee426db089a2366 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 10:29:11 +0200 Subject: [PATCH 0640/3097] Fix BC break --- src/Type/Generic/GenericObjectType.php | 6 ++++-- src/Type/Generic/TemplateType.php | 4 ++-- src/Type/Generic/TemplateTypeTrait.php | 2 +- src/Type/Generic/TemplateTypeVariance.php | 21 +++++++++++---------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 24e4aa8f9fe..6945ccaf407 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -198,9 +198,11 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSupe $thisVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant(); $ancestorVariance = $ancestor->variances[$i] ?? TemplateTypeVariance::createInvariant(); if (!$thisVariance->invariant()) { - $results[] = $thisVariance->isValidVarianceWithReason($templateType, $this->types[$i], $ancestor->types[$i]); + $result = $thisVariance->isValidVarianceWithReason($templateType, $this->types[$i], $ancestor->types[$i]); + $results[] = new IsSuperTypeOfResult($result->result, $result->reasons); } else { - $results[] = $templateType->isValidVarianceWithReason($this->types[$i], $ancestor->types[$i]); + $result = $templateType->isValidVarianceWithReason($this->types[$i], $ancestor->types[$i]); + $results[] = new IsSuperTypeOfResult($result->result, $result->reasons); } $results[] = IsSuperTypeOfResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 7398fe836f9..7661078ca1b 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -3,8 +3,8 @@ namespace PHPStan\Type\Generic; use PHPStan\TrinaryLogic; +use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; -use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Type; /** @api */ @@ -24,7 +24,7 @@ public function isArgument(): bool; public function isValidVariance(Type $a, Type $b): TrinaryLogic; - public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult; + public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult; public function getVariance(): TemplateTypeVariance; diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index b344be36905..b6cca8b2905 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -99,7 +99,7 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason($a, $b)->result; } - public function isValidVarianceWithReason(Type $a, Type $b): IsSuperTypeOfResult + public function isValidVarianceWithReason(Type $a, Type $b): AcceptsResult { return $this->variance->isValidVarianceWithReason($this, $a, $b); } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index c48a457b19b..b0062b42111 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -5,6 +5,7 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\AcceptsResult; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; @@ -134,30 +135,30 @@ public function isValidVariance(Type $a, Type $b): TrinaryLogic return $this->isValidVarianceWithReason(null, $a, $b)->result; } - public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult + public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): AcceptsResult { if ($b instanceof NeverType) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } if ($a instanceof MixedType && !$a instanceof TemplateType) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } if ($a instanceof BenevolentUnionType) { if (!$a->isSuperTypeOf($b)->no()) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } } if ($b instanceof BenevolentUnionType) { if (!$b->isSuperTypeOf($a)->no()) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } } if ($b instanceof MixedType && !$b instanceof TemplateType) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } if ($this->invariant()) { @@ -177,19 +178,19 @@ public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, } } - return new IsSuperTypeOfResult(TrinaryLogic::createFromBoolean($result), $reasons); + return new AcceptsResult(TrinaryLogic::createFromBoolean($result), $reasons); } if ($this->covariant()) { - return $a->isSuperTypeOfWithReason($b); + return $a->isSuperTypeOfWithReason($b)->toAcceptsResult(); } if ($this->contravariant()) { - return $b->isSuperTypeOfWithReason($a); + return $b->isSuperTypeOfWithReason($a)->toAcceptsResult(); } if ($this->bivariant()) { - return IsSuperTypeOfResult::createYes(); + return AcceptsResult::createYes(); } throw new ShouldNotHappenException(); From b256d502e7889454f5255b563afea936557612d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 10:37:46 +0200 Subject: [PATCH 0641/3097] [BCB] Changed `TemplateType::isValidVariance()` return type to IsSuperTypeOfResult --- UPGRADING.md | 1 + src/Type/Generic/GenericObjectType.php | 6 ++---- src/Type/Generic/TemplateType.php | 4 ++-- src/Type/Generic/TemplateTypeTrait.php | 2 +- src/Type/Generic/TemplateTypeVariance.php | 21 ++++++++++----------- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 6cfb483e2af..c46f3e17426 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -313,6 +313,7 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint * Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to `IsSuperTypeOfResult` * `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * Changes around `ClassConstantReflection` * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index b02995393b2..779bfcc98dd 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -190,11 +190,9 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSupe $thisVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant(); $ancestorVariance = $ancestor->variances[$i] ?? TemplateTypeVariance::createInvariant(); if (!$thisVariance->invariant()) { - $result = $thisVariance->isValidVariance($templateType, $this->types[$i], $ancestor->types[$i]); - $results[] = new IsSuperTypeOfResult($result->result, $result->reasons); + $results[] = $thisVariance->isValidVariance($templateType, $this->types[$i], $ancestor->types[$i]); } else { - $result = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); - $results[] = new IsSuperTypeOfResult($result->result, $result->reasons); + $results[] = $templateType->isValidVariance($this->types[$i], $ancestor->types[$i]); } $results[] = IsSuperTypeOfResult::createFromBoolean($thisVariance->validPosition($ancestorVariance)); diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 367c94e7095..4858d69242b 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -2,8 +2,8 @@ namespace PHPStan\Type\Generic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\Type; /** @api */ @@ -21,7 +21,7 @@ public function toArgument(): TemplateType; public function isArgument(): bool; - public function isValidVariance(Type $a, Type $b): AcceptsResult; + public function isValidVariance(Type $a, Type $b): IsSuperTypeOfResult; public function getVariance(): TemplateTypeVariance; diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 8e8df311f97..053bbf81604 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -92,7 +92,7 @@ public function toArgument(): TemplateType ); } - public function isValidVariance(Type $a, Type $b): AcceptsResult + public function isValidVariance(Type $a, Type $b): IsSuperTypeOfResult { return $this->variance->isValidVariance($this, $a, $b); } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index bbd07903508..a2269616c9d 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -5,7 +5,6 @@ use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\AcceptsResult; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; @@ -127,30 +126,30 @@ public function compose(self $other): self return $other; } - public function isValidVariance(TemplateType $templateType, Type $a, Type $b): AcceptsResult + public function isValidVariance(TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult { if ($b instanceof NeverType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof MixedType && !$a instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($a instanceof BenevolentUnionType) { if (!$a->isSuperTypeOf($b)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof BenevolentUnionType) { if (!$b->isSuperTypeOf($a)->no()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } } if ($b instanceof MixedType && !$b instanceof TemplateType) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($this->invariant()) { @@ -169,19 +168,19 @@ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): A } } - return new AcceptsResult(TrinaryLogic::createFromBoolean($result), $reasons); + return new IsSuperTypeOfResult(TrinaryLogic::createFromBoolean($result), $reasons); } if ($this->covariant()) { - return $a->isSuperTypeOfWithReason($b)->toAcceptsResult(); + return $a->isSuperTypeOfWithReason($b); } if ($this->contravariant()) { - return $b->isSuperTypeOfWithReason($a)->toAcceptsResult(); + return $b->isSuperTypeOfWithReason($a); } if ($this->bivariant()) { - return AcceptsResult::createYes(); + return IsSuperTypeOfResult::createYes(); } throw new ShouldNotHappenException(); From c119c9eca6ffebd5e1112f098292c0ca60503ed6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 27 Sep 2024 23:22:43 +0200 Subject: [PATCH 0642/3097] Int::toString is lowercase --- src/Type/IntegerRangeType.php | 3 ++ src/Type/IntegerType.php | 2 + .../Analyser/nsrt/array-key-exists.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-10863.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-11129.php | 52 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-11716.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4587.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-7387.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-8568.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8635.php | 2 +- .../Analyser/nsrt/cast-to-numeric-string.php | 28 +++++----- tests/PHPStan/Analyser/nsrt/filter-var.php | 2 +- tests/PHPStan/Analyser/nsrt/generics.php | 6 +-- tests/PHPStan/Analyser/nsrt/key-exists.php | 8 +-- .../PHPStan/Analyser/nsrt/range-to-string.php | 2 +- .../nsrt/set-type-type-specifying.php | 2 +- tests/PHPStan/Analyser/nsrt/strval.php | 4 +- .../PHPStan/Rules/DeadCode/data/bug-8620.php | 2 +- .../PHPStan/Rules/Generics/data/bug-6301.php | 2 +- 19 files changed, 74 insertions(+), 69 deletions(-) diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 1b914804dcd..248d994266e 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -10,6 +10,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -485,6 +486,7 @@ public function toString(): Type if ($isZero->no()) { return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), new AccessoryNonFalsyStringType(), ]); @@ -492,6 +494,7 @@ public function toString(): Type return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index f91a646c9dd..354067f0a97 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -6,6 +6,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -81,6 +82,7 @@ public function toString(): Type { return new IntersectionType([ new StringType(), + new AccessoryLowercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/tests/PHPStan/Analyser/nsrt/array-key-exists.php b/tests/PHPStan/Analyser/nsrt/array-key-exists.php index ed6f552d155..3ae615b2d9a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/array-key-exists.php @@ -50,16 +50,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (array_key_exists($key2, $a)) { - assertType('numeric-string', $key2); + assertType('lowercase-string&numeric-string', $key2); } if (array_key_exists($key3, $a)) { - assertType('int|numeric-string', $key3); + assertType('int|(lowercase-string&numeric-string)', $key3); } if (array_key_exists($key4, $a)) { - assertType('(int|numeric-string)', $key4); + assertType('(int|(lowercase-string&numeric-string))', $key4); } if (array_key_exists($key5, $a)) { - assertType('int|numeric-string', $key5); + assertType('int|(lowercase-string&numeric-string)', $key5); } if (array_key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10863.php b/tests/PHPStan/Analyser/nsrt/bug-10863.php index ecad48736ec..275bc3774e8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10863.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10863.php @@ -12,7 +12,7 @@ class Foo */ public function doFoo($b): void { - assertType('non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string', '@' . $b); } /** @@ -20,7 +20,7 @@ public function doFoo($b): void */ public function doFoo2($b): void { - assertType('non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string', '@' . $b); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11129.php b/tests/PHPStan/Analyser/nsrt/bug-11129.php index 8637ad7c4a1..1f60b4089fb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11129.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11129.php @@ -21,14 +21,14 @@ public function foo( $maybeNegativeConstStrings, $maybeNonNumericConstStrings, $maybeFloatConstStrings, bool $bool, float $float ): void { - assertType('non-falsy-string', '0'.$i); - assertType('non-falsy-string&numeric-string', $i.'0'); + assertType('lowercase-string&non-falsy-string', '0'.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.'0'); - assertType('non-falsy-string&numeric-string', '0'.$positiveInt); - assertType('non-falsy-string&numeric-string', $positiveInt.'0'); + assertType('lowercase-string&non-falsy-string&numeric-string', '0'.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.'0'); - assertType('non-falsy-string', '0'.$negativeInt); - assertType('non-falsy-string&numeric-string', $negativeInt.'0'); + assertType('lowercase-string&non-falsy-string', '0'.$negativeInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.'0'); assertType("'00'|'01'|'02'", '0'.$positiveConstStrings); assertType( "'00'|'10'|'20'", $positiveConstStrings.'0'); @@ -39,29 +39,29 @@ public function foo( assertType("'00'|'01'|'0a'", '0'.$maybeNonNumericConstStrings); assertType("'00'|'10'|'a0'", $maybeNonNumericConstStrings.'0'); - assertType('non-falsy-string&numeric-string', $i.$positiveConstStrings); - assertType( 'non-falsy-string', $positiveConstStrings.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $i.$positiveConstStrings); + assertType('lowercase-string&non-falsy-string', $positiveConstStrings.$i); - assertType('non-falsy-string', $i.$maybeNegativeConstStrings); - assertType('non-falsy-string', $maybeNegativeConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$i); - assertType('non-falsy-string', $i.$maybeNonNumericConstStrings); - assertType('non-falsy-string', $maybeNonNumericConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeNonNumericConstStrings); + assertType('lowercase-string&non-falsy-string', $maybeNonNumericConstStrings.$i); - assertType('non-falsy-string', $i.$maybeFloatConstStrings); // could be 'non-falsy-string&numeric-string' - assertType('non-falsy-string', $maybeFloatConstStrings.$i); + assertType('lowercase-string&non-falsy-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' + assertType('lowercase-string&non-falsy-string', $maybeFloatConstStrings.$i); - assertType('non-empty-string&numeric-string', $i.$bool); - assertType('non-empty-string', $bool.$i); - assertType('non-falsy-string&numeric-string', $positiveInt.$bool); - assertType('non-falsy-string&numeric-string', $bool.$positiveInt); - assertType('non-falsy-string&numeric-string', $negativeInt.$bool); - assertType('non-falsy-string', $bool.$negativeInt); + assertType('lowercase-string&non-empty-string&numeric-string', $i.$bool); + assertType('lowercase-string&non-empty-string', $bool.$i); + assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.$bool); + assertType('lowercase-string&non-falsy-string&numeric-string', $bool.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.$bool); + assertType('lowercase-string&non-falsy-string', $bool.$negativeInt); - assertType('non-falsy-string', $i.$i); - assertType('non-falsy-string', $negativeInt.$negativeInt); - assertType('non-falsy-string', $maybeNegativeConstStrings.$negativeInt); - assertType('non-falsy-string', $negativeInt.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string', $i.$i); + assertType('lowercase-string&non-falsy-string', $negativeInt.$negativeInt); + assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$negativeInt); + assertType('lowercase-string&non-falsy-string', $negativeInt.$maybeNegativeConstStrings); // https://3v4l.org/BCS2K assertType('non-falsy-string', $float.$float); @@ -75,9 +75,9 @@ public function foo( // https://3v4l.org/Ia4r0 $scientificFloatAsString = '3e4'; assertType('non-falsy-string', $numericString.$scientificFloatAsString); - assertType('non-falsy-string', $i.$scientificFloatAsString); + assertType('lowercase-string&non-falsy-string', $i.$scientificFloatAsString); assertType('non-falsy-string', $scientificFloatAsString.$numericString); - assertType('non-falsy-string', $scientificFloatAsString.$i); + assertType('lowercase-string&non-falsy-string', $scientificFloatAsString.$i); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index e637669483c..83c10f32258 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -75,7 +75,7 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyed assertType('int', $i); if (isset($intKeyedArr[$s])) { - assertType("numeric-string", $s); + assertType("lowercase-string&numeric-string", $s); } else { assertType('string', $s); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4587.php b/tests/PHPStan/Analyser/nsrt/bug-4587.php index 644b9d7b797..623fea93af1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4587.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4587.php @@ -27,11 +27,11 @@ public function b(): void $type = array_map(static function (array $result): array { assertType('array{a: int}', $result); $result['a'] = (string) $result['a']; - assertType('array{a: numeric-string}', $result); + assertType('array{a: lowercase-string&numeric-string}', $result); return $result; }, $results); - assertType('list', $type); + assertType('list', $type); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index e1929a795a4..4f28696fb99 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -29,7 +29,7 @@ public function inputTypes(int $i, float $f, string $s, int $intRange) { public function specifiers(int $i) { // https://3v4l.org/fmVIg - assertType('numeric-string', sprintf('%14s', $i)); + assertType('lowercase-string&numeric-string', sprintf('%14s', $i)); assertType('lowercase-string&numeric-string', sprintf('%d', $i)); @@ -59,9 +59,9 @@ public function specifiers(int $i) { */ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) { // https://3v4l.org/vVL0c - assertType('numeric-string', sprintf('%2$6s', $mixed, $i)); - assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); - assertType('non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); + assertType('lowercase-string&numeric-string', sprintf('%2$6s', $mixed, $i)); + assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); + assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange)); // https://3v4l.org/1ECIq diff --git a/tests/PHPStan/Analyser/nsrt/bug-8568.php b/tests/PHPStan/Analyser/nsrt/bug-8568.php index 71db7a6c98a..9236447acf6 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8568.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8568.php @@ -8,7 +8,7 @@ class HelloWorld { public function sayHello(): void { - assertType('non-falsy-string', 'a' . $this->get()); + assertType('lowercase-string&non-falsy-string', 'a' . $this->get()); } public function get(): ?int diff --git a/tests/PHPStan/Analyser/nsrt/bug-8635.php b/tests/PHPStan/Analyser/nsrt/bug-8635.php index 18952545792..fe49aa8a2db 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8635.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8635.php @@ -8,6 +8,6 @@ class HelloWorld { public function EchoInt(int $value): void { - assertType('numeric-string', "$value"); + assertType('lowercase-string&numeric-string', "$value"); } } diff --git a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php index c7d6fcb84c1..7c13500af25 100644 --- a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php @@ -13,13 +13,13 @@ * @param 1 $constantInt */ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('numeric-string', (string)$a); + assertType('lowercase-string&numeric-string', (string)$a); assertType('numeric-string', (string)$b); assertType('numeric-string', (string)$numeric); assertType('numeric-string', (string)$numeric2); assertType('numeric-string', (string)$number); - assertType('non-falsy-string&numeric-string', (string)$positive); - assertType('non-falsy-string&numeric-string', (string)$negative); + assertType('lowercase-string&non-falsy-string&numeric-string', (string)$positive); + assertType('lowercase-string&non-falsy-string&numeric-string', (string)$negative); assertType("'1'", (string)$constantInt); } @@ -32,28 +32,28 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param 1 $constantInt */ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('numeric-string', '' . $a); + assertType('lowercase-string&numeric-string', '' . $a); assertType('numeric-string', '' . $b); assertType('numeric-string', '' . $numeric); assertType('numeric-string', '' . $numeric2); assertType('numeric-string', '' . $number); - assertType('non-falsy-string&numeric-string', '' . $positive); - assertType('non-falsy-string&numeric-string', '' . $negative); + assertType('lowercase-string&non-falsy-string&numeric-string', '' . $positive); + assertType('lowercase-string&non-falsy-string&numeric-string', '' . $negative); assertType("'1'", '' . $constantInt); - assertType('numeric-string', $a . ''); + assertType('lowercase-string&numeric-string', $a . ''); assertType('numeric-string', $b . ''); assertType('numeric-string', $numeric . ''); assertType('numeric-string', $numeric2 . ''); assertType('numeric-string', $number . ''); - assertType('non-falsy-string&numeric-string', $positive . ''); - assertType('non-falsy-string&numeric-string', $negative . ''); + assertType('lowercase-string&non-falsy-string&numeric-string', $positive . ''); + assertType('lowercase-string&non-falsy-string&numeric-string', $negative . ''); assertType("'1'", $constantInt . ''); } function concatAssignEmptyString(int $i, float $f) { $i .= ''; - assertType('numeric-string', $i); + assertType('lowercase-string&numeric-string', $i); $s = ''; $s .= $f; @@ -66,13 +66,13 @@ function concatAssignEmptyString(int $i, float $f) { */ function integerRangeToString($positive, $negative) { - assertType('numeric-string', (string) $positive); - assertType('numeric-string', (string) $negative); + assertType('lowercase-string&numeric-string', (string) $positive); + assertType('lowercase-string&numeric-string', (string) $negative); if ($positive !== 0) { - assertType('non-falsy-string&numeric-string', (string) $positive); + assertType('lowercase-string&non-falsy-string&numeric-string', (string) $positive); } if ($negative !== 0) { - assertType('non-falsy-string&numeric-string', (string) $negative); + assertType('lowercase-string&non-falsy-string&numeric-string', (string) $negative); } } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index e726c77d75c..28580c448c4 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -158,7 +158,7 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType("'17'", filter_var(17.0)); assertType("'17.1'", filter_var(17.1)); assertType("'1.0E-50'", filter_var(1e-50)); - assertType('numeric-string', filter_var($int)); + assertType('lowercase-string&numeric-string', filter_var($int)); assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index bcd7ecf6160..873ad66b6d5 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -147,10 +147,10 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('Closure(int): numeric-string', function (int $a): string { + assertType('Closure(int): (lowercase-string&numeric-string)', function (int $a): string { return (string)$a; }); - assertType('array', f($arrayOfInt, function (int $a): string { + assertType('array', f($arrayOfInt, function (int $a): string { return (string)$a; })); assertType('Closure(mixed): string', function ($a): string { @@ -224,7 +224,7 @@ function testArrayMap(array $listOfIntegers) return (string) $int; }, $listOfIntegers); - assertType('array', $strings); + assertType('array', $strings); } /** diff --git a/tests/PHPStan/Analyser/nsrt/key-exists.php b/tests/PHPStan/Analyser/nsrt/key-exists.php index 11c2ed6a2a3..3c85802e739 100644 --- a/tests/PHPStan/Analyser/nsrt/key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/key-exists.php @@ -49,16 +49,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (key_exists($key2, $a)) { - assertType('numeric-string', $key2); + assertType('lowercase-string&numeric-string', $key2); } if (key_exists($key3, $a)) { - assertType('int|numeric-string', $key3); + assertType('int|(lowercase-string&numeric-string)', $key3); } if (key_exists($key4, $a)) { - assertType('(int|numeric-string)', $key4); + assertType('(int|(lowercase-string&numeric-string))', $key4); } if (key_exists($key5, $a)) { - assertType('int|numeric-string', $key5); + assertType('int|(lowercase-string&numeric-string)', $key5); } if (key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/range-to-string.php b/tests/PHPStan/Analyser/nsrt/range-to-string.php index 494c135d95b..2d70eb4babc 100644 --- a/tests/PHPStan/Analyser/nsrt/range-to-string.php +++ b/tests/PHPStan/Analyser/nsrt/range-to-string.php @@ -17,6 +17,6 @@ public function sayHello($i, $ii, $maxlong, $toolong): void assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i); assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii); assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong); - assertType("numeric-string", (string) $toolong); + assertType("lowercase-string&numeric-string", (string) $toolong); } } diff --git a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php index 11869c0623f..1ab4badbe56 100644 --- a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php @@ -11,7 +11,7 @@ function doString(string $s, int $i, float $f, array $a, object $o) assertType('string', $s); settype($i, 'string'); - assertType('numeric-string', $i); + assertType('lowercase-string&numeric-string', $i); settype($f, 'string'); assertType('numeric-string', $f); diff --git a/tests/PHPStan/Analyser/nsrt/strval.php b/tests/PHPStan/Analyser/nsrt/strval.php index 870aafd6b8b..a6c43978936 100644 --- a/tests/PHPStan/Analyser/nsrt/strval.php +++ b/tests/PHPStan/Analyser/nsrt/strval.php @@ -17,9 +17,9 @@ function strvalTest(string $string, string $class): void assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); assertType('\'42\'', strval(42)); - assertType('numeric-string', strval(rand())); + assertType('lowercase-string&numeric-string', strval(rand())); assertType('numeric-string', strval(rand() * 0.1)); - assertType('numeric-string', strval(strval(rand()))); + assertType('lowercase-string&numeric-string', strval(strval(rand()))); assertType('class-string', strval($class)); assertType('string', strval(new \Exception())); assertType('*ERROR*', strval(new \stdClass())); diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php index cad6d811c29..44bc78cd456 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-8620.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-8620.php @@ -9,7 +9,7 @@ class HelloWorld public function nullCoalesceAndConcatenation (?int $a = null): int { $key = ($a ?? "x") . "-"; - assertType('non-falsy-string', $key); + assertType('lowercase-string&non-falsy-string', $key); if ($key === "x-") { return 0; } return 1; diff --git a/tests/PHPStan/Rules/Generics/data/bug-6301.php b/tests/PHPStan/Rules/Generics/data/bug-6301.php index 9811b632531..73dff935f20 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-6301.php +++ b/tests/PHPStan/Rules/Generics/data/bug-6301.php @@ -22,7 +22,7 @@ public function str($s) * @param literal-string $literalString */ public function foo(int $i, $nonEmpty, $numericString, $literalString):void { - assertType('numeric-string', $this->str((string) $i)); + assertType('lowercase-string&numeric-string', $this->str((string) $i)); assertType('non-empty-string', $this->str($nonEmpty)); assertType('numeric-string', $this->str($numericString)); assertType('literal-string', $this->str($literalString)); From f81bc5d33a10b517bb8834bfe21ff85b26802193 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 10:45:30 +0200 Subject: [PATCH 0643/3097] [BCB] Remove `Type::isSuperTypeOfWithReason()`, `Type::isSuperTypeOf()` return type changed to IsSuperTypeOfResult --- UPGRADING.md | 4 +- src/Analyser/TypeSpecifier.php | 4 +- src/Reflection/ParametersAcceptorSelector.php | 2 +- .../Comparison/ImpossibleCheckTypeHelper.php | 4 +- src/Rules/Methods/MethodSignatureRule.php | 4 +- src/Type/Accessory/AccessoryArrayListType.php | 22 +++------- .../Accessory/AccessoryLiteralStringType.php | 20 +++------- .../AccessoryLowercaseStringType.php | 20 +++------- .../Accessory/AccessoryNonEmptyStringType.php | 20 +++------- .../Accessory/AccessoryNonFalsyStringType.php | 20 +++------- .../Accessory/AccessoryNumericStringType.php | 20 +++------- src/Type/Accessory/HasMethodType.php | 18 ++------- src/Type/Accessory/HasOffsetType.php | 18 ++------- src/Type/Accessory/HasOffsetValueType.php | 22 +++------- src/Type/Accessory/HasPropertyType.php | 18 ++------- src/Type/Accessory/NonEmptyArrayType.php | 20 +++------- src/Type/Accessory/OversizedArrayType.php | 20 +++------- src/Type/ArrayType.php | 13 ++---- src/Type/CallableType.php | 20 +++------- src/Type/CallableTypeHelper.php | 4 +- src/Type/ClassStringType.php | 9 +---- src/Type/ClosureType.php | 11 ++--- src/Type/CompoundType.php | 11 +---- src/Type/ConditionalType.php | 12 ++---- src/Type/ConditionalTypeForParameter.php | 12 ++---- src/Type/Constant/ConstantArrayType.php | 15 +++---- src/Type/Constant/ConstantIntegerType.php | 10 +---- src/Type/Constant/ConstantStringType.php | 15 +++---- src/Type/Enum/EnumCaseObjectType.php | 15 +++---- src/Type/FloatType.php | 9 +---- src/Type/Generic/GenericClassStringType.php | 16 +++----- src/Type/Generic/GenericObjectType.php | 12 ++---- src/Type/Generic/TemplateMixedType.php | 6 +-- src/Type/Generic/TemplateStrictMixedType.php | 6 +-- src/Type/Generic/TemplateTypeTrait.php | 26 ++++-------- src/Type/Generic/TemplateTypeVariance.php | 4 +- src/Type/IntegerRangeType.php | 26 ++++-------- src/Type/IntersectionType.php | 20 +++------- src/Type/IsSuperTypeOfResult.php | 5 +++ src/Type/IterableType.php | 40 +++++++------------ src/Type/JustNullableTypeTrait.php | 9 +---- src/Type/MixedType.php | 40 +++++++------------ src/Type/NeverType.php | 16 ++------ src/Type/NonAcceptingNeverType.php | 9 +---- src/Type/NullType.php | 9 +---- src/Type/ObjectShapeType.php | 11 ++--- src/Type/ObjectType.php | 13 ++---- src/Type/ObjectWithoutClassType.php | 13 ++---- src/Type/StaticType.php | 13 ++---- src/Type/StrictMixedType.php | 14 +------ ...gAlwaysAcceptingObjectWithToStringType.php | 12 ++---- src/Type/ThisType.php | 14 ++----- src/Type/Traits/ConstantScalarTypeTrait.php | 9 +---- src/Type/Traits/LateResolvableTypeTrait.php | 20 +++------- src/Type/Type.php | 11 +---- src/Type/UnionType.php | 22 +++------- src/Type/VoidType.php | 9 +---- 57 files changed, 231 insertions(+), 586 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index c46f3e17426..5fe143b645a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -313,7 +313,9 @@ Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::i * `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint * Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) -* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to `IsSuperTypeOfResult` +Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) * `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) * Changes around `ClassConstantReflection` * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1006f336338..a78fc63cbc7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -963,7 +963,7 @@ private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argT $isNormalCount = TrinaryLogic::createYes(); } else { $mode = $scope->getType($countFuncCall->getArgs()[1]->value); - $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate()); + $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($argType->getIterableValueType()->isArray()->negate()); } if ( @@ -1007,7 +1007,7 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, $isNormalCount = TrinaryLogic::createYes(); } else { $mode = $scope->getType($countFuncCall->getArgs()[1]->value); - $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate()); + $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($argType->getIterableValueType()->isArray()->negate()); } if ( diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index cc36c1d9512..455e72a7040 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -510,7 +510,7 @@ public static function selectFromTypes( if ($parameter->getType() instanceof MixedType) { $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); } else { - $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)); + $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)->result); } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index b61904f26b9..48eea97d6f7 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -282,7 +282,7 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureType[1]; - $results[] = $resultType->isSuperTypeOf($argumentType); + $results[] = $resultType->isSuperTypeOf($argumentType)->result; } foreach ($sureNotTypes as $sureNotType) { @@ -300,7 +300,7 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureNotType[1]; - $results[] = $resultType->isSuperTypeOf($argumentType)->negate(); + $results[] = $resultType->isSuperTypeOf($argumentType)->negate()->result; } if (count($results) === 0) { diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index c3c04089ce7..86b4614133b 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -219,7 +219,7 @@ private function checkReturnTypeCompatibility( return [TrinaryLogic::createYes(), $returnType, $parentReturnType]; } - return [$parentReturnType->isSuperTypeOf($returnType), TypehintHelper::decideType( + return [$parentReturnType->isSuperTypeOf($returnType)->result, TypehintHelper::decideType( $currentVariant->getNativeReturnType(), $currentVariant->getPhpDocReturnType(), ), $originalParentReturnType]; @@ -253,7 +253,7 @@ private function checkParameterTypeCompatibility( ); $parentParameterType = $this->transformStaticType($declaringClass, $originalParameterType); - $parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), TypehintHelper::decideType( + $parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType)->result, TypehintHelper::decideType( $parameter->getNativeType(), $parameter->getPhpDocType(), ), $originalParameterType]; diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 3e00d609d95..62e5205b8f2 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -87,33 +87,23 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($isArray->and($isList), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return new IsSuperTypeOfResult($type->isArray()->and($type->isList()), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isList()), [])) @@ -122,7 +112,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool @@ -147,7 +137,7 @@ public function isOffsetAccessLegal(): TrinaryLogic public function hasOffsetValueType(Type $offsetType): TrinaryLogic { - return $this->getIterableKeyType()->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); + return $this->getIterableKeyType()->isSuperTypeOf($offsetType)->result->and(TrinaryLogic::createMaybe()); } public function getOffsetValueType(Type $offsetType): Type diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index a2068428738..4c99637c60e 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -80,15 +80,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isLiteralString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($this->equals($type)) { @@ -98,15 +93,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isLiteralString(), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isLiteralString(), [])) @@ -115,7 +105,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 0893a0e3df9..a959dfbebff 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -76,15 +76,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isLowercaseString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($this->equals($type)) { @@ -94,15 +89,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isLowercaseString(), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [])) @@ -111,7 +101,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 13740719036..d3d4aa406cf 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -78,15 +78,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isNonEmptyString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($this->equals($type)) { @@ -100,15 +95,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isNonEmptyString(), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isNonEmptyString(), [])) @@ -117,7 +107,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index ea97eebab15..871a3473fa4 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -78,15 +78,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isNonFalsyString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($this->equals($type)) { @@ -96,15 +91,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isNonFalsyString(), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } if ($otherType instanceof AccessoryNonEmptyStringType) { @@ -117,7 +107,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 05da3126c50..78fde66dab1 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -77,15 +77,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isNumericString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($this->equals($type)) { @@ -95,15 +90,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isNumericString(), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isNumericString(), [])) @@ -120,7 +110,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes return AcceptsResult::createYes(); } - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 04424d84f0a..94364bb21fc 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -71,25 +71,15 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createFromBoolean($this->equals($type)); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { return new IsSuperTypeOfResult($type->hasMethod($this->methodName), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) { @@ -107,7 +97,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 7401ee56297..10c3722dabc 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -89,12 +89,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { return IsSuperTypeOfResult::createYes(); @@ -102,15 +97,10 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); @@ -121,7 +111,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index ef4a70adf31..c30f30e1c06 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -95,12 +95,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult ); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { return IsSuperTypeOfResult::createYes(); @@ -109,30 +104,25 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $result = new IsSuperTypeOfResult($type->isOffsetAccessible()->and($type->hasOffsetValueType($this->offsetType)), []); return $result - ->and($this->valueType->isSuperTypeOfWithReason($type->getOffsetValueType($this->offsetType))); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; + ->and($this->valueType->isSuperTypeOf($type->getOffsetValueType($this->offsetType))); } - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } $result = new IsSuperTypeOfResult($otherType->isOffsetAccessible()->and($otherType->hasOffsetValueType($this->offsetType)), []); return $result - ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOfWithReason($this->valueType)) + ->and($otherType->getOffsetValueType($this->offsetType)->isSuperTypeOf($this->valueType)) ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 2b8a2d1d06c..71b6b42759d 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -70,25 +70,15 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createFromBoolean($this->equals($type)); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { return new IsSuperTypeOfResult($type->hasProperty($this->propertyName), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } if ($otherType instanceof self) { @@ -102,7 +92,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index beaa20c06bc..b7da64e0b8b 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -85,33 +85,23 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($isArray->and($isIterableAtLeastOnce), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return new IsSuperTypeOfResult($type->isArray()->and($type->isIterableAtLeastOnce()), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isIterableAtLeastOnce()), [])) @@ -120,7 +110,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index ac9e45da1b9..07f76e888a6 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -81,33 +81,23 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isArray()->and($type->isIterableAtLeastOnce()), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->equals($type)) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return new IsSuperTypeOfResult($type->isArray()->and($type->isOversizedArray()), []); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isArray()->and($otherType->isOversizedArray()), [])) @@ -116,7 +106,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 93ce993005f..8fb1a219d53 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -105,20 +105,15 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self || $type instanceof ConstantArrayType) { - return $this->getItemType()->isSuperTypeOfWithReason($type->getItemType()) - ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); + return $this->getItemType()->isSuperTypeOf($type->getItemType()) + ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 74d3b2cc493..c42961fce50 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -135,15 +135,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType && !$type instanceof self) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return $this->isSuperTypeOfInternal($type, false); @@ -191,15 +186,10 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup return $isCallable->and($variantsResult); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return (new IsSuperTypeOfResult($otherType->isCallable(), [])) @@ -208,7 +198,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 203e0d048ec..4e99e94cc9a 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -75,7 +75,7 @@ public static function isParametersAcceptorSuperTypeOf( $isSuperType = $theirParameter->getType()->accepts($ourParameterType, true); $isSuperType = new IsSuperTypeOfResult($isSuperType->result, $isSuperType->reasons); } else { - $isSuperType = $theirParameter->getType()->isSuperTypeOfWithReason($ourParameterType); + $isSuperType = $theirParameter->getType()->isSuperTypeOf($ourParameterType); } if ($isSuperType->maybe()) { @@ -102,7 +102,7 @@ public static function isParametersAcceptorSuperTypeOf( $isReturnTypeSuperType = $ours->getReturnType()->accepts($theirReturnType, true); $isReturnTypeSuperType = new IsSuperTypeOfResult($isReturnTypeSuperType->result, $isReturnTypeSuperType->reasons); } else { - $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOfWithReason($theirReturnType); + $isReturnTypeSuperType = $ours->getReturnType()->isSuperTypeOf($theirReturnType); } $pure = $ours->isPure(); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 57d85316c7b..b5b09e9e737 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -30,15 +30,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isClassString(), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return new IsSuperTypeOfResult($type->isClassString(), []); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 64defc15248..26063e2fa99 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -198,15 +198,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return $this->isSuperTypeOfInternal($type, false); @@ -226,7 +221,7 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup return IsSuperTypeOfResult::createMaybe(); } - return $this->objectType->isSuperTypeOfWithReason($type); + return $this->objectType->isSuperTypeOf($type); } public function equals(Type $type): bool diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index 54637fd45b9..f66e10c0912 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -9,18 +9,9 @@ interface CompoundType extends Type { - public function isSubTypeOf(Type $otherType): TrinaryLogic; - public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult; - /** - * This is like isSubTypeOf() but gives reasons - * why the type was not/might not be accepted in some non-intuitive scenarios. - * - * In PHPStan 2.0 this method will be removed and the return type of isSubTypeOf() - * will change to IsSuperTypeOfResult. - */ - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult; + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult; public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index ab283f5e23d..f154fb5368a 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -4,7 +4,6 @@ use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\LateResolvableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -61,16 +60,11 @@ public function isNegated(): bool return $this->negated; } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOfWithReason($type->if) - ->and($this->else->isSuperTypeOfWithReason($type->else)); + return $this->if->isSuperTypeOf($type->if) + ->and($this->else->isSuperTypeOf($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index 89ae748982b..0fd8cf44759 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -4,7 +4,6 @@ use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\LateResolvableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -75,16 +74,11 @@ public function toConditional(Type $subject): Type ); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->if->isSuperTypeOfWithReason($type->if) - ->and($this->else->isSuperTypeOfWithReason($type->else)); + return $this->if->isSuperTypeOf($type->if) + ->and($this->else->isSuperTypeOf($type->else)); } return $this->isSuperTypeOfDefault($type); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index b77802d7595..46315a4eae3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -365,12 +365,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $result; } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { if (count($this->keyTypes) === 0) { @@ -391,7 +386,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $results[] = IsSuperTypeOfResult::createMaybe(); } - $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOfWithReason($type->getOffsetValueType($keyType)); + $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOf($type->getOffsetValueType($keyType)); if ($isValueSuperType->no()) { return $isValueSuperType; } @@ -407,16 +402,16 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return $result; } - $isKeySuperType = $this->getKeyType()->isSuperTypeOfWithReason($type->getKeyType()); + $isKeySuperType = $this->getKeyType()->isSuperTypeOf($type->getKeyType()); if ($isKeySuperType->no()) { return $isKeySuperType; } - return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOfWithReason($type->getItemType())); + return $result->and($isKeySuperType, $this->getItemType()->isSuperTypeOf($type->getItemType())); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index ce9d4901164..52f29d37a29 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -5,7 +5,6 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\GeneralizePrecision; @@ -38,12 +37,7 @@ public function getValue(): int return $this->value; } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return $this->value === $type->value ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createNo(); @@ -64,7 +58,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 2643ab810aa..f2991857b7b 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -139,12 +139,7 @@ private function export(string $value): string return "'" . addcslashes($value, '\\\'') . "'"; } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof GenericClassStringType) { $genericType = $type->getGenericType(); @@ -162,9 +157,9 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); } else { - $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); + $isSuperType = $genericType->isSuperTypeOf($objectType); } // Explicitly handle the uncertainty for Yes & Maybe. @@ -186,7 +181,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); @@ -351,7 +346,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType->isInteger()->yes()) { $strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1); - return $strLenType->isSuperTypeOf($offsetType); + return $strLenType->isSuperTypeOf($offsetType)->result; } return parent::hasOffsetValueType($offsetType); diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 72ee1bf9d2e..5e803a6af95 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -62,15 +62,10 @@ public function equals(Type $type): bool public function accepts(Type $type, bool $strictTypes): AcceptsResult { - return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); + return $this->isSuperTypeOf($type)->toAcceptsResult(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createFromBoolean( @@ -79,14 +74,14 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ( $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); if ($isSuperType->yes()) { return IsSuperTypeOfResult::createNo(); } @@ -94,7 +89,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $parent = new parent($this->getClassName(), $this->getSubtractedType(), $this->getClassReflection()); - return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); + return $parent->isSuperTypeOf($type)->and(IsSuperTypeOfResult::createMaybe()); } public function subtract(Type $type): Type diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index d1a77c2f76b..531b177af93 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -74,19 +74,14 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index 9fe2da3a792..4e04a9df287 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -6,7 +6,6 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ReflectionProviderStaticAccessor; -use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; @@ -86,15 +85,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->type->accepts($objectType, $strictTypes); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($type instanceof ConstantStringType) { @@ -114,9 +108,9 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult // Do not use TemplateType's isSuperTypeOf handling directly because it takes ObjectType // uncertainty into account. if ($genericType instanceof TemplateType) { - $isSuperType = $genericType->getBound()->isSuperTypeOfWithReason($objectType); + $isSuperType = $genericType->getBound()->isSuperTypeOf($objectType); } else { - $isSuperType = $genericType->isSuperTypeOfWithReason($objectType); + $isSuperType = $genericType->isSuperTypeOf($objectType); } if (!$type->isClassString()->yes()) { @@ -125,7 +119,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return $isSuperType; } elseif ($type instanceof self) { - return $this->type->isSuperTypeOfWithReason($type->type); + return $this->type->isSuperTypeOf($type->type); } elseif ($type instanceof StringType) { return IsSuperTypeOfResult::createMaybe(); } diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 779bfcc98dd..a2bdadd7aec 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -13,7 +13,6 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; -use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; @@ -125,15 +124,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return $this->isSuperTypeOfInternal($type, false); @@ -141,7 +135,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSuperTypeOfResult { - $nakedSuperTypeOf = parent::isSuperTypeOfWithReason($type); + $nakedSuperTypeOf = parent::isSuperTypeOf($type); if ($nakedSuperTypeOf->no()) { return $nakedSuperTypeOf; } diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index e421d8f9a33..a62f9d6f14f 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -2,8 +2,8 @@ namespace PHPStan\Type\Generic; -use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; @@ -35,14 +35,14 @@ public function __construct( $this->bound = $bound; } - public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + public function isSuperTypeOfMixed(MixedType $type): IsSuperTypeOfResult { return $this->isSuperTypeOf($type); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); + $isSuperType = $this->isSuperTypeOf($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index f363f249cde..13f52b276c0 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -2,8 +2,8 @@ namespace PHPStan\Type\Generic; -use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; @@ -33,14 +33,14 @@ public function __construct( $this->bound = $bound; } - public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + public function isSuperTypeOfMixed(MixedType $type): IsSuperTypeOfResult { return $this->isSuperTypeOf($type); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } } diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 053bbf81604..5ea5a7da041 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -189,31 +189,21 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->strategy->accepts($this, $type, $strictTypes); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof TemplateType || $type instanceof IntersectionType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($type instanceof NeverType) { return IsSuperTypeOfResult::createYes(); } - return $this->getBound()->isSuperTypeOfWithReason($type) + return $this->getBound()->isSuperTypeOf($type) ->and(IsSuperTypeOfResult::createMaybe()); } - public function isSubTypeOf(Type $type): TrinaryLogic - { - return $this->isSubTypeOfWithReason($type)->result; - } - - public function isSubTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSubTypeOf(Type $type): IsSuperTypeOfResult { /** @var TBound $bound */ $bound = $this->getBound(); @@ -223,18 +213,18 @@ public function isSubTypeOfWithReason(Type $type): IsSuperTypeOfResult && !$type instanceof TemplateType && ($type instanceof UnionType || $type instanceof IntersectionType) ) { - return $type->isSuperTypeOfWithReason($this); + return $type->isSuperTypeOf($this); } if (!$type instanceof TemplateType) { - return $type->isSuperTypeOfWithReason($this->getBound()); + return $type->isSuperTypeOf($this->getBound()); } if ($this->getScope()->equals($type->getScope()) && $this->getName() === $type->getName()) { - return $type->getBound()->isSuperTypeOfWithReason($this->getBound()); + return $type->getBound()->isSuperTypeOf($this->getBound()); } - return $type->getBound()->isSuperTypeOfWithReason($this->getBound()) + return $type->getBound()->isSuperTypeOf($this->getBound()) ->and(IsSuperTypeOfResult::createMaybe()); } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index a2269616c9d..a630895beda 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -172,11 +172,11 @@ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): I } if ($this->covariant()) { - return $a->isSuperTypeOfWithReason($b); + return $a->isSuperTypeOf($b); } if ($this->contravariant()) { - return $b->isSuperTypeOfWithReason($a); + return $b->isSuperTypeOf($a); } if ($this->bivariant()) { diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 1f3ed5450b2..2e9391e9d11 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -206,7 +206,7 @@ public function shift(int $amount): Type public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof parent) { - return $this->isSuperTypeOfWithReason($type)->toAcceptsResult(); + return $this->isSuperTypeOf($type)->toAcceptsResult(); } if ($type instanceof CompoundType) { @@ -216,12 +216,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self || $type instanceof ConstantIntegerType) { if ($type instanceof self) { @@ -251,21 +246,16 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof parent) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } if ($otherType instanceof UnionType) { @@ -273,7 +263,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult } if ($otherType instanceof IntersectionType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } return IsSuperTypeOfResult::createNo(); @@ -292,12 +282,12 @@ private function isSubTypeOfUnionWithReason(UnionType $otherType): IsSuperTypeOf } } - return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOfWithReason($innerType), $otherType->getTypes())); + return IsSuperTypeOfResult::createNo()->or(...array_map(fn (Type $innerType) => $this->isSubTypeOf($innerType), $otherType->getTypes())); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 10fbe76b420..2dec6cc4569 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -218,12 +218,7 @@ public function accepts(Type $otherType, bool $strictTypes): AcceptsResult return $result; } - public function isSuperTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($otherType)->result; - } - - public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSuperTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType && $this->equals($otherType)) { return IsSuperTypeOfResult::createYes(); @@ -233,21 +228,16 @@ public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return IsSuperTypeOfResult::createYes(); } - return IsSuperTypeOfResult::createYes()->and(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; + return IsSuperTypeOfResult::createYes()->and(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType), $this->types)); } - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if (($otherType instanceof self || $otherType instanceof UnionType) && !$otherType instanceof TemplateType) { - return $otherType->isSuperTypeOfWithReason($this); + return $otherType->isSuperTypeOf($this); } - $result = IsSuperTypeOfResult::maxMin(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); + $result = IsSuperTypeOfResult::maxMin(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType), $this->types)); if ($this->isOversizedArray()->yes()) { if (!$result->no()) { return IsSuperTypeOfResult::createYes(); diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php index 30e9fe6acc6..61e9b4e9c8a 100644 --- a/src/Type/IsSuperTypeOfResult.php +++ b/src/Type/IsSuperTypeOfResult.php @@ -139,6 +139,11 @@ public function negate(): self return new self($this->result->negate(), $this->reasons); } + public function describe(): string + { + return $this->result->describe(); + } + /** * @param array $operands * diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 861b927d3ca..c7ce1499ca2 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -92,30 +92,25 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return (new IsSuperTypeOfResult($type->isIterable(), [])) - ->and($this->getIterableValueType()->isSuperTypeOfWithReason($type->getIterableValueType())) - ->and($this->getIterableKeyType()->isSuperTypeOfWithReason($type->getIterableKeyType())); + ->and($this->getIterableValueType()->isSuperTypeOf($type->getIterableValueType())) + ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); } - public function isSuperTypeOfMixed(Type $type): TrinaryLogic + public function isSuperTypeOfMixed(Type $type): IsSuperTypeOfResult { - return $type->isIterable() + return (new IsSuperTypeOfResult($type->isIterable(), [])) ->and($this->isNestedTypeSuperTypeOf($this->getIterableValueType(), $type->getIterableValueType())) ->and($this->isNestedTypeSuperTypeOf($this->getIterableKeyType(), $type->getIterableKeyType())); } - private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic + private function isNestedTypeSuperTypeOf(Type $a, Type $b): IsSuperTypeOfResult { if (!$a instanceof MixedType || !$b instanceof MixedType) { return $a->isSuperTypeOf($b); @@ -127,24 +122,19 @@ private function isNestedTypeSuperTypeOf(Type $a, Type $b): TrinaryLogic if ($a->isExplicitMixed()) { if ($b->isExplicitMixed()) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createYes(); - } - - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; + return IsSuperTypeOfResult::createYes(); } - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { - return $otherType->isSuperTypeOfWithReason(new UnionType([ + return $otherType->isSuperTypeOf(new UnionType([ new ArrayType($this->keyType, $this->itemType), new IntersectionType([ new ObjectType(Traversable::class), @@ -165,14 +155,14 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return $limit->and( new IsSuperTypeOfResult($otherType->isIterable(), []), - $otherType->getIterableValueType()->isSuperTypeOfWithReason($this->itemType), - $otherType->getIterableKeyType()->isSuperTypeOfWithReason($this->keyType), + $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), + $otherType->getIterableKeyType()->isSuperTypeOf($this->keyType), ); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function equals(Type $type): bool diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index ac875ed66ad..2100901a867 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -36,19 +36,14 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index f484a52c2b6..810b0f59345 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -96,44 +96,39 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createYes(); } - public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + public function isSuperTypeOfMixed(MixedType $type): IsSuperTypeOfResult { if ($this->subtractedType === null) { if ($this->isExplicitMixed) { if ($type->isExplicitMixed) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } if ($type->subtractedType === null) { - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); if ($isSuperType->yes()) { if ($this->isExplicitMixed) { if ($type->isExplicitMixed) { - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); + return IsSuperTypeOfResult::createMaybe(); } - return TrinaryLogic::createYes(); + return IsSuperTypeOfResult::createYes(); } - return TrinaryLogic::createMaybe(); - } - - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; + return IsSuperTypeOfResult::createMaybe(); } - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($this->subtractedType === null || $type instanceof NeverType) { return IsSuperTypeOfResult::createYes(); @@ -143,7 +138,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult if ($type->subtractedType === null) { return IsSuperTypeOfResult::createMaybe(); } - $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); if ($isSuperType->yes()) { return $isSuperType; } @@ -151,7 +146,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createMaybe(); } - return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); + return $this->subtractedType->isSuperTypeOf($type)->negate(); } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type @@ -322,19 +317,14 @@ public function equals(Type $type): bool return $this->subtractedType->equals($type->subtractedType); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self && !$otherType instanceof TemplateMixedType) { return IsSuperTypeOfResult::createYes(); } if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($otherType); + $isSuperType = $this->subtractedType->isSuperTypeOf($otherType); if ($isSuperType->yes()) { return IsSuperTypeOfResult::createNo(); } @@ -345,7 +335,7 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - $isSuperType = $this->isSuperTypeOfWithReason($acceptingType)->toAcceptsResult(); + $isSuperType = $this->isSuperTypeOf($acceptingType)->toAcceptsResult(); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 5d5dbc81bcf..878dfc36236 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -74,12 +74,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createYes(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); @@ -93,19 +88,14 @@ public function equals(Type $type): bool return $type instanceof self; } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { return IsSuperTypeOfResult::createYes(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult { - return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + return $this->isSubTypeOf($acceptingType)->toAcceptsResult(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/NonAcceptingNeverType.php b/src/Type/NonAcceptingNeverType.php index f8c9dfc835c..dd14d3f9d2d 100644 --- a/src/Type/NonAcceptingNeverType.php +++ b/src/Type/NonAcceptingNeverType.php @@ -2,8 +2,6 @@ namespace PHPStan\Type; -use PHPStan\TrinaryLogic; - /** @api */ class NonAcceptingNeverType extends NeverType { @@ -14,12 +12,7 @@ public function __construct() parent::__construct(true); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 1f24c8a890e..d3c0b94e7b8 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -82,19 +82,14 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return AcceptsResult::createNo(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index b80a6d14571..1a1beed6c03 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -230,15 +230,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $result->and(new AcceptsResult($type->isObject(), [])); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($type instanceof ObjectWithoutClassType) { @@ -292,7 +287,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } $otherPropertyType = $otherProperty->getReadableType(); - $isSuperType = $propertyType->isSuperTypeOfWithReason($otherPropertyType); + $isSuperType = $propertyType->isSuperTypeOf($otherPropertyType); if ($isSuperType->no()) { return $isSuperType; } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 93d6346813d..a9fc033a675 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -305,12 +305,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->checkSubclassAcceptability($thatClassNames[0]); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { $thatClassNames = $type->getObjectClassNames(); if (!$type instanceof CompoundType && $thatClassNames === [] && !$type instanceof ObjectWithoutClassType) { @@ -330,7 +325,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOfWithReason($this); + return self::$superTypes[$thisDescription][$description] = $type->isSubTypeOf($this); } if ($type instanceof ClosureType) { @@ -349,7 +344,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $transformResult = static fn (IsSuperTypeOfResult $result) => $result; if ($this->subtractedType !== null) { - $isSuperType = $this->subtractedType->isSuperTypeOfWithReason($type); + $isSuperType = $this->subtractedType->isSuperTypeOf($type); if ($isSuperType->yes()) { return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } @@ -362,7 +357,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $type instanceof SubtractableType && $type->getSubtractedType() !== null ) { - $isSuperType = $type->getSubtractedType()->isSuperTypeOfWithReason($this); + $isSuperType = $type->getSubtractedType()->isSuperTypeOf($this); if ($isSuperType->yes()) { return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 8cd1ebcf743..3abe064e3ff 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -60,15 +60,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult ); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } if ($type instanceof self) { @@ -76,7 +71,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createYes(); } if ($type->subtractedType !== null) { - $isSuperType = $type->subtractedType->isSuperTypeOfWithReason($this->subtractedType); + $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType); if ($isSuperType->yes()) { return $isSuperType; } @@ -97,7 +92,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createYes(); } - return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); + return $this->subtractedType->isSuperTypeOf($type)->negate(); } public function equals(Type $type): bool diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 990ac2e4b09..4729fdadf02 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -142,15 +142,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->getStaticObjectType()->accepts($type->getStaticObjectType(), $strictTypes); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); + return $this->getStaticObjectType()->isSuperTypeOf($type); } if ($type instanceof ObjectWithoutClassType) { @@ -158,7 +153,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof ObjectType) { - $result = $this->getStaticObjectType()->isSuperTypeOfWithReason($type); + $result = $this->getStaticObjectType()->isSuperTypeOf($type); if ($result->yes()) { $classReflection = $type->getClassReflection(); if ($classReflection !== null && $classReflection->isFinal()) { @@ -170,7 +165,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 5fca1a75d51..f1b9a0b6941 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -68,22 +68,12 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes return AcceptsResult::createMaybe(); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { return IsSuperTypeOfResult::createYes(); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { if ($otherType instanceof self) { return IsSuperTypeOfResult::createYes(); diff --git a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php index 3bdf4e8631c..feb18e97d61 100644 --- a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php +++ b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php @@ -3,25 +3,19 @@ namespace PHPStan\Type; use PHPStan\Reflection\ReflectionProviderStaticAccessor; -use PHPStan\TrinaryLogic; class StringAlwaysAcceptingObjectWithToStringType extends StringType { - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } $thatClassNames = $type->getObjectClassNames(); if ($thatClassNames === []) { - return parent::isSuperTypeOfWithReason($type); + return parent::isSuperTypeOf($type); } $result = IsSuperTypeOfResult::createNo(); diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 052e9ab64f4..39d4949ca8f 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -5,7 +5,6 @@ use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassReflection; -use PHPStan\TrinaryLogic; use function sprintf; /** @api */ @@ -33,24 +32,19 @@ public function describe(VerbosityLevel $level): string return sprintf('$this(%s)', $this->getStaticObjectType()->describe($level)); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { - return $this->getStaticObjectType()->isSuperTypeOfWithReason($type); + return $this->getStaticObjectType()->isSuperTypeOf($type); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } $parent = new parent($this->getClassReflection(), $this->getSubtractedType()); - return $parent->isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); + return $parent->isSuperTypeOf($type)->and(IsSuperTypeOfResult::createMaybe()); } public function changeSubtractedType(?Type $subtractedType): Type diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 4aac9818fc6..b4757aaac25 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -30,12 +30,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return parent::accepts($type, $strictTypes)->and(AcceptsResult::createMaybe()); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createFromBoolean($this->equals($type)); @@ -46,7 +41,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 16dde870012..6753d7ed207 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -54,12 +54,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $this->resolve()->accepts($type, $strictTypes); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { return $this->isSuperTypeOfDefault($type); } @@ -74,7 +69,7 @@ private function isSuperTypeOfDefault(Type $type): IsSuperTypeOfResult $type = $type->resolve(); } - $isSuperType = $this->resolve()->isSuperTypeOfWithReason($type); + $isSuperType = $this->resolve()->isSuperTypeOf($type); if (!$this->isResolvable()) { $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); @@ -523,20 +518,15 @@ public function tryRemove(Type $typeToRemove): ?Type return $this->resolve()->tryRemove($typeToRemove); } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { $result = $this->resolve(); if ($result instanceof CompoundType) { - return $result->isSubTypeOfWithReason($otherType); + return $result->isSubTypeOf($otherType); } - return $otherType->isSuperTypeOfWithReason($result); + return $otherType->isSuperTypeOf($result); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult diff --git a/src/Type/Type.php b/src/Type/Type.php index 6a0f25ef335..62d2b84d3a2 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -73,16 +73,7 @@ public function getConstantStrings(): array; */ public function accepts(Type $type, bool $strictTypes): AcceptsResult; - public function isSuperTypeOf(Type $type): TrinaryLogic; - - /** - * This is like isSuperTypeOf() but gives reasons - * why the type was not/might not be accepted in some non-intuitive scenarios. - * - * In PHPStan 2.0 this method will be removed and the return type of isSuperTypeOf() - * will change to IsSuperTypeOfResult. - */ - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult; + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult; public function equals(Type $type): bool; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 897aed75db5..6e2caf675f9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -196,12 +196,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return $result; } - public function isSuperTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($otherType)->result; - } - - public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSuperTypeOf(Type $otherType): IsSuperTypeOfResult { if ( ($otherType instanceof self && !$otherType instanceof TemplateUnionType) @@ -211,29 +206,24 @@ public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult || $otherType instanceof ConditionalTypeForParameter || $otherType instanceof IntegerRangeType ) { - return $otherType->isSubTypeOfWithReason($this); + return $otherType->isSubTypeOf($this); } - $result = IsSuperTypeOfResult::createNo()->or(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); + $result = IsSuperTypeOfResult::createNo()->or(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOf($otherType), $this->types)); if ($result->yes()) { return $result; } if ($otherType instanceof TemplateUnionType) { - return $result->or($otherType->isSubTypeOfWithReason($this)); + return $result->or($otherType->isSubTypeOf($this)); } return $result; } - public function isSubTypeOf(Type $otherType): TrinaryLogic - { - return $this->isSubTypeOfWithReason($otherType)->result; - } - - public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult { - return IsSuperTypeOfResult::extremeIdentity(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOfWithReason($innerType), $this->types)); + return IsSuperTypeOfResult::extremeIdentity(...array_map(static fn (Type $innerType) => $otherType->isSuperTypeOf($innerType), $this->types)); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index e4f082d0bf3..95c0bce1362 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -61,19 +61,14 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult return new AcceptsResult($type->isVoid()->or($type->isNull()), []); } - public function isSuperTypeOf(Type $type): TrinaryLogic - { - return $this->isSuperTypeOfWithReason($type)->result; - } - - public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof self) { return IsSuperTypeOfResult::createYes(); } if ($type instanceof CompoundType) { - return $type->isSubTypeOfWithReason($this); + return $type->isSubTypeOf($this); } return IsSuperTypeOfResult::createNo(); From f240ebde51f4f98bebfe5b329b8ca816a213065e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 11:06:31 +0200 Subject: [PATCH 0644/3097] Do not comment on PRs on 2.0.x anymore --- .github/workflows/pr-base-on-previous-branch.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-base-on-previous-branch.yml b/.github/workflows/pr-base-on-previous-branch.yml index f522ea446e0..7c2f57188d7 100644 --- a/.github/workflows/pr-base-on-previous-branch.yml +++ b/.github/workflows/pr-base-on-previous-branch.yml @@ -7,7 +7,7 @@ on: types: - opened branches: - - '2.0.x' + - '2.1.x' jobs: @@ -19,6 +19,6 @@ jobs: - name: Comment PR uses: peter-evans/create-or-update-comment@v4 with: - body: "You've opened the pull request against the latest branch 2.0.x. PHPStan 2.0 is not going to be released for months. If your code is relevant on 1.12.x and you want it to be released sooner, please rebase your pull request and change its target to 1.12.x." + body: "You've opened the pull request against the latest branch 2.1.x. PHPStan 2.1 is not going to be released for months. If your code is relevant on 2.0.x and you want it to be released sooner, please rebase your pull request and change its target to 2.0.x." token: ${{ secrets.PHPSTAN_BOT_TOKEN }} issue-number: ${{ github.event.pull_request.number }} From d300e745d8bc0d7593bc61c1e38e4d3d45bcf0b8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 11:19:48 +0200 Subject: [PATCH 0645/3097] Fix CS --- src/Type/Generic/TemplateTypeVariance.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index b0062b42111..a426d29f215 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -7,7 +7,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\BenevolentUnionType; -use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; From 2fce656d249a3f3e4c1585a19e497ba9aa9b4f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Mon, 7 Oct 2024 11:29:44 +0200 Subject: [PATCH 0646/3097] Update changelog-2.0.md --- changelog-2.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index f0abba38dea..b4967463c65 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -1,4 +1,6 @@ -When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases) + a separate [UPGRADING](./UPGRADING.md) document. +When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases). + +Check out the [**UPGRADING guide**](https://github.com/phpstan/phpstan-src/blob/2.0.x/UPGRADING.md)!. Major new features 🚀 ===================== From 97d5e8956891f8357d96e50aa7a8cd62b90d6db8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 11:34:53 +0200 Subject: [PATCH 0647/3097] Fix --- src/Type/Generic/TemplateTypeVariance.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index e48eb81bfbb..a630895beda 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -6,6 +6,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; From 8cfc080f04a5a8b98e4cb17181d1173d4ee61f94 Mon Sep 17 00:00:00 2001 From: Filippo Tessarotto Date: Mon, 7 Oct 2024 14:19:42 +0200 Subject: [PATCH 0648/3097] UPGRADING.md: typo --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 5fe143b645a..5aae2eeb291 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -151,7 +151,7 @@ return ['My error']; ```php return [ - RuleErrorBuilder::mesage('My error') + RuleErrorBuilder::message('My error') ->identifier('my.error') ->build(), ]; From 0df14b1e5d0fddd8e621bb1cf86ff7c3f92e37ce Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 7 Oct 2024 17:50:56 +0200 Subject: [PATCH 0649/3097] functionMap: more precise get_defined_vars Co-authored-by: Markus Staab --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 5ea25339854..f8c4bf30e03 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3305,7 +3305,7 @@ 'get_declared_traits' => ['list'], 'get_defined_constants' => ['array', 'categorize='=>'bool'], 'get_defined_functions' => ['array{internal:non-empty-list,user:list}', 'exclude_disabled='=>'bool'], -'get_defined_vars' => ['array'], +'get_defined_vars' => ['array'], 'get_extension_funcs' => ['list|false', 'extension_name'=>'string'], 'get_headers' => ['array|false', 'url'=>'string', 'format='=>'int', 'context='=>'resource'], 'get_html_translation_table' => ['array', 'table='=>'int', 'flags='=>'int', 'encoding='=>'string'], From c4ba43462cecb03ef57805ffd683f77d57b79a4a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 17:57:03 +0200 Subject: [PATCH 0650/3097] Fix nextAutoIndexes in array coming from ArrayCombineFunctionReturnTypeExtension --- .github/workflows/e2e-tests.yml | 3 +++ e2e/bug-11819/phpstan.neon | 4 ++++ e2e/bug-11819/test.php | 11 +++++++++++ .../Php/ArrayCombineFunctionReturnTypeExtension.php | 13 ++++++++----- tests/PHPStan/Analyser/nsrt/array-combine-php8.php | 8 ++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 e2e/bug-11819/phpstan.neon create mode 100644 e2e/bug-11819/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c9357fa047c..51891fb45ca 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -308,6 +308,9 @@ jobs: cd e2e/discussion-11362 composer install ../../bin/phpstan + - script: | + cd e2e/bug-11819 + ../../bin/phpstan steps: - name: "Checkout" diff --git a/e2e/bug-11819/phpstan.neon b/e2e/bug-11819/phpstan.neon new file mode 100644 index 00000000000..5fbd64483ab --- /dev/null +++ b/e2e/bug-11819/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 5 + paths: + - test.php diff --git a/e2e/bug-11819/test.php b/e2e/bug-11819/test.php new file mode 100644 index 00000000000..267e1647ad2 --- /dev/null +++ b/e2e/bug-11819/test.php @@ -0,0 +1,11 @@ +sanitizeConstantArrayKeyTypes($keyTypes); if ($keyTypes !== null) { - return new ConstantArrayType( - $keyTypes, - $valueTypes, - $keysParamType->getNextAutoIndexes(), - ); + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($keyTypes as $i => $keyType) { + $valueType = $valueTypes[$i]; + $builder->setOffsetValueType($keyType, $valueType); + } + + return $builder->getArray(); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-combine-php8.php b/tests/PHPStan/Analyser/nsrt/array-combine-php8.php index 073fb23770d..1e02759b5e3 100644 --- a/tests/PHPStan/Analyser/nsrt/array-combine-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-combine-php8.php @@ -83,3 +83,11 @@ function withDifferentNumberOfElements(): void { assertType('*NEVER*', array_combine(['foo'], ['bar', 'baz'])); } + +function bug11819(): void +{ + $keys = [1, 2, 3]; + $types = array_combine($keys, array_fill(0, \count($keys), false)); + $types[] = 'foo'; + assertType('array{1: false, 2: false, 3: false, 4: \'foo\'}', $types); +} From f818ac594f1ac9e3c072aa13888921fffa47b29c Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 6 Oct 2024 20:26:06 +0200 Subject: [PATCH 0651/3097] Get rid of unnecessary `instanceof self` in `ConstantArrayType` --- phpstan-baseline.neon | 2 +- src/Type/Constant/ConstantArrayType.php | 55 +++++++------------------ 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 246118a8d5e..fb4b06b2791 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -882,7 +882,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 7 + count: 5 path: src/Type/Constant/ConstantArrayType.php - diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 46315a4eae3..fdc0bdeceff 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -52,7 +52,6 @@ use function array_merge; use function array_pop; use function array_push; -use function array_reverse; use function array_slice; use function array_unique; use function array_values; @@ -242,7 +241,7 @@ public function getAllArrays(): array } $array = $builder->getArray(); - if (!$array instanceof ConstantArrayType) { + if (!$array instanceof self) { throw new ShouldNotHappenException(); } @@ -858,14 +857,16 @@ public function popArray(): Type public function reverseArray(TrinaryLogic $preserveKeys): Type { - $keyTypesReversed = array_reverse($this->keyTypes, true); - $keyTypes = array_values($keyTypesReversed); - $keyTypesReversedKeys = array_keys($keyTypesReversed); - $optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); + $builder = ConstantArrayTypeBuilder::createEmpty(); - $reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo()); + for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { + $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() + ? $this->keyTypes[$i] + : null; + $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $this->isOptionalKey($i)); + } - return $preserveKeys->yes() ? $reversed : $reversed->reindex(); + return $builder->getArray(); } public function searchArray(Type $needleType): Type @@ -994,15 +995,14 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre $isOptional = true; } - $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); - } + $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() + ? $this->keyTypes[$i] + : null; - $slice = $builder->getArray(); - if (!$slice instanceof self) { - throw new ShouldNotHappenException(); + $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $isOptional); } - return $preserveKeys->yes() ? $slice : $slice->reindex(); + return $builder->getArray(); } public function isIterableAtLeastOnce(): TrinaryLogic @@ -1148,7 +1148,7 @@ private function removeLastElements(int $length): self } /** @param positive-int $length */ - private function removeFirstElements(int $length, bool $reindex = true): self + private function removeFirstElements(int $length, bool $reindex = true): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -1175,30 +1175,7 @@ private function removeFirstElements(int $length, bool $reindex = true): self $builder->setOffsetValueType($keyType, $valueType, $isOptional); } - $array = $builder->getArray(); - if (!$array instanceof self) { - throw new ShouldNotHappenException(); - } - - return $array; - } - - private function reindex(): self - { - $keyTypes = []; - $autoIndex = 0; - - foreach ($this->keyTypes as $keyType) { - if (!$keyType instanceof ConstantIntegerType) { - $keyTypes[] = $keyType; - continue; - } - - $keyTypes[] = new ConstantIntegerType($autoIndex); - $autoIndex++; - } - - return new self($keyTypes, $this->valueTypes, [$autoIndex], $this->optionalKeys, TrinaryLogic::createYes()); + return $builder->getArray(); } public function toBoolean(): BooleanType From 2e6547208a042db5330092d38a484f00c5e5a375 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 5 Sep 2024 23:32:15 +0200 Subject: [PATCH 0652/3097] Fix return type of `array_reverse()` with optional keys --- src/Type/Constant/ConstantArrayType.php | 15 ++++++++------- tests/PHPStan/Analyser/nsrt/array-reverse.php | 6 +++++- .../Rules/Functions/ReturnTypeRuleTest.php | 7 +++++++ tests/PHPStan/Rules/Functions/data/bug-11549.php | 13 +++++++++++++ 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11549.php diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 6838aa336b3..74786c7e89f 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -51,7 +51,6 @@ use function array_merge; use function array_pop; use function array_push; -use function array_reverse; use function array_slice; use function array_unique; use function array_values; @@ -892,14 +891,16 @@ public function popArray(): Type public function reverseArray(TrinaryLogic $preserveKeys): Type { - $keyTypesReversed = array_reverse($this->keyTypes, true); - $keyTypes = array_values($keyTypesReversed); - $keyTypesReversedKeys = array_keys($keyTypesReversed); - $optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); + $builder = ConstantArrayTypeBuilder::createEmpty(); - $reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo()); + for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { + $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() + ? $this->keyTypes[$i] + : null; + $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $this->isOptionalKey($i)); + } - return $preserveKeys->yes() ? $reversed : $reversed->reindex(); + return $builder->getArray(); } public function searchArray(Type $needleType): Type diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse.php b/tests/PHPStan/Analyser/nsrt/array-reverse.php index 413e1d5f2a2..28dbd009c4b 100644 --- a/tests/PHPStan/Analyser/nsrt/array-reverse.php +++ b/tests/PHPStan/Analyser/nsrt/array-reverse.php @@ -22,8 +22,9 @@ public function normalArrays(array $a, array $b): void /** * @param array{a: 'foo', b: 'bar', c?: 'baz'} $a * @param array{17: 'foo', 19: 'bar'}|array{foo: 17, bar: 19} $b + * @param array{0: 'A', 1?: 'B', 2?: 'C'} $c */ - public function constantArrays(array $a, array $b): void + public function constantArrays(array $a, array $b, array $c): void { assertType('array{}', array_reverse([])); assertType('array{}', array_reverse([], true)); @@ -45,6 +46,9 @@ public function constantArrays(array $a, array $b): void assertType('array{\'bar\', \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b)); assertType('array{19: \'bar\', 17: \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b, true)); + + assertType("array{0: 'A'|'B'|'C', 1?: 'A'|'B', 2?: 'A'}", array_reverse($c)); + assertType("array{2?: 'C', 1?: 'B', 0: 'A'}", array_reverse($c, true)); } /** diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index f16d288869e..28d766512f0 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -297,4 +297,11 @@ public function testBug8881(): void $this->analyse([__DIR__ . '/data/bug-8881.php'], []); } + public function testBug11549(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-11549.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11549.php b/tests/PHPStan/Rules/Functions/data/bug-11549.php new file mode 100644 index 00000000000..afc7c1b6eb5 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11549.php @@ -0,0 +1,13 @@ + Date: Mon, 7 Oct 2024 18:19:12 +0200 Subject: [PATCH 0653/3097] Revert "Get rid of unnecessary `instanceof self` in `ConstantArrayType`" This reverts commit f818ac594f1ac9e3c072aa13888921fffa47b29c. --- phpstan-baseline.neon | 2 +- src/Type/Constant/ConstantArrayType.php | 55 ++++++++++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fb4b06b2791..246118a8d5e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -882,7 +882,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 5 + count: 7 path: src/Type/Constant/ConstantArrayType.php - diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index fdc0bdeceff..46315a4eae3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -52,6 +52,7 @@ use function array_merge; use function array_pop; use function array_push; +use function array_reverse; use function array_slice; use function array_unique; use function array_values; @@ -241,7 +242,7 @@ public function getAllArrays(): array } $array = $builder->getArray(); - if (!$array instanceof self) { + if (!$array instanceof ConstantArrayType) { throw new ShouldNotHappenException(); } @@ -857,16 +858,14 @@ public function popArray(): Type public function reverseArray(TrinaryLogic $preserveKeys): Type { - $builder = ConstantArrayTypeBuilder::createEmpty(); + $keyTypesReversed = array_reverse($this->keyTypes, true); + $keyTypes = array_values($keyTypesReversed); + $keyTypesReversedKeys = array_keys($keyTypesReversed); + $optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys); - for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { - $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() - ? $this->keyTypes[$i] - : null; - $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $this->isOptionalKey($i)); - } + $reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo()); - return $builder->getArray(); + return $preserveKeys->yes() ? $reversed : $reversed->reindex(); } public function searchArray(Type $needleType): Type @@ -995,14 +994,15 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre $isOptional = true; } - $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() - ? $this->keyTypes[$i] - : null; + $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); + } - $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $isOptional); + $slice = $builder->getArray(); + if (!$slice instanceof self) { + throw new ShouldNotHappenException(); } - return $builder->getArray(); + return $preserveKeys->yes() ? $slice : $slice->reindex(); } public function isIterableAtLeastOnce(): TrinaryLogic @@ -1148,7 +1148,7 @@ private function removeLastElements(int $length): self } /** @param positive-int $length */ - private function removeFirstElements(int $length, bool $reindex = true): Type + private function removeFirstElements(int $length, bool $reindex = true): self { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -1175,7 +1175,30 @@ private function removeFirstElements(int $length, bool $reindex = true): Type $builder->setOffsetValueType($keyType, $valueType, $isOptional); } - return $builder->getArray(); + $array = $builder->getArray(); + if (!$array instanceof self) { + throw new ShouldNotHappenException(); + } + + return $array; + } + + private function reindex(): self + { + $keyTypes = []; + $autoIndex = 0; + + foreach ($this->keyTypes as $keyType) { + if (!$keyType instanceof ConstantIntegerType) { + $keyTypes[] = $keyType; + continue; + } + + $keyTypes[] = new ConstantIntegerType($autoIndex); + $autoIndex++; + } + + return new self($keyTypes, $this->valueTypes, [$autoIndex], $this->optionalKeys, TrinaryLogic::createYes()); } public function toBoolean(): BooleanType From f7f2be8f334bff5f5c0883b337e7ea0340acc3b7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 7 Oct 2024 18:21:41 +0200 Subject: [PATCH 0654/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/10872 --- .../Methods/CallStaticMethodsRuleTest.php | 8 ++++ .../PHPStan/Rules/Methods/data/bug-10872.php | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10872.php diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 27718dc9c79..1246699ff80 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -841,4 +841,12 @@ public function testClosureBind(): void ]); } + public function testBug10872(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-10872.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10872.php b/tests/PHPStan/Rules/Methods/data/bug-10872.php new file mode 100644 index 00000000000..9b1e68ce174 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10872.php @@ -0,0 +1,42 @@ + + */ + public function getRow(): array + { + return []; + } + + /** + * @template ExpectedType of array + * @param ExpectedType $expected + * @param array $actual + * @psalm-assert =ExpectedType $actual + */ + public static function assertSame(array $expected, array $actual): void + { + if ($actual !== $expected) { + throw new \Exception(); + } + } + + public function testEscapeIdentifier(): void + { + $names = [ + 'foo', + '2', + ]; + + $expected = array_combine($names, array_fill(0, count($names), 'x')); + + self::assertSame( + $expected, + $this->getRow() + ); + } +} From ee802d682e2a8a0b03cc6bcce25ecf7b25749933 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Thu, 26 Sep 2024 13:07:35 +0200 Subject: [PATCH 0655/3097] Reflection `getAttributes` returns `list` --- ...GetAttributesMethodReturnTypeExtension.php | 5 +-- stubs/ReflectionClass.stub | 2 +- stubs/ReflectionClassConstant.stub | 2 +- stubs/ReflectionFunctionAbstract.stub | 2 +- stubs/ReflectionParameter.stub | 2 +- stubs/ReflectionProperty.stub | 2 +- .../nsrt/reflectionclass-issue-5511-php8.php | 34 +++++++++---------- 7 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 621831d168e..04454c63b26 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -5,10 +5,11 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Generic\GenericObjectType; -use PHPStan\Type\MixedType; +use PHPStan\Type\IntegerType; use PHPStan\Type\Type; use ReflectionAttribute; use function count; @@ -42,7 +43,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $argType = $scope->getType($methodCall->getArgs()[0]->value); $classType = $argType->getClassStringObjectType(); - return new ArrayType(new MixedType(), new GenericObjectType(ReflectionAttribute::class, [$classType])); + return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new GenericObjectType(ReflectionAttribute::class, [$classType]))); } } diff --git a/stubs/ReflectionClass.stub b/stubs/ReflectionClass.stub index f47d5d89a17..65b02ffaacd 100644 --- a/stubs/ReflectionClass.stub +++ b/stubs/ReflectionClass.stub @@ -42,7 +42,7 @@ class ReflectionClass public function newInstanceWithoutConstructor(); /** - * @return array> + * @return list> */ public function getAttributes(?string $name = null, int $flags = 0) { diff --git a/stubs/ReflectionClassConstant.stub b/stubs/ReflectionClassConstant.stub index 669ccbef89b..4396980e060 100644 --- a/stubs/ReflectionClassConstant.stub +++ b/stubs/ReflectionClassConstant.stub @@ -3,7 +3,7 @@ class ReflectionClassConstant { /** - * @return array> + * @return list> */ public function getAttributes(?string $name = null, int $flags = 0) { diff --git a/stubs/ReflectionFunctionAbstract.stub b/stubs/ReflectionFunctionAbstract.stub index 36e48df100c..0154996741e 100644 --- a/stubs/ReflectionFunctionAbstract.stub +++ b/stubs/ReflectionFunctionAbstract.stub @@ -9,7 +9,7 @@ abstract class ReflectionFunctionAbstract public function getFileName () {} /** - * @return array> + * @return list> */ public function getAttributes(?string $name = null, int $flags = 0) { diff --git a/stubs/ReflectionParameter.stub b/stubs/ReflectionParameter.stub index a29fa35e832..e92f0b51f3a 100644 --- a/stubs/ReflectionParameter.stub +++ b/stubs/ReflectionParameter.stub @@ -3,7 +3,7 @@ class ReflectionParameter { /** - * @return array> + * @return list> */ public function getAttributes(?string $name = null, int $flags = 0) { diff --git a/stubs/ReflectionProperty.stub b/stubs/ReflectionProperty.stub index 312688067d7..002daeeeee2 100644 --- a/stubs/ReflectionProperty.stub +++ b/stubs/ReflectionProperty.stub @@ -3,7 +3,7 @@ class ReflectionProperty { /** - * @return array> + * @return list> */ public function getAttributes(?string $name = null, int $flags = 0) { diff --git a/tests/PHPStan/Analyser/nsrt/reflectionclass-issue-5511-php8.php b/tests/PHPStan/Analyser/nsrt/reflectionclass-issue-5511-php8.php index 94b28068504..d0a80299b28 100644 --- a/tests/PHPStan/Analyser/nsrt/reflectionclass-issue-5511-php8.php +++ b/tests/PHPStan/Analyser/nsrt/reflectionclass-issue-5511-php8.php @@ -36,38 +36,38 @@ function testGetAttributes( $classStr = $reflectionClass->getAttributes($str); $classNonsense = $reflectionClass->getAttributes("some random string"); - assertType('array>', $classAll); - assertType('array>', $classAbc1); - assertType('array>', $classAbc2); - assertType('array>', $classGCN); - assertType('array>', $classCN); - assertType('array>', $classStr); - assertType('array>', $classNonsense); + assertType('list>', $classAll); + assertType('list>', $classAbc1); + assertType('list>', $classAbc2); + assertType('list>', $classGCN); + assertType('list>', $classCN); + assertType('list>', $classStr); + assertType('list>', $classNonsense); $methodAll = $reflectionMethod->getAttributes(); $methodAbc = $reflectionMethod->getAttributes(Abc::class); - assertType('array>', $methodAll); - assertType('array>', $methodAbc); + assertType('list>', $methodAll); + assertType('list>', $methodAbc); $paramAll = $reflectionParameter->getAttributes(); $paramAbc = $reflectionParameter->getAttributes(Abc::class); - assertType('array>', $paramAll); - assertType('array>', $paramAbc); + assertType('list>', $paramAll); + assertType('list>', $paramAbc); $propAll = $reflectionProperty->getAttributes(); $propAbc = $reflectionProperty->getAttributes(Abc::class); - assertType('array>', $propAll); - assertType('array>', $propAbc); + assertType('list>', $propAll); + assertType('list>', $propAbc); $constAll = $reflectionClassConstant->getAttributes(); $constAbc = $reflectionClassConstant->getAttributes(Abc::class); - assertType('array>', $constAll); - assertType('array>', $constAbc); + assertType('list>', $constAll); + assertType('list>', $constAbc); $funcAll = $reflectionFunction->getAttributes(); $funcAbc = $reflectionFunction->getAttributes(Abc::class); - assertType('array>', $funcAll); - assertType('array>', $funcAbc); + assertType('list>', $funcAll); + assertType('list>', $funcAbc); } /** From e542a61c38f27a371cb3136bcf0030572466d3cc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 09:00:28 +0200 Subject: [PATCH 0656/3097] Fix --- .../Php/ReflectionGetAttributesMethodReturnTypeExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 04454c63b26..493ec0e9c30 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -11,6 +11,7 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use ReflectionAttribute; use function count; @@ -43,7 +44,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $argType = $scope->getType($methodCall->getArgs()[0]->value); $classType = $argType->getClassStringObjectType(); - return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new GenericObjectType(ReflectionAttribute::class, [$classType]))); + return TypeCombinator::intersect(new ArrayType(new IntegerType(), new GenericObjectType(ReflectionAttribute::class, [$classType])), new AccessoryArrayListType()); } } From 37f4e564109dc69fd391c55d44747ed2e0bd130b Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:19:20 +0000 Subject: [PATCH 0657/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1b3a8bedf29..54283827b43 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.9", - "phpstan/php-8-stubs": "0.4.0", + "phpstan/php-8-stubs": "0.4.1", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 660c3ad5ec1..810f663d6ba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eb6f30bebe27d08d2b3b9e2c729ccf20", + "content-hash": "416d829025e6a2d8f7115f4c142b0718", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "693817d86d0d0de1d39b97a70bff4fa728384aa1" + "reference": "212d2b20c3c6f8c06a224efb748ec4cd069ef251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/693817d86d0d0de1d39b97a70bff4fa728384aa1", - "reference": "693817d86d0d0de1d39b97a70bff4fa728384aa1", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/212d2b20c3c6f8c06a224efb748ec4cd069ef251", + "reference": "212d2b20c3c6f8c06a224efb748ec4cd069ef251", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.0" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.1" }, - "time": "2024-09-30T19:56:21+00:00" + "time": "2024-10-08T00:18:48+00:00" }, { "name": "phpstan/phpdoc-parser", From 14a3b36d72f149fda2837cf11cc899db5aa87a7a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 09:05:37 +0200 Subject: [PATCH 0658/3097] Fix generate-function-metadata.php script --- .github/workflows/update-phpstorm-stubs.yml | 2 +- bin/generate-function-metadata.php | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-phpstorm-stubs.yml b/.github/workflows/update-phpstorm-stubs.yml index c559efc2086..320b4eba1be 100644 --- a/.github/workflows/update-phpstorm-stubs.yml +++ b/.github/workflows/update-phpstorm-stubs.yml @@ -16,7 +16,7 @@ jobs: - name: "Checkout" uses: actions/checkout@v4 with: - ref: 1.12.x + ref: 2.0.x fetch-depth: '0' token: ${{ secrets.PHPSTAN_BOT_TOKEN }} - name: "Install PHP" diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index f6f5131a53a..7b834e2cbb4 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -16,7 +16,7 @@ (function (): void { require_once __DIR__ . '/../vendor/autoload.php'; - $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); + $parser = (new ParserFactory())->createForNewestSupportedVersion(); $finder = new Finder(); $finder->in(__DIR__ . '/../vendor/jetbrains/phpstorm-stubs')->files()->name('*.php'); @@ -69,8 +69,19 @@ public function enterNode(Node $node) $traverser->addVisitor(new NodeConnectingVisitor()); $traverser->addVisitor($visitor); + $contents = FileReader::read($path); + if (str_ends_with($path, '/vendor/jetbrains/phpstorm-stubs/Core/Core.php')) { + $contents = str_replace([ + 'function exit', + 'function die', + ], [ + 'function _exit', + 'function _die', + ], $contents); + } + $traverser->traverse( - $parser->parse(FileReader::read($path)), + $parser->parse($contents), ); } From 9d19cd0a7babeb959ccc8f54e1b0d35ae5656882 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 6 Oct 2024 20:26:06 +0200 Subject: [PATCH 0659/3097] Get rid of unnecessary `instanceof self` in `ConstantArrayType` --- phpstan-baseline.neon | 2 +- src/Type/Constant/ConstantArrayType.php | 40 +++++-------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 246118a8d5e..fb4b06b2791 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -882,7 +882,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 7 + count: 5 path: src/Type/Constant/ConstantArrayType.php - diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index e77e12857d9..fdc0bdeceff 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -241,7 +241,7 @@ public function getAllArrays(): array } $array = $builder->getArray(); - if (!$array instanceof ConstantArrayType) { + if (!$array instanceof self) { throw new ShouldNotHappenException(); } @@ -995,15 +995,14 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre $isOptional = true; } - $builder->setOffsetValueType($this->keyTypes[$i], $this->valueTypes[$i], $isOptional); - } + $offsetType = $preserveKeys->yes() || $this->keyTypes[$i]->isInteger()->no() + ? $this->keyTypes[$i] + : null; - $slice = $builder->getArray(); - if (!$slice instanceof self) { - throw new ShouldNotHappenException(); + $builder->setOffsetValueType($offsetType, $this->valueTypes[$i], $isOptional); } - return $preserveKeys->yes() ? $slice : $slice->reindex(); + return $builder->getArray(); } public function isIterableAtLeastOnce(): TrinaryLogic @@ -1149,7 +1148,7 @@ private function removeLastElements(int $length): self } /** @param positive-int $length */ - private function removeFirstElements(int $length, bool $reindex = true): self + private function removeFirstElements(int $length, bool $reindex = true): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -1176,30 +1175,7 @@ private function removeFirstElements(int $length, bool $reindex = true): self $builder->setOffsetValueType($keyType, $valueType, $isOptional); } - $array = $builder->getArray(); - if (!$array instanceof self) { - throw new ShouldNotHappenException(); - } - - return $array; - } - - private function reindex(): self - { - $keyTypes = []; - $autoIndex = 0; - - foreach ($this->keyTypes as $keyType) { - if (!$keyType instanceof ConstantIntegerType) { - $keyTypes[] = $keyType; - continue; - } - - $keyTypes[] = new ConstantIntegerType($autoIndex); - $autoIndex++; - } - - return new self($keyTypes, $this->valueTypes, [$autoIndex], $this->optionalKeys, TrinaryLogic::createYes()); + return $builder->getArray(); } public function toBoolean(): BooleanType From dddf98463200f8f050937821915a7744007fdeba Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 8 Oct 2024 13:51:40 +0200 Subject: [PATCH 0660/3097] Remove inefficient caching from `PhpMethodReflection` and `PhpFunctionReflection::isVariadic()` Co-authored-by: Ondrej Mirtes --- conf/config.neon | 13 +- src/Parser/FunctionCallStatementFinder.php | 47 ------- src/Parser/SimpleParser.php | 4 + src/Parser/VariadicFunctionsVisitor.php | 94 +++++++++++++ src/Parser/VariadicMethodsVisitor.php | 125 ++++++++++++++++++ src/Reflection/Php/PhpFunctionReflection.php | 97 +++----------- src/Reflection/Php/PhpMethodReflection.php | 103 ++++----------- tests/PHPStan/Parser/CleaningParserTest.php | 2 + tests/PHPStan/Parser/ParserTest.php | 99 ++++++++++++++ .../Parser/data/variadic-functions.php | 31 +++++ .../Parser/data/variadic-methods-in-enum.php | 16 +++ .../PHPStan/Parser/data/variadic-methods.php | 68 ++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 15 +++ .../PHPStan/Rules/Methods/data/bug-11559c.php | 16 +++ 14 files changed, 528 insertions(+), 202 deletions(-) delete mode 100644 src/Parser/FunctionCallStatementFinder.php create mode 100644 src/Parser/VariadicFunctionsVisitor.php create mode 100644 src/Parser/VariadicMethodsVisitor.php create mode 100644 tests/PHPStan/Parser/ParserTest.php create mode 100644 tests/PHPStan/Parser/data/variadic-functions.php create mode 100644 tests/PHPStan/Parser/data/variadic-methods-in-enum.php create mode 100644 tests/PHPStan/Parser/data/variadic-methods.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11559c.php diff --git a/conf/config.neon b/conf/config.neon index 6a8f4d68970..583cceac81e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -302,6 +302,16 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\VariadicMethodsVisitor + tags: + - phpstan.parser.richParserNodeVisitor + + - + class: PHPStan\Parser\VariadicFunctionsVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Node\Printer\ExprPrinter @@ -634,9 +644,6 @@ services: tags: - phpstan.diagnoseExtension - - - class: PHPStan\Parser\FunctionCallStatementFinder - - class: PHPStan\Process\CpuCoreCounter diff --git a/src/Parser/FunctionCallStatementFinder.php b/src/Parser/FunctionCallStatementFinder.php deleted file mode 100644 index 9a4c1dd6bbf..00000000000 --- a/src/Parser/FunctionCallStatementFinder.php +++ /dev/null @@ -1,47 +0,0 @@ -findFunctionCallInStatements($functionNames, $statement); - if ($result !== null) { - return $result; - } - } - - if (!($statement instanceof Node)) { - continue; - } - - if ($statement instanceof FuncCall && $statement->name instanceof Name) { - if (in_array((string) $statement->name, $functionNames, true)) { - return $statement; - } - } - - $result = $this->findFunctionCallInStatements($functionNames, $statement); - if ($result !== null) { - return $result; - } - } - - return null; - } - -} diff --git a/src/Parser/SimpleParser.php b/src/Parser/SimpleParser.php index 713c1502ef2..8fbd1127420 100644 --- a/src/Parser/SimpleParser.php +++ b/src/Parser/SimpleParser.php @@ -15,6 +15,8 @@ final class SimpleParser implements Parser public function __construct( private \PhpParser\Parser $parser, private NameResolver $nameResolver, + private VariadicMethodsVisitor $variadicMethodsVisitor, + private VariadicFunctionsVisitor $variadicFunctionsVisitor, ) { } @@ -48,6 +50,8 @@ public function parseString(string $sourceCode): array $nodeTraverser = new NodeTraverser(); $nodeTraverser->addVisitor($this->nameResolver); + $nodeTraverser->addVisitor($this->variadicMethodsVisitor); + $nodeTraverser->addVisitor($this->variadicFunctionsVisitor); /** @var array */ return $nodeTraverser->traverse($nodes); diff --git a/src/Parser/VariadicFunctionsVisitor.php b/src/Parser/VariadicFunctionsVisitor.php new file mode 100644 index 00000000000..5276d0eb47d --- /dev/null +++ b/src/Parser/VariadicFunctionsVisitor.php @@ -0,0 +1,94 @@ + */ + public static array $cache = []; + + /** @var array */ + private array $variadicFunctions = []; + + public const ATTRIBUTE_NAME = 'variadicFunctions'; + + public function beforeTraverse(array $nodes): ?array + { + $this->topNode = null; + $this->variadicFunctions = []; + $this->inNamespace = null; + $this->inFunction = null; + + return null; + } + + public function enterNode(Node $node): ?Node + { + if ($this->topNode === null) { + $this->topNode = $node; + } + + if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) { + $this->inNamespace = $node->name->toString(); + } + + if ($node instanceof Node\Stmt\Function_) { + $this->inFunction = $this->inNamespace !== null ? $this->inNamespace . '\\' . $node->name->name : $node->name->name; + } + + if ( + $this->inFunction !== null + && $node instanceof Node\Expr\FuncCall + && $node->name instanceof Name + && in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true) + && !array_key_exists($this->inFunction, $this->variadicFunctions) + ) { + $this->variadicFunctions[$this->inFunction] = true; + } + + return null; + } + + public function leaveNode(Node $node): ?Node + { + if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) { + $this->inNamespace = null; + } + + if ($node instanceof Node\Stmt\Function_ && $this->inFunction !== null) { + $this->variadicFunctions[$this->inFunction] ??= false; + $this->inFunction = null; + } + + return null; + } + + public function afterTraverse(array $nodes): ?array + { + if ($this->topNode !== null && $this->variadicFunctions !== []) { + foreach ($this->variadicFunctions as $name => $variadic) { + self::$cache[$name] = $variadic; + } + $functions = array_filter($this->variadicFunctions, static fn (bool $variadic) => $variadic); + $this->topNode->setAttribute(self::ATTRIBUTE_NAME, $functions); + } + + return null; + } + +} diff --git a/src/Parser/VariadicMethodsVisitor.php b/src/Parser/VariadicMethodsVisitor.php new file mode 100644 index 00000000000..50882efc549 --- /dev/null +++ b/src/Parser/VariadicMethodsVisitor.php @@ -0,0 +1,125 @@ + */ + private array $classStack = []; + + private ?string $inMethod = null; + + /** @var array> */ + public static array $cache = []; + + /** @var array> */ + private array $variadicMethods = []; + + public function beforeTraverse(array $nodes): ?array + { + $this->topNode = null; + $this->variadicMethods = []; + $this->inNamespace = null; + $this->classStack = []; + $this->inMethod = null; + + return null; + } + + public function enterNode(Node $node): ?Node + { + if ($this->topNode === null) { + $this->topNode = $node; + } + + if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) { + $this->inNamespace = $node->name->toString(); + } + + if ($node instanceof Node\Stmt\ClassLike) { + if (!$node->name instanceof Node\Identifier) { + $className = sprintf('%s:%s:%s', self::ANONYMOUS_CLASS_PREFIX, $node->getStartLine(), $node->getEndLine()); + $this->classStack[] = $className; + } else { + $className = $node->name->name; + $this->classStack[] = $this->inNamespace !== null ? $this->inNamespace . '\\' . $className : $className; + } + } + + if ($node instanceof ClassMethod) { + $this->inMethod = $node->name->name; + } + + if ( + $this->inMethod !== null + && $node instanceof Node\Expr\FuncCall + && $node->name instanceof Name + && in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true) + ) { + $lastClass = $this->classStack[count($this->classStack) - 1] ?? null; + if ($lastClass !== null) { + if ( + !array_key_exists($lastClass, $this->variadicMethods) + || !array_key_exists($this->inMethod, $this->variadicMethods[$lastClass]) + ) { + $this->variadicMethods[$lastClass][$this->inMethod] = true; + } + } + + } + + return null; + } + + public function leaveNode(Node $node): ?Node + { + if ($node instanceof ClassMethod) { + $this->inMethod = null; + } + + if ($node instanceof Node\Stmt\ClassLike) { + array_pop($this->classStack); + } + + if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) { + $this->inNamespace = null; + } + + return null; + } + + public function afterTraverse(array $nodes): ?array + { + if ($this->topNode !== null && $this->variadicMethods !== []) { + foreach ($this->variadicMethods as $class => $methods) { + foreach ($methods as $name => $variadic) { + self::$cache[$class][$name] = $variadic; + } + } + $this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods); + } + + return null; + } + +} diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index e28f19f8792..e8fbc2e8245 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -2,20 +2,16 @@ namespace PHPStan\Reflection\Php; -use PhpParser\Node; -use PhpParser\Node\Stmt\Function_; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; -use PHPStan\Cache\Cache; -use PHPStan\Parser\FunctionCallStatementFinder; use PHPStan\Parser\Parser; +use PHPStan\Parser\VariadicFunctionsVisitor; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\ParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -23,11 +19,9 @@ use PHPStan\Type\TypehintHelper; use function array_key_exists; use function array_map; -use function filemtime; +use function count; use function is_array; use function is_file; -use function sprintf; -use function time; final class PhpFunctionReflection implements FunctionReflection { @@ -35,6 +29,8 @@ final class PhpFunctionReflection implements FunctionReflection /** @var list|null */ private ?array $variants = null; + private ?bool $containsVariadicCalls = null; + /** * @param array $phpDocParameterTypes * @param array $phpDocParameterOutTypes @@ -45,8 +41,6 @@ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionFunction $reflection, private Parser $parser, - private FunctionCallStatementFinder $functionCallStatementFinder, - private Cache $cache, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, private ?Type $phpDocReturnType, @@ -139,67 +133,33 @@ private function getParameters(): array private function isVariadic(): bool { $isNativelyVariadic = $this->reflection->isVariadic(); - if (!$isNativelyVariadic && $this->reflection - ->getFileName() !== false) { - $fileName = $this->reflection->getFileName(); - if (is_file($fileName)) { - $functionName = $this->reflection->getName(); - $modifiedTime = filemtime($fileName); - if ($modifiedTime === false) { - $modifiedTime = time(); - } - $variableCacheKey = sprintf('%d-v4', $modifiedTime); - $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); - $cachedResult = $this->cache->load($key, $variableCacheKey); - if ($cachedResult === null) { - $nodes = $this->parser->parseFile($fileName); - $result = !$this->containsVariadicFunction($nodes)->no(); - $this->cache->save($key, $variableCacheKey, $result); - return $result; - } + if (!$isNativelyVariadic && $this->reflection->getFileName() !== false && !$this->isBuiltin()) { + $filename = $this->reflection->getFileName(); - return $cachedResult; + if ($this->containsVariadicCalls !== null) { + return $this->containsVariadicCalls; } - } - - return $isNativelyVariadic; - } - /** - * @param Node[]|scalar[]|Node $node - */ - private function containsVariadicFunction(array|Node $node): TrinaryLogic - { - $result = TrinaryLogic::createMaybe(); - - if ($node instanceof Node) { - if ($node instanceof Function_) { - $functionName = (string) $node->namespacedName; - - if ($functionName === $this->reflection->getName()) { - return TrinaryLogic::createFromBoolean($this->isFunctionNodeVariadic($node)); - } + if (array_key_exists($this->reflection->getName(), VariadicFunctionsVisitor::$cache)) { + return $this->containsVariadicCalls = VariadicFunctionsVisitor::$cache[$this->reflection->getName()]; } - foreach ($node->getSubNodeNames() as $subNodeName) { - $innerNode = $node->{$subNodeName}; - if (!$innerNode instanceof Node && !is_array($innerNode)) { - continue; - } + $nodes = $this->parser->parseFile($filename); + if (count($nodes) > 0) { + $variadicFunctions = $nodes[0]->getAttribute(VariadicFunctionsVisitor::ATTRIBUTE_NAME); - $result = $result->and($this->containsVariadicFunction($innerNode)); - } - } elseif (is_array($node)) { - foreach ($node as $subNode) { - if (!$subNode instanceof Node) { - continue; + if ( + is_array($variadicFunctions) + && array_key_exists($this->reflection->getName(), $variadicFunctions) + ) { + return $this->containsVariadicCalls = $variadicFunctions[$this->reflection->getName()]; } - - $result = $result->and($this->containsVariadicFunction($subNode)); } + + return $this->containsVariadicCalls = false; } - return $result; + return $isNativelyVariadic; } private function getReturnType(): Type @@ -296,19 +256,4 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } - private function isFunctionNodeVariadic(Function_ $node): bool - { - foreach ($node->params as $parameter) { - if ($parameter->variadic) { - return true; - } - } - - if ($this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null) { - return true; - } - - return false; - } - } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index d0e008a7ed0..53f5d800544 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -2,16 +2,10 @@ namespace PHPStan\Reflection\Php; -use PhpParser\Node; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Declare_; -use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Namespace_; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; -use PHPStan\Cache\Cache; -use PHPStan\Parser\FunctionCallStatementFinder; use PHPStan\Parser\Parser; +use PHPStan\Parser\VariadicMethodsVisitor; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -21,7 +15,6 @@ use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodPrototypeReflection; -use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; @@ -36,14 +29,14 @@ use PHPStan\Type\TypehintHelper; use PHPStan\Type\VoidType; use ReflectionException; +use function array_key_exists; use function array_map; +use function count; use function explode; -use function filemtime; use function in_array; -use function is_bool; +use function is_array; use function sprintf; use function strtolower; -use function time; use const PHP_VERSION_ID; /** @@ -62,6 +55,8 @@ final class PhpMethodReflection implements ExtendedMethodReflection /** @var list|null */ private ?array $variants = null; + private ?bool $containsVariadicCalls = null; + /** * @param Type[] $phpDocParameterTypes * @param Type[] $phpDocParameterOutTypes @@ -75,8 +70,6 @@ public function __construct( private ReflectionMethod $reflection, private ReflectionProvider $reflectionProvider, private Parser $parser, - private FunctionCallStatementFinder $functionCallStatementFinder, - private Cache $cache, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, private ?Type $phpDocReturnType, @@ -252,82 +245,40 @@ private function isVariadic(): bool $filename = $this->declaringTrait->getFileName(); } - if (!$isNativelyVariadic && $filename !== null) { - $modifiedTime = @filemtime($filename); - if ($modifiedTime === false) { - $modifiedTime = time(); - } - $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); - $variableCacheKey = sprintf('%d-v4', $modifiedTime); - $cachedResult = $this->cache->load($key, $variableCacheKey); - if ($cachedResult === null || !is_bool($cachedResult)) { - $nodes = $this->parser->parseFile($filename); - $result = $this->callsFuncGetArgs($declaringClass, $nodes); - $this->cache->save($key, $variableCacheKey, $result); - return $result; + if (!$isNativelyVariadic && $filename !== null && !$this->declaringClass->isBuiltin()) { + if ($this->containsVariadicCalls !== null) { + return $this->containsVariadicCalls; } - return $cachedResult; - } - - return $isNativelyVariadic; - } - - /** - * @param Node[] $nodes - */ - private function callsFuncGetArgs(ClassReflection $declaringClass, array $nodes): bool - { - foreach ($nodes as $node) { - if ( - $node instanceof Node\Stmt\ClassLike - ) { - if (!isset($node->namespacedName)) { - continue; - } - if ($declaringClass->getName() !== (string) $node->namespacedName) { - continue; - } - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; - } - continue; + $className = $declaringClass->getName(); + if ($declaringClass->isAnonymous()) { + $className = sprintf('%s:%s:%s', VariadicMethodsVisitor::ANONYMOUS_CLASS_PREFIX, $declaringClass->getNativeReflection()->getStartLine(), $declaringClass->getNativeReflection()->getEndLine()); } - - if ($node instanceof ClassMethod) { - if ($node->getStmts() === null) { - continue; // interface - } - - $methodName = $node->name->name; - if ($methodName === $this->reflection->getName()) { - return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null; + if (array_key_exists($className, VariadicMethodsVisitor::$cache)) { + if (array_key_exists($this->reflection->getName(), VariadicMethodsVisitor::$cache[$className])) { + return $this->containsVariadicCalls = VariadicMethodsVisitor::$cache[$className][$this->reflection->getName()]; } - continue; + return $this->containsVariadicCalls = false; } - if ($node instanceof Function_) { - continue; - } + $nodes = $this->parser->parseFile($filename); + if (count($nodes) > 0) { + $variadicMethods = $nodes[0]->getAttribute(VariadicMethodsVisitor::ATTRIBUTE_NAME); - if ($node instanceof Namespace_) { - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; + if ( + is_array($variadicMethods) + && array_key_exists($className, $variadicMethods) + && array_key_exists($this->reflection->getName(), $variadicMethods[$className]) + ) { + return $this->containsVariadicCalls = $variadicMethods[$className][$this->reflection->getName()]; } - continue; - } - - if (!$node instanceof Declare_ || $node->stmts === null) { - continue; } - if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) { - return true; - } + return $this->containsVariadicCalls = false; } - return false; + return $isNativelyVariadic; } public function isPrivate(): bool diff --git a/tests/PHPStan/Parser/CleaningParserTest.php b/tests/PHPStan/Parser/CleaningParserTest.php index 692ea7b6995..835486fdc2c 100644 --- a/tests/PHPStan/Parser/CleaningParserTest.php +++ b/tests/PHPStan/Parser/CleaningParserTest.php @@ -68,6 +68,8 @@ public function testParse( new SimpleParser( new Php7(new Emulative()), new NameResolver(), + new VariadicMethodsVisitor(), + new VariadicFunctionsVisitor(), ), new PhpVersion($phpVersionId), ); diff --git a/tests/PHPStan/Parser/ParserTest.php b/tests/PHPStan/Parser/ParserTest.php new file mode 100644 index 00000000000..94fe9a406b8 --- /dev/null +++ b/tests/PHPStan/Parser/ParserTest.php @@ -0,0 +1,99 @@ + true, + ], + ]; + + yield [ + __DIR__ . '/data/variadic-methods.php', + VariadicMethodsVisitor::ATTRIBUTE_NAME, + [ + 'VariadicMethod\X' => [ + 'implicit_variadic_fn1' => true, + ], + 'VariadicMethod\Z' => [ + 'implicit_variadic_fnZ' => true, + ], + 'class@anonymous:20:30' => [ + 'implicit_variadic_subZ' => true, + ], + 'class@anonymous:42:52' => [ + 'implicit_variadic_fn' => true, + ], + 'class@anonymous:54:58' => [ + 'implicit_variadic_fn' => true, + ], + 'class@anonymous:61:68' => [ + 'implicit_variadic_fn' => true, + ], + ], + ]; + + yield [ + __DIR__ . '/data/variadic-methods-in-enum.php', + VariadicMethodsVisitor::ATTRIBUTE_NAME, + [ + 'VariadicMethodEnum\X' => [ + 'implicit_variadic_fn1' => true, + ], + ], + ]; + } + + /** + * @dataProvider dataVariadicCallLikes + * @param array|array> $expectedVariadics + * @throws ParserErrorsException + */ + public function testSimpleParserVariadicCallLikes(string $file, string $attributeName, array $expectedVariadics): void + { + /** @var SimpleParser $parser */ + $parser = self::getContainer()->getService('currentPhpVersionSimpleParser'); + $ast = $parser->parseFile($file); + $variadics = $ast[0]->getAttribute($attributeName); + $this->assertIsArray($variadics); + $this->assertCount(count($expectedVariadics), $variadics); + foreach ($expectedVariadics as $key => $expectedVariadic) { + $this->assertArrayHasKey($key, $variadics); + $this->assertSame($expectedVariadic, $variadics[$key]); + } + } + + /** + * @dataProvider dataVariadicCallLikes + * @param array|array> $expectedVariadics + * @throws ParserErrorsException + */ + public function testRichParserVariadicCallLikes(string $file, string $attributeName, array $expectedVariadics): void + { + /** @var RichParser $parser */ + $parser = self::getContainer()->getService('currentPhpVersionRichParser'); + $ast = $parser->parseFile($file); + $variadics = $ast[0]->getAttribute($attributeName); + $this->assertIsArray($variadics); + $this->assertCount(count($expectedVariadics), $variadics); + foreach ($expectedVariadics as $key => $expectedVariadic) { + $this->assertArrayHasKey($key, $variadics); + $this->assertSame($expectedVariadic, $variadics[$key]); + } + } + +} diff --git a/tests/PHPStan/Parser/data/variadic-functions.php b/tests/PHPStan/Parser/data/variadic-functions.php new file mode 100644 index 00000000000..d1a572e1e09 --- /dev/null +++ b/tests/PHPStan/Parser/data/variadic-functions.php @@ -0,0 +1,31 @@ += 8.1 + +namespace VariadicMethodEnum; + +enum X { + + function non_variadic_fn1($v) { + } + + function variadic_fn1(...$v) { + } + + function implicit_variadic_fn1() { + $args = func_get_args(); + } +} diff --git a/tests/PHPStan/Parser/data/variadic-methods.php b/tests/PHPStan/Parser/data/variadic-methods.php new file mode 100644 index 00000000000..da6135b9671 --- /dev/null +++ b/tests/PHPStan/Parser/data/variadic-methods.php @@ -0,0 +1,68 @@ +checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-11559c.php'], [ + [ + 'Method class@anonymous/tests/PHPStan/Rules/Methods/data/bug-11559c.php:6:1::regular_fn() invoked with 3 parameters, 1 required.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11559c.php b/tests/PHPStan/Rules/Methods/data/bug-11559c.php new file mode 100644 index 00000000000..54e3ba27b05 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11559c.php @@ -0,0 +1,16 @@ +implicit_variadic_fn(1, 2, 3); + $c->regular_fn(1, 2, 3); +} From 2f2d6a3848e637e27b175e067666d9b0a25c83f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 14:37:23 +0200 Subject: [PATCH 0661/3097] Cleanup - visitor already registered --- conf/config.neon | 3 --- 1 file changed, 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 583cceac81e..990ae2969c3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -259,9 +259,6 @@ services: tags: - phpstan.parser.richParserNodeVisitor - - - class: PHPStan\Parser\TypeTraverserInstanceofVisitor - - class: PHPStan\Parser\ArrowFunctionArgVisitor tags: From 19cd999f252ce582061c26b126819103425c75be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 14:37:59 +0200 Subject: [PATCH 0662/3097] Fixed optimization --- src/Parser/VariadicMethodsVisitor.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Parser/VariadicMethodsVisitor.php b/src/Parser/VariadicMethodsVisitor.php index 50882efc549..cc3821d9f21 100644 --- a/src/Parser/VariadicMethodsVisitor.php +++ b/src/Parser/VariadicMethodsVisitor.php @@ -29,10 +29,10 @@ final class VariadicMethodsVisitor extends NodeVisitorAbstract private ?string $inMethod = null; - /** @var array> */ + /** @var array> */ public static array $cache = []; - /** @var array> */ + /** @var array> */ private array $variadicMethods = []; public function beforeTraverse(array $nodes): ?array @@ -94,6 +94,10 @@ public function enterNode(Node $node): ?Node public function leaveNode(Node $node): ?Node { if ($node instanceof ClassMethod) { + $lastClass = $this->classStack[count($this->classStack) - 1] ?? null; + if ($lastClass !== null) { + $this->variadicMethods[$lastClass][$this->inMethod] ??= false; + } $this->inMethod = null; } @@ -111,12 +115,18 @@ public function leaveNode(Node $node): ?Node public function afterTraverse(array $nodes): ?array { if ($this->topNode !== null && $this->variadicMethods !== []) { + $filteredMethods = []; foreach ($this->variadicMethods as $class => $methods) { foreach ($methods as $name => $variadic) { self::$cache[$class][$name] = $variadic; + if (!$variadic) { + continue; + } + + $filteredMethods[$class][$name] = true; } } - $this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods); + $this->topNode->setAttribute(self::ATTRIBUTE_NAME, $filteredMethods); } return null; From 466ad51740d629c9137a77dac28a676b71ef7197 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 15:13:38 +0200 Subject: [PATCH 0663/3097] Clean file cache from unused items --- src/Cache/FileCacheStorage.php | 110 +++++++++++++++++++++++++++++++++ src/Command/CommandHelper.php | 5 ++ 2 files changed, 115 insertions(+) diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 5ba76a768a5..8337b859ecb 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -4,16 +4,32 @@ use InvalidArgumentException; use Nette\Utils\Random; +use PHPStan\File\CouldNotReadFileException; +use PHPStan\File\CouldNotWriteFileException; +use PHPStan\File\FileReader; use PHPStan\File\FileWriter; use PHPStan\Internal\DirectoryCreator; use PHPStan\Internal\DirectoryCreatorException; use PHPStan\ShouldNotHappenException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use function array_keys; +use function closedir; +use function dirname; use function error_get_last; +use function is_dir; use function is_file; +use function opendir; +use function readdir; use function rename; +use function rmdir; use function sha1; use function sprintf; +use function str_contains; +use function str_starts_with; +use function strlen; use function substr; +use function uksort; use function unlink; use function var_export; use const DIRECTORY_SEPARATOR; @@ -21,6 +37,8 @@ final class FileCacheStorage implements CacheStorage { + private const CACHED_CLEARED_VERSION = 'v1-variadic'; + public function __construct(private string $directory) { } @@ -100,4 +118,96 @@ private function getFilePaths(string $key): array ]; } + public function clearUnusedFiles(): void + { + if (!is_dir($this->directory)) { + return; + } + + $cachedClearedFile = $this->directory . '/cache-cleared'; + if (is_file($cachedClearedFile)) { + try { + $cachedClearedContents = FileReader::read($cachedClearedFile); + if ($cachedClearedContents === self::CACHED_CLEARED_VERSION) { + return; + } + } catch (CouldNotReadFileException) { + return; + } + } + + $iterator = new RecursiveDirectoryIterator($this->directory); + $iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator($iterator); + $beginFunction = sprintf( + "getPathname(); + $contents = FileReader::read($path); + if (str_contains($contents, 'odsl-')) { + continue; + } + if ( + !str_starts_with($contents, $beginFunction) + && !str_starts_with($contents, $beginMethod) + && !str_starts_with($contents, $beginOld) + ) { + continue; + } + + $emptyDirectoriesToCheck[dirname($path)] = true; + $emptyDirectoriesToCheck[dirname($path, 2)] = true; + + @unlink($path); + } catch (CouldNotReadFileException) { + continue; + } + } + + uksort($emptyDirectoriesToCheck, static fn ($a, $b) => strlen($b) - strlen($a)); + + foreach (array_keys($emptyDirectoriesToCheck) as $directory) { + if (!$this->isDirectoryEmpty($directory)) { + continue; + } + + @rmdir($directory); + } + + try { + FileWriter::write($cachedClearedFile, self::CACHED_CLEARED_VERSION); + } catch (CouldNotWriteFileException) { + // pass + } + } + + private function isDirectoryEmpty(string $directory): bool + { + $handle = opendir($directory); + if ($handle === false) { + return false; + } + while (($entry = readdir($handle)) !== false) { + if ($entry !== '.' && $entry !== '..') { + closedir($handle); + return false; + } + } + + closedir($handle); + return true; + } + } diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 7c81447cd89..65b059073eb 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -12,6 +12,7 @@ use Nette\Schema\ValidationException; use Nette\Utils\AssertionException; use Nette\Utils\Strings; +use PHPStan\Cache\FileCacheStorage; use PHPStan\Command\Symfony\SymfonyOutput; use PHPStan\Command\Symfony\SymfonyStyle; use PHPStan\DependencyInjection\Container; @@ -434,6 +435,10 @@ public static function begin( if ($cleanupContainerCache) { $containerFactory->clearOldContainers($tmpDir); + $cacheStorage = $container->getService('cacheStorage'); + if ($cacheStorage instanceof FileCacheStorage) { + $cacheStorage->clearUnusedFiles(); + } } /** @var bool|null $customRulesetUsed */ From 12a19b231ad9b46971682eb38d0acaa259c4ab9e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 15:38:20 +0200 Subject: [PATCH 0664/3097] This was also used format --- src/Cache/FileCacheStorage.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 8337b859ecb..18075f7e366 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -37,7 +37,7 @@ final class FileCacheStorage implements CacheStorage { - private const CACHED_CLEARED_VERSION = 'v1-variadic'; + private const CACHED_CLEARED_VERSION = 'v2-old-two'; public function __construct(private string $directory) { @@ -151,6 +151,10 @@ public function clearUnusedFiles(): void " Date: Tue, 8 Oct 2024 15:40:22 +0200 Subject: [PATCH 0665/3097] Update cache deletion logic --- src/Cache/FileCacheStorage.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 18075f7e366..1b66f26e2a5 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -25,7 +25,6 @@ use function rmdir; use function sha1; use function sprintf; -use function str_contains; use function str_starts_with; use function strlen; use function substr; @@ -37,7 +36,7 @@ final class FileCacheStorage implements CacheStorage { - private const CACHED_CLEARED_VERSION = 'v2-old-two'; + private const CACHED_CLEARED_VERSION = 'v2-new'; public function __construct(private string $directory) { @@ -147,27 +146,16 @@ public function clearUnusedFiles(): void "getPathname(); $contents = FileReader::read($path); - if (str_contains($contents, 'odsl-')) { - continue; - } if ( !str_starts_with($contents, $beginFunction) && !str_starts_with($contents, $beginMethod) - && !str_starts_with($contents, $beginOld) - && !str_starts_with($contents, $beginOld2) + && str_starts_with($contents, $beginNew) ) { continue; } From 57c65888e6372a4056afbbacc8207d411ea8559a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 8 Oct 2024 17:11:13 +0200 Subject: [PATCH 0666/3097] Journal for used generated containers --- src/Command/CommandHelper.php | 4 +- src/DependencyInjection/Configurator.php | 114 +++++++++++++++++- src/DependencyInjection/ContainerFactory.php | 49 ++------ .../DerivativeContainerFactory.php | 1 + 4 files changed, 124 insertions(+), 44 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 65b059073eb..499ebb48e8a 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -213,6 +213,9 @@ public static function begin( $analysedPathsFromConfig = []; $containerFactory = new ContainerFactory($currentWorkingDirectory); + if ($cleanupContainerCache) { + $containerFactory->setJournalContainer(); + } $projectConfig = null; if ($projectConfigFile !== null) { if (!is_file($projectConfigFile)) { @@ -434,7 +437,6 @@ public static function begin( } if ($cleanupContainerCache) { - $containerFactory->clearOldContainers($tmpDir); $cacheStorage = $container->getService('cacheStorage'); if ($cacheStorage instanceof FileCacheStorage) { $cacheStorage->clearUnusedFiles(); diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 31886c52bf6..524365e77eb 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -2,15 +2,31 @@ namespace PHPStan\DependencyInjection; +use DirectoryIterator; use Nette\DI\Config\Loader; use Nette\DI\Container as OriginalNetteContainer; use Nette\DI\ContainerLoader; use PHPStan\File\CouldNotReadFileException; +use PHPStan\File\CouldNotWriteFileException; +use PHPStan\File\FileReader; +use PHPStan\File\FileWriter; use function array_keys; +use function count; use function error_reporting; +use function explode; +use function implode; +use function in_array; +use function is_dir; +use function is_file; use function restore_error_handler; use function set_error_handler; use function sha1_file; +use function sprintf; +use function str_ends_with; +use function substr; +use function time; +use function trim; +use function unlink; use const E_USER_DEPRECATED; use const PHP_RELEASE_VERSION; use const PHP_VERSION_ID; @@ -21,7 +37,7 @@ final class Configurator extends \Nette\Bootstrap\Configurator /** @var string[] */ private array $allConfigFiles = []; - public function __construct(private LoaderFactory $loaderFactory) + public function __construct(private LoaderFactory $loaderFactory, private bool $journalContainer) { parent::__construct(); } @@ -59,10 +75,104 @@ public function loadContainer(): string $this->staticParameters['debugMode'], ); - return $loader->load( + $className = $loader->load( [$this, 'generateContainer'], [$this->staticParameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY, $this->getAllConfigFilesHashes()], ); + + if ($this->journalContainer) { + $this->journal($className); + } + + return $className; + } + + private function journal(string $currentContainerClassName): void + { + $directory = $this->getContainerCacheDirectory(); + if (!is_dir($directory)) { + return; + } + + $journalFile = $directory . '/container.journal'; + if (!is_file($journalFile)) { + try { + FileWriter::write($journalFile, sprintf("%s:%d\n", $currentContainerClassName, time())); + } catch (CouldNotWriteFileException) { + // pass + } + + return; + } + + try { + $journalContents = FileReader::read($journalFile); + } catch (CouldNotReadFileException) { + return; + } + + $journalLines = explode("\n", trim($journalContents)); + $linesToWrite = []; + $usedInTheLastWeek = []; + $now = time(); + $currentAlreadyInTheJournal = false; + foreach ($journalLines as $journalLine) { + if ($journalLine === '') { + continue; + } + $journalLineParts = explode(':', $journalLine); + if (count($journalLineParts) !== 2) { + return; + } + $className = $journalLineParts[0]; + $containerLastUsedTime = (int) $journalLineParts[1]; + + $week = 3600 * 24 * 7; + + if ($containerLastUsedTime + $week >= $now) { + $usedInTheLastWeek[] = $className; + } + + if ($currentContainerClassName !== $className) { + $linesToWrite[] = sprintf('%s:%d', $className, $containerLastUsedTime); + continue; + } + + $linesToWrite[] = sprintf('%s:%d', $currentContainerClassName, $now); + $currentAlreadyInTheJournal = true; + } + + if (!$currentAlreadyInTheJournal) { + $linesToWrite[] = sprintf('%s:%d', $currentContainerClassName, $now); + $usedInTheLastWeek[] = $currentContainerClassName; + } + + try { + FileWriter::write($journalFile, implode("\n", $linesToWrite) . "\n"); + } catch (CouldNotWriteFileException) { + return; + } + + foreach (new DirectoryIterator($directory) as $fileInfo) { + if ($fileInfo->isDot()) { + continue; + } + $fileName = $fileInfo->getFilename(); + if ($fileName === 'container.journal') { + continue; + } + if (!str_ends_with($fileName, '.php')) { + continue; + } + $fileClassName = substr($fileName, 0, -4); + if (in_array($fileClassName, $usedInTheLastWeek, true)) { + continue; + } + $basePathname = $fileInfo->getPathname(); + @unlink($basePathname); + @unlink($basePathname . '.lock'); + @unlink($basePathname . '.meta'); + } } public function createContainer(bool $initialize = true): OriginalNetteContainer diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index c997c65ec29..7ac72d4fc49 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -31,7 +31,6 @@ use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; -use Symfony\Component\Finder\Finder; use function array_diff_key; use function array_map; use function array_merge; @@ -42,15 +41,12 @@ use function getenv; use function ini_get; use function is_array; -use function is_dir; use function is_file; use function is_readable; use function spl_object_id; use function sprintf; use function str_ends_with; use function substr; -use function time; -use function unlink; /** * @api @@ -66,6 +62,8 @@ final class ContainerFactory private static ?int $lastInitializedContainerId = null; + private bool $journalContainer = false; + /** @api */ public function __construct(private string $currentWorkingDirectory) { @@ -83,6 +81,11 @@ public function __construct(private string $currentWorkingDirectory) $this->configDirectory = $originalRootDir . '/conf'; } + public function setJournalContainer(): void + { + $this->journalContainer = true; + } + /** * @param string[] $additionalConfigFiles * @param string[] $analysedPaths @@ -114,7 +117,7 @@ public function create( $this->rootDirectory, $this->currentWorkingDirectory, $generateBaselineFile, - )); + ), $this->journalContainer); $configurator->defaultExtensions = [ 'php' => PhpExtension::class, 'extensions' => ExtensionsExtension::class, @@ -188,42 +191,6 @@ public static function postInitializeContainer(Container $container): void BleedingEdgeToggle::setBleedingEdge($container->getParameter('featureToggles')['bleedingEdge']); } - public function clearOldContainers(string $tempDirectory): void - { - $configurator = new Configurator(new LoaderFactory( - $this->fileHelper, - $this->rootDirectory, - $this->currentWorkingDirectory, - null, - )); - $configurator->setDebugMode(true); - $configurator->setTempDirectory($tempDirectory); - - $containerDirectory = $configurator->getContainerCacheDirectory(); - if (!is_dir($containerDirectory)) { - return; - } - - $finder = new Finder(); - $finder->name('Container_*')->in($containerDirectory); - $twoDaysAgo = time() - 24 * 60 * 60 * 2; - - foreach ($finder as $containerFile) { - $path = $containerFile->getRealPath(); - if ($path === false) { - continue; - } - if ($containerFile->getATime() > $twoDaysAgo) { - continue; - } - if ($containerFile->getCTime() > $twoDaysAgo) { - continue; - } - - @unlink($path); - } - } - public function getCurrentWorkingDirectory(): string { return $this->currentWorkingDirectory; diff --git a/src/DependencyInjection/DerivativeContainerFactory.php b/src/DependencyInjection/DerivativeContainerFactory.php index a32bc2eb6a0..218eb4e2524 100644 --- a/src/DependencyInjection/DerivativeContainerFactory.php +++ b/src/DependencyInjection/DerivativeContainerFactory.php @@ -35,6 +35,7 @@ public function create(array $additionalConfigFiles): Container $containerFactory = new ContainerFactory( $this->currentWorkingDirectory, ); + $containerFactory->setJournalContainer(); return $containerFactory->create( $this->tempDirectory, From 98e2b6e72a22bb2c287aa0b84a011839697c36c5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 8 Oct 2024 17:46:14 +0200 Subject: [PATCH 0667/3097] More precise md5/sha1 return type --- resources/functionMap.php | 8 ++--- ...rictComparisonOfDifferentTypesRuleTest.php | 20 +++++++++++ .../PHPStan/Rules/Comparison/data/hashing.php | 34 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/hashing.php diff --git a/resources/functionMap.php b/resources/functionMap.php index f8c4bf30e03..2dcf0feb8a7 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6403,8 +6403,8 @@ 'mcrypt_module_open' => ['resource|false', 'cipher'=>'string', 'cipher_directory'=>'string', 'mode'=>'string', 'mode_directory'=>'string'], 'mcrypt_module_self_test' => ['bool', 'algorithm'=>'string', 'lib_dir='=>'string'], 'mcrypt_ofb' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], -'md5' => ['non-falsy-string', 'str'=>'string', 'raw_output='=>'bool'], -'md5_file' => ['non-falsy-string|false', 'filename'=>'string', 'raw_output='=>'bool'], +'md5' => ['non-falsy-string&lowercase-string', 'str'=>'string', 'raw_output='=>'bool'], +'md5_file' => ['(non-falsy-string&lowercase-string)|false', 'filename'=>'string', 'raw_output='=>'bool'], 'mdecrypt_generic' => ['string', 'td'=>'resource', 'data'=>'string'], 'Memcache::add' => ['bool', 'key'=>'string', 'var'=>'mixed', 'flag='=>'int', 'expire='=>'int'], 'Memcache::addServer' => ['bool', 'host'=>'string', 'port='=>'int', 'persistent='=>'bool', 'weight='=>'int', 'timeout='=>'int', 'retry_interval='=>'int', 'status='=>'bool', 'failure_callback='=>'callable', 'timeoutms='=>'int'], @@ -10446,8 +10446,8 @@ 'setRightFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], 'setthreadtitle' => ['bool', 'title'=>'string'], 'settype' => ['bool', '&rw_var'=>'mixed', 'type'=>'string'], -'sha1' => ['non-falsy-string', 'str'=>'string', 'raw_output='=>'bool'], -'sha1_file' => ['non-falsy-string|false', 'filename'=>'string', 'raw_output='=>'bool'], +'sha1' => ['non-falsy-string&lowercase-string', 'str'=>'string', 'raw_output='=>'bool'], +'sha1_file' => ['(non-falsy-string&lowercase-string)|false', 'filename'=>'string', 'raw_output='=>'bool'], 'sha256' => ['string', 'str'=>'string', 'raw_output='=>'bool'], 'sha256_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'], 'shapefileObj::__construct' => ['void', 'filename'=>'string', 'type'=>'int'], diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index e50ab41805a..4721b4e78bc 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1121,4 +1121,24 @@ public function testBug10493(): void $this->analyse([__DIR__ . '/data/bug-10493.php'], []); } + public function testHashing(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/hashing.php'], [ + [ + "Strict comparison using === between lowercase-string&non-falsy-string and 'ABC' will always evaluate to false.", + 9, + ], + [ + "Strict comparison using === between (lowercase-string&non-falsy-string)|false and 'ABC' will always evaluate to false.", + 12, + ], + [ + "Strict comparison using === between (lowercase-string&non-falsy-string)|(non-falsy-string&numeric-string) and 'A' will always evaluate to false.", + 31, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/hashing.php b/tests/PHPStan/Rules/Comparison/data/hashing.php new file mode 100644 index 00000000000..14fad8b550f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/hashing.php @@ -0,0 +1,34 @@ + Date: Wed, 9 Oct 2024 09:20:24 +0200 Subject: [PATCH 0668/3097] Update nikic/php-parser --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 810f663d6ba..3e705fbf757 100644 --- a/composer.lock +++ b/composer.lock @@ -2057,16 +2057,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -2109,9 +2109,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "ondram/ci-detector", From ca1e144cf83b1560d81292eb0a67e78d45e9525d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 09:22:22 +0200 Subject: [PATCH 0669/3097] Revert "Fix generate-function-metadata.php script" This reverts commit 14a3b36d72f149fda2837cf11cc899db5aa87a7a. --- bin/generate-function-metadata.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index 7b834e2cbb4..97737499a5e 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -69,19 +69,8 @@ public function enterNode(Node $node) $traverser->addVisitor(new NodeConnectingVisitor()); $traverser->addVisitor($visitor); - $contents = FileReader::read($path); - if (str_ends_with($path, '/vendor/jetbrains/phpstorm-stubs/Core/Core.php')) { - $contents = str_replace([ - 'function exit', - 'function die', - ], [ - 'function _exit', - 'function _die', - ], $contents); - } - $traverser->traverse( - $parser->parse($contents), + $parser->parse(FileReader::read($path)), ); } From 3316a152a417141318744461aebba28db47252a8 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 9 Oct 2024 07:26:28 +0000 Subject: [PATCH 0670/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 54283827b43..01d6a7219f2 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.9", - "phpstan/php-8-stubs": "0.4.1", + "phpstan/php-8-stubs": "0.4.2", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 3e705fbf757..a0e88b728cb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "416d829025e6a2d8f7115f4c142b0718", + "content-hash": "ff0567b9fb64e25665e322c6daa69896", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.1", + "version": "0.4.2", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "212d2b20c3c6f8c06a224efb748ec4cd069ef251" + "reference": "64fbb357f86728a3d0a06d57178bf968bcf82206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/212d2b20c3c6f8c06a224efb748ec4cd069ef251", - "reference": "212d2b20c3c6f8c06a224efb748ec4cd069ef251", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/64fbb357f86728a3d0a06d57178bf968bcf82206", + "reference": "64fbb357f86728a3d0a06d57178bf968bcf82206", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.1" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.2" }, - "time": "2024-10-08T00:18:48+00:00" + "time": "2024-10-09T07:25:55+00:00" }, { "name": "phpstan/phpdoc-parser", From 44a28cf81ca534970ba5d090285bf3858239c07c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 09:28:38 +0200 Subject: [PATCH 0671/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- resources/functionMetadata.php | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 01d6a7219f2..22bc7b1060a 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#56f6b9e55f5885e651553843a1aaf9ec9c586c04", + "jetbrains/phpstorm-stubs": "dev-master#a45eab9318f66864e9840379d3a1976ffe9b8d63", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index a0e88b728cb..8f8c4a28705 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ff0567b9fb64e25665e322c6daa69896", + "content-hash": "e1fa4f06ec0cf6213d6cc617ffcc992d", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "56f6b9e55f5885e651553843a1aaf9ec9c586c04" + "reference": "a45eab9318f66864e9840379d3a1976ffe9b8d63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/56f6b9e55f5885e651553843a1aaf9ec9c586c04", - "reference": "56f6b9e55f5885e651553843a1aaf9ec9c586c04", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/a45eab9318f66864e9840379d3a1976ffe9b8d63", + "reference": "a45eab9318f66864e9840379d3a1976ffe9b8d63", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-09-06T13:20:48+00:00" + "time": "2024-10-05T19:50:06+00:00" }, { "name": "nette/bootstrap", diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index 4ba615393d5..0c5c33759a9 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -651,7 +651,6 @@ 'StringBackedEnum::tryFrom' => ['hasSideEffects' => false], 'StubTests\\CodeStyle\\BracesOneLineFixer::getDefinition' => ['hasSideEffects' => false], 'StubTests\\Parsers\\ExpectedFunctionArgumentsInfo::__toString' => ['hasSideEffects' => false], - 'StubTests\\Parsers\\Visitors\\CoreStubASTVisitor::__construct' => ['hasSideEffects' => false], 'StubTests\\StubsMetaExpectedArgumentsTest::getClassMemberFqn' => ['hasSideEffects' => false], 'StubTests\\StubsParameterNamesTest::printParameters' => ['hasSideEffects' => false], 'Transliterator::createInverse' => ['hasSideEffects' => false], From c79b69ad56cda8600ee3d68b1b07ad82bc4e8de7 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 9 Oct 2024 09:31:30 +0200 Subject: [PATCH 0672/3097] Move result cache output from debug to very verbose mode --- .../ResultCache/ResultCacheManager.php | 32 +++++++++---------- src/Command/AnalyseApplication.php | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index d8cfceb646c..567b61f798d 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -92,13 +92,13 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? { $startTime = microtime(true); if ($debug) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because of debug mode.'); } return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); } if ($onlyFiles) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.'); } return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); @@ -106,7 +106,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $cacheFilePath = $this->cacheFilePath; if (!is_file($cacheFilePath)) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because the cache file does not exist.'); } return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); @@ -115,7 +115,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? try { $data = require $cacheFilePath; } catch (Throwable $e) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache not used because an error occurred while loading the cache file: %s', $e->getMessage())); } @@ -126,7 +126,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if (!is_array($data)) { @unlink($cacheFilePath); - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because the cache file is corrupted.'); } @@ -135,7 +135,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $meta = $this->getMeta($allAnalysedFiles, $projectConfigArray); if ($this->isMetaDifferent($data['meta'], $meta)) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $diffs = $this->getMetaKeyDifferences($data['meta'], $meta); $output->writeLineFormatted('Result cache not used because the metadata do not match: ' . implode(', ', $diffs)); } @@ -143,7 +143,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? } if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.'); } // run full analysis if the result cache is older than 7 days @@ -159,7 +159,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? continue; } if (!is_file($extensionFile)) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile)); } return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); @@ -169,7 +169,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? continue; } - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile)); } @@ -287,7 +287,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $filesToAnalyse = array_unique($filesToAnalyse); $filesToAnalyseCount = count($filesToAnalyse); - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $elapsed = microtime(true) - $startTime; $elapsedString = $elapsed > 5 ? sprintf(' in %f seconds', round($elapsed, 1)) @@ -412,20 +412,20 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } $doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, ?array $dependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { if ($onlyFiles) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.'); } return false; } if ($dependencies === null) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because of error in dependencies.'); } return false; } if (count($internalErrors) > 0) { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because of internal errors.'); } return false; @@ -437,7 +437,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache continue; } - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache was not saved because of non-ignorable exception: %s', $error->getMessage())); } @@ -447,7 +447,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $meta); - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache is saved.'); } @@ -463,7 +463,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } $saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $analyserResult->getLinesToIgnore(), $analyserResult->getUnmatchedLineIgnores(), $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles); } else { - if ($output->isDebug()) { + if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because it was not requested.'); } } diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index af739bc02c6..88589db6cc6 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -60,7 +60,7 @@ public function analyse( $collectedData = []; $savedResultCache = false; $memoryUsageBytes = memory_get_peak_usage(true); - if ($errorOutput->isDebug()) { + if ($errorOutput->isVeryVerbose()) { $errorOutput->writeLineFormatted('Result cache was not saved because of ignoredErrorHelperResult errors.'); } $changedProjectExtensionFilesOutsideOfAnalysedPaths = []; From f9a264832bb10783c6a0f2f4cd2df9985786bd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Wed, 9 Oct 2024 09:35:32 +0200 Subject: [PATCH 0673/3097] Implement template default types Co-authored-by: Richard van Velzen Co-authored-by: Richard van Velzen --- src/Analyser/MutatingScope.php | 6 + src/Dependency/DependencyResolver.php | 11 ++ src/PhpDoc/PhpDocNodeResolver.php | 8 +- src/PhpDoc/Tag/TemplateTag.php | 7 +- src/PhpDoc/TypeNodeResolver.php | 13 ++ src/Reflection/ClassReflection.php | 4 +- src/Rules/Classes/LocalTypeAliasesCheck.php | 3 +- src/Rules/Classes/MethodTagCheck.php | 3 +- src/Rules/Classes/MixinCheck.php | 3 +- src/Rules/Classes/PropertyTagCheck.php | 3 +- .../MissingClassConstantTypehintRule.php | 3 +- src/Rules/FunctionCallParametersCheck.php | 4 +- .../MissingFunctionParameterTypehintRule.php | 3 +- .../MissingFunctionReturnTypehintRule.php | 3 +- src/Rules/Generics/ClassTemplateTypeRule.php | 3 + .../Generics/FunctionTemplateTypeRule.php | 3 + src/Rules/Generics/GenericAncestorsCheck.php | 16 ++- src/Rules/Generics/GenericObjectTypeCheck.php | 19 ++- .../Generics/InterfaceTemplateTypeRule.php | 3 + .../Generics/MethodTagTemplateTypeCheck.php | 3 + src/Rules/Generics/MethodTemplateTypeRule.php | 3 + src/Rules/Generics/TemplateTypeCheck.php | 63 ++++++++ src/Rules/Generics/TraitTemplateTypeRule.php | 3 + .../MissingMethodParameterTypehintRule.php | 3 +- .../MissingMethodReturnTypehintRule.php | 3 +- .../Methods/MissingMethodSelfOutTypeRule.php | 3 +- src/Rules/MissingTypehintCheck.php | 20 ++- src/Rules/PhpDoc/AssertRuleHelper.php | 3 +- .../PhpDoc/GenericCallableRuleHelper.php | 3 + .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 3 +- .../MissingPropertyTypehintRule.php | 3 +- src/Type/Generic/TemplateArrayType.php | 3 + .../Generic/TemplateBenevolentUnionType.php | 3 + src/Type/Generic/TemplateBooleanType.php | 3 + .../Generic/TemplateConstantArrayType.php | 3 + .../Generic/TemplateConstantIntegerType.php | 3 + .../Generic/TemplateConstantStringType.php | 3 + src/Type/Generic/TemplateFloatType.php | 3 + .../Generic/TemplateGenericObjectType.php | 3 + src/Type/Generic/TemplateIntegerType.php | 3 + src/Type/Generic/TemplateIntersectionType.php | 3 + src/Type/Generic/TemplateKeyOfType.php | 3 + src/Type/Generic/TemplateMixedType.php | 3 + src/Type/Generic/TemplateObjectShapeType.php | 3 + src/Type/Generic/TemplateObjectType.php | 3 + .../TemplateObjectWithoutClassType.php | 3 + src/Type/Generic/TemplateStrictMixedType.php | 2 + src/Type/Generic/TemplateStringType.php | 3 + src/Type/Generic/TemplateType.php | 2 + src/Type/Generic/TemplateTypeFactory.php | 42 +++--- src/Type/Generic/TemplateTypeHelper.php | 2 +- src/Type/Generic/TemplateTypeMap.php | 6 +- src/Type/Generic/TemplateTypeTrait.php | 33 ++++- src/Type/Generic/TemplateUnionType.php | 3 + src/Type/TypeCombinator.php | 2 + src/Type/TypeUtils.php | 1 + .../Analyser/NodeScopeResolverTest.php | 3 + .../Analyser/data/template-default.php | 136 ++++++++++++++++++ .../Generics/ClassTemplateTypeRuleTest.php | 12 ++ .../Generics/FunctionTemplateTypeRuleTest.php | 12 ++ .../InterfaceTemplateTypeRuleTest.php | 12 ++ .../Generics/MethodTemplateTypeRuleTest.php | 12 ++ .../Generics/TraitTemplateTypeRuleTest.php | 12 ++ .../Generics/data/class-ancestors-extends.php | 13 ++ .../data/class-ancestors-implements.php | 15 ++ .../Rules/Generics/data/class-template.php | 26 ++++ .../Rules/Generics/data/enum-ancestors.php | 13 ++ .../Rules/Generics/data/function-template.php | 26 ++++ .../Generics/data/interface-template.php | 26 ++++ .../Rules/Generics/data/method-template.php | 31 ++++ .../Rules/Generics/data/trait-template.php | 26 ++++ .../Rules/Generics/data/used-traits.php | 14 ++ .../Rules/Methods/CallMethodsRuleTest.php | 9 ++ ...MissingMethodParameterTypehintRuleTest.php | 4 + .../MissingMethodReturnTypehintRuleTest.php | 4 + tests/PHPStan/Rules/Methods/data/bug-4801.php | 25 ++++ .../missing-method-parameter-typehint.php | 32 +++++ .../data/missing-method-return-typehint.php | 32 +++++ .../InvalidPhpDocVarTagTypeRuleTest.php | 6 +- .../data/invalid-phpdoc-definitions.php | 17 +++ .../PhpDoc/data/invalid-var-tag-type.php | 6 + .../MissingPropertyTypehintRuleTest.php | 4 + .../data/missing-property-typehint.php | 28 ++++ 83 files changed, 861 insertions(+), 67 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/template-default.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4801.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d8cc9faf069..5d6636683fb 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2539,6 +2539,7 @@ private function createFirstClassCallable( $templateTags[$templateType->getName()] = new TemplateTag( $templateType->getName(), $templateType->getBound(), + $templateType->getDefault(), $templateType->getVariance(), ); } @@ -5606,6 +5607,11 @@ private function exactInstantiation(New_ $node, string $className): ?Type $list[] = $templateType; continue; } + $default = $tag->getDefault(); + if ($default !== null) { + $list[] = $default; + continue; + } $bound = $tag->getBound(); if ($bound instanceof MixedType && $bound->isExplicitMixed()) { $bound = new MixedType(false); diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index f9bfcf314b1..231db1ba7d7 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -531,6 +531,17 @@ private function addClassToDependencies(string $className, array &$dependenciesR } $dependenciesReflections[] = $this->reflectionProvider->getClass($referencedClass); } + + $default = $templateTag->getDefault(); + if ($default === null) { + continue; + } + foreach ($default->getReferencedClasses() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + $dependenciesReflections[] = $this->reflectionProvider->getClass($referencedClass); + } } foreach ($classReflection->getPropertyTags() as $propertyTag) { diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 402f8c7dd1f..02fed04bcc6 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -176,6 +176,9 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope): $templateType->bound !== null ? $this->typeNodeResolver->resolve($templateType->bound, $nameScope) : new MixedType(), + $templateType->default !== null + ? $this->typeNodeResolver->resolve($templateType->default, $nameScope) + : null, TemplateTypeVariance::createInvariant(), ); } @@ -327,9 +330,12 @@ public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope } } + $nameScopeWithoutCurrent = $nameScope->unsetTemplateType($valueNode->name); + $resolved[$valueNode->name] = new TemplateTag( $valueNode->name, - $valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(true), + $valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScopeWithoutCurrent) : new MixedType(true), + $valueNode->default !== null ? $this->typeNodeResolver->resolve($valueNode->default, $nameScopeWithoutCurrent) : null, $variance, ); $resolvedPrefix[$valueNode->name] = $prefix; diff --git a/src/PhpDoc/Tag/TemplateTag.php b/src/PhpDoc/Tag/TemplateTag.php index a14fa2c6abb..4ae755597fa 100644 --- a/src/PhpDoc/Tag/TemplateTag.php +++ b/src/PhpDoc/Tag/TemplateTag.php @@ -15,7 +15,7 @@ class TemplateTag /** * @param non-empty-string $name */ - public function __construct(private string $name, private Type $bound, private TemplateTypeVariance $variance) + public function __construct(private string $name, private Type $bound, private ?Type $default, private TemplateTypeVariance $variance) { } @@ -32,6 +32,11 @@ public function getBound(): Type return $this->bound; } + public function getDefault(): ?Type + { + return $this->default; + } + public function getVariance(): TemplateTypeVariance { return $this->variance; diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 06098d79388..6e483222d7b 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -106,6 +106,7 @@ use Traversable; use function array_key_exists; use function array_map; +use function array_values; use function count; use function explode; use function get_class; @@ -792,6 +793,15 @@ static function (string $variance): TemplateTypeVariance { $classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName); if ($classReflection->isGeneric()) { + $templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes()); + for ($i = count($genericTypes), $templateTypesCount = count($templateTypes); $i < $templateTypesCount; $i++) { + $templateType = $templateTypes[$i]; + if (!$templateType instanceof TemplateType || $templateType->getDefault() === null) { + continue; + } + $genericTypes[] = $templateType->getDefault(); + } + if (in_array($mainTypeClassName, [ Traversable::class, IteratorAggregate::class, @@ -910,6 +920,9 @@ private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $ $templateType->bound !== null ? $this->resolve($templateType->bound, $nameScope) : new MixedType(), + $templateType->default !== null + ? $this->resolve($templateType->default, $nameScope) + : null, TemplateTypeVariance::createInvariant(), ); } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index d980be78649..0254204a639 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1442,7 +1442,7 @@ public function typeMapFromList(array $types): TemplateTypeMap $map = []; $i = 0; foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $map[$tag->getName()] = $types[$i] ?? $tag->getBound(); + $map[$tag->getName()] = $types[$i] ?? $tag->getDefault() ?? $tag->getBound(); $i++; } @@ -1479,7 +1479,7 @@ public function typeMapToList(TemplateTypeMap $typeMap): array $list = []; foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { - $list[] = $typeMap->getType($tag->getName()) ?? $tag->getBound(); + $list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound(); } return $list; diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 5347681a906..e2d463ab9cd 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -24,7 +24,6 @@ use PHPStan\Type\VerbosityLevel; use function array_key_exists; use function array_merge; -use function implode; use function in_array; use function sprintf; @@ -211,7 +210,7 @@ public function checkInTraitDefinitionContext(ClassReflection $reflection): arra $reflection->getDisplayName(), $aliasName, $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index b37d373772a..5730ea3a9bb 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -16,7 +16,6 @@ use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function array_merge; -use function implode; use function sprintf; final class MethodTagCheck @@ -174,7 +173,7 @@ private function checkMethodTypeInTraitDefinitionContext(ClassReflection $classR $methodName, $description, $innerName, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index a17ef3d2001..3ce9535164b 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -14,7 +14,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; use function array_merge; -use function implode; use function sprintf; final class MixinCheck @@ -90,7 +89,7 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): $errors[] = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @mixin contains generic %s but does not specify its types: %s', $innerName, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index e05c4c676bb..c3e9fda73fb 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -18,7 +18,6 @@ use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function array_merge; -use function implode; use function sprintf; final class PropertyTagCheck @@ -155,7 +154,7 @@ private function checkPropertyTypeInTraitDefinitionContext(ClassReflection $clas $classReflection->getDisplayName(), $propertyName, $innerName, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Constants/MissingClassConstantTypehintRule.php b/src/Rules/Constants/MissingClassConstantTypehintRule.php index 8a9aee13556..e0cbaa844c6 100644 --- a/src/Rules/Constants/MissingClassConstantTypehintRule.php +++ b/src/Rules/Constants/MissingClassConstantTypehintRule.php @@ -12,7 +12,6 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\VerbosityLevel; use function array_merge; -use function implode; use function sprintf; /** @@ -76,7 +75,7 @@ private function processSingleConstant(ClassReflection $classReflection, string $constantReflection->getDeclaringClass()->getDisplayName(), $constantName, $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 6a1d2f16b11..40fb657bcce 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -432,7 +432,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty $type = $type->resolve(); } - if ($type instanceof TemplateType) { + if ($type instanceof TemplateType && $type->getDefault() === null) { $returnTemplateTypes[$type->getName()] = true; return $type; } @@ -444,7 +444,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty $parameterTemplateTypes = []; foreach ($originalParametersAcceptor->getParameters() as $parameter) { TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$parameterTemplateTypes): Type { - if ($type instanceof TemplateType) { + if ($type instanceof TemplateType && $type->getDefault() === null) { $parameterTemplateTypes[$type->getName()] = true; return $type; } diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php index a7519de7a57..c988c70c08d 100644 --- a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -13,7 +13,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -100,7 +99,7 @@ private function checkFunctionParameter(FunctionReflection $functionReflection, $functionReflection->getName(), $parameterMessage, $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php index d49e7f9aa93..648636973e8 100644 --- a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php @@ -10,7 +10,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\MixedType; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -58,7 +57,7 @@ public function processNode(Node $node, Scope $scope): array 'Function %s() return type with generic %s does not specify its types: %s', $functionReflection->getName(), $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Generics/ClassTemplateTypeRule.php b/src/Rules/Generics/ClassTemplateTypeRule.php index 6c21c3a33d3..f574d76460e 100644 --- a/src/Rules/Generics/ClassTemplateTypeRule.php +++ b/src/Rules/Generics/ClassTemplateTypeRule.php @@ -49,6 +49,9 @@ public function processNode(Node $node, Scope $scope): array sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName), sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName), sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName), + sprintf('PHPDoc tag @template %%s for %s has invalid default type %%s.', $displayName), + sprintf('Default type %%s in PHPDoc tag @template %%s for %s is not subtype of bound type %%s.', $displayName), + sprintf('PHPDoc tag @template %%s for %s does not have a default type but follows an optional @template %%s.', $displayName), ); } diff --git a/src/Rules/Generics/FunctionTemplateTypeRule.php b/src/Rules/Generics/FunctionTemplateTypeRule.php index 2fe0ab6bfbf..d4b56da5f2b 100644 --- a/src/Rules/Generics/FunctionTemplateTypeRule.php +++ b/src/Rules/Generics/FunctionTemplateTypeRule.php @@ -60,6 +60,9 @@ public function processNode(Node $node, Scope $scope): array sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $escapedFunctionName), sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $escapedFunctionName), sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $escapedFunctionName), + sprintf('PHPDoc tag @template %%s for function %s() has invalid default type %%s.', $escapedFunctionName), + sprintf('Default type %%s in PHPDoc tag @template %%s for function %s() is not subtype of bound type %%s.', $escapedFunctionName), + sprintf('PHPDoc tag @template %%s for function %s() does not have a default type but follows an optional @template %%s.', $escapedFunctionName), ); } diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index ef9ce469b57..c2eaef580e7 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -9,11 +9,13 @@ use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TypeProjectionHelper; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function array_fill_keys; +use function array_filter; use function array_keys; use function array_map; use function array_merge; @@ -173,10 +175,22 @@ public function check( continue; } + $templateTypes = $unusedNameClassReflection->getTemplateTypeMap()->getTypes(); + $templateTypesCount = count($templateTypes); + $requiredTemplateTypesCount = count(array_filter($templateTypes, static fn (Type $type) => $type instanceof TemplateType && $type->getDefault() === null)); + if ($requiredTemplateTypesCount === 0) { + continue; + } + + $templateTypesList = implode(', ', array_keys($templateTypes)); + if ($requiredTemplateTypesCount !== $templateTypesCount) { + $templateTypesList .= sprintf(' (%d-%d required)', $requiredTemplateTypesCount, $templateTypesCount); + } + $messages[] = RuleErrorBuilder::message(sprintf( $genericClassInNonGenericObjectType, $unusedName, - implode(', ', array_keys($unusedNameClassReflection->getTemplateTypeMap()->getTypes())), + $templateTypesList, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Generics/GenericObjectTypeCheck.php b/src/Rules/Generics/GenericObjectTypeCheck.php index 46901218ef2..3f437a0b91e 100644 --- a/src/Rules/Generics/GenericObjectTypeCheck.php +++ b/src/Rules/Generics/GenericObjectTypeCheck.php @@ -14,6 +14,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\VerbosityLevel; +use function array_filter; use function array_keys; use function array_values; use function count; @@ -59,15 +60,26 @@ public function check( $genericTypeVariances = $genericType->getVariances(); $templateTypesCount = count($templateTypes); $genericTypeTypesCount = count($genericTypeTypes); - if ($templateTypesCount > $genericTypeTypesCount) { + $requiredTemplateTypesCount = count(array_filter($templateTypes, static fn (Type $type) => $type instanceof TemplateType && $type->getDefault() === null)); + if ($requiredTemplateTypesCount > $genericTypeTypesCount) { + $templateTypesList = implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())); + if ($requiredTemplateTypesCount !== $templateTypesCount) { + $templateTypesList .= sprintf(' (%d-%d required).', $requiredTemplateTypesCount, $templateTypesCount); + } + $messages[] = RuleErrorBuilder::message(sprintf( $notEnoughTypesMessage, $genericType->describe(VerbosityLevel::typeOnly()), $classLikeDescription, $classReflection->getDisplayName(false), - implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())), + $templateTypesList, ))->identifier('generics.lessTypes')->build(); } elseif ($templateTypesCount < $genericTypeTypesCount) { + $templateTypesList = implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())); + if ($requiredTemplateTypesCount !== $templateTypesCount) { + $templateTypesList .= sprintf(' (%d-%d required)', $requiredTemplateTypesCount, $templateTypesCount); + } + $messages[] = RuleErrorBuilder::message(sprintf( $extraTypesMessage, $genericType->describe(VerbosityLevel::typeOnly()), @@ -75,11 +87,10 @@ public function check( $classLikeDescription, $classReflection->getDisplayName(false), $templateTypesCount, - implode(', ', array_keys($classReflection->getTemplateTypeMap()->getTypes())), + $templateTypesList, ))->identifier('generics.moreTypes')->build(); } - $templateTypesCount = count($templateTypes); for ($i = 0; $i < $templateTypesCount; $i++) { if (!isset($genericTypeTypes[$i])) { continue; diff --git a/src/Rules/Generics/InterfaceTemplateTypeRule.php b/src/Rules/Generics/InterfaceTemplateTypeRule.php index 30be451eaed..53adafb43a8 100644 --- a/src/Rules/Generics/InterfaceTemplateTypeRule.php +++ b/src/Rules/Generics/InterfaceTemplateTypeRule.php @@ -46,6 +46,9 @@ public function processNode(Node $node, Scope $scope): array sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $escapadInterfaceName), sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $escapadInterfaceName), sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $escapadInterfaceName), + sprintf('PHPDoc tag @template %%s for interface %s has invalid default type %%s.', $escapadInterfaceName), + sprintf('Default type %%s in PHPDoc tag @template %%s for interface %s is not subtype of bound type %%s.', $escapadInterfaceName), + sprintf('PHPDoc tag @template %%s for interface %s does not have a default type but follows an optional @template %%s.', $escapadInterfaceName), ); } diff --git a/src/Rules/Generics/MethodTagTemplateTypeCheck.php b/src/Rules/Generics/MethodTagTemplateTypeCheck.php index b0b6441c922..afa672f9837 100644 --- a/src/Rules/Generics/MethodTagTemplateTypeCheck.php +++ b/src/Rules/Generics/MethodTagTemplateTypeCheck.php @@ -61,6 +61,9 @@ public function check( sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid default type %%s', $escapedClassName, $escapedMethodName), + sprintf('Default type %%s in PHPDoc tag @method template %%s for method %s::%s() is not subtype of bound type %%s', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() does not have a default type but follows an optional @template %%s.', $escapedClassName, $escapedMethodName), )); foreach (array_keys($methodTemplateTags) as $name) { diff --git a/src/Rules/Generics/MethodTemplateTypeRule.php b/src/Rules/Generics/MethodTemplateTypeRule.php index fa9a6ecb060..65653f833f6 100644 --- a/src/Rules/Generics/MethodTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTemplateTypeRule.php @@ -66,6 +66,9 @@ public function processNode(Node $node, Scope $scope): array sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid default type %%s.', $escapedClassName, $escapedMethodName), + sprintf('Default type %%s in PHPDoc tag @template %%s for method %s::%s() is not subtype of bound type %%s.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() does not have a default type but follows an optional @template %%s.', $escapedClassName, $escapedMethodName), ); $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 5eb8d8bdf4d..dda84c8629e 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -62,9 +62,13 @@ public function check( string $sameTemplateTypeNameAsTypeMessage, string $invalidBoundTypeMessage, string $notSupportedBoundMessage, + string $invalidDefaultTypeMessage, + string $defaultNotSubtypeOfBoundMessage, + string $requiredTypeAfterOptionalMessage, ): array { $messages = []; + $templateTagWithDefaultType = null; foreach ($templateTags as $templateTag) { $templateTagName = $scope->resolveName(new Node\Name($templateTag->getName())); if ($this->reflectionProvider->hasClass($templateTagName)) { @@ -141,6 +145,65 @@ public function check( foreach ($genericObjectErrors as $genericObjectError) { $messages[] = $genericObjectError; } + + $defaultType = $templateTag->getDefault(); + if ($defaultType === null) { + if ($templateTagWithDefaultType !== null) { + $messages[] = RuleErrorBuilder::message(sprintf( + $requiredTypeAfterOptionalMessage, + $templateTagName, + $templateTagWithDefaultType, + ))->identifier('generics.requiredTypeAfterOptional')->build(); + } + + continue; + } + + $templateTagWithDefaultType = $templateTagName; + + foreach ($defaultType->getReferencedClasses() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + $messages[] = RuleErrorBuilder::message(sprintf( + $invalidDefaultTypeMessage, + $templateTagName, + $referencedClass, + ))->identifier('class.notFound')->build(); + continue; + } + if (!$this->reflectionProvider->getClass($referencedClass)->isTrait()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + $invalidDefaultTypeMessage, + $templateTagName, + $referencedClass, + ))->identifier('generics.traitBound')->build(); + } + + $classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $defaultType->getReferencedClasses()); + $messages = array_merge($messages, $this->classCheck->checkClassNames($classNameNodePairs, $this->checkClassCaseSensitivity)); + + $genericDefaultErrors = $this->genericObjectTypeCheck->check( + $defaultType, + sprintf('PHPDoc tag @template %s default contains generic type %%s but class %%s is not generic.', $escapedTemplateTagName), + sprintf('PHPDoc tag @template %s default has type %%s which does not specify all template types of class %%s: %%s', $escapedTemplateTagName), + sprintf('PHPDoc tag @template %s default has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $escapedTemplateTagName), + sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s default is not subtype of template type %%s of class %%s.', $escapedTemplateTagName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @template %s default is in conflict with %%s template type %%s of %%s %%s.', $escapedTemplateTagName), + sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @template %s default is redundant, template type %%s of %%s %%s has the same variance.', $escapedTemplateTagName), + ); + foreach ($genericDefaultErrors as $genericDefaultError) { + $messages[] = $genericDefaultError; + } + + if (!$boundType->accepts($defaultType, $scope->isDeclareStrictTypes())->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf($defaultNotSubtypeOfBoundMessage, $defaultType->describe(VerbosityLevel::typeOnly()), $templateTagName, $boundType->describe(VerbosityLevel::typeOnly()))) + ->identifier('generics.templateDefaultOutOfBounds') + ->build(); } return $messages; diff --git a/src/Rules/Generics/TraitTemplateTypeRule.php b/src/Rules/Generics/TraitTemplateTypeRule.php index b08f12f32d4..27ce74e2980 100644 --- a/src/Rules/Generics/TraitTemplateTypeRule.php +++ b/src/Rules/Generics/TraitTemplateTypeRule.php @@ -60,6 +60,9 @@ public function processNode(Node $node, Scope $scope): array sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $escapedTraitName), sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $escapedTraitName), sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $escapedTraitName), + sprintf('PHPDoc tag @template %%s for trait %s has invalid default type %%s.', $escapedTraitName), + sprintf('Default type %%s in PHPDoc tag @template %%s for trait %s is not subtype of bound type %%s.', $escapedTraitName), + sprintf('PHPDoc tag @template %%s for trait %s does not have a default type but follows an optional @template %%s.', $escapedTraitName), ); } diff --git a/src/Rules/Methods/MissingMethodParameterTypehintRule.php b/src/Rules/Methods/MissingMethodParameterTypehintRule.php index 32d64a68ade..8d5edd78900 100644 --- a/src/Rules/Methods/MissingMethodParameterTypehintRule.php +++ b/src/Rules/Methods/MissingMethodParameterTypehintRule.php @@ -13,7 +13,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -103,7 +102,7 @@ private function checkMethodParameter(MethodReflection $methodReflection, string $methodReflection->getName(), $parameterMessage, $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Methods/MissingMethodReturnTypehintRule.php b/src/Rules/Methods/MissingMethodReturnTypehintRule.php index e48ed2d7850..2b3c563f2d0 100644 --- a/src/Rules/Methods/MissingMethodReturnTypehintRule.php +++ b/src/Rules/Methods/MissingMethodReturnTypehintRule.php @@ -10,7 +10,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\MixedType; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -70,7 +69,7 @@ public function processNode(Node $node, Scope $scope): array $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Methods/MissingMethodSelfOutTypeRule.php b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php index 4b602b5fa14..e4e023ce211 100644 --- a/src/Rules/Methods/MissingMethodSelfOutTypeRule.php +++ b/src/Rules/Methods/MissingMethodSelfOutTypeRule.php @@ -9,7 +9,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -63,7 +62,7 @@ public function processNode(Node $node, Scope $scope): array $methodReflection->getName(), $phpDocTagMessage, $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 75dd681fb1d..34677039213 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -21,8 +21,11 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use Traversable; +use function array_filter; use function array_keys; use function array_merge; +use function count; +use function implode; use function in_array; use function sprintf; use function strtolower; @@ -100,7 +103,7 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array } /** - * @return array + * @return array */ public function getNonGenericObjectTypesWithGenericClass(Type $type): array { @@ -140,9 +143,22 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array if (!$resolvedType instanceof ObjectType) { throw new ShouldNotHappenException(); } + + $templateTypes = $classReflection->getTemplateTypeMap()->getTypes(); + $templateTypesCount = count($templateTypes); + $requiredTemplateTypesCount = count(array_filter($templateTypes, static fn (Type $type) => $type instanceof TemplateType && $type->getDefault() === null)); + if ($requiredTemplateTypesCount === 0) { + return $type; + } + + $templateTypesList = implode(', ', array_keys($templateTypes)); + if ($requiredTemplateTypesCount !== $templateTypesCount) { + $templateTypesList .= sprintf(' (%d-%d required)', $requiredTemplateTypesCount, $templateTypesCount); + } + $objectTypes[] = [ sprintf('%s %s', strtolower($classReflection->getClassTypeDescription()), $classReflection->getDisplayName(false)), - array_keys($classReflection->getTemplateTypeMap()->getTypes()), + $templateTypesList, ]; return $type; } diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 073d131922c..0dec8f4d240 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -23,7 +23,6 @@ use PHPStan\Type\VerbosityLevel; use function array_key_exists; use function array_merge; -use function implode; use function sprintf; use function substr; @@ -198,7 +197,7 @@ public function check( $tagName, $assertedExprString, $innerName, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/PhpDoc/GenericCallableRuleHelper.php b/src/Rules/PhpDoc/GenericCallableRuleHelper.php index 3e849cd1dc8..c32491fe427 100644 --- a/src/Rules/PhpDoc/GenericCallableRuleHelper.php +++ b/src/Rules/PhpDoc/GenericCallableRuleHelper.php @@ -60,6 +60,9 @@ public function check( sprintf('PHPDoc tag %s template of %s cannot have existing type alias %%s as its name.', $location, $typeDescription), sprintf('PHPDoc tag %s template %%s of %s has invalid bound type %%s.', $location, $typeDescription), sprintf('PHPDoc tag %s template %%s of %s with bound type %%s is not supported.', $location, $typeDescription), + sprintf('PHPDoc tag %s template %%s of %s has invalid default type %%s.', $location, $typeDescription), + sprintf('Default type %%s in PHPDoc tag %s template %%s of %s is not subtype of bound type %%s.', $location, $typeDescription), + sprintf('PHPDoc tag %s template %%s of %s does not have a default type but follows an optional template %%s.', $location, $typeDescription), ); $templateTags = $type->getTemplateTags(); diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 4a227ffd07f..53d4c4e6a68 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -16,7 +16,6 @@ use PHPStan\Type\VerbosityLevel; use function array_map; use function array_merge; -use function implode; use function is_string; use function sprintf; @@ -116,7 +115,7 @@ public function processNode(Node $node, Scope $scope): array '%s contains generic %s but does not specify its types: %s', $identifier, $innerName, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Rules/Properties/MissingPropertyTypehintRule.php b/src/Rules/Properties/MissingPropertyTypehintRule.php index c429a30fddd..84c8a20325c 100644 --- a/src/Rules/Properties/MissingPropertyTypehintRule.php +++ b/src/Rules/Properties/MissingPropertyTypehintRule.php @@ -10,7 +10,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\MixedType; use PHPStan\Type\VerbosityLevel; -use function implode; use function sprintf; /** @@ -68,7 +67,7 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection->getDeclaringClass()->getDisplayName(), $node->getName(), $name, - implode(', ', $genericTypeNames), + $genericTypeNames, )) ->identifier('missingType.generics') ->build(); diff --git a/src/Type/Generic/TemplateArrayType.php b/src/Type/Generic/TemplateArrayType.php index bb0192d02b3..e6658632cd1 100644 --- a/src/Type/Generic/TemplateArrayType.php +++ b/src/Type/Generic/TemplateArrayType.php @@ -4,6 +4,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateArrayType extends ArrayType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ArrayType $bound, + ?Type $default, ) { parent::__construct($bound->getKeyType(), $bound->getItemType()); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateBenevolentUnionType.php b/src/Type/Generic/TemplateBenevolentUnionType.php index 3107542f29d..cc630fd0dde 100644 --- a/src/Type/Generic/TemplateBenevolentUnionType.php +++ b/src/Type/Generic/TemplateBenevolentUnionType.php @@ -21,6 +21,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, BenevolentUnionType $bound, + ?Type $default, ) { parent::__construct($bound->getTypes()); @@ -30,6 +31,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } /** @param Type[] $types */ @@ -41,6 +43,7 @@ public function withTypes(array $types): self $this->variance, $this->name, new BenevolentUnionType($types), + $this->default, ); } diff --git a/src/Type/Generic/TemplateBooleanType.php b/src/Type/Generic/TemplateBooleanType.php index 66ce5db4ece..27fc50f21be 100644 --- a/src/Type/Generic/TemplateBooleanType.php +++ b/src/Type/Generic/TemplateBooleanType.php @@ -4,6 +4,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateBooleanType extends BooleanType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, BooleanType $bound, + ?Type $default, ) { parent::__construct(); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateConstantArrayType.php b/src/Type/Generic/TemplateConstantArrayType.php index b291dab5771..53ea9949357 100644 --- a/src/Type/Generic/TemplateConstantArrayType.php +++ b/src/Type/Generic/TemplateConstantArrayType.php @@ -4,6 +4,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateConstantArrayType extends ConstantArrayType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ConstantArrayType $bound, + ?Type $default, ) { parent::__construct($bound->getKeyTypes(), $bound->getValueTypes(), $bound->getNextAutoIndexes(), $bound->getOptionalKeys(), $bound->isList()); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateConstantIntegerType.php b/src/Type/Generic/TemplateConstantIntegerType.php index e411af4edc2..a4bc35b8489 100644 --- a/src/Type/Generic/TemplateConstantIntegerType.php +++ b/src/Type/Generic/TemplateConstantIntegerType.php @@ -4,6 +4,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateConstantIntegerType extends ConstantIntegerType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ConstantIntegerType $bound, + ?Type $default, ) { parent::__construct($bound->getValue()); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateConstantStringType.php b/src/Type/Generic/TemplateConstantStringType.php index bcb3b0cf949..f4d3b8dbbb0 100644 --- a/src/Type/Generic/TemplateConstantStringType.php +++ b/src/Type/Generic/TemplateConstantStringType.php @@ -4,6 +4,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateConstantStringType extends ConstantStringType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ConstantStringType $bound, + ?Type $default, ) { parent::__construct($bound->getValue()); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateFloatType.php b/src/Type/Generic/TemplateFloatType.php index 32332bb3ef8..b3df6ccd2e9 100644 --- a/src/Type/Generic/TemplateFloatType.php +++ b/src/Type/Generic/TemplateFloatType.php @@ -4,6 +4,7 @@ use PHPStan\Type\FloatType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateFloatType extends FloatType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, FloatType $bound, + ?Type $default, ) { parent::__construct(); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateGenericObjectType.php b/src/Type/Generic/TemplateGenericObjectType.php index 3810841ec9c..0c58b3b41e7 100644 --- a/src/Type/Generic/TemplateGenericObjectType.php +++ b/src/Type/Generic/TemplateGenericObjectType.php @@ -22,6 +22,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, GenericObjectType $bound, + ?Type $default, ) { parent::__construct($bound->getClassName(), $bound->getTypes(), null, null, $bound->getVariances()); @@ -31,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function recreate(string $className, array $types, ?Type $subtractedType, array $variances = []): GenericObjectType @@ -41,6 +43,7 @@ protected function recreate(string $className, array $types, ?Type $subtractedTy $this->variance, $this->name, $this->getBound(), + $this->default, ); } diff --git a/src/Type/Generic/TemplateIntegerType.php b/src/Type/Generic/TemplateIntegerType.php index 64c631980d8..b4057fa3271 100644 --- a/src/Type/Generic/TemplateIntegerType.php +++ b/src/Type/Generic/TemplateIntegerType.php @@ -4,6 +4,7 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateIntegerType extends IntegerType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, IntegerType $bound, + ?Type $default, ) { parent::__construct(); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateIntersectionType.php b/src/Type/Generic/TemplateIntersectionType.php index 87f1ca18a74..7576541dbcb 100644 --- a/src/Type/Generic/TemplateIntersectionType.php +++ b/src/Type/Generic/TemplateIntersectionType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Generic; use PHPStan\Type\IntersectionType; +use PHPStan\Type\Type; /** @api */ final class TemplateIntersectionType extends IntersectionType implements TemplateType @@ -20,6 +21,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, IntersectionType $bound, + ?Type $default, ) { parent::__construct($bound->getTypes()); @@ -29,6 +31,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } } diff --git a/src/Type/Generic/TemplateKeyOfType.php b/src/Type/Generic/TemplateKeyOfType.php index 7312ea2ef4e..d8522eb5034 100644 --- a/src/Type/Generic/TemplateKeyOfType.php +++ b/src/Type/Generic/TemplateKeyOfType.php @@ -23,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, KeyOfType $bound, + ?Type $default, ) { parent::__construct($bound->getType()); @@ -31,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function getResult(): Type @@ -43,6 +45,7 @@ protected function getResult(): Type $result, $this->getVariance(), $this->getStrategy(), + $this->getDefault(), ); } diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 132cb20d6bf..c06b082d52e 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -24,6 +24,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, MixedType $bound, + ?Type $default, ) { parent::__construct(true); @@ -33,6 +34,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic @@ -62,6 +64,7 @@ public function toStrictMixedType(): TemplateStrictMixedType $this->variance, $this->name, new StrictMixedType(), + $this->default, ); } diff --git a/src/Type/Generic/TemplateObjectShapeType.php b/src/Type/Generic/TemplateObjectShapeType.php index 5b1f187c6db..270af37931c 100644 --- a/src/Type/Generic/TemplateObjectShapeType.php +++ b/src/Type/Generic/TemplateObjectShapeType.php @@ -4,6 +4,7 @@ use PHPStan\Type\ObjectShapeType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateObjectShapeType extends ObjectShapeType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ObjectShapeType $bound, + ?Type $default, ) { parent::__construct($bound->getProperties(), $bound->getOptionalProperties()); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateObjectType.php b/src/Type/Generic/TemplateObjectType.php index a67aa723ddd..220414ca148 100644 --- a/src/Type/Generic/TemplateObjectType.php +++ b/src/Type/Generic/TemplateObjectType.php @@ -4,6 +4,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateObjectType extends ObjectType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ObjectType $bound, + ?Type $default, ) { parent::__construct($bound->getClassName()); @@ -31,6 +33,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } } diff --git a/src/Type/Generic/TemplateObjectWithoutClassType.php b/src/Type/Generic/TemplateObjectWithoutClassType.php index 3d3cb9e8ca3..7d6aebc6f98 100644 --- a/src/Type/Generic/TemplateObjectWithoutClassType.php +++ b/src/Type/Generic/TemplateObjectWithoutClassType.php @@ -4,6 +4,7 @@ use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ class TemplateObjectWithoutClassType extends ObjectWithoutClassType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, ObjectWithoutClassType $bound, + ?Type $default, ) { parent::__construct(); @@ -31,6 +33,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index 071475e2154..fa204aade2a 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -24,6 +24,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, StrictMixedType $bound, + ?Type $default, ) { $this->scope = $scope; @@ -31,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic diff --git a/src/Type/Generic/TemplateStringType.php b/src/Type/Generic/TemplateStringType.php index 084612c6414..1ae72a33840 100644 --- a/src/Type/Generic/TemplateStringType.php +++ b/src/Type/Generic/TemplateStringType.php @@ -4,6 +4,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use PHPStan\Type\Type; /** @api */ final class TemplateStringType extends StringType implements TemplateType @@ -22,6 +23,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, StringType $bound, + ?Type $default, ) { parent::__construct(); @@ -30,6 +32,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } protected function shouldGeneralizeInferredType(): bool diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index 7661078ca1b..8374c6c0acd 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -18,6 +18,8 @@ public function getScope(): TemplateTypeScope; public function getBound(): Type; + public function getDefault(): ?Type; + public function toArgument(): TemplateType; public function isArgument(): bool; diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index c29a175d2c8..8f56cc6cbbf 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -28,91 +28,91 @@ final class TemplateTypeFactory /** * @param non-empty-string $name */ - public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance, ?TemplateTypeStrategy $strategy = null): TemplateType + public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance, ?TemplateTypeStrategy $strategy = null, ?Type $default = null): TemplateType { $strategy ??= new TemplateTypeParameterStrategy(); if ($bound === null) { - return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true)); + return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true), $default); } $boundClass = get_class($bound); if ($bound instanceof ObjectType && ($boundClass === ObjectType::class || $bound instanceof TemplateType)) { - return new TemplateObjectType($scope, $strategy, $variance, $name, $bound); + return new TemplateObjectType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof GenericObjectType && ($boundClass === GenericObjectType::class || $bound instanceof TemplateType)) { - return new TemplateGenericObjectType($scope, $strategy, $variance, $name, $bound); + return new TemplateGenericObjectType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ObjectWithoutClassType && ($boundClass === ObjectWithoutClassType::class || $bound instanceof TemplateType)) { - return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name, $bound); + return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ArrayType && ($boundClass === ArrayType::class || $bound instanceof TemplateType)) { - return new TemplateArrayType($scope, $strategy, $variance, $name, $bound); + return new TemplateArrayType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ConstantArrayType && ($boundClass === ConstantArrayType::class || $bound instanceof TemplateType)) { - return new TemplateConstantArrayType($scope, $strategy, $variance, $name, $bound); + return new TemplateConstantArrayType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ObjectShapeType && ($boundClass === ObjectShapeType::class || $bound instanceof TemplateType)) { - return new TemplateObjectShapeType($scope, $strategy, $variance, $name, $bound); + return new TemplateObjectShapeType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof StringType && ($boundClass === StringType::class || $bound instanceof TemplateType)) { - return new TemplateStringType($scope, $strategy, $variance, $name, $bound); + return new TemplateStringType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ConstantStringType && ($boundClass === ConstantStringType::class || $bound instanceof TemplateType)) { - return new TemplateConstantStringType($scope, $strategy, $variance, $name, $bound); + return new TemplateConstantStringType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof IntegerType && ($boundClass === IntegerType::class || $bound instanceof TemplateType)) { - return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound); + return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof ConstantIntegerType && ($boundClass === ConstantIntegerType::class || $bound instanceof TemplateType)) { - return new TemplateConstantIntegerType($scope, $strategy, $variance, $name, $bound); + return new TemplateConstantIntegerType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof FloatType && ($boundClass === FloatType::class || $bound instanceof TemplateType)) { - return new TemplateFloatType($scope, $strategy, $variance, $name, $bound); + return new TemplateFloatType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof BooleanType && ($boundClass === BooleanType::class || $bound instanceof TemplateType)) { - return new TemplateBooleanType($scope, $strategy, $variance, $name, $bound); + return new TemplateBooleanType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof MixedType && ($boundClass === MixedType::class || $bound instanceof TemplateType)) { - return new TemplateMixedType($scope, $strategy, $variance, $name, $bound); + return new TemplateMixedType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof UnionType) { if ($boundClass === UnionType::class || $bound instanceof TemplateUnionType) { - return new TemplateUnionType($scope, $strategy, $variance, $name, $bound); + return new TemplateUnionType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof BenevolentUnionType) { - return new TemplateBenevolentUnionType($scope, $strategy, $variance, $name, $bound); + return new TemplateBenevolentUnionType($scope, $strategy, $variance, $name, $bound, $default); } } if ($bound instanceof IntersectionType) { - return new TemplateIntersectionType($scope, $strategy, $variance, $name, $bound); + return new TemplateIntersectionType($scope, $strategy, $variance, $name, $bound, $default); } if ($bound instanceof KeyOfType && ($boundClass === KeyOfType::class || $bound instanceof TemplateType)) { - return new TemplateKeyOfType($scope, $strategy, $variance, $name, $bound); + return new TemplateKeyOfType($scope, $strategy, $variance, $name, $bound, $default); } - return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true)); + return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true), $default); } public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): TemplateType { - return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance()); + return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance(), null, $tag->getDefault()); } } diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index a38d6565564..58460efaee1 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -45,7 +45,7 @@ public static function resolveTemplateTypes( } if ($newType instanceof ErrorType && !$keepErrorTypes) { - return $traverse($type->getBound()); + return $traverse($type->getDefault() ?? $type->getBound()); } $callSiteVariance = $callSiteVariances->getVariance($type->getName()); diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 00fbc34a1dc..30cd0e52c0a 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -5,6 +5,7 @@ use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use function array_key_exists; use function count; @@ -211,7 +212,10 @@ public function resolveToBounds(): self if ($this->resolvedToBounds !== null) { return $this->resolvedToBounds; } - return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::resolveToBounds($type)); + return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TypeTraverser::map( + $type, + static fn (Type $type, callable $traverse): Type => $type instanceof TemplateType ? $traverse($type->getDefault() ?? $type->getBound()) : $traverse($type), + )); } /** diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index b6cca8b2905..9a0fa73a59d 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -38,6 +38,8 @@ trait TemplateTypeTrait /** @var TBound */ private Type $bound; + private ?Type $default; + /** @return non-empty-string */ public function getName(): string { @@ -55,6 +57,11 @@ public function getBound(): Type return $this->bound; } + public function getDefault(): ?Type + { + return $this->default; + } + public function describe(VerbosityLevel $level): string { $basicDescription = function () use ($level): string { @@ -64,10 +71,12 @@ public function describe(VerbosityLevel $level): string } else { $boundDescription = sprintf(' of %s', $this->bound->describe($level)); } + $defaultDescription = $this->default !== null ? sprintf(' = %s', $this->default->describe($level)) : ''; return sprintf( - '%s%s', + '%s%s%s', $this->name, $boundDescription, + $defaultDescription, ); }; @@ -91,6 +100,7 @@ public function toArgument(): TemplateType $this->variance, $this->name, TemplateTypeHelper::toArgument($this->getBound()), + $this->default !== null ? TemplateTypeHelper::toArgument($this->default) : null, ); } @@ -113,6 +123,7 @@ public function subtract(Type $typeToRemove): Type $removedBound, $this->getVariance(), $this->getStrategy(), + $this->getDefault(), ); } @@ -129,6 +140,7 @@ public function getTypeWithoutSubtractedType(): Type $bound->getTypeWithoutSubtractedType(), $this->getVariance(), $this->getStrategy(), + $this->getDefault(), ); } @@ -145,6 +157,7 @@ public function changeSubtractedType(?Type $subtractedType): Type $bound->changeSubtractedType($subtractedType), $this->getVariance(), $this->getStrategy(), + $this->getDefault(), ); } @@ -163,7 +176,11 @@ public function equals(Type $type): bool return $type instanceof self && $type->scope->equals($this->scope) && $type->name === $this->name - && $this->bound->equals($type->bound); + && $this->bound->equals($type->bound) + && ( + ($this->default === null && $type->default === null) + || ($this->default !== null && $type->default !== null && $this->default->equals($type->default)) + ); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic @@ -317,7 +334,9 @@ protected function shouldGeneralizeInferredType(): bool public function traverse(callable $cb): Type { $bound = $cb($this->getBound()); - if ($this->getBound() === $bound) { + $default = $this->getDefault() !== null ? $cb($this->getDefault()) : null; + + if ($this->getBound() === $bound && $this->getDefault() === $default) { return $this; } @@ -327,6 +346,7 @@ public function traverse(callable $cb): Type $bound, $this->getVariance(), $this->getStrategy(), + $default, ); } @@ -337,7 +357,9 @@ public function traverseSimultaneously(Type $right, callable $cb): Type } $bound = $cb($this->getBound(), $right->getBound()); - if ($this->getBound() === $bound) { + $default = $this->getDefault() !== null && $right->getDefault() !== null ? $cb($this->getDefault(), $right->getDefault()) : null; + + if ($this->getBound() === $bound && $this->getDefault() === $default) { return $this; } @@ -347,6 +369,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type $bound, $this->getVariance(), $this->getStrategy(), + $default, ); } @@ -363,6 +386,7 @@ public function tryRemove(Type $typeToRemove): ?Type $bound, $this->getVariance(), $this->getStrategy(), + $this->getDefault(), ); } @@ -382,6 +406,7 @@ public static function __set_state(array $properties): Type $properties['variance'], $properties['name'], $properties['bound'], + $properties['default'] ?? null, ); } diff --git a/src/Type/Generic/TemplateUnionType.php b/src/Type/Generic/TemplateUnionType.php index 997fb21238b..cc196a07f4c 100644 --- a/src/Type/Generic/TemplateUnionType.php +++ b/src/Type/Generic/TemplateUnionType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Generic; +use PHPStan\Type\Type; use PHPStan\Type\UnionType; /** @api */ @@ -20,6 +21,7 @@ public function __construct( TemplateTypeVariance $templateTypeVariance, string $name, UnionType $bound, + ?Type $default, ) { parent::__construct($bound->getTypes()); @@ -29,6 +31,7 @@ public function __construct( $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = $bound; + $this->default = $default; } } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 1db0ce23a26..d27fd6fbdb6 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -750,6 +750,7 @@ private static function processArrayTypes(array $arrayTypes): array $templateArray->getVariance(), $templateArray->getName(), $arrayType, + $templateArray->getDefault(), ); } @@ -1015,6 +1016,7 @@ public static function intersect(Type ...$types): Type $union, $type->getVariance(), $type->getStrategy(), + $type->getDefault(), ); } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 8ae601b8323..9998d644226 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -292,6 +292,7 @@ public static function toStrictUnion(Type $type): Type $type->getVariance(), $type->getName(), static::toStrictUnion($type->getBound()), + $type->getDefault(), ); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e0998e52525..1e3afb4e700 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -206,6 +206,9 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Classes/data/bug-11591-method-tag.php'; yield __DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'; yield __DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'; + + yield __DIR__ . '/../Rules/Methods/data/bug-4801.php'; + yield __DIR__ . '/data/template-default.php'; } /** diff --git a/tests/PHPStan/Analyser/data/template-default.php b/tests/PHPStan/Analyser/data/template-default.php new file mode 100644 index 00000000000..979fbc3636b --- /dev/null +++ b/tests/PHPStan/Analyser/data/template-default.php @@ -0,0 +1,136 @@ + $one + * @param Test $two + * @param Test $three + */ +function foo(Test $one, Test $two, Test $three) +{ + assertType('TemplateDefault\\Test', $one); + assertType('TemplateDefault\\Test', $two); + assertType('TemplateDefault\\Test', $three); +} + + +/** + * @template S = false + * @template T = false + */ +class Builder +{ + /** + * @phpstan-self-out self + */ + public function one(): void + { + } + + /** + * @phpstan-self-out self + */ + public function two(): void + { + } + + /** + * @return ($this is self ? void : never) + */ + public function execute(): void + { + } +} + +class FormData {} +class Form +{ + /** + * @template Data of object = \stdClass + * @param Data|null $values + * @return Data + */ + public function mapValues(object|null $values = null): object + { + $values ??= new \stdClass; + // ... map into $values ... + return $values; + } +} + +function () { + $qb = new Builder(); + assertType('TemplateDefault\\Builder', $qb); + $qb->one(); + assertType('TemplateDefault\\Builder', $qb); + $qb->two(); + assertType('TemplateDefault\\Builder', $qb); + assertType('null', $qb->execute()); +}; + +function () { + $qb = new Builder(); + assertType('TemplateDefault\\Builder', $qb); + $qb->two(); + assertType('TemplateDefault\\Builder', $qb); + $qb->one(); + assertType('TemplateDefault\\Builder', $qb); + assertType('null', $qb->execute()); +}; + +function () { + $qb = new Builder(); + assertType('TemplateDefault\\Builder', $qb); + $qb->one(); + assertType('TemplateDefault\\Builder', $qb); + assertType('never', $qb->execute()); +}; + +function () { + $form = new Form(); + + assertType('TemplateDefault\\FormData', $form->mapValues(new FormData)); + assertType('stdClass', $form->mapValues()); +}; + +/** + * @template T + * @template U = string + */ +interface Foo +{ + /** + * @return U + */ + public function get(): mixed; +} + +/** + * @extends Foo + */ +interface Bar extends Foo +{ +} + +/** + * @extends Foo + */ +interface Baz extends Foo +{ +} + +function (Bar $bar, Baz $baz) { + assertType('string', $bar->get()); + assertType('bool', $baz->get()); +}; diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index 788be4cbd56..0538311ee53 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -86,6 +86,18 @@ public function testRule(): void 'Call-site variance of contravariant int in generic type ClassTemplateType\Consecteur in PHPDoc tag @template W is in conflict with covariant template type T of class ClassTemplateType\Consecteur.', 113, ], + [ + 'PHPDoc tag @template T for class ClassTemplateType\Elit has invalid default type ClassTemplateType\Zazzzu.', + 121, + ], + [ + 'Default type bool in PHPDoc tag @template T for class ClassTemplateType\Venenatis is not subtype of bound type object.', + 129, + ], + [ + 'PHPDoc tag @template V for class ClassTemplateType\Mauris does not have a default type but follows an optional @template U.', + 139, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index 35df2068658..f9d15da674b 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -67,6 +67,18 @@ public function testRule(): void 'Call-site variance of contravariant int in generic type FunctionTemplateType\GenericCovariant in PHPDoc tag @template W is in conflict with covariant template type T of class FunctionTemplateType\GenericCovariant.', 94, ], + [ + 'PHPDoc tag @template T for function FunctionTemplateType\invalidDefault() has invalid default type FunctionTemplateType\Zazzzu.', + 102, + ], + [ + 'Default type bool in PHPDoc tag @template T for function FunctionTemplateType\outOfBoundsDefault() is not subtype of bound type object.', + 110, + ], + [ + 'PHPDoc tag @template V for function FunctionTemplateType\requiredAfterOptional() does not have a default type but follows an optional @template U.', + 120, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index 0623a7d1c26..b997498703f 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -65,6 +65,18 @@ public function testRule(): void 'Call-site variance of contravariant int in generic type InterfaceTemplateType\Covariant in PHPDoc tag @template W is in conflict with covariant template type T of interface InterfaceTemplateType\Covariant.', 74, ], + [ + 'PHPDoc tag @template T for interface InterfaceTemplateType\InvalidDefault has invalid default type InterfaceTemplateType\Zazzzu.', + 82, + ], + [ + 'Default type bool in PHPDoc tag @template T for interface InterfaceTemplateType\OutOfBoundsDefault is not subtype of bound type object.', + 90, + ], + [ + 'PHPDoc tag @template V for interface InterfaceTemplateType\RequiredAfterOptional does not have a default type but follows an optional @template U.', + 100, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php index a8459933446..9276fec7c16 100644 --- a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php @@ -73,6 +73,18 @@ public function testRule(): void 'Call-site variance of contravariant int in generic type MethodTemplateType\Dolor in PHPDoc tag @template W is in conflict with covariant template type T of class MethodTemplateType\Dolor.', 109, ], + [ + 'PHPDoc tag @template T for method MethodTemplateType\InvalidDefault::invalid() has invalid default type MethodTemplateType\Zazzzu.', + 122, + ], + [ + 'Default type bool in PHPDoc tag @template T for method MethodTemplateType\InvalidDefault::outOfBounds() is not subtype of bound type object.', + 130, + ], + [ + 'PHPDoc tag @template V for method MethodTemplateType\InvalidDefault::requiredAfterOptional() does not have a default type but follows an optional @template U.', + 140, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php index 99ad8391231..84351d9ea9a 100644 --- a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php @@ -69,6 +69,18 @@ public function testRule(): void 'Call-site variance of contravariant int in generic type TraitTemplateType\Dolor in PHPDoc tag @template W is in conflict with covariant template type T of class TraitTemplateType\Dolor.', 64, ], + [ + 'PHPDoc tag @template T for trait TraitTemplateType\Adipiscing has invalid default type TraitTemplateType\Zazzzu.', + 72, + ], + [ + 'Default type bool in PHPDoc tag @template T for trait TraitTemplateType\Elit is not subtype of bound type object.', + 80, + ], + [ + 'PHPDoc tag @template V for trait TraitTemplateType\Consecteur does not have a default type but follows an optional @template U.', + 90, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php index e66591168ed..c04a5665a4d 100644 --- a/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php +++ b/tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php @@ -260,3 +260,16 @@ class TraitInExtends extends FooGeneric { } + +/** + * @template T = string + */ +class FooGenericDefault +{ + +} + +class FooGenericExtendsDefault extends FooGenericDefault +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php b/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php index 7a016c36ce7..abbc514279a 100644 --- a/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php +++ b/tests/PHPStan/Rules/Generics/data/class-ancestors-implements.php @@ -242,3 +242,18 @@ class FooCollection implements AbstractFooCollection class FooTypeProjection implements FooGeneric { } + +/** + * @template T = string + */ +interface FooGenericDefault +{ +} + +interface FooGenericExtendsDefault extends FooGenericDefault +{ +} + +class FooGenericImplementsDefault implements FooGenericDefault +{ +} diff --git a/tests/PHPStan/Rules/Generics/data/class-template.php b/tests/PHPStan/Rules/Generics/data/class-template.php index a76c2eeab03..06400bd5364 100644 --- a/tests/PHPStan/Rules/Generics/data/class-template.php +++ b/tests/PHPStan/Rules/Generics/data/class-template.php @@ -114,3 +114,29 @@ class Adipiscing { } + +/** + * @template T = Zazzzu + */ +class Elit +{ + +} + +/** + * @template T of object = bool + */ +class Venenatis +{ + +} + +/** + * @template T + * @template U = string + * @template V + */ +class Mauris +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/enum-ancestors.php b/tests/PHPStan/Rules/Generics/data/enum-ancestors.php index e5d78484989..1cda1bcbcd4 100644 --- a/tests/PHPStan/Rules/Generics/data/enum-ancestors.php +++ b/tests/PHPStan/Rules/Generics/data/enum-ancestors.php @@ -94,3 +94,16 @@ enum TypeProjection implements Generic { } + +/** + * @template T = string + */ +interface GenericDefault +{ + +} + +enum Foo9 implements GenericDefault +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 8b9de2cdaff..8a1ff456f9a 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -95,3 +95,29 @@ function typeProjections() { } + +/** + * @template T = Zazzzu + */ +function invalidDefault() +{ + +} + +/** + * @template T of object = bool + */ +function outOfBoundsDefault() +{ + +} + +/** + * @template T + * @template U = string + * @template V + */ +function requiredAfterOptional() +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/interface-template.php b/tests/PHPStan/Rules/Generics/data/interface-template.php index 8ae819c99b9..7f0da436e7e 100644 --- a/tests/PHPStan/Rules/Generics/data/interface-template.php +++ b/tests/PHPStan/Rules/Generics/data/interface-template.php @@ -75,3 +75,29 @@ interface TypeProjections { } + +/** + * @template T = Zazzzu + */ +interface InvalidDefault +{ + +} + +/** + * @template T of object = bool + */ +interface OutOfBoundsDefault +{ + +} + +/** + * @template T + * @template U = string + * @template V + */ +interface RequiredAfterOptional +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/method-template.php b/tests/PHPStan/Rules/Generics/data/method-template.php index 0fc67c1b245..edf5d622019 100644 --- a/tests/PHPStan/Rules/Generics/data/method-template.php +++ b/tests/PHPStan/Rules/Generics/data/method-template.php @@ -112,3 +112,34 @@ public function doSit() } } + +class InvalidDefault +{ + + /** + * @template T = Zazzzu + */ + public function invalid() + { + + } + + /** + * @template T of object = bool + */ + public function outOfBounds() + { + + } + + /** + * @template T + * @template U = string + * @template V + */ + public function requiredAfterOptional() + { + + } + +} diff --git a/tests/PHPStan/Rules/Generics/data/trait-template.php b/tests/PHPStan/Rules/Generics/data/trait-template.php index 7c9e1792959..39126a88f2f 100644 --- a/tests/PHPStan/Rules/Generics/data/trait-template.php +++ b/tests/PHPStan/Rules/Generics/data/trait-template.php @@ -65,3 +65,29 @@ trait Sit { } + +/** + * @template T = Zazzzu + */ +trait Adipiscing +{ + +} + +/** + * @template T of object = bool + */ +trait Elit +{ + +} + +/** + * @template T + * @template U = string + * @template V + */ +trait Consecteur +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/used-traits.php b/tests/PHPStan/Rules/Generics/data/used-traits.php index f01c5e9dfb2..f34fb5ffb99 100644 --- a/tests/PHPStan/Rules/Generics/data/used-traits.php +++ b/tests/PHPStan/Rules/Generics/data/used-traits.php @@ -69,3 +69,17 @@ class Dolor use GenericTrait; } + +/** + * @template T = string + */ +trait GenericDefault +{ +} + +class Sit +{ + + use GenericDefault; + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 93b5b9303e2..7a46d849d32 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3386,4 +3386,13 @@ public function testBug10159(): void $this->analyse([__DIR__ . '/data/bug-10159.php'], []); } + public function testBug4801(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = false; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/bug-4801.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index fecb8a1045a..89e29bb6063 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -82,6 +82,10 @@ public function testRule(): void 'Method MissingMethodParameterTypehint\MissingPureClosureSignatureType::doFoo() has parameter $cb with no signature specified for Closure.', 238, ], + [ + 'Method MissingMethodParameterTypehint\Baz::acceptsGenericWithSomeDefaults() has parameter $c with generic class MissingMethodParameterTypehint\GenericClassWithSomeDefaults but does not specify its types: T, U (1-2 required)', + 270, + ], ]; $this->analyse([__DIR__ . '/data/missing-method-parameter-typehint.php'], $errors); diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 07629009018..49fc0b97d17 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -53,6 +53,10 @@ public function testRule(): void 'Method MissingMethodReturnTypehint\CallableSignature::doFoo() return type has no signature specified for callable.', 99, ], + [ + 'Method MissingMethodReturnTypehint\Baz::returnsGenericWithSomeDefaults() return type with generic class MissingMethodReturnTypehint\GenericClassWithSomeDefaults does not specify its types: T, U (1-2 required)', + 142, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4801.php b/tests/PHPStan/Rules/Methods/data/bug-4801.php new file mode 100644 index 00000000000..a09d1133677 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4801.php @@ -0,0 +1,25 @@ + + */ + public function work(?callable $a): I; +} + +/** + * @param I $i + */ +function x(I $i) { + assertType('Bug4801\\I', $i->work(null)); + assertType('Bug4801\\I', $i->work(fn(string $a) => (int) $a)); +} diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php b/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php index d5a333491b1..27fa039ef4d 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php +++ b/tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php @@ -241,3 +241,35 @@ function doFoo(\Closure $cb): void } } + +/** + * @template T = string + */ +class GenericClassWithDefault +{ + +} + +/** + * @template T + * @template U = string + */ +class GenericClassWithSomeDefaults +{ + +} + +class Baz +{ + + public function acceptsGenericWithDefault(GenericClassWithDefault $i) + { + + } + + public function acceptsGenericWithSomeDefaults(GenericClassWithSomeDefaults $c) + { + + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php b/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php index 5b708cad893..480373825ae 100644 --- a/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php +++ b/tests/PHPStan/Rules/Methods/data/missing-method-return-typehint.php @@ -113,3 +113,35 @@ public function doFoo(): \Traversable } } + +/** + * @template T = string + */ +class GenericClassWithDefault +{ + +} + +/** + * @template T + * @template U = string + */ +class GenericClassWithSomeDefaults +{ + +} + +class Baz +{ + + public function returnsGenericWithDefault(): GenericClassWithDefault + { + + } + + public function returnsGenericWithSomeDefaults(): GenericClassWithSomeDefaults + { + + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 9d4dc0bb3e3..c2916f98648 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -107,8 +107,12 @@ public function testRule(): void 73, ], [ - 'PHPDoc tag @var for variable $foo contains unknown class InvalidVarTagType\Blabla.', + 'PHPDoc tag @var for variable $test contains generic class InvalidPhpDocDefinitions\FooGenericWithSomeDefaults but does not specify its types: T, U (1-2 required)', 79, + ], + [ + 'PHPDoc tag @var for variable $foo contains unknown class InvalidVarTagType\Blabla.', + 85, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], ]); diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php index f52cae0d042..74ad37a7799 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-definitions.php @@ -23,3 +23,20 @@ class FooCovariantGeneric { } + +/** + * @template T = string + */ +class FooGenericWithDefault +{ + +} + +/** + * @template T + * @template U = string + */ +class FooGenericWithSomeDefaults +{ + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php index fd9688445d5..cd996b4a21f 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php @@ -71,6 +71,12 @@ public function doFoo() /** @var \InvalidPhpDocDefinitions\FooCovariantGeneric $test */ $test = doFoo(); + + /** @var \InvalidPhpDocDefinitions\FooGenericWithDefault $test */ + $test = doFoo(); + + /** @var \InvalidPhpDocDefinitions\FooGenericWithSomeDefaults $test */ + $test = doFoo(); } public function doBar($foo) diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index bd92fe752eb..324946835af 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -54,6 +54,10 @@ public function testRule(): void 106, MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, ], + [ + 'Property MissingPropertyTypehint\Baz::$bar with generic class MissingPropertyTypehint\GenericClassWithSomeDefaults does not specify its types: T, U (1-2 required)', + 134, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php b/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php index 952016e8603..374397cd0aa 100644 --- a/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php +++ b/tests/PHPStan/Rules/Properties/data/missing-property-typehint.php @@ -106,3 +106,31 @@ class NestedArrayInProperty public $args; } + +/** + * @template T = string + */ +class GenericClassWithDefault +{ + +} + +/** + * @template T + * @template U = string + */ +class GenericClassWithSomeDefaults +{ + +} + +class Baz +{ + + /** @var \MissingPropertyTypehint\GenericClassWithDefault */ + private $foo; + + /** @var \MissingPropertyTypehint\GenericClassWithSomeDefaults */ + private $bar; + +} From 35b532af83b42c3c1c1aebb7c9b961846cf66408 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 09:53:51 +0200 Subject: [PATCH 0674/3097] Remove old containers from the journal --- src/DependencyInjection/Configurator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 524365e77eb..f5a9d9e7134 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -131,6 +131,8 @@ private function journal(string $currentContainerClassName): void if ($containerLastUsedTime + $week >= $now) { $usedInTheLastWeek[] = $className; + } else { + continue; } if ($currentContainerClassName !== $className) { From a883c66f5390fa83bb3f2ef936c8501af716a516 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 10:06:44 +0200 Subject: [PATCH 0675/3097] Fix CS --- src/DependencyInjection/Configurator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index f5a9d9e7134..9536925b9de 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -129,12 +129,12 @@ private function journal(string $currentContainerClassName): void $week = 3600 * 24 * 7; - if ($containerLastUsedTime + $week >= $now) { - $usedInTheLastWeek[] = $className; - } else { + if ($containerLastUsedTime + $week < $now) { continue; } + $usedInTheLastWeek[] = $className; + if ($currentContainerClassName !== $className) { $linesToWrite[] = sprintf('%s:%d', $className, $containerLastUsedTime); continue; From 8dd41e9e3a1fc5cfaaec0659ec2c9161fe31d179 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 16:49:30 +0900 Subject: [PATCH 0676/3097] Improve return type of token_name() and PhpToken::getTokenName() --- resources/functionMap.php | 2 +- resources/functionMap_php80delta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 2dcf0feb8a7..c9eba8373a8 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -12606,7 +12606,7 @@ 'timezone_version_get' => ['string'], 'tmpfile' => ['__benevolent'], 'token_get_all' => ['list', 'source'=>'string', 'flags='=>'int'], -'token_name' => ['non-empty-string', 'type'=>'int'], +'token_name' => ['non-falsy-string', 'type'=>'int'], 'TokyoTyrant::__construct' => ['void', 'host='=>'string', 'port='=>'int', 'options='=>'array'], 'TokyoTyrant::add' => ['int|float', 'key'=>'string', 'increment'=>'float', 'type='=>'int'], 'TokyoTyrant::connect' => ['TokyoTyrant', 'host'=>'string', 'port='=>'int', 'options='=>'array'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 11c65e5258d..d242a5410a4 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -91,7 +91,7 @@ 'PhpToken::tokenize' => ['list', 'code'=>'string', 'flags='=>'int'], 'PhpToken::is' => ['bool', 'kind'=>'string|int|string[]|int[]'], 'PhpToken::isIgnorable' => ['bool'], - 'PhpToken::getTokenName' => ['string'], + 'PhpToken::getTokenName' => ['non-falsy-string'], 'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], 'set_error_handler' => ['?callable', 'callback'=>'null|callable(int,string,string,int):bool', 'error_types='=>'int'], From ce3ffbd0725e2a5b0527b6fe8b0208c917649eac Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Tue, 8 Oct 2024 19:30:15 +0200 Subject: [PATCH 0677/3097] Use argument types as parameter types for inline closures when assigned The functionality was introduced in #1628. It works. But as soon as you use an inline assign expression it breaks. Let's support this case too. Sometimes, you want to call something inline and also use the callback later. --- src/Parser/ArrowFunctionArgVisitor.php | 25 +++++++++++++++---- src/Parser/ClosureArgVisitor.php | 25 +++++++++++++++---- .../nsrt/arrow-function-argument-type.php | 2 ++ .../Analyser/nsrt/closure-argument-type.php | 4 +++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Parser/ArrowFunctionArgVisitor.php b/src/Parser/ArrowFunctionArgVisitor.php index f8149dad21c..93cce45dd93 100644 --- a/src/Parser/ArrowFunctionArgVisitor.php +++ b/src/Parser/ArrowFunctionArgVisitor.php @@ -13,13 +13,28 @@ final class ArrowFunctionArgVisitor extends NodeVisitorAbstract public function enterNode(Node $node): ?Node { - if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\ArrowFunction && !$node->isFirstClassCallable()) { - $args = $node->getArgs(); + if (!$node instanceof Node\Expr\FuncCall) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } - if (count($args) > 0) { - $node->name->setAttribute(self::ATTRIBUTE_NAME, $args); - } + if ($node->name instanceof Node\Expr\Assign && $node->name->expr instanceof Node\Expr\ArrowFunction) { + $arrow = $node->name->expr; + } elseif ($node->name instanceof Node\Expr\ArrowFunction) { + $arrow = $node->name; + } else { + return null; } + + $args = $node->getArgs(); + + if (count($args) > 0) { + $arrow->setAttribute(self::ATTRIBUTE_NAME, $args); + } + return null; } diff --git a/src/Parser/ClosureArgVisitor.php b/src/Parser/ClosureArgVisitor.php index 58d53a808e3..c9435f826eb 100644 --- a/src/Parser/ClosureArgVisitor.php +++ b/src/Parser/ClosureArgVisitor.php @@ -13,13 +13,28 @@ final class ClosureArgVisitor extends NodeVisitorAbstract public function enterNode(Node $node): ?Node { - if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\Closure && !$node->isFirstClassCallable()) { - $args = $node->getArgs(); + if (!$node instanceof Node\Expr\FuncCall) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } - if (count($args) > 0) { - $node->name->setAttribute(self::ATTRIBUTE_NAME, $args); - } + if ($node->name instanceof Node\Expr\Assign && $node->name->expr instanceof Node\Expr\Closure) { + $closure = $node->name->expr; + } elseif ($node->name instanceof Node\Expr\Closure) { + $closure = $node->name; + } else { + return null; } + + $args = $node->getArgs(); + + if (count($args) > 0) { + $closure->setAttribute(self::ATTRIBUTE_NAME, $args); + } + return null; } diff --git a/tests/PHPStan/Analyser/nsrt/arrow-function-argument-type.php b/tests/PHPStan/Analyser/nsrt/arrow-function-argument-type.php index 3e1448e6ff1..a508035d19e 100644 --- a/tests/PHPStan/Analyser/nsrt/arrow-function-argument-type.php +++ b/tests/PHPStan/Analyser/nsrt/arrow-function-argument-type.php @@ -21,6 +21,8 @@ public function doFoo(int $integer, array $array, ?string $nullableString) (fn($a, $b, $c) => assertType('array{int, array{a: int}, string|null}', [$a, $b, $c]))($integer, $array, $nullableString); (fn($a, $b, $c = null) => assertType('array{int, array{a: int}, mixed}', [$a, $b, $c]))($integer, $array); + + ($callback = fn($context) => assertType('int', $context))($integer); } } diff --git a/tests/PHPStan/Analyser/nsrt/closure-argument-type.php b/tests/PHPStan/Analyser/nsrt/closure-argument-type.php index 6fd537211d5..b24570b298a 100644 --- a/tests/PHPStan/Analyser/nsrt/closure-argument-type.php +++ b/tests/PHPStan/Analyser/nsrt/closure-argument-type.php @@ -35,6 +35,10 @@ public function doFoo(int $integer, array $array, ?string $nullableString) assertType('array{a: int}', $context2); assertType('mixed', $context3); })($integer, $array); + + ($callback = function($context) { + assertType('int', $context); + })($integer); } } From df39688b3849004f61fc4ed83ee4c1e6e3b3399d Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 19:37:25 +0900 Subject: [PATCH 0678/3097] Add function `PHPStan\dumpPhpDocType()` --- conf/config.neon | 4 + src/Rules/Debug/DumpPhpDocTypeRule.php | 59 ++++++++++ ...unctionStatementWithoutSideEffectsRule.php | 1 + src/dumpType.php | 12 ++ .../Rules/Debug/DumpPhpDocTypeRuleTest.php | 106 ++++++++++++++++++ .../Rules/Debug/data/dump-phpdoc-type.php | 39 +++++++ 6 files changed, 221 insertions(+) create mode 100644 src/Rules/Debug/DumpPhpDocTypeRule.php create mode 100644 tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php diff --git a/conf/config.neon b/conf/config.neon index aa096cc686e..3146a8fd3f9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -282,6 +282,7 @@ extensions: rules: - PHPStan\Rules\Debug\DebugScopeRule + - PHPStan\Rules\Debug\DumpPhpDocTypeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule @@ -433,6 +434,9 @@ services: usedAttributes: lines: %featureToggles.phpDocParserIncludeLines% + - + class: PHPStan\PhpDocParser\Printer\Printer + - class: PHPStan\PhpDoc\ConstExprParserFactory arguments: diff --git a/src/Rules/Debug/DumpPhpDocTypeRule.php b/src/Rules/Debug/DumpPhpDocTypeRule.php new file mode 100644 index 00000000000..be867366b03 --- /dev/null +++ b/src/Rules/Debug/DumpPhpDocTypeRule.php @@ -0,0 +1,59 @@ + + */ +final class DumpPhpDocTypeRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider, private Printer $printer) + { + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ($functionName === null) { + return []; + } + + if (strtolower($functionName) !== 'phpstan\dumpphpdoctype') { + return []; + } + + if (count($node->getArgs()) === 0) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf( + 'Dumped type: %s', + $this->printer->print($scope->getType($node->getArgs()[0]->value)->toPhpDocNode()), + ), + )->nonIgnorable()->identifier('phpstan.dumpPhpDocType')->build(), + ]; + } + +} diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 3ecbecaa7f8..23338924eb0 100644 --- a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -29,6 +29,7 @@ final class CallToFunctionStatementWithoutSideEffectsRule implements Rule public const PHPSTAN_TESTING_FUNCTIONS = [ 'PHPStan\\dumpType', + 'PHPStan\\dumpPhpDocType', 'PHPStan\\debugScope', 'PHPStan\\Testing\\assertType', 'PHPStan\\Testing\\assertNativeType', diff --git a/src/dumpType.php b/src/dumpType.php index 7fa89bc8967..3d4cda24f7d 100644 --- a/src/dumpType.php +++ b/src/dumpType.php @@ -13,3 +13,15 @@ function dumpType($value) // phpcs:ignore Squiz.Functions.GlobalFunction.Found { return null; } + +/** + * @phpstan-pure + * @param mixed $value + * @return mixed + * + * @throws void + */ +function dumpPhpDocType($value) // phpcs:ignore Squiz.Functions.GlobalFunction.Found +{ + return null; +} diff --git a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php new file mode 100644 index 00000000000..ed56b46bd79 --- /dev/null +++ b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php @@ -0,0 +1,106 @@ + + */ +class DumpPhpDocTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DumpPhpDocTypeRule($this->createReflectionProvider(), new Printer()); + } + + public function testRuleSymbols(): void + { + $this->analyse([__DIR__ . '/data/dump-phpdoc-type.php'], [ + [ + "Dumped type: array{'': ''}", + 5, + ], + [ + "Dumped type: array{'\0': 'NUL', NUL: '\0'}", + 6, + ], + [ + "Dumped type: array{'\001': 'SOH', SOH: '\001'}", + 7, + ], + [ + "Dumped type: array{'\t': 'HT', HT: '\t'}", + 8, + ], + [ + "Dumped type: array{' ': 'SP', SP: ' '}", + 11, + ], + [ + "Dumped type: array{'foo ': 'ends with SP', ' foo': 'starts with SP', ' foo ': 'surrounded by SP', foo: 'no SP'}", + 12, + ], + [ + "Dumped type: array{'foo?': 'foo?'}", + 15, + ], + [ + "Dumped type: array{shallwedance: 'yes'}", + 16, + ], + [ + "Dumped type: array{'shallwedance?': 'yes'}", + 17, + ], + [ + "Dumped type: array{'Shall we dance': 'yes'}", + 18, + ], + [ + "Dumped type: array{'Shall we dance?': 'yes'}", + 19, + ], + [ + "Dumped type: array{shall_we_dance: 'yes'}", + 20, + ], + [ + "Dumped type: array{'shall_we_dance?': 'yes'}", + 21, + ], + [ + "Dumped type: array{shall-we-dance: 'yes'}", + 22, + ], + [ + "Dumped type: array{'shall-we-dance?': 'yes'}", + 23, + ], + [ + "Dumped type: array{'Let\'s go': 'Let\'s go'}", + 24, + ], + [ + "Dumped type: array{Foo\\Bar: 'Foo\\\\Bar'}", + 25, + ], + [ + "Dumped type: array{'3.14': 3.14}", + 26, + ], + [ + 'Dumped type: array{1: true, 0: false}', + 27, + ], + [ + 'Dumped type: T', + 36, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php new file mode 100644 index 00000000000..e8d009fe0fa --- /dev/null +++ b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php @@ -0,0 +1,39 @@ + '']); +dumpPhpDocType(["\0" => 'NUL', 'NUL' => "\0"]); +dumpPhpDocType(["\x01" => 'SOH', 'SOH' => "\x01"]); +dumpPhpDocType(["\t" => 'HT', 'HT' => "\t"]); + +// Space +dumpPhpDocType([" " => 'SP', 'SP' => ' ']); +dumpPhpDocType(["foo " => 'ends with SP', " foo" => 'starts with SP', " foo " => 'surrounded by SP', 'foo' => 'no SP']); + +// Punctuation marks +dumpPhpDocType(["foo?" => 'foo?']); +dumpPhpDocType(["shallwedance" => 'yes']); +dumpPhpDocType(["shallwedance?" => 'yes']); +dumpPhpDocType(["Shall we dance" => 'yes']); +dumpPhpDocType(["Shall we dance?" => 'yes']); +dumpPhpDocType(["shall_we_dance" => 'yes']); +dumpPhpDocType(["shall_we_dance?" => 'yes']); +dumpPhpDocType(["shall-we-dance" => 'yes']); +dumpPhpDocType(["shall-we-dance?" => 'yes']); +dumpPhpDocType(['Let\'s go' => "Let's go"]); +dumpPhpDocType(['Foo\\Bar' => 'Foo\\Bar']); +dumpPhpDocType(['3.14' => 3.14]); +dumpPhpDocType([true => true, false => false]); + +/** + * @template T + * @param T $value + * @return T + */ +function id(mixed $value): mixed +{ + dumpPhpDocType($value); + + return $value; +} From 48f8c85ee7198a5bd480023815f8b8cba809dc64 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 12:43:08 +0200 Subject: [PATCH 0679/3097] Move template-default.php to nsrt --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 1 - tests/PHPStan/Analyser/{data => nsrt}/template-default.php | 0 2 files changed, 1 deletion(-) rename tests/PHPStan/Analyser/{data => nsrt}/template-default.php (100%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 37b26eb3f25..1363deb2fe0 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -204,7 +204,6 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'; yield __DIR__ . '/../Rules/Methods/data/bug-4801.php'; - yield __DIR__ . '/data/template-default.php'; } /** diff --git a/tests/PHPStan/Analyser/data/template-default.php b/tests/PHPStan/Analyser/nsrt/template-default.php similarity index 100% rename from tests/PHPStan/Analyser/data/template-default.php rename to tests/PHPStan/Analyser/nsrt/template-default.php From 4a8d584308f04ad41161300112bac5dbadd5f7d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 12:43:25 +0200 Subject: [PATCH 0680/3097] Test template-default.php only on PHP 8+ --- tests/PHPStan/Analyser/nsrt/template-default.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/template-default.php b/tests/PHPStan/Analyser/nsrt/template-default.php index 979fbc3636b..c73ce4ab38b 100644 --- a/tests/PHPStan/Analyser/nsrt/template-default.php +++ b/tests/PHPStan/Analyser/nsrt/template-default.php @@ -1,4 +1,4 @@ -= 8.0 namespace TemplateDefault; From ee6e0ef18a6a1b365c8f0f5bc35e8a8b59694c20 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 9 Oct 2024 13:42:10 +0200 Subject: [PATCH 0681/3097] Added regression test --- .../Rules/Functions/ReturnTypeRuleTest.php | 12 +++++ .../Rules/Functions/data/bug-11301.php | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11301.php diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index d0a65124cc1..de61bd96eb9 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -322,4 +322,16 @@ public function testBug11549(): void $this->analyse([__DIR__ . '/data/bug-11549.php'], []); } + public function testBug11301(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-11301.php'], [ + [ + 'Function Bug11301\cString() should return array but returns array.', + 35, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11301.php b/tests/PHPStan/Rules/Functions/data/bug-11301.php new file mode 100644 index 00000000000..5c9f80cc089 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11301.php @@ -0,0 +1,48 @@ + + */ +function cInt(): array +{ + $a = ['12345']; + $b = ['abc']; + + return array_combine($a, $b); +} + +/** + * @return array + */ +function cInt2(): array +{ + $a = ['12345', 123]; + $b = ['abc', 'def']; + + return array_combine($a, $b); +} + +/** + * @return array + */ +function cString(): array +{ + $a = ['12345']; + $b = ['abc']; + + return array_combine($a, $b); +} + + +/** + * @return array + */ +function cString2(): array +{ + $a = ['12345', 123, 'a']; + $b = ['abc', 'def', 'xy']; + + return array_combine($a, $b); +} From eed9282f97246c03ec4374549b7f3e1712c48f06 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 9 Oct 2024 17:00:33 +0200 Subject: [PATCH 0682/3097] Update simple-downgrader --- composer.lock | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 8f8c4a28705..beef5f73d80 100644 --- a/composer.lock +++ b/composer.lock @@ -4445,21 +4445,21 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "760b4c5c0b5ae631e6604bdcf074387e40e35ed1" + "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/760b4c5c0b5ae631e6604bdcf074387e40e35ed1", - "reference": "760b4c5c0b5ae631e6604bdcf074387e40e35ed1", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fb8b7833034f0396d5e4518ed090e3d099b7d9bc", + "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc", "shasum": "" }, "require": { "nette/utils": "^3.2.5", - "nikic/php-parser": "^5.0", + "nikic/php-parser": "^5.3.0", "php": "^7.4|^8.0", "phpstan/phpdoc-parser": "^2.0", "symfony/console": "^5.4", @@ -4470,7 +4470,6 @@ "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "bin": [ "bin/simple-downgrade" ], @@ -4489,9 +4488,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.x" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.0.0" }, - "time": "2024-09-15T14:01:11+00:00" + "time": "2024-10-09T14:55:47+00:00" }, { "name": "phar-io/manifest", From 2e8b2606915d199f0394cc644a6f064bdca9e8d6 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 3 Oct 2024 15:44:48 +0200 Subject: [PATCH 0683/3097] deps: use bashunit:0.17 --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 51891fb45ca..c4f626994a0 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -268,7 +268,7 @@ jobs: run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" - name: "Install bashunit" - run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.13.0" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.17.0" - name: "Test" run: "${{ matrix.script }}" From 728ae75699494e476731afc606febadc986c4611 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Thu, 3 Oct 2024 15:45:43 +0200 Subject: [PATCH 0684/3097] test: use bashunit -a exit_code to check for errors and defer the execution call inside bashunit --- .github/workflows/e2e-tests.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4d56668a565..6d486bc70e8 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -160,49 +160,52 @@ jobs: cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ patch -b data/TraitOne.php < TraitOne.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") echo "$OUTPUT" - ../bashunit -a line_count 1 "$OUTPUT" + ../bashunit -a line_count 2 "$OUTPUT" + ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" ../bashunit -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ patch -b data/TraitTwo.php < TraitTwo.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") echo "$OUTPUT" - ../bashunit -a line_count 1 "$OUTPUT" + ../bashunit -a line_count 2 "$OUTPUT" + ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" ../bashunit -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ patch -b data/TraitOne.php < TraitOne.patch patch -b data/TraitTwo.php < TraitTwo.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/") echo "$OUTPUT" - ../bashunit -a line_count 2 "$OUTPUT" + ../bashunit -a line_count 3 "$OUTPUT" + ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" ../bashunit -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" ../bashunit -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths - OUTPUT=$(../../bin/phpstan analyse -c ignore.neon 2>&1 || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c ignore.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in ignoreErrors' "$OUTPUT" ../bashunit -a contains 'tests is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths - OUTPUT=$(../../bin/phpstan analyse -c phpneon.php 2>&1 || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon.php") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in ignoreErrors' "$OUTPUT" ../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths - OUTPUT=$(../../bin/phpstan analyse -c excludePaths.neon 2>&1 || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c excludePaths.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" ../bashunit -a contains 'tests is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths - OUTPUT=$(../../bin/phpstan analyse -c phpneon2.php 2>&1 || true) + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon2.php") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" ../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" From dcd69eba14ed57b32c2f1b759cd8289772f364ac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 11 Oct 2024 09:36:06 +0200 Subject: [PATCH 0685/3097] Try to quit the child process only after internal errors were accounted for --- .github/workflows/e2e-tests.yml | 7 ++++ e2e/bug-11826/.gitignore | 2 ++ e2e/bug-11826/composer.json | 8 +++++ e2e/bug-11826/phpstan.neon.dist | 8 +++++ e2e/bug-11826/rules/DummyRule.php | 35 +++++++++++++++++++ .../src/FatalErrorWhenAutoloaded.php | 11 ++++++ src/Parallel/ParallelAnalyser.php | 6 +++- 7 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 e2e/bug-11826/.gitignore create mode 100644 e2e/bug-11826/composer.json create mode 100644 e2e/bug-11826/phpstan.neon.dist create mode 100644 e2e/bug-11826/rules/DummyRule.php create mode 100644 e2e/bug-11826/src/FatalErrorWhenAutoloaded.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c4f626994a0..8914cca9048 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -248,6 +248,13 @@ jobs: cd e2e/bad-exclude-paths OUTPUT=$(../../bin/phpstan analyse -c ignoreReportUnmatchedFalse.neon) echo "$OUTPUT" + - script: | + cd e2e/bug-11826 + composer install + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan") + echo "$OUTPUT" + ../bashunit -a contains 'Child process error (exit code 255): PHP Fatal error' "$OUTPUT" + ../bashunit -a contains 'Result is incomplete because of severe errors.' "$OUTPUT" steps: - name: "Checkout" diff --git a/e2e/bug-11826/.gitignore b/e2e/bug-11826/.gitignore new file mode 100644 index 00000000000..de4a392c331 --- /dev/null +++ b/e2e/bug-11826/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/e2e/bug-11826/composer.json b/e2e/bug-11826/composer.json new file mode 100644 index 00000000000..e33fb45ab0d --- /dev/null +++ b/e2e/bug-11826/composer.json @@ -0,0 +1,8 @@ +{ + "autoload": { + "psr-4": { + "App\\": "src/", + "Rules\\": "rules/" + } + } +} diff --git a/e2e/bug-11826/phpstan.neon.dist b/e2e/bug-11826/phpstan.neon.dist new file mode 100644 index 00000000000..3f491198cff --- /dev/null +++ b/e2e/bug-11826/phpstan.neon.dist @@ -0,0 +1,8 @@ +parameters: + level: 9 + paths: + - src + - rules + +rules: + - Rules\DummyRule diff --git a/e2e/bug-11826/rules/DummyRule.php b/e2e/bug-11826/rules/DummyRule.php new file mode 100644 index 00000000000..0a182255315 --- /dev/null +++ b/e2e/bug-11826/rules/DummyRule.php @@ -0,0 +1,35 @@ + + */ +class DummyRule implements Rule +{ + + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return list + */ + public function processNode( + Node $node, + Scope $scope, + ): array + { + return [FatalErrorWhenAutoloaded::AUTOLOAD]; + } + +} diff --git a/e2e/bug-11826/src/FatalErrorWhenAutoloaded.php b/e2e/bug-11826/src/FatalErrorWhenAutoloaded.php new file mode 100644 index 00000000000..a75127a356e --- /dev/null +++ b/e2e/bug-11826/src/FatalErrorWhenAutoloaded.php @@ -0,0 +1,11 @@ +processPool->tryQuitProcess($processIdentifier); if ($exitCode === 0) { + $this->processPool->tryQuitProcess($processIdentifier); return; } if ($exitCode === null) { + $this->processPool->tryQuitProcess($processIdentifier); return; } @@ -294,6 +295,7 @@ public function analyse( continue; } + $this->processPool->tryQuitProcess($processIdentifier); return; } $internalErrors[] = new InternalError(sprintf( @@ -303,11 +305,13 @@ public function analyse( 'Increase your memory limit in php.ini or run PHPStan with --memory-limit CLI option.', ), 'running parallel worker', [], null, false); $internalErrorsCount++; + $this->processPool->tryQuitProcess($processIdentifier); return; } $internalErrors[] = new InternalError(sprintf('Child process error (exit code %d): %s', $exitCode, $output), 'running parallel worker', [], null, true); $internalErrorsCount++; + $this->processPool->tryQuitProcess($processIdentifier); }); $this->processPool->attachProcess($processIdentifier, $process); } From 66664bb612b4cfb4e8a870f5d11f5af731f5e21a Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Sat, 12 Oct 2024 09:21:39 +0000 Subject: [PATCH 0686/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 35b021fe668..65970767903 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.19.4", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.19", + "ondrejmirtes/better-reflection": "6.25.0.20", "phpstan/php-8-stubs": "0.3.111", "phpstan/phpdoc-parser": "1.32.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 916baf9f89c..b3c3720962c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9d6cc3eb297a7d5f481c67f8c671b208", + "content-hash": "a26a44dbb1ffe6df5fcd90884b4be5d4", "packages": [ { "name": "clue/ndjson-react", @@ -2184,16 +2184,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.19", + "version": "6.25.0.20", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "3c20bd3dea6f5a04a729891f9dd4326feb2e288c" + "reference": "a04fde59afcca51b0b2b439e05eb74503994cf4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/3c20bd3dea6f5a04a729891f9dd4326feb2e288c", - "reference": "3c20bd3dea6f5a04a729891f9dd4326feb2e288c", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/a04fde59afcca51b0b2b439e05eb74503994cf4b", + "reference": "a04fde59afcca51b0b2b439e05eb74503994cf4b", "shasum": "" }, "require": { @@ -2250,9 +2250,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.19" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.20" }, - "time": "2024-09-10T09:53:53+00:00" + "time": "2024-10-12T09:20:20+00:00" }, { "name": "phpstan/php-8-stubs", @@ -6589,7 +6589,7 @@ "php": "^8.1", "composer-runtime-api": "^2.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.99" }, From ff7521302711c2eca765d47f8606179f0ca0d19b Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Sat, 12 Oct 2024 09:30:33 +0000 Subject: [PATCH 0687/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 65970767903..866afda4257 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^4.19.4", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.25.0.20", + "ondrejmirtes/better-reflection": "6.25.0.21", "phpstan/php-8-stubs": "0.3.111", "phpstan/phpdoc-parser": "1.32.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index b3c3720962c..aae4ed9cd03 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a26a44dbb1ffe6df5fcd90884b4be5d4", + "content-hash": "a76e95ec9a6020d405d9495cb67cf0ce", "packages": [ { "name": "clue/ndjson-react", @@ -2184,16 +2184,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.25.0.20", + "version": "6.25.0.21", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "a04fde59afcca51b0b2b439e05eb74503994cf4b" + "reference": "c1bcfaa130718e4004ab8260bed4bfe96a46dc02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/a04fde59afcca51b0b2b439e05eb74503994cf4b", - "reference": "a04fde59afcca51b0b2b439e05eb74503994cf4b", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c1bcfaa130718e4004ab8260bed4bfe96a46dc02", + "reference": "c1bcfaa130718e4004ab8260bed4bfe96a46dc02", "shasum": "" }, "require": { @@ -2250,9 +2250,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.20" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.25.0.21" }, - "time": "2024-10-12T09:20:20+00:00" + "time": "2024-10-12T09:29:10+00:00" }, { "name": "phpstan/php-8-stubs", From def9f5abccb4b2e761dc7886f444e56aae25d97a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 12:55:40 +0200 Subject: [PATCH 0688/3097] Simulate level 10 in issue bot on 1.12.x --- .github/workflows/issue-bot.yml | 3 +++ issue-bot/config.level10.neon | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 issue-bot/config.level10.neon diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 6d6af362f17..530886afff3 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -106,6 +106,9 @@ jobs: attempt_limit: 5 attempt_delay: 1000 + - name: "Add level 10 config file" + run: "cp issue-bot/config.level10.neon conf/" + - name: "Run PHPStan" working-directory: "issue-bot" timeout-minutes: 5 diff --git a/issue-bot/config.level10.neon b/issue-bot/config.level10.neon new file mode 100644 index 00000000000..5d052692c9c --- /dev/null +++ b/issue-bot/config.level10.neon @@ -0,0 +1,5 @@ +includes: + - config.level9.neon + +parameters: + checkImplicitMixed: true From 93126b4590bb1c0c934be1dc52af180144ef45ef Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 13:07:36 +0200 Subject: [PATCH 0689/3097] Revert "Simulate level 10 in issue bot on 1.12.x" This reverts commit def9f5abccb4b2e761dc7886f444e56aae25d97a. --- .github/workflows/issue-bot.yml | 3 --- issue-bot/config.level10.neon | 5 ----- 2 files changed, 8 deletions(-) delete mode 100644 issue-bot/config.level10.neon diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 530886afff3..6d6af362f17 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -106,9 +106,6 @@ jobs: attempt_limit: 5 attempt_delay: 1000 - - name: "Add level 10 config file" - run: "cp issue-bot/config.level10.neon conf/" - - name: "Run PHPStan" working-directory: "issue-bot" timeout-minutes: 5 diff --git a/issue-bot/config.level10.neon b/issue-bot/config.level10.neon deleted file mode 100644 index 5d052692c9c..00000000000 --- a/issue-bot/config.level10.neon +++ /dev/null @@ -1,5 +0,0 @@ -includes: - - config.level9.neon - -parameters: - checkImplicitMixed: true From a9346394eafb588efe7af2c0650dfeb2b757a427 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 13:16:12 +0200 Subject: [PATCH 0690/3097] Fix test --- tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php index e8d009fe0fa..2b5c9c1d402 100644 --- a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php +++ b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php @@ -31,7 +31,7 @@ * @param T $value * @return T */ -function id(mixed $value): mixed +function id($value) { dumpPhpDocType($value); From 6cf223840f89c972551f373ade9eea16d12e143b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 15:46:37 +0200 Subject: [PATCH 0691/3097] ArrayType::describe - explicit mixed should be stated explicitly --- phpstan-baseline.neon | 2 +- src/Type/ArrayType.php | 8 +-- ...ySearchFunctionTypeSpecifyingExtension.php | 5 +- .../Analyser/AnalyserIntegrationTest.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 8 +-- tests/PHPStan/Analyser/TypeSpecifierTest.php | 10 ++-- tests/PHPStan/Analyser/data/param-out.php | 6 +- tests/PHPStan/Analyser/nsrt/array-chunk.php | 2 +- .../Analyser/nsrt/array-fill-keys-php8.php | 2 +- .../PHPStan/Analyser/nsrt/array-flip-php8.php | 2 +- .../nsrt/array-intersect-key-php8.php | 2 +- tests/PHPStan/Analyser/nsrt/array-pop.php | 2 +- tests/PHPStan/Analyser/nsrt/array-reverse.php | 8 +-- .../Analyser/nsrt/array-search-php8.php | 2 +- tests/PHPStan/Analyser/nsrt/array-shift.php | 2 +- tests/PHPStan/Analyser/nsrt/array-slice.php | 2 +- tests/PHPStan/Analyser/nsrt/array_keys.php | 2 +- tests/PHPStan/Analyser/nsrt/array_values.php | 2 +- .../PHPStan/Analyser/nsrt/assert-docblock.php | 22 +++---- .../PHPStan/Analyser/nsrt/assert-methods.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-1209.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-1233.php | 6 +- tests/PHPStan/Analyser/nsrt/bug-3446.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6329.php | 14 ++--- .../nsrt/bug-7144-composer-integration.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-7915.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9662.php | 58 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-9734.php | 4 +- .../nsrt/conditional-types-inference.php | 2 +- .../Analyser/nsrt/filter-var-array.php | 6 +- tests/PHPStan/Analyser/nsrt/generics.php | 4 +- tests/PHPStan/Analyser/nsrt/globals.php | 18 +++--- .../Analyser/nsrt/has-offset-type-bug.php | 6 +- tests/PHPStan/Analyser/nsrt/memcache-get.php | 2 +- .../PHPStan/Analyser/nsrt/mixed-subtract.php | 4 +- .../PHPStan/Analyser/nsrt/mixed-to-number.php | 2 +- tests/PHPStan/Analyser/nsrt/narrow-cast.php | 4 +- .../Analyser/nsrt/native-expressions.php | 2 +- .../prestashop-breakdowns-empty-array.php | 6 +- .../Php8SignatureMapProviderTest.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- tests/PHPStan/Rules/Classes/data/bug-5333.php | 2 +- .../CallToFunctionParametersRuleTest.php | 8 +-- .../Rules/Methods/CallMethodsRuleTest.php | 6 +- .../Methods/CallStaticMethodsRuleTest.php | 6 +- .../Rules/Methods/ReturnTypeRuleTest.php | 4 +- .../VarTagChangedExpressionTypeRuleTest.php | 8 +-- .../WrongVariableNameInVarTagRuleTest.php | 2 +- .../TypesAssignedToPropertiesRuleTest.php | 6 +- .../PHPStan/Rules/Variables/data/bug-8113.php | 12 ++-- 50 files changed, 151 insertions(+), 154 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fb4b06b2791..a652b6a8364 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -13,7 +13,7 @@ parameters: path: src/Analyser/AnalyserResultFinalizer.php - - message: '#^Cannot assign offset ''realCount'' to array\|string\.$#' + message: '#^Cannot assign offset ''realCount'' to array\\|string\.$#' identifier: offsetAssign.dimType count: 1 path: src/Analyser/Ignore/IgnoredErrorHelperResult.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 8fb1a219d53..488dac119e9 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -128,8 +128,8 @@ public function equals(Type $type): bool public function describe(VerbosityLevel $level): string { - $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed'; - $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed'; + $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->keyType->isExplicitMixed(); + $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->itemType->isExplicitMixed(); $valueHandler = function () use ($level, $isMixedKeyType, $isMixedItemType): string { if ($isMixedKeyType || $this->keyType instanceof NeverType) { @@ -500,8 +500,8 @@ public function traverse(callable $cb): Type public function toPhpDocNode(): TypeNode { - $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed'; - $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed'; + $isMixedKeyType = $this->keyType instanceof MixedType && $this->keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->keyType->isExplicitMixed(); + $isMixedItemType = $this->itemType instanceof MixedType && $this->itemType->describe(VerbosityLevel::precise()) === 'mixed' && !$this->itemType->isExplicitMixed(); if ($isMixedKeyType) { if ($isMixedItemType) { diff --git a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php index b382891275d..68f2992096a 100644 --- a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php @@ -10,10 +10,7 @@ use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\ArrayType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\MixedType; -use PHPStan\Type\TypeCombinator; use function strtolower; final class ArraySearchFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension @@ -45,7 +42,7 @@ public function specifyTypes( return $this->typeSpecifier->create( $arrayArg, - TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType()), + new NonEmptyArrayType(), $context, $scope, ); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 52791b8b95e..d64cd886171 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1198,7 +1198,7 @@ public function testBug9459(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php'); $this->assertCount(1, $errors); - $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); + $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); $this->assertSame(10, $errors[0]->getLine()); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 6113db22b3a..d24d24338d5 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1763,7 +1763,7 @@ public function dataProperties(): array '$this->arrayPropertyOne', ], [ - 'array', + 'array', '$this->arrayPropertyOther', ], [ @@ -3423,7 +3423,7 @@ public function dataTypeFromFunctionPhpDocs(): array '$arrayParameterOne', ], [ - 'array', + 'array', '$arrayParameterOther', ], [ @@ -5779,7 +5779,7 @@ public function dataSpecifiedTypesUsingIsFunctions(): array '$null', ], [ - 'array', + 'array', '$array', ], [ @@ -9252,7 +9252,7 @@ public function dataInferPrivatePropertyTypeFromConstructor(): array '$this->bool', ], [ - 'array', + 'array', '$this->array', ], ]; diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 04abebab3c8..99cbb8db714 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1218,8 +1218,8 @@ public function dataCondition(): iterable ), new Identical(new Expr\ConstFetch(new Name('null')), new Variable('a')), ), - ['$a' => 'non-empty-array|null'], - ['$a' => 'mixed~non-empty-array & ~null'], + ['$a' => 'non-empty-array|null'], + ['$a' => 'mixed~non-empty-array & ~null'], ], [ new Expr\BinaryOp\BooleanAnd( @@ -1234,7 +1234,7 @@ public function dataCondition(): iterable ), [ '$foo' => 'array', - 'array_filter($foo, \'is_string\', ARRAY_FILTER_USE_KEY)' => 'array', // could be 'array' + 'array_filter($foo, \'is_string\', ARRAY_FILTER_USE_KEY)' => 'array', // could be 'array' ], [], ], @@ -1250,7 +1250,7 @@ public function dataCondition(): iterable ), ), [ - '$foo' => 'non-empty-array', + '$foo' => 'non-empty-array', 'count($foo)' => 'mixed~(0.0|int|false|null)', ], [], @@ -1267,7 +1267,7 @@ public function dataCondition(): iterable ), ), [ - '$foo' => 'non-empty-array', + '$foo' => 'non-empty-array', 'count($foo)' => '2', ], [], diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index 05684938b8e..d172b358e15 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -315,7 +315,7 @@ function testParseStr() { echo $output['arr'][1];//baz */ - \PHPStan\Testing\assertType('array', $output); + \PHPStan\Testing\assertType('array|lowercase-string>', $output); } function fooSimilar() { @@ -392,10 +392,10 @@ function fooHeadersSent() { function fooMbParseStr() { mb_parse_str("foo=bar", $output); - assertType('array', $output); + assertType('array|string>', $output); mb_parse_str('email=mail@example.org&city=town&x=1&y[g]=3&f=1.23', $output); - assertType('array', $output); + assertType('array|string>', $output); } function fooPreg() diff --git a/tests/PHPStan/Analyser/nsrt/array-chunk.php b/tests/PHPStan/Analyser/nsrt/array-chunk.php index 8e6fa67cf29..c0b79ffbae8 100644 --- a/tests/PHPStan/Analyser/nsrt/array-chunk.php +++ b/tests/PHPStan/Analyser/nsrt/array-chunk.php @@ -11,7 +11,7 @@ public function generalArrays(array $arr): void { /** @var mixed[] $arr */ assertType('list>', array_chunk($arr, 2)); - assertType('list', array_chunk($arr, 2, true)); + assertType('list>', array_chunk($arr, 2, true)); /** @var array $arr */ assertType('list>', array_chunk($arr, 2)); diff --git a/tests/PHPStan/Analyser/nsrt/array-fill-keys-php8.php b/tests/PHPStan/Analyser/nsrt/array-fill-keys-php8.php index 07c06367dc7..47104825342 100644 --- a/tests/PHPStan/Analyser/nsrt/array-fill-keys-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-fill-keys-php8.php @@ -7,7 +7,7 @@ function mixedAndSubtractedArray($mixed): void { if (is_array($mixed)) { - assertType("array<'b'>", array_fill_keys($mixed, 'b')); + assertType("array", array_fill_keys($mixed, 'b')); } else { assertType("*NEVER*", array_fill_keys($mixed, 'b')); } diff --git a/tests/PHPStan/Analyser/nsrt/array-flip-php8.php b/tests/PHPStan/Analyser/nsrt/array-flip-php8.php index b8f0e6793d1..2b75c17aba1 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip-php8.php @@ -9,7 +9,7 @@ function mixedAndSubtractedArray($mixed) if (is_array($mixed)) { assertType('array', array_flip($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('*NEVER*', array_flip($mixed)); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-intersect-key-php8.php b/tests/PHPStan/Analyser/nsrt/array-intersect-key-php8.php index 4bfd3140e06..9ffde9da832 100644 --- a/tests/PHPStan/Analyser/nsrt/array-intersect-key-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-intersect-key-php8.php @@ -16,7 +16,7 @@ public function mixedAndSubtractedArray($mixed, array $otherArrs): void /** @var array $otherArrs */ assertType('array', array_intersect_key($mixed, $otherArrs)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); /** @var array $otherArrs */ assertType('*NEVER*', array_intersect_key($mixed, $otherArrs)); /** @var array $otherArrs */ diff --git a/tests/PHPStan/Analyser/nsrt/array-pop.php b/tests/PHPStan/Analyser/nsrt/array-pop.php index 04494a96dfb..37a986b0cea 100644 --- a/tests/PHPStan/Analyser/nsrt/array-pop.php +++ b/tests/PHPStan/Analyser/nsrt/array-pop.php @@ -77,7 +77,7 @@ public function foo1($mixed): void if(is_array($mixed)) { assertType('mixed', array_pop($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('mixed', array_pop($mixed)); assertType('*ERROR*', $mixed); } diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse.php b/tests/PHPStan/Analyser/nsrt/array-reverse.php index 5d341b62902..6f05e9b9f04 100644 --- a/tests/PHPStan/Analyser/nsrt/array-reverse.php +++ b/tests/PHPStan/Analyser/nsrt/array-reverse.php @@ -14,8 +14,8 @@ class Foo */ public function normalArrays(array $a, array $b): void { - assertType('array', array_reverse($a)); - assertType('array', array_reverse($a, true)); + assertType('array', array_reverse($a)); + assertType('array', array_reverse($a, true)); assertType('array', array_reverse($b)); assertType('array', array_reverse($b, true)); @@ -68,8 +68,8 @@ public function list(array $a, array $b): void public function mixed(mixed $mixed): void { - assertType('array', array_reverse($mixed)); - assertType('array', array_reverse($mixed, true)); + assertType('array', array_reverse($mixed)); + assertType('array', array_reverse($mixed, true)); if (array_key_exists('foo', $mixed)) { assertType('non-empty-array', array_reverse($mixed)); diff --git a/tests/PHPStan/Analyser/nsrt/array-search-php8.php b/tests/PHPStan/Analyser/nsrt/array-search-php8.php index c3430cc1b52..30b9527e10b 100644 --- a/tests/PHPStan/Analyser/nsrt/array-search-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-search-php8.php @@ -16,7 +16,7 @@ public function mixedAndSubtractedArray($mixed, string $string): void assertType('int|string|false', array_search('foo', $mixed)); assertType('int|string|false', array_search($string, $mixed, true)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('*NEVER*', array_search('foo', $mixed, true)); assertType('*NEVER*', array_search('foo', $mixed)); assertType('*NEVER*', array_search($string, $mixed, true)); diff --git a/tests/PHPStan/Analyser/nsrt/array-shift.php b/tests/PHPStan/Analyser/nsrt/array-shift.php index eb227de3f6f..2d8ef21d7ed 100644 --- a/tests/PHPStan/Analyser/nsrt/array-shift.php +++ b/tests/PHPStan/Analyser/nsrt/array-shift.php @@ -77,7 +77,7 @@ public function foo1($mixed): void if(is_array($mixed)) { assertType('mixed', array_shift($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('mixed', array_shift($mixed)); assertType('*ERROR*', $mixed); } diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index 87fa61e36f0..e2faabd113f 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -24,7 +24,7 @@ public function nonEmpty(array $a, array $b, array $c): void */ public function fromMixed($arr): void { - assertType('array', array_slice($arr, 1, 2)); + assertType('array', array_slice($arr, 1, 2)); } public function normalArrays(array $arr): void diff --git a/tests/PHPStan/Analyser/nsrt/array_keys.php b/tests/PHPStan/Analyser/nsrt/array_keys.php index 7ea0a40ff5d..6808bf36b3c 100644 --- a/tests/PHPStan/Analyser/nsrt/array_keys.php +++ b/tests/PHPStan/Analyser/nsrt/array_keys.php @@ -11,7 +11,7 @@ public function sayHello($mixed): void if(is_array($mixed)) { assertType('list<(int|string)>', array_keys($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('*NEVER*', array_keys($mixed)); } } diff --git a/tests/PHPStan/Analyser/nsrt/array_values.php b/tests/PHPStan/Analyser/nsrt/array_values.php index a9fc01c9470..18074963a4b 100644 --- a/tests/PHPStan/Analyser/nsrt/array_values.php +++ b/tests/PHPStan/Analyser/nsrt/array_values.php @@ -11,7 +11,7 @@ public function foo1($mixed): void if(is_array($mixed)) { assertType('list', array_values($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('*NEVER*', array_values($mixed)); } } diff --git a/tests/PHPStan/Analyser/nsrt/assert-docblock.php b/tests/PHPStan/Analyser/nsrt/assert-docblock.php index 8451a48ebd3..a1cbcc9b3e1 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-docblock.php +++ b/tests/PHPStan/Analyser/nsrt/assert-docblock.php @@ -56,7 +56,7 @@ function validateNotNull($value) : void {} * @param mixed[] $arr */ function takesArray(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); validateStringArray($arr); assertType('array', $arr); @@ -66,22 +66,22 @@ function takesArray(array $arr) : void { * @param mixed[] $arr */ function takesArrayIfTrue(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (validateStringArrayIfTrue($arr)) { assertType('array', $arr); } else { - assertType('array', $arr); + assertType('array', $arr); } } /** * @param mixed[] $arr */ function takesArrayIfTrue1(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (!validateStringArrayIfTrue($arr)) { - assertType('array', $arr); + assertType('array', $arr); } else { assertType('array', $arr); } @@ -91,12 +91,12 @@ function takesArrayIfTrue1(array $arr) : void { * @param mixed[] $arr */ function takesArrayIfFalse(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (!validateStringArrayIfFalse($arr)) { assertType('array', $arr); } else { - assertType('array', $arr); + assertType('array', $arr); } } @@ -104,10 +104,10 @@ function takesArrayIfFalse(array $arr) : void { * @param mixed[] $arr */ function takesArrayIfFalse1(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (validateStringArrayIfFalse($arr)) { - assertType('array', $arr); + assertType('array', $arr); } else { assertType('array', $arr); } @@ -117,7 +117,7 @@ function takesArrayIfFalse1(array $arr) : void { * @param mixed[] $arr */ function takesStringOrIntArray(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (validateStringOrIntArray($arr)) { assertType('array', $arr); @@ -130,7 +130,7 @@ function takesStringOrIntArray(array $arr) : void { * @param mixed[] $arr */ function takesStringOrNonEmptyIntArray(array $arr) : void { - assertType('array', $arr); + assertType('array', $arr); if (validateStringOrNonEmptyIntArray($arr)) { assertType('array', $arr); diff --git a/tests/PHPStan/Analyser/nsrt/assert-methods.php b/tests/PHPStan/Analyser/nsrt/assert-methods.php index f6609f3f8f7..6c278c21ed7 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-methods.php +++ b/tests/PHPStan/Analyser/nsrt/assert-methods.php @@ -37,7 +37,7 @@ public function doBar($mixed) public function doBar2(array $objects) { self::doFoo($objects, stdClass::class); - assertType('array', $objects); + assertType('array', $objects); } /** @@ -47,7 +47,7 @@ public function doBar2(array $objects) public function doBar3(array $strings) { self::doFoo($strings, stdClass::class); - assertType('array>', $strings); + assertType('array>', $strings); } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-1209.php b/tests/PHPStan/Analyser/nsrt/bug-1209.php index fff8d13dff3..4b4ce770d17 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1209.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1209.php @@ -13,7 +13,7 @@ public function sayHello($value): void { $isArray = is_array($value); if($isArray){ - assertType('array', $value); + assertType('array', $value); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-1233.php b/tests/PHPStan/Analyser/nsrt/bug-1233.php index 7d70679585e..17267e0001b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1233.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1233.php @@ -10,18 +10,18 @@ public function toArray($value): array { assertType('mixed', $value); if (is_array($value)) { - assertType('array', $value); + assertType('array', $value); return $value; } - assertType('mixed~array', $value); + assertType('mixed~array', $value); if (is_iterable($value)) { assertType('Traversable', $value); return iterator_to_array($value); } - assertType('mixed~array', $value); + assertType('mixed~array', $value); throw new \LogicException(); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-3446.php b/tests/PHPStan/Analyser/nsrt/bug-3446.php index cc02033da31..c01b4bdbdc2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3446.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3446.php @@ -15,7 +15,7 @@ public function takesString(string $s) : void{} public function main2(string $input) : void{ if(is_array($var = json_decode($input))){ - assertType('array', $var); + assertType('array', $var); } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6329.php b/tests/PHPStan/Analyser/nsrt/bug-6329.php index c6b49e87e46..b31842e05f8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6329.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6329.php @@ -106,19 +106,19 @@ function true($a): void function nonEmptyArray1($a): void { if (is_array($a) && [] !== $a || null === $a) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } if ([] !== $a && is_array($a) || null === $a) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } if (null === $a || is_array($a) && [] !== $a) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } if (null === $a || [] !== $a && is_array($a)) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } } @@ -128,11 +128,11 @@ function nonEmptyArray1($a): void function nonEmptyArray2($a): void { if (is_array($a) && count($a) > 0 || null === $a) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } if (null === $a || is_array($a) && count($a) > 0) { - assertType('non-empty-array|null', $a); + assertType('non-empty-array|null', $a); } } @@ -155,7 +155,7 @@ function inverse($a, $b, $c): void if (null !== $c && (!is_array($c) || count($c) <= 0)) { } else { - assertType('non-empty-array|null', $c); + assertType('non-empty-array|null', $c); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7144-composer-integration.php b/tests/PHPStan/Analyser/nsrt/bug-7144-composer-integration.php index 057067411cc..c60d776d38e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7144-composer-integration.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7144-composer-integration.php @@ -32,14 +32,14 @@ public function test3(array $options): void $curlHandle = curl_init(); foreach (self::$options as $type => $curlOptions) { foreach ($curlOptions as $name => $curlOption) { - \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); + \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); if (isset($options[$type][$name])) { - \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); + \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); if ($type === 'ssl' && $name === 'verify_peer_name') { - \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); + \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]); } else { - \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); + \PHPStan\Testing\assertType('array{http: array{header: array, proxy?: string, request_fulluri: bool}, ssl?: array}', $options); curl_setopt($curlHandle, $curlOption, $options[$type][$name]); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7915.php b/tests/PHPStan/Analyser/nsrt/bug-7915.php index 1dee1a63d58..a2e63375a7b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7915.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7915.php @@ -63,7 +63,7 @@ public function setConfigMimicConditionalParamType($name, $value): void function to_utf8($data, bool $isArray = false) { if ($isArray) { - assertType('array', $data); + assertType('array', $data); if (is_array($data)) { // always true foreach ($data as $k => $value) { $data[$k] = to_utf8($value, is_array($value)); diff --git a/tests/PHPStan/Analyser/nsrt/bug-9662.php b/tests/PHPStan/Analyser/nsrt/bug-9662.php index 4da94355c76..d88555a8632 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9662.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9662.php @@ -11,74 +11,74 @@ */ function doFoo(string $s, $a, $strings, $mixed) { if (in_array('foo', $a, true)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array('foo', $a, false)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array('foo', $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array('0', $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array('1', $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array(true, $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array(false, $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array($s, $a, true)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array($s, $a, false)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array($s, $a)) { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } else { - assertType("array", $a); + assertType("array", $a); } - assertType('array', $a); + assertType('array', $a); if (in_array($mixed, $strings, true)) { assertType('non-empty-array', $strings); diff --git a/tests/PHPStan/Analyser/nsrt/bug-9734.php b/tests/PHPStan/Analyser/nsrt/bug-9734.php index ce75fa71977..353b1d91f92 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9734.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9734.php @@ -17,7 +17,7 @@ public function doFoo(array $a): void if (array_is_list($a)) { assertType('list', $a); } else { - assertType('array', $a); // could be non-empty-array + assertType('array', $a); // could be non-empty-array } } @@ -40,7 +40,7 @@ public function doFoo3(array $a): void if (array_is_list($a)) { assertType('non-empty-list', $a); } else { - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } } diff --git a/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php b/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php index 89bfa50a22b..596e97634ac 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-types-inference.php @@ -89,5 +89,5 @@ function invariant(bool $condition, string $message): void function (mixed $value) { invariant(is_array($value), 'must be array'); - assertType('array', $value); + assertType('array', $value); }; diff --git a/tests/PHPStan/Analyser/nsrt/filter-var-array.php b/tests/PHPStan/Analyser/nsrt/filter-var-array.php index 38db914722c..1151d370c12 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var-array.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var-array.php @@ -85,11 +85,11 @@ function mixedInput(mixed $input): void ], false)); // filter flag with add_empty=default - assertType('array', filter_var_array($input, FILTER_VALIDATE_INT)); + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT)); // filter flag with add_empty=true - assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, true)); + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, true)); // filter flag with add_empty=false - assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, false)); + assertType('array', filter_var_array($input, FILTER_VALIDATE_INT, false)); $filter = [ 'filter' => FILTER_VALIDATE_INT, diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index 873ad66b6d5..60840465c14 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -163,8 +163,8 @@ function testF($arrayOfInt, $callableOrNull) return $a; })); assertType('array', f($arrayOfInt, $callableOrNull)); - assertType('array', f($arrayOfInt, null)); - assertType('array', f($arrayOfInt, '')); + assertType('array', f($arrayOfInt, null)); + assertType('array', f($arrayOfInt, '')); } /** diff --git a/tests/PHPStan/Analyser/nsrt/globals.php b/tests/PHPStan/Analyser/nsrt/globals.php index 7b7b7b8a015..da2ae357879 100644 --- a/tests/PHPStan/Analyser/nsrt/globals.php +++ b/tests/PHPStan/Analyser/nsrt/globals.php @@ -1,11 +1,11 @@ ', $GLOBALS); +\PHPStan\Testing\assertType('array', $_SERVER); +\PHPStan\Testing\assertType('array', $_GET); +\PHPStan\Testing\assertType('array', $_POST); +\PHPStan\Testing\assertType('array', $_FILES); +\PHPStan\Testing\assertType('array', $_COOKIE); +\PHPStan\Testing\assertType('array', $_SESSION); +\PHPStan\Testing\assertType('array', $_REQUEST); +\PHPStan\Testing\assertType('array', $_ENV); diff --git a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php index 8b470ba3164..d1e8fd92a02 100644 --- a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php +++ b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php @@ -44,14 +44,14 @@ public function doFoo(array $errorMessages): void */ public function doBar(array $result): void { - assertType('array', $result); + assertType('array', $result); assert($result['totals']['file_errors'] === 3); - assertType("array", $result); + assertType("array", $result); assertType("mixed", $result['totals']); assertType('3', $result['totals']['file_errors']); assertType('mixed', $result['totals']['errors']); assert($result['totals']['errors'] === 0); - assertType("array", $result); + assertType("array", $result); assertType("mixed", $result['totals']); assertType('3', $result['totals']['file_errors']); assertType('0', $result['totals']['errors']); diff --git a/tests/PHPStan/Analyser/nsrt/memcache-get.php b/tests/PHPStan/Analyser/nsrt/memcache-get.php index 735e85b545c..533e483682c 100644 --- a/tests/PHPStan/Analyser/nsrt/memcache-get.php +++ b/tests/PHPStan/Analyser/nsrt/memcache-get.php @@ -10,5 +10,5 @@ function (): void { $memcache = new Memcache(); assertType('mixed', $memcache->get("key1")); - assertType('array|false', $memcache->get(array("key1", "key2", "key3"))); + assertType('array|false', $memcache->get(array("key1", "key2", "key3"))); }; diff --git a/tests/PHPStan/Analyser/nsrt/mixed-subtract.php b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php index 59fa2afa13a..c544802655b 100644 --- a/tests/PHPStan/Analyser/nsrt/mixed-subtract.php +++ b/tests/PHPStan/Analyser/nsrt/mixed-subtract.php @@ -21,7 +21,7 @@ function subtract(mixed $m, $moreThenFalsy) { assertType('bool', (bool) $m); } if (!is_array($m)) { - assertType('mixed~array', $m); + assertType('mixed~array', $m); assertType('bool', (bool) $m); } @@ -55,7 +55,7 @@ function subtract(mixed $m, $moreThenFalsy) { } if ($m != 0 && !is_array($m) && $m != null && !is_object($m)) { // subtract more types then falsy - assertType("mixed~(0|0.0|''|'0'|array|object|false|null)", $m); + assertType("mixed~(0|0.0|''|'0'|array|object|false|null)", $m); assertType('true', (bool) $m); } } diff --git a/tests/PHPStan/Analyser/nsrt/mixed-to-number.php b/tests/PHPStan/Analyser/nsrt/mixed-to-number.php index 97cc9d08428..fee2d7bed40 100644 --- a/tests/PHPStan/Analyser/nsrt/mixed-to-number.php +++ b/tests/PHPStan/Analyser/nsrt/mixed-to-number.php @@ -35,7 +35,7 @@ function doFoo($mixed, int $i, float $f, $numericS) { } if (!is_array($mixed)) { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('(float|int)', $mixed + $mixed); } } diff --git a/tests/PHPStan/Analyser/nsrt/narrow-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-cast.php index 82e09e0bd3f..fc70c128e9f 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-cast.php @@ -14,9 +14,9 @@ function doFoo(string $x, array $arr): void { assertType('string', $x); if ((bool) array_search($x, $arr, true)) { - assertType('non-empty-array', $arr); + assertType('non-empty-array', $arr); } else { - assertType('array', $arr); + assertType('array', $arr); } assertType('string', $x); diff --git a/tests/PHPStan/Analyser/nsrt/native-expressions.php b/tests/PHPStan/Analyser/nsrt/native-expressions.php index 72f5a47e7e8..abe041686a6 100644 --- a/tests/PHPStan/Analyser/nsrt/native-expressions.php +++ b/tests/PHPStan/Analyser/nsrt/native-expressions.php @@ -34,7 +34,7 @@ public function __construct( /** @var non-empty-array */ private array $array ){ - assertType('non-empty-array', $this->array); + assertType('non-empty-array', $this->array); assertNativeType('array', $this->array); if(count($array) === 0){ throw new \InvalidArgumentException(); diff --git a/tests/PHPStan/Analyser/nsrt/prestashop-breakdowns-empty-array.php b/tests/PHPStan/Analyser/nsrt/prestashop-breakdowns-empty-array.php index b9e0920141d..a115a32359c 100644 --- a/tests/PHPStan/Analyser/nsrt/prestashop-breakdowns-empty-array.php +++ b/tests/PHPStan/Analyser/nsrt/prestashop-breakdowns-empty-array.php @@ -21,13 +21,13 @@ public function getTaxBreakdown($mixed, $arrayMixed): void foreach ($breakdowns as $type => $bd) { if (empty($bd)) { - assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); + assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); unset($breakdowns[$type]); - assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); + assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); } } - assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); + assertType('array{product_tax?: mixed, shipping_tax?: array, ecotax_tax?: array, wrapping_tax?: array}', $breakdowns); } public function doFoo(): void diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index f5511bfc335..41d62d1c36f 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -107,7 +107,7 @@ public function dataFunctions(): array ]), new UnionType([ new ConstantBooleanType(false), - new ArrayType(new MixedType(true), new MixedType(true)), + new ArrayType(new MixedType(), new MixedType()), ]), false, ], diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index b45c1200891..0383dadc521 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -551,7 +551,7 @@ public function testBug8356(): void { $this->analyse([__DIR__ . '/data/bug-8356.php'], [ [ - "Offset 'x' might not exist on array|string.", + "Offset 'x' might not exist on array|string.", 7, ], ]); diff --git a/tests/PHPStan/Rules/Classes/data/bug-5333.php b/tests/PHPStan/Rules/Classes/data/bug-5333.php index f38c46cddf3..67845acd46d 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-5333.php +++ b/tests/PHPStan/Rules/Classes/data/bug-5333.php @@ -27,7 +27,7 @@ public function sayHello($foo): Route } assertType('array', $foo); - assertNativeType('array', $foo); + assertNativeType('array', $foo); assertType('Bug5333\Route', $res); assertNativeType('Bug5333\Route', $res); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index eda1ad4ee3b..98927b4bd24 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1688,22 +1688,22 @@ public function testCountArrayShift(): void if (PHP_VERSION_ID < 80000) { $errors = [ [ - 'Parameter #1 $var of function count expects array|Countable, array|false given.', + 'Parameter #1 $var of function count expects array|Countable, array|false given.', 8, ], [ - 'Parameter #1 $var of function count expects array|Countable, array|false given.', + 'Parameter #1 $var of function count expects array|Countable, array|false given.', 16, ], ]; } else { $errors = [ [ - 'Parameter #1 $value of function count expects array|Countable, array|false given.', + 'Parameter #1 $value of function count expects array|Countable, array|false given.', 8, ], [ - 'Parameter #1 $value of function count expects array|Countable, array|false given.', + 'Parameter #1 $value of function count expects array|Countable, array|false given.', 16, ], ]; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index ed96439687b..88b0eb949f2 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1949,7 +1949,7 @@ public function testOnlyRelevantUnableToResolveTemplateType(): void $this->checkUnionTypes = true; $this->analyse([__DIR__ . '/data/only-relevant-unable-to-resolve-template-type.php'], [ [ - 'Parameter #1 $a of method OnlyRelevantUnableToResolve\Foo::doBaz() expects array, int given.', + 'Parameter #1 $a of method OnlyRelevantUnableToResolve\Foo::doBaz() expects array, int given.', 41, ], [ @@ -2260,11 +2260,11 @@ public function testLiteralString(): void 58, ], [ - 'Parameter #1 $a of method LiteralStringMethod\Foo::requireArrayOfLiteralStrings() expects array, array given.', + 'Parameter #1 $a of method LiteralStringMethod\Foo::requireArrayOfLiteralStrings() expects array, array given.', 60, ], [ - 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, array given.', + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, array given.', 65, ], [ diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index c3842d461b1..fe23c44a832 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -546,15 +546,15 @@ public function testDiscussion7004(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/discussion-7004.php'], [ [ - 'Parameter #1 $data of static method Discussion7004\Foo::fromArray1() expects array, array given.', + 'Parameter #1 $data of static method Discussion7004\Foo::fromArray1() expects array, array given.', 46, ], [ - 'Parameter #1 $data of static method Discussion7004\Foo::fromArray2() expects array{array{newsletterName: string, subscriberCount: int}}, array given.', + 'Parameter #1 $data of static method Discussion7004\Foo::fromArray2() expects array{array{newsletterName: string, subscriberCount: int}}, array given.', 47, ], [ - 'Parameter #1 $data of static method Discussion7004\Foo::fromArray3() expects array{newsletterName: string, subscriberCount: int}, array given.', + 'Parameter #1 $data of static method Discussion7004\Foo::fromArray3() expects array{newsletterName: string, subscriberCount: int}, array given.', 48, ], ]); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 4c19971d5e8..5ebfb238e7f 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -901,7 +901,7 @@ public function testMagicSerialization(): void { $this->analyse([__DIR__ . '/data/magic-serialization.php'], [ [ - 'Method MagicSerialization\WrongSignature::__serialize() should return array but returns string.', + 'Method MagicSerialization\WrongSignature::__serialize() should return array but returns string.', 23, ], [ @@ -928,7 +928,7 @@ public function testMagicSignatures(): void 43, ], [ - 'Method MagicSignatures\WrongSignature::__debugInfo() should return array|null but returns string.', + 'Method MagicSignatures\WrongSignature::__debugInfo() should return array|null but returns string.', 47, ], [ diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index f3ea56d12f3..c092304ff15 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -52,19 +52,19 @@ public function testBug10130(): void { $this->analyse([__DIR__ . '/data/bug-10130.php'], [ [ - 'PHPDoc tag @var with type array is not subtype of type array.', + 'PHPDoc tag @var with type array is not subtype of type array.', 14, ], [ - 'PHPDoc tag @var with type array is not subtype of type list.', + 'PHPDoc tag @var with type array is not subtype of type list.', 17, ], [ - 'PHPDoc tag @var with type array is not subtype of type array{id: int}.', + 'PHPDoc tag @var with type array is not subtype of type array{id: int}.', 20, ], [ - 'PHPDoc tag @var with type array is not subtype of type list.', + 'PHPDoc tag @var with type array is not subtype of type list.', 23, ], ]); diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 155c0d6ea49..8a1ce3ba511 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -466,7 +466,7 @@ public function dataReportWrongType(): iterable 204, ], [ - 'PHPDoc tag @var with type array|null is not subtype of type array{id: int}|null.', + 'PHPDoc tag @var with type array|null is not subtype of type array{id: int}|null.', 235, ], ]]; diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 3616824c7f2..e75b7cec107 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -656,15 +656,15 @@ public function testBug11617(): void $this->checkExplicitMixed = true; $this->analyse([__DIR__ . '/data/bug-11617.php'], [ [ - 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 'Property Bug11617\HelloWorld::$params (array) does not accept array|string>.', 14, ], [ - 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 'Property Bug11617\HelloWorld::$params (array) does not accept array|string>.', 16, ], [ - 'Property Bug11617\HelloWorld::$params (array) does not accept array.', + 'Property Bug11617\HelloWorld::$params (array) does not accept array|string>.', 21, ], ]); diff --git a/tests/PHPStan/Rules/Variables/data/bug-8113.php b/tests/PHPStan/Rules/Variables/data/bug-8113.php index 2c2807e48ff..97b58419413 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-8113.php +++ b/tests/PHPStan/Rules/Variables/data/bug-8113.php @@ -20,25 +20,25 @@ function () { ), ); - assertType('array', $review); + assertType('array>', $review); if ( array_key_exists('review', $review['SurveyInvitation']) && $review['SurveyInvitation']['review'] === null ) { - assertType("array&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); + assertType("array>&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); $review['Review'] = [ 'id' => null, 'text' => null, 'answer' => null, ]; - assertType("non-empty-array&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); + assertType("non-empty-array>&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); unset($review['SurveyInvitation']['review']); - assertType("non-empty-array&hasOffsetValue('Review', array)&hasOffsetValue('SurveyInvitation', array)", $review); + assertType("non-empty-array>&hasOffsetValue('Review', array)&hasOffsetValue('SurveyInvitation', array)", $review); } - assertType('array', $review); + assertType('array>', $review); if (array_key_exists('User', $review['Review'])) { - assertType("array&hasOffsetValue('Review', array&hasOffset('User'))", $review); + assertType("array>&hasOffsetValue('Review', array&hasOffset('User'))", $review); $review['User'] = $review['Review']['User']; assertType("hasOffsetValue('Review', array&hasOffset('User'))&hasOffsetValue('User', mixed)&non-empty-array", $review); unset($review['Review']['User']); From 4007e653b9a9fe9db7d8c4b9d977ca4175786ff1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Sep 2024 16:11:41 +0200 Subject: [PATCH 0692/3097] Issue bot - let all comments about improved `ArrayType::describe()` through --- issue-bot/src/Console/EvaluateCommand.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..f18941039b9 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,10 +158,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { - if (count($toPost) > 20) { - $output->writeln('Too many comments to post, something is probably wrong.'); - return 1; - } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 2de40d19b19b150eb4c05171e24d9377fc529c1a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 17:40:02 +0200 Subject: [PATCH 0693/3097] Revert "Issue bot - let all comments about improved `ArrayType::describe()` through" This reverts commit 4007e653b9a9fe9db7d8c4b9d977ca4175786ff1. --- issue-bot/src/Console/EvaluateCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index f18941039b9..0f8d05a8d88 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -158,6 +158,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $postComments = (bool) $input->getOption('post-comments'); if ($postComments) { + if (count($toPost) > 20) { + $output->writeln('Too many comments to post, something is probably wrong.'); + return 1; + } foreach ($toPost as ['issue' => $issue, 'hash' => $hash, 'users' => $users, 'diff' => $diff, 'details' => $details]) { $text = sprintf( "%s After [the latest push in %s](https://github.com/phpstan/phpstan-src/compare/%s...%s), PHPStan now reports different result with your [code snippet](https://phpstan.org/r/%s):\n\n```diff\n%s```", From 856208a53b3b1c4eada01f7e4892573d756cdf3f Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:28:48 +0000 Subject: [PATCH 0694/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 866afda4257..5b816f87463 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.25.0.21", "phpstan/php-8-stubs": "0.3.111", - "phpstan/phpdoc-parser": "1.32.0", + "phpstan/phpdoc-parser": "1.33.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index aae4ed9cd03..91ca3e19318 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a76e95ec9a6020d405d9495cb67cf0ce", + "content-hash": "07635a5e2bdedfa64a641735fba09971", "packages": [ { "name": "clue/ndjson-react", @@ -2288,16 +2288,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -2329,9 +2329,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "psr/container", From 107a7e38e2173d8b8b4f18e5d592fc8ead02b96a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 13:35:47 +0200 Subject: [PATCH 0695/3097] Support for non-empty-array and non-empty-list array shape kind --- src/PhpDoc/TypeNodeResolver.php | 12 +++++++++++- .../Analyser/nsrt/array-shape-list-optional.php | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 6e483222d7b..43f93cb8f52 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1016,10 +1016,20 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name } $arrayType = $builder->getArray(); - if ($typeNode->kind === ArrayShapeNode::KIND_LIST) { + if (in_array($typeNode->kind, [ + ArrayShapeNode::KIND_LIST, + ArrayShapeNode::KIND_NON_EMPTY_LIST, + ], true)) { $arrayType = AccessoryArrayListType::intersectWith($arrayType); } + if (in_array($typeNode->kind, [ + ArrayShapeNode::KIND_NON_EMPTY_ARRAY, + ArrayShapeNode::KIND_NON_EMPTY_LIST, + ], true)) { + $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + return $arrayType; } diff --git a/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php b/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php index 0eaa4471d29..f059d14c4df 100644 --- a/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php +++ b/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php @@ -9,16 +9,22 @@ class Foo /** * @param list{0: string, 1: int, 2?: string, 3?: string} $valid1 + * @param non-empty-list{0: string, 1: int, 2?: string, 3?: string} $valid2 + * @param non-empty-array{0?: string, 1?: int, 2?: string, 3?: string} $valid3 * @param list{0: string, 1: int, 2?: string, 4?: string} $invalid1 * @param list{0: string, 1: int, 2?: string, foo?: string} $invalid2 */ public function doFoo( $valid1, + $valid2, + $valid3, $invalid1, $invalid2 ): void { assertType('array{0: string, 1: int, 2?: string, 3?: string}&list', $valid1); + assertType('array{0: string, 1: int, 2?: string, 3?: string}&list', $valid2); + assertType('array{0?: string, 1?: int, 2?: string, 3?: string}&non-empty-array', $valid3); assertType('*NEVER*', $invalid1); assertType('*NEVER*', $invalid2); } From 96bd303c6c818b8ddd270ebc6101a585849251e4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 13:45:56 +0200 Subject: [PATCH 0696/3097] Fix build --- changelog-generator/phpstan.neon | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog-generator/phpstan.neon b/changelog-generator/phpstan.neon index d187d34c968..a1fee9d3a70 100644 --- a/changelog-generator/phpstan.neon +++ b/changelog-generator/phpstan.neon @@ -11,4 +11,6 @@ parameters: paths: - src - run.php - checkGenericClassInNonGenericObjectType: false + ignoreErrors: + - + identifier: missingType.generics From 67fbfaee6585c2d47485dc2a159ee76d3ed02b35 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 12 Oct 2024 13:09:35 +0200 Subject: [PATCH 0697/3097] Refactor IntersectionType::describe() --- phpstan-baseline.neon | 4 +- src/Type/IntersectionType.php | 152 ++++++++---- .../Analyser/LegacyNodeScopeResolverTest.php | 8 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 4 +- tests/PHPStan/Analyser/data/param-out.php | 2 +- tests/PHPStan/Analyser/nsrt/array-chunk.php | 8 +- .../Analyser/nsrt/array-column-php82.php | 6 +- tests/PHPStan/Analyser/nsrt/array-flip.php | 10 +- .../Analyser/nsrt/array-intersect-key.php | 6 +- .../nsrt/array-is-list-type-specifying.php | 8 +- tests/PHPStan/Analyser/nsrt/array-reverse.php | 2 +- .../nsrt/array-shape-list-optional.php | 8 +- tests/PHPStan/Analyser/nsrt/array-slice.php | 6 +- .../Analyser/nsrt/assert-intersected.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-10721.php | 50 ++-- .../PHPStan/Analyser/nsrt/bug-11518-types.php | 6 +- tests/PHPStan/Analyser/nsrt/bug-2112.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-2911.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4099.php | 6 +- tests/PHPStan/Analyser/nsrt/bug-4117.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4398.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4565.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-4708.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6859.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-7805.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-9734.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-9985.php | 2 +- .../Analyser/nsrt/composer-array-bug.php | 4 +- .../Analyser/nsrt/conditional-vars.php | 4 +- ...namic-return-type-extension-regression.php | 4 +- .../Analyser/nsrt/has-offset-type-bug.php | 4 +- tests/PHPStan/Analyser/nsrt/list-count.php | 12 +- tests/PHPStan/Analyser/nsrt/list-type.php | 16 +- .../PHPStan/Analyser/nsrt/non-empty-array.php | 2 +- tests/PHPStan/Analyser/nsrt/shuffle.php | 44 ++-- tests/PHPStan/Analyser/nsrt/sort.php | 6 +- .../Rules/Comparison/data/bug-4708.php | 2 +- .../PHPStan/Rules/Functions/data/bug-3931.php | 2 +- .../PHPStan/Rules/Functions/data/bug-7156.php | 4 +- .../Rules/Methods/ReturnTypeRuleTest.php | 2 +- .../PHPStan/Rules/Variables/data/bug-3391.php | 4 +- .../PHPStan/Rules/Variables/data/bug-7417.php | 4 +- .../PHPStan/Rules/Variables/data/bug-8113.php | 12 +- tests/PHPStan/Type/IntersectionTypeTest.php | 217 +++++++++++++++++- tests/PHPStan/Type/TypeCombinatorTest.php | 24 +- tests/PHPStan/Type/TypeToPhpDocNodeTest.php | 121 +++++++++- 46 files changed, 601 insertions(+), 199 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a652b6a8364..122462ff008 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1314,7 +1314,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 1 + count: 3 path: src/Type/IntersectionType.php - @@ -1332,7 +1332,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 1 + count: 3 path: src/Type/IntersectionType.php - diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 2dec6cc4569..496910eab38 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode; @@ -45,7 +46,6 @@ use function ksort; use function md5; use function sprintf; -use function str_starts_with; use function strcasecmp; use function strlen; use function substr; @@ -347,6 +347,10 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) $nonEmptyStr = false; $nonFalsyStr = false; + $isList = $this->isList()->yes(); + $isArray = $this->isArray()->yes(); + $isNonEmptyArray = $this->isIterableAtLeastOnce()->yes(); + $describedTypes = []; foreach ($this->getSortedTypes() as $i => $type) { if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType @@ -379,10 +383,45 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) $skipTypeNames[] = 'string'; continue; } - if ($type instanceof NonEmptyArrayType || $type instanceof AccessoryArrayListType) { - $typesToDescribe[$i] = $type; - $skipTypeNames[] = 'array'; - continue; + if ($isList || $isArray) { + if ($type instanceof ArrayType) { + $keyType = $type->getKeyType(); + $valueType = $type->getItemType(); + if ($isList) { + $isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed(); + $valueTypeDescription = ''; + if (!$isMixedValueType) { + $valueTypeDescription = sprintf('<%s>', $valueType->describe($level)); + } + + $describedTypes[$i] = ($isNonEmptyArray ? 'non-empty-list' : 'list') . $valueTypeDescription; + } else { + $isMixedKeyType = $keyType instanceof MixedType && $keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$keyType->isExplicitMixed(); + $isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed(); + $typeDescription = ''; + if (!$isMixedKeyType) { + $typeDescription = sprintf('<%s, %s>', $keyType->describe($level), $valueType->describe($level)); + } elseif (!$isMixedValueType) { + $typeDescription = sprintf('<%s>', $valueType->describe($level)); + } + + $describedTypes[$i] = ($isNonEmptyArray ? 'non-empty-array' : 'array') . $typeDescription; + } + continue; + } elseif ($type instanceof ConstantArrayType) { + $description = $type->describe($level); + $descriptionWithoutKind = substr($description, strlen('array')); + $begin = $isList ? 'list' : 'array'; + if ($isNonEmptyArray && !$type->isIterableAtLeastOnce()->yes()) { + $begin = 'non-empty-' . $begin; + } + + $describedTypes[$i] = $begin . $descriptionWithoutKind; + continue; + } + if ($type instanceof NonEmptyArrayType || $type instanceof AccessoryArrayListType) { + continue; + } } if ($type instanceof CallableType && $type->isCommonCallable()) { @@ -404,7 +443,6 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) $typesToDescribe[$i] = $type; } - $describedTypes = []; foreach ($baseTypes as $i => $type) { $typeDescription = $type->describe($level); @@ -418,36 +456,6 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) } } - if ( - str_starts_with($typeDescription, 'array<') - && in_array('array', $skipTypeNames, true) - ) { - $nonEmpty = false; - $typeName = 'array'; - foreach ($typesToDescribe as $j => $typeToDescribe) { - if ( - $typeToDescribe instanceof AccessoryArrayListType - && substr($typeDescription, 0, strlen('array, ')) === 'array, ' - ) { - $typeName = 'list'; - $typeDescription = 'array<' . substr($typeDescription, strlen('array, ')); - } elseif ($typeToDescribe instanceof NonEmptyArrayType) { - $nonEmpty = true; - } else { - continue; - } - - unset($typesToDescribe[$j]); - } - - if ($nonEmpty) { - $typeName = 'non-empty-' . $typeName; - } - - $describedTypes[$i] = $typeName . '<' . substr($typeDescription, strlen('array<')); - continue; - } - if (in_array($typeDescription, $skipTypeNames, true)) { continue; } @@ -1139,6 +1147,10 @@ public function toPhpDocNode(): TypeNode $nonEmptyStr = false; $nonFalsyStr = false; + $isList = $this->isList()->yes(); + $isArray = $this->isArray()->yes(); + $isNonEmptyArray = $this->isIterableAtLeastOnce()->yes(); + $describedTypes = []; foreach ($this->getSortedTypes() as $i => $type) { if ($type instanceof AccessoryNonEmptyStringType @@ -1168,11 +1180,70 @@ public function toPhpDocNode(): TypeNode $skipTypeNames[] = 'string'; continue; } - if ($type instanceof NonEmptyArrayType || $type instanceof AccessoryArrayListType) { - $typesToDescribe[$i] = $type; - $skipTypeNames[] = 'array'; - continue; + + if ($isList || $isArray) { + if ($type instanceof ArrayType) { + $keyType = $type->getKeyType(); + $valueType = $type->getItemType(); + if ($isList) { + $isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed(); + $identifierTypeNode = new IdentifierTypeNode($isNonEmptyArray ? 'non-empty-list' : 'list'); + if (!$isMixedValueType) { + $describedTypes[$i] = new GenericTypeNode($identifierTypeNode, [ + $valueType->toPhpDocNode(), + ]); + } else { + $describedTypes[$i] = $identifierTypeNode; + } + } else { + $isMixedKeyType = $keyType instanceof MixedType && $keyType->describe(VerbosityLevel::precise()) === 'mixed' && !$keyType->isExplicitMixed(); + $isMixedValueType = $valueType instanceof MixedType && $valueType->describe(VerbosityLevel::precise()) === 'mixed' && !$valueType->isExplicitMixed(); + $identifierTypeNode = new IdentifierTypeNode($isNonEmptyArray ? 'non-empty-array' : 'array'); + if (!$isMixedKeyType) { + $describedTypes[$i] = new GenericTypeNode($identifierTypeNode, [ + $keyType->toPhpDocNode(), + $valueType->toPhpDocNode(), + ]); + } elseif (!$isMixedValueType) { + $describedTypes[$i] = new GenericTypeNode($identifierTypeNode, [ + $valueType->toPhpDocNode(), + ]); + } else { + $describedTypes[$i] = $identifierTypeNode; + } + } + continue; + } elseif ($type instanceof ConstantArrayType) { + $constantArrayTypeNode = $type->toPhpDocNode(); + if ($constantArrayTypeNode instanceof ArrayShapeNode) { + $newKind = $constantArrayTypeNode->kind; + if ($isList) { + if ($isNonEmptyArray && !$type->isIterableAtLeastOnce()->yes()) { + $newKind = ArrayShapeNode::KIND_NON_EMPTY_LIST; + } else { + $newKind = ArrayShapeNode::KIND_LIST; + } + } elseif ($isNonEmptyArray && !$type->isIterableAtLeastOnce()->yes()) { + $newKind = ArrayShapeNode::KIND_NON_EMPTY_ARRAY; + } + + if ($newKind !== $constantArrayTypeNode->kind) { + if ($constantArrayTypeNode->sealed) { + $constantArrayTypeNode = ArrayShapeNode::createSealed($constantArrayTypeNode->items, $newKind); + } else { + $constantArrayTypeNode = ArrayShapeNode::createUnsealed($constantArrayTypeNode->items, $constantArrayTypeNode->unsealedType, $newKind); + } + } + + $describedTypes[$i] = $constantArrayTypeNode; + continue; + } + } + if ($type instanceof NonEmptyArrayType || $type instanceof AccessoryArrayListType) { + continue; + } } + if (!$type instanceof AccessoryType) { $baseTypes[$i] = $type; continue; @@ -1186,7 +1257,6 @@ public function toPhpDocNode(): TypeNode $typesToDescribe[$i] = $type; } - $describedTypes = []; foreach ($baseTypes as $i => $type) { $typeNode = $type->toPhpDocNode(); if ($typeNode instanceof GenericTypeNode && $typeNode->type->name === 'array') { diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index d24d24338d5..e97a5f4a063 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -4846,7 +4846,7 @@ public function dataArrayFunctions(): array 'array_pop($stringKeys)', ], [ - 'array&hasOffsetValue(\'baz\', stdClass)', + 'non-empty-array&hasOffsetValue(\'baz\', stdClass)', '$stdClassesWithIsset', ], [ @@ -8077,7 +8077,7 @@ public function dataArrayKeysInBranches(): array '$array', ], [ - 'array&hasOffsetValue(\'key\', mixed)', + 'non-empty-array&hasOffsetValue(\'key\', mixed)', '$generalArray', ], [ @@ -8563,11 +8563,11 @@ public function dataIsset(): array '$mixedIsset', ], [ - 'array&hasOffset(\'a\')', + 'non-empty-array&hasOffset(\'a\')', '$mixedArrayKeyExists', ], [ - 'array&hasOffsetValue(\'a\', int)', + 'non-empty-array&hasOffsetValue(\'a\', int)', '$integers', ], [ diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 99cbb8db714..36bf122d87a 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1064,7 +1064,7 @@ public function dataCondition(): iterable new Arg(new Variable('array')), ]), [ - '$array' => 'array&hasOffset(\'foo\')', + '$array' => 'non-empty-array&hasOffset(\'foo\')', ], [ '$array' => '~hasOffset(\'foo\')', @@ -1112,7 +1112,7 @@ public function dataCondition(): iterable new Arg(new Variable('array')), ]), [ - '$array' => 'array&hasOffset(\'foo\')', + '$array' => 'non-empty-array&hasOffset(\'foo\')', ], [ '$array' => '~hasOffset(\'foo\')', diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index d172b358e15..36d7837034c 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -240,7 +240,7 @@ function foo16() { function fooShuffle() { $array = ["foo" => 123, "bar" => 456]; shuffle($array); - assertType('non-empty-array<0|1, 123|456>&list', $array); + assertType('non-empty-list<123|456>', $array); $emptyArray = []; shuffle($emptyArray); diff --git a/tests/PHPStan/Analyser/nsrt/array-chunk.php b/tests/PHPStan/Analyser/nsrt/array-chunk.php index c0b79ffbae8..cedb50ddb78 100644 --- a/tests/PHPStan/Analyser/nsrt/array-chunk.php +++ b/tests/PHPStan/Analyser/nsrt/array-chunk.php @@ -60,8 +60,8 @@ public function chunkUnionTypeLength(array $arr, $positiveRange, $positiveUnion) * @param int<50, max> $bigger50 */ public function lengthIntRanges(array $arr, int $positiveInt, int $bigger50) { - assertType('list>', array_chunk($arr, $positiveInt)); - assertType('list>', array_chunk($arr, $bigger50)); + assertType('list', array_chunk($arr, $positiveInt)); + assertType('list', array_chunk($arr, $bigger50)); } /** @@ -78,11 +78,11 @@ function testLimits(array $arr, int $oneToFour, int $tooBig) { public function offsets(array $arr, array $map): void { if (array_key_exists('foo', $arr)) { - assertType('non-empty-list>', array_chunk($arr, 2)); + assertType('non-empty-list', array_chunk($arr, 2)); assertType('non-empty-list', array_chunk($arr, 2, true)); } if (array_key_exists('foo', $arr) && $arr['foo'] === 'bar') { - assertType('non-empty-list>', array_chunk($arr, 2)); + assertType('non-empty-list', array_chunk($arr, 2)); assertType('non-empty-list', array_chunk($arr, 2, true)); } diff --git a/tests/PHPStan/Analyser/nsrt/array-column-php82.php b/tests/PHPStan/Analyser/nsrt/array-column-php82.php index 62350f59927..07dc05f3999 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column-php82.php +++ b/tests/PHPStan/Analyser/nsrt/array-column-php82.php @@ -175,7 +175,7 @@ public function testImprecise5(array $array): void assertType('list', array_column($array, 'nodeName')); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -187,7 +187,7 @@ public function testObjects1(array $array): void assertType('non-empty-list', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -199,7 +199,7 @@ public function testObjects2(array $array): void assertType('array{string}', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); diff --git a/tests/PHPStan/Analyser/nsrt/array-flip.php b/tests/PHPStan/Analyser/nsrt/array-flip.php index 2f02f1e733f..ef91f40c365 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip.php @@ -71,25 +71,25 @@ function foo8($mixed) function foo10(array $array) { if (array_key_exists('foo', $array)) { - assertType('array&hasOffset(\'foo\')', $array); + assertType('non-empty-array&hasOffset(\'foo\')', $array); assertType('array', array_flip($array)); } if (array_key_exists('foo', $array) && is_int($array['foo'])) { - assertType("array&hasOffsetValue('foo', int)", $array); + assertType("non-empty-array&hasOffsetValue('foo', int)", $array); assertType('array', array_flip($array)); } if (array_key_exists('foo', $array) && $array['foo'] === 17) { - assertType("array&hasOffsetValue('foo', 17)", $array); - assertType("array&hasOffsetValue(17, 'foo')", array_flip($array)); + assertType("non-empty-array&hasOffsetValue('foo', 17)", $array); + assertType("non-empty-array&hasOffsetValue(17, 'foo')", array_flip($array)); } if ( array_key_exists('foo', $array) && $array['foo'] === 17 && array_key_exists('bar', $array) && $array['bar'] === 17 ) { - assertType("array&hasOffsetValue('bar', 17)&hasOffsetValue('foo', 17)", $array); + assertType("non-empty-array&hasOffsetValue('bar', 17)&hasOffsetValue('foo', 17)", $array); assertType("*NEVER*", array_flip($array)); // this could be array&hasOffsetValue(17, 'bar') according to https://3v4l.org/1TAFk } } diff --git a/tests/PHPStan/Analyser/nsrt/array-intersect-key.php b/tests/PHPStan/Analyser/nsrt/array-intersect-key.php index bf620b508b5..288ba539a23 100644 --- a/tests/PHPStan/Analyser/nsrt/array-intersect-key.php +++ b/tests/PHPStan/Analyser/nsrt/array-intersect-key.php @@ -48,7 +48,7 @@ public function normalArrays(array $arr, array $arr2, array $otherArrs): void assertType('array{}', array_intersect_key($arr, $otherArrs)); if (array_key_exists(17, $arr2)) { - assertType('array<17, string>&hasOffset(17)', array_intersect_key($arr2, [17 => 'bar'])); + assertType('non-empty-array<17, string>&hasOffset(17)', array_intersect_key($arr2, [17 => 'bar'])); /** @var array $otherArrs */ assertType('array', array_intersect_key($arr2, $otherArrs)); /** @var array $otherArrs */ @@ -56,7 +56,7 @@ public function normalArrays(array $arr, array $arr2, array $otherArrs): void } if (array_key_exists(17, $arr2) && $arr2[17] === 'foo') { - assertType("array<17, string>&hasOffsetValue(17, 'foo')", array_intersect_key($arr2, [17 => 'bar'])); + assertType("non-empty-array<17, string>&hasOffsetValue(17, 'foo')", array_intersect_key($arr2, [17 => 'bar'])); /** @var array $otherArrs */ assertType('array', array_intersect_key($arr2, $otherArrs)); /** @var array $otherArrs */ @@ -79,7 +79,7 @@ public function arrayUnpacking(array $arrs, array $arrs2): void /** @param list $arr */ public function list(array $arr, array $otherArrs): void { - assertType('array<0|1, string>&list', array_intersect_key($arr, ['foo', 'bar'])); + assertType('list', array_intersect_key($arr, ['foo', 'bar'])); /** @var array $otherArrs */ assertType('array, string>', array_intersect_key($arr, $otherArrs)); /** @var array $otherArrs */ diff --git a/tests/PHPStan/Analyser/nsrt/array-is-list-type-specifying.php b/tests/PHPStan/Analyser/nsrt/array-is-list-type-specifying.php index aa21248ec65..1b788158d60 100644 --- a/tests/PHPStan/Analyser/nsrt/array-is-list-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/array-is-list-type-specifying.php @@ -6,7 +6,7 @@ function foo(array $foo) { if (array_is_list($foo)) { - assertType('list', $foo); + assertType('list', $foo); } else { assertType('array', $foo); } @@ -14,9 +14,9 @@ function foo(array $foo) { function foo2($foo) { if (array_is_list($foo)) { - assertType('list', $foo); + assertType('list', $foo); } else { - assertType('mixed~list', $foo); + assertType('mixed~list', $foo); } } @@ -49,7 +49,7 @@ function foo4(array $foo) { /** @var array $foo */ if (array_is_list($foo)) { - assertType('list', $foo); + assertType('list', $foo); } else { assertType('array', $foo); } diff --git a/tests/PHPStan/Analyser/nsrt/array-reverse.php b/tests/PHPStan/Analyser/nsrt/array-reverse.php index 6f05e9b9f04..86a3bb72cfe 100644 --- a/tests/PHPStan/Analyser/nsrt/array-reverse.php +++ b/tests/PHPStan/Analyser/nsrt/array-reverse.php @@ -73,7 +73,7 @@ public function mixed(mixed $mixed): void if (array_key_exists('foo', $mixed)) { assertType('non-empty-array', array_reverse($mixed)); - assertType("array&hasOffset('foo')", array_reverse($mixed, true)); + assertType("non-empty-array&hasOffset('foo')", array_reverse($mixed, true)); } } } diff --git a/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php b/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php index f059d14c4df..10049a8317a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php +++ b/tests/PHPStan/Analyser/nsrt/array-shape-list-optional.php @@ -9,7 +9,7 @@ class Foo /** * @param list{0: string, 1: int, 2?: string, 3?: string} $valid1 - * @param non-empty-list{0: string, 1: int, 2?: string, 3?: string} $valid2 + * @param non-empty-list{0?: string, 1?: int, 2?: string, 3?: string} $valid2 * @param non-empty-array{0?: string, 1?: int, 2?: string, 3?: string} $valid3 * @param list{0: string, 1: int, 2?: string, 4?: string} $invalid1 * @param list{0: string, 1: int, 2?: string, foo?: string} $invalid2 @@ -22,9 +22,9 @@ public function doFoo( $invalid2 ): void { - assertType('array{0: string, 1: int, 2?: string, 3?: string}&list', $valid1); - assertType('array{0: string, 1: int, 2?: string, 3?: string}&list', $valid2); - assertType('array{0?: string, 1?: int, 2?: string, 3?: string}&non-empty-array', $valid3); + assertType('list{0: string, 1: int, 2?: string, 3?: string}', $valid1); + assertType('non-empty-list{0?: string, 1?: int, 2?: string, 3?: string}', $valid2); + assertType('non-empty-array{0?: string, 1?: int, 2?: string, 3?: string}', $valid3); assertType('*NEVER*', $invalid1); assertType('*NEVER*', $invalid2); } diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index e2faabd113f..847f535df1f 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -15,7 +15,7 @@ class Foo public function nonEmpty(array $a, array $b, array $c): void { assertType('array', array_slice($a, 1)); - assertType('list', array_slice($b, 1)); + assertType('list', array_slice($b, 1)); assertType('array', array_slice($c, 1)); } @@ -94,11 +94,11 @@ public function offsets(array $arr): void { if (array_key_exists(1, $arr)) { assertType('non-empty-array', array_slice($arr, 1, null, false)); - assertType('hasOffset(1)&non-empty-array', array_slice($arr, 1, null, true)); + assertType('non-empty-array&hasOffset(1)', array_slice($arr, 1, null, true)); } if (array_key_exists(1, $arr) && $arr[1] === 'foo') { assertType('non-empty-array', array_slice($arr, 1, null, false)); - assertType("hasOffsetValue(1, 'foo')&non-empty-array", array_slice($arr, 1, null, true)); + assertType("non-empty-array&hasOffsetValue(1, 'foo')", array_slice($arr, 1, null, true)); } } diff --git a/tests/PHPStan/Analyser/nsrt/assert-intersected.php b/tests/PHPStan/Analyser/nsrt/assert-intersected.php index 17aa63957a3..913f1e9034c 100644 --- a/tests/PHPStan/Analyser/nsrt/assert-intersected.php +++ b/tests/PHPStan/Analyser/nsrt/assert-intersected.php @@ -26,5 +26,5 @@ public function assert(mixed $value): void; function intersection($assert, mixed $value): void { $assert->assert($value); - assertType('non-empty-list', $value); + assertType('non-empty-list', $value); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10721.php b/tests/PHPStan/Analyser/nsrt/bug-10721.php index cf7ce492722..52d511c163a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10721.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10721.php @@ -22,9 +22,9 @@ public function retrieve(?int $limit = 20): array assertType("array{'zib', 'zib 2', 'zeit im bild', 'soko', 'landkrimi', 'tatort'}", $list); shuffle($list); - assertType("non-empty-array<0|1|2|3|4|5, 'landkrimi'|'soko'|'tatort'|'zeit im bild'|'zib'|'zib 2'>&list", $list); + assertType("non-empty-list<'landkrimi'|'soko'|'tatort'|'zeit im bild'|'zib'|'zib 2'>", $list); - assertType("non-empty-array<0|1|2|3|4|5, 'landkrimi'|'soko'|'tatort'|'zeit im bild'|'zib'|'zib 2'>&list", array_slice($list, 0, max($limit, 1))); + assertType("non-empty-list<'landkrimi'|'soko'|'tatort'|'zeit im bild'|'zib'|'zib 2'>", array_slice($list, 0, max($limit, 1))); return array_slice($list, 0, max($limit, 1)); } @@ -37,7 +37,7 @@ public function listVariants(): void assertType("array{2: 'zib', 4: 'zib 2'}", $arr); shuffle($arr); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", $arr); + assertType("non-empty-list<'zib'|'zib 2'>", $arr); $list = [ 'zib', @@ -46,37 +46,37 @@ public function listVariants(): void assertType("array{'zib', 'zib 2'}", $list); shuffle($list); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", $list); + assertType("non-empty-list<'zib'|'zib 2'>", $list); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2)); + assertType("list<'zib'|'zib 2'>", array_slice($list, -1)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0)); + assertType("list<'zib'|'zib 2'>", array_slice($list, 1)); // could be non-empty-array + assertType("list<'zib'|'zib 2'>", array_slice($list, 2)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 1)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 1)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 1)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 1)); + assertType("list<'zib'|'zib 2'>", array_slice($list, -1, 1)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0, 1)); + assertType("list<'zib'|'zib 2'>", array_slice($list, 1, 1)); // could be non-empty-array + assertType("list<'zib'|'zib 2'>", array_slice($list, 2, 1)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 2)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 2)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 2)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 2)); + assertType("list<'zib'|'zib 2'>", array_slice($list, -1, 2)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0, 2)); + assertType("list<'zib'|'zib 2'>", array_slice($list, 1, 2)); // could be non-empty-array + assertType("list<'zib'|'zib 2'>", array_slice($list, 2, 2)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 3)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 3)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 3)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 3)); + assertType("list<'zib'|'zib 2'>", array_slice($list, -1, 3)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0, 3)); + assertType("list<'zib'|'zib 2'>", array_slice($list, 1, 3)); // could be non-empty-array + assertType("list<'zib'|'zib 2'>", array_slice($list, 2, 3)); assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, -1, 3, true)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 3, true)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0, 3, true)); assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, 1, 3, true)); // could be non-empty-array assertType("array<0|1, 'zib'|'zib 2'>", array_slice($list, 2, 3, true)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, -1, 3, false)); - assertType("non-empty-array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 0, 3, false)); - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 1, 3, false)); // could be non-empty-array - assertType("array<0|1, 'zib'|'zib 2'>&list", array_slice($list, 2, 3, false)); + assertType("list<'zib'|'zib 2'>", array_slice($list, -1, 3, false)); + assertType("non-empty-list<'zib'|'zib 2'>", array_slice($list, 0, 3, false)); + assertType("list<'zib'|'zib 2'>", array_slice($list, 1, 3, false)); // could be non-empty-array + assertType("list<'zib'|'zib 2'>", array_slice($list, 2, 3, false)); } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-11518-types.php b/tests/PHPStan/Analyser/nsrt/bug-11518-types.php index 4f66f4f0af8..19d5aeb15f9 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11518-types.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11518-types.php @@ -12,12 +12,12 @@ function blah(array $a): array { if (!array_key_exists('thing', $a)) { $a['thing'] = 'bla'; - assertType('hasOffsetValue(\'thing\', \'bla\')&non-empty-array', $a); + assertType('non-empty-array&hasOffsetValue(\'thing\', \'bla\')', $a); } else { - assertType('array&hasOffset(\'thing\')', $a); + assertType('non-empty-array&hasOffset(\'thing\')', $a); } - assertType('array&hasOffsetValue(\'thing\', mixed)', $a); + assertType('non-empty-array&hasOffsetValue(\'thing\', mixed)', $a); return $a; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-2112.php b/tests/PHPStan/Analyser/nsrt/bug-2112.php index a7d33d15382..65634c415b2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-2112.php +++ b/tests/PHPStan/Analyser/nsrt/bug-2112.php @@ -19,7 +19,7 @@ public function doBar(): void $foos[0] = null; assertType('null', $foos[0]); - assertType('hasOffsetValue(0, null)&non-empty-array', $foos); + assertType('non-empty-array&hasOffsetValue(0, null)', $foos); } /** @return self[] */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-2911.php b/tests/PHPStan/Analyser/nsrt/bug-2911.php index 1d75efdcbef..3cfbd308f11 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-2911.php +++ b/tests/PHPStan/Analyser/nsrt/bug-2911.php @@ -134,6 +134,6 @@ private function getResultSettings(array $settings): array function foo(array $array): void { $array['bar'] = 'string'; - assertType("hasOffsetValue('bar', 'string')&non-empty-array", $array); + assertType("non-empty-array&hasOffsetValue('bar', 'string')", $array); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4099.php b/tests/PHPStan/Analyser/nsrt/bug-4099.php index 0a8d1b4b484..5e5eb30ca2e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4099.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4099.php @@ -22,20 +22,20 @@ function arrayHint(array $arr): void throw new \Exception('no key "key" found.'); } assertType('array{key: array{inner: mixed}}', $arr); - assertNativeType('array&hasOffset(\'key\')', $arr); + assertNativeType('non-empty-array&hasOffset(\'key\')', $arr); assertType('array{inner: mixed}', $arr['key']); assertNativeType('mixed', $arr['key']); if (!array_key_exists('inner', $arr['key'])) { assertType('*NEVER*', $arr); - assertNativeType('array&hasOffset(\'key\')', $arr); + assertNativeType('non-empty-array&hasOffset(\'key\')', $arr); assertType('*NEVER*', $arr['key']); assertNativeType("mixed~hasOffset('inner')", $arr['key']); throw new \Exception('need key.inner'); } assertType('array{key: array{inner: mixed}}', $arr); - assertNativeType('array&hasOffset(\'key\')', $arr); + assertNativeType('non-empty-array&hasOffset(\'key\')', $arr); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4117.php b/tests/PHPStan/Analyser/nsrt/bug-4117.php index 510df695f12..14732b77b6b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4117.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4117.php @@ -34,7 +34,7 @@ public function broken(int $key) if ($item) { assertType("T of mixed~(0|0.0|''|'0'|array{}|false|null) (class Bug4117Types\GenericList, argument)", $item); } else { - assertType("(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(array{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|null", $item); + assertType("(T of mixed~null (class Bug4117Types\GenericList, argument)&false)|(0.0&T of mixed~null (class Bug4117Types\GenericList, argument))|(0&T of mixed~null (class Bug4117Types\GenericList, argument))|(list{}&T of mixed~null (class Bug4117Types\GenericList, argument))|(''&T of mixed~null (class Bug4117Types\GenericList, argument))|('0'&T of mixed~null (class Bug4117Types\GenericList, argument))|null", $item); } assertType('T of mixed~null (class Bug4117Types\GenericList, argument)|null', $item); diff --git a/tests/PHPStan/Analyser/nsrt/bug-4398.php b/tests/PHPStan/Analyser/nsrt/bug-4398.php index 297484c2cf4..6fd566cd620 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4398.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4398.php @@ -14,5 +14,5 @@ function (array $meters): void { assertType('array', array_reverse()); assertType('non-empty-array', array_reverse($meters)); assertType('non-empty-list<(int|string)>', array_keys($meters)); - assertType('non-empty-list', array_values($meters)); + assertType('non-empty-list', array_values($meters)); }; diff --git a/tests/PHPStan/Analyser/nsrt/bug-4565.php b/tests/PHPStan/Analyser/nsrt/bug-4565.php index af941f8098f..55a1c372a58 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4565.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4565.php @@ -10,9 +10,9 @@ function test(array $variables) { if (!empty($variables['button'])) { assertType('non-empty-array', $attributes); $attributes['type'] = 'button'; - assertType("hasOffsetValue('type', 'button')&non-empty-array", $attributes); + assertType("non-empty-array&hasOffsetValue('type', 'button')", $attributes); unset($attributes['href']); - assertType("array&hasOffsetValue('type', 'button')", $attributes); + assertType("non-empty-array&hasOffsetValue('type', 'button')", $attributes); } assertType('array', $attributes); return $attributes; diff --git a/tests/PHPStan/Analyser/nsrt/bug-4708.php b/tests/PHPStan/Analyser/nsrt/bug-4708.php index d6bae28afcb..b6f23027223 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4708.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4708.php @@ -57,7 +57,7 @@ function GetASCConfig() } else { - assertType("array&hasOffsetValue('bsw', string)", $result); + assertType("non-empty-array&hasOffsetValue('bsw', string)", $result); $result['bsw'] = (int) $result['bsw']; assertType("non-empty-array&hasOffsetValue('bsw', int)", $result); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6859.php b/tests/PHPStan/Analyser/nsrt/bug-6859.php index 0f6d43963e6..56cd257c5e8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6859.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6859.php @@ -30,7 +30,7 @@ public function keys($body) public function values($body) { if (array_key_exists("someParam", $body)) { - assertType('non-empty-list', array_values($body)); + assertType('non-empty-list', array_values($body)); } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7805.php b/tests/PHPStan/Analyser/nsrt/bug-7805.php index 859b504e504..ec9464ebd32 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7805.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7805.php @@ -14,7 +14,7 @@ function foo(array $params) assertNativeType('array', $params); if (array_key_exists('help', $params)) { assertType('array{help: null}', $params); - assertNativeType("array&hasOffset('help')", $params); + assertNativeType("non-empty-array&hasOffset('help')", $params); unset($params['help']); assertType('array{}', $params); diff --git a/tests/PHPStan/Analyser/nsrt/bug-9734.php b/tests/PHPStan/Analyser/nsrt/bug-9734.php index 353b1d91f92..1628ad6859d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9734.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9734.php @@ -15,7 +15,7 @@ class Foo public function doFoo(array $a): void { if (array_is_list($a)) { - assertType('list', $a); + assertType('list', $a); } else { assertType('array', $a); // could be non-empty-array } @@ -38,7 +38,7 @@ public function doFoo2(): void public function doFoo3(array $a): void { if (array_is_list($a)) { - assertType('non-empty-list', $a); + assertType('non-empty-list', $a); } else { assertType('non-empty-array', $a); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-9985.php b/tests/PHPStan/Analyser/nsrt/bug-9985.php index edbfebffc5f..09a7ad92eac 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9985.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9985.php @@ -20,6 +20,6 @@ function (): void { assertType('array{}|array{a?: true, b: true}|array{a?: true, c?: true}', $warnings); if (!empty($warnings)) { - assertType('array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', $warnings); + assertType('array{a?: true, b: true}|non-empty-array{a?: true, c?: true}', $warnings); } }; diff --git a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php index fbbcff38a81..354577f0987 100644 --- a/tests/PHPStan/Analyser/nsrt/composer-array-bug.php +++ b/tests/PHPStan/Analyser/nsrt/composer-array-bug.php @@ -44,14 +44,14 @@ public function doFoo(): void } } - assertType("array&hasOffsetValue('authors', mixed)", $this->config); + assertType("non-empty-array&hasOffsetValue('authors', mixed)", $this->config); assertType("mixed", $this->config['authors']); if (empty($this->config['authors'])) { unset($this->config['authors']); assertType("array", $this->config); } else { - assertType("array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config); + assertType("non-empty-array&hasOffsetValue('authors', mixed~(0|0.0|''|'0'|array{}|false|null))", $this->config); } assertType('array', $this->config); diff --git a/tests/PHPStan/Analyser/nsrt/conditional-vars.php b/tests/PHPStan/Analyser/nsrt/conditional-vars.php index 6d86c880149..e76b112f4de 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-vars.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-vars.php @@ -12,7 +12,7 @@ public function conditionalVarInTernary(array $innerHits): void if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { assertType('array', $innerHits); $x = array_key_exists('nearest_premise', $innerHits) - ? assertType("array&hasOffset('nearest_premise')", $innerHits) + ? assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits) : assertType('array', $innerHits); assertType('array', $innerHits); @@ -25,7 +25,7 @@ public function conditionalVarInIf(array $innerHits): void if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { assertType('array', $innerHits); if (array_key_exists('nearest_premise', $innerHits)) { - assertType("array&hasOffset('nearest_premise')", $innerHits); + assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits); } else { assertType('array', $innerHits); } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var-dynamic-return-type-extension-regression.php b/tests/PHPStan/Analyser/nsrt/filter-var-dynamic-return-type-extension-regression.php index 69fc99f6139..172fa40b4f8 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var-dynamic-return-type-extension-regression.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var-dynamic-return-type-extension-regression.php @@ -46,9 +46,9 @@ public function test() } assertType('array{default?: PHPStan\Type\Type, range: PHPStan\Type\Type}', $otherTypes); } - assertType('array{default?: PHPStan\Type\Type, range?: PHPStan\Type\Type}&non-empty-array', $otherTypes); + assertType('non-empty-array{default?: PHPStan\Type\Type, range?: PHPStan\Type\Type}', $otherTypes); if ($exactType !== null) { - assertType('array{default?: PHPStan\Type\Type, range?: PHPStan\Type\Type}&non-empty-array', $otherTypes); + assertType('non-empty-array{default?: PHPStan\Type\Type, range?: PHPStan\Type\Type}', $otherTypes); unset($otherTypes['default']); assertType('array{range?: PHPStan\Type\Type}', $otherTypes); } diff --git a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php index d1e8fd92a02..eacfb06af68 100644 --- a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php +++ b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php @@ -65,7 +65,7 @@ public function testIsset($range): void { assertType("array{}|array{min?: bool|float|int|string|null, max?: bool|float|int|string|null}", $range); if (isset($range['min']) || isset($range['max'])) { - assertType("array{min?: bool|float|int|string|null, max?: bool|float|int|string|null}&non-empty-array", $range); + assertType("non-empty-array{min?: bool|float|int|string|null, max?: bool|float|int|string|null}", $range); } else { assertType("array{}|array{min?: bool|float|int|string|null, max?: bool|float|int|string|null}", $range); } @@ -144,7 +144,7 @@ public function doBar(array $a, int $i) public function doFoo2(array $a) { if (is_int($a['a'])) { - assertType("array&hasOffsetValue('a', *NEVER*)", $a); + assertType("non-empty-array&hasOffsetValue('a', *NEVER*)", $a); } } diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index caf0a17c87a..c51ea31efcd 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -351,25 +351,25 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t if (count($row) >= $twoOrThree) { assertType('array{0: int, 1: string|null, 2?: int|null}', $row); } else { - assertType('(array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}&list)|array{string}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } if (count($row) >= $tenOrEleven) { assertType('*NEVER*', $row); } else { - assertType('(array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}&list)|array{string}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } if (count($row) >= $twoOrMore) { - assertType('array{0: int, 1: string|null, 2?: int|null, 3?: float|null}&list', $row); + assertType('list{0: int, 1: string|null, 2?: int|null, 3?: float|null}', $row); } else { - assertType('(array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}&list)|array{string}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } if (count($row) >= $maxThree) { - assertType('(array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}&list)|array{string}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } else { - assertType('array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}&list', $row); + assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } } diff --git a/tests/PHPStan/Analyser/nsrt/list-type.php b/tests/PHPStan/Analyser/nsrt/list-type.php index a80e8b066dc..647d44f7184 100644 --- a/tests/PHPStan/Analyser/nsrt/list-type.php +++ b/tests/PHPStan/Analyser/nsrt/list-type.php @@ -9,19 +9,19 @@ class Foo /** @param list $list */ public function directAssertion($list): void { - assertType('list', $list); + assertType('list', $list); } /** @param list $list */ public function directAssertionParamHint(array $list): void { - assertType('list', $list); + assertType('list', $list); } /** @param list $list */ public function directAssertionNullableParamHint(array $list = null): void { - assertType('list|null', $list); + assertType('list|null', $list); } /** @param list<\DateTime> $list */ @@ -37,7 +37,7 @@ public function withoutGenerics(): void $list[] = '1'; $list[] = true; $list[] = new \stdClass(); - assertType('non-empty-list', $list); + assertType('non-empty-list', $list); } @@ -83,17 +83,17 @@ public function withFullListFunctionality(): void // These won't output errors for now but should when list type will be fully implemented /** @var list $list */ $list = []; - assertType('list', $list); + assertType('list', $list); $list[] = '1'; - assertType('non-empty-list', $list); + assertType('non-empty-list', $list); $list[] = '2'; - assertType('non-empty-list', $list); + assertType('non-empty-list', $list); unset($list[0]);//break list behaviour assertType('array, mixed>', $list); /** @var list $list2 */ $list2 = []; - assertType('list', $list2); + assertType('list', $list2); $list2[2] = '1';//Most likely to create a gap in indexes assertType('non-empty-array, mixed>&hasOffsetValue(2, \'1\')', $list2); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-array.php b/tests/PHPStan/Analyser/nsrt/non-empty-array.php index a7cdc6540ac..af34c0da258 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-array.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-array.php @@ -26,7 +26,7 @@ public function doFoo( ): void { assertType('non-empty-array', $array); - assertType('non-empty-list', $list); + assertType('non-empty-list', $list); assertType('non-empty-array', $arrayOfStrings); assertType('non-empty-list', $listOfStd); assertType('non-empty-list', $listOfStd2); diff --git a/tests/PHPStan/Analyser/nsrt/shuffle.php b/tests/PHPStan/Analyser/nsrt/shuffle.php index c2bf8537764..6b699e598ae 100644 --- a/tests/PHPStan/Analyser/nsrt/shuffle.php +++ b/tests/PHPStan/Analyser/nsrt/shuffle.php @@ -13,7 +13,7 @@ public function normalArrays1(array $arr): void /** @var mixed[] $arr */ shuffle($arr); assertType('list', $arr); - assertNativeType('list', $arr); + assertNativeType('list', $arr); assertType('list>', array_keys($arr)); assertType('list', array_values($arr)); } @@ -23,7 +23,7 @@ public function normalArrays2(array $arr): void /** @var non-empty-array $arr */ shuffle($arr); assertType('non-empty-list', $arr); - assertNativeType('list', $arr); + assertNativeType('list', $arr); assertType('non-empty-list>', array_keys($arr)); assertType('non-empty-list', array_values($arr)); } @@ -33,10 +33,10 @@ public function normalArrays3(array $arr): void /** @var array $arr */ if (array_key_exists('foo', $arr)) { shuffle($arr); - assertType('non-empty-list', $arr); - assertNativeType('non-empty-list', $arr); + assertType('non-empty-list', $arr); + assertNativeType('non-empty-list', $arr); assertType('non-empty-list>', array_keys($arr)); - assertType('non-empty-list', array_values($arr)); + assertType('non-empty-list', array_values($arr)); } } @@ -45,10 +45,10 @@ public function normalArrays4(array $arr): void /** @var array $arr */ if (array_key_exists('foo', $arr) && $arr['foo'] === 'bar') { shuffle($arr); - assertType('non-empty-list', $arr); - assertNativeType('non-empty-list', $arr); + assertType('non-empty-list', $arr); + assertNativeType('non-empty-list', $arr); assertType('non-empty-list>', array_keys($arr)); - assertType('non-empty-list', array_values($arr)); + assertType('non-empty-list', array_values($arr)); } } @@ -66,8 +66,8 @@ public function constantArrays2(array $arr): void { /** @var array{0?: 1, 1?: 2, 2?: 3} $arr */ shuffle($arr); - assertType('array<0|1|2, 1|2|3>&list', $arr); - assertNativeType('list', $arr); + assertType('list<1|2|3>', $arr); + assertNativeType('list', $arr); assertType('list<0|1|2>', array_keys($arr)); assertType('list<1|2|3>', array_values($arr)); } @@ -76,8 +76,8 @@ public function constantArrays3(array $arr): void { $arr = [1, 2, 3]; shuffle($arr); - assertType('non-empty-array<0|1|2, 1|2|3>&list', $arr); - assertNativeType('non-empty-array<0|1|2, 1|2|3>&list', $arr); + assertType('non-empty-list<1|2|3>', $arr); + assertNativeType('non-empty-list<1|2|3>', $arr); assertType('non-empty-list<0|1|2>', array_keys($arr)); assertType('non-empty-list<1|2|3>', array_values($arr)); } @@ -86,8 +86,8 @@ public function constantArrays4(array $arr): void { $arr = ['a' => 1, 'b' => 2, 'c' => 3]; shuffle($arr); - assertType('non-empty-array<0|1|2, 1|2|3>&list', $arr); - assertNativeType('non-empty-array<0|1|2, 1|2|3>&list', $arr); + assertType('non-empty-list<1|2|3>', $arr); + assertNativeType('non-empty-list<1|2|3>', $arr); assertType('non-empty-list<0|1|2>', array_keys($arr)); assertType('non-empty-list<1|2|3>', array_values($arr)); } @@ -96,8 +96,8 @@ public function constantArrays5(array $arr): void { $arr = [0 => 1, 3 => 2, 42 => 3]; shuffle($arr); - assertType('non-empty-array<0|1|2, 1|2|3>&list', $arr); - assertNativeType('non-empty-array<0|1|2, 1|2|3>&list', $arr); + assertType('non-empty-list<1|2|3>', $arr); + assertNativeType('non-empty-list<1|2|3>', $arr); assertType('non-empty-list<0|1|2>', array_keys($arr)); assertType('non-empty-list<1|2|3>', array_values($arr)); } @@ -106,8 +106,8 @@ public function constantArrays6(array $arr): void { /** @var array{foo?: 1, bar: 2, }|array{baz: 3, foobar?: 4} $arr */ shuffle($arr); - assertType('non-empty-array<0|1, 1|2|3|4>&list', $arr); - assertNativeType('list', $arr); + assertType('non-empty-list<1|2|3|4>', $arr); + assertNativeType('list', $arr); assertType('non-empty-list<0|1>', array_keys($arr)); assertType('non-empty-list<1|2|3|4>', array_values($arr)); } @@ -115,10 +115,10 @@ public function constantArrays6(array $arr): void public function mixed($arr): void { shuffle($arr); - assertType('list', $arr); - assertNativeType('list', $arr); + assertType('list', $arr); + assertNativeType('list', $arr); assertType('list>', array_keys($arr)); - assertType('list', array_values($arr)); + assertType('list', array_values($arr)); } public function subtractedArray($arr): void @@ -134,7 +134,7 @@ public function subtractedArray($arr): void assertType('*ERROR*', $arr); assertNativeType('*ERROR*', $arr); assertType('list', array_keys($arr)); - assertType('list', array_values($arr)); + assertType('list', array_values($arr)); } } diff --git a/tests/PHPStan/Analyser/nsrt/sort.php b/tests/PHPStan/Analyser/nsrt/sort.php index e92b0067136..93dfe0d1473 100644 --- a/tests/PHPStan/Analyser/nsrt/sort.php +++ b/tests/PHPStan/Analyser/nsrt/sort.php @@ -91,17 +91,17 @@ public function normalArray(array $arr): void $arr1 = $arr; sort($arr1); assertType('list', $arr1); - assertNativeType('list', $arr1); + assertNativeType('list', $arr1); $arr2 = $arr; rsort($arr2); assertType('list', $arr2); - assertNativeType('list', $arr2); + assertNativeType('list', $arr2); $arr3 = $arr; usort($arr3, fn(int $a, int $b) => $a <=> $b); assertType('list', $arr3); - assertNativeType('list', $arr3); + assertNativeType('list', $arr3); } public function mixed($arr): void diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4708.php b/tests/PHPStan/Rules/Comparison/data/bug-4708.php index 5c60d3dec13..2afa1643402 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-4708.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-4708.php @@ -57,7 +57,7 @@ function GetASCConfig() } else { - assertType('array&hasOffsetValue(\'bsw\', string)', $result); + assertType('non-empty-array&hasOffsetValue(\'bsw\', string)', $result); $result['bsw'] = (int) $result['bsw']; assertType("non-empty-array&hasOffsetValue('bsw', int)", $result); } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3931.php b/tests/PHPStan/Rules/Functions/data/bug-3931.php index ca846489383..424c7ca236f 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-3931.php +++ b/tests/PHPStan/Rules/Functions/data/bug-3931.php @@ -11,7 +11,7 @@ */ function addSomeKey(array $arr, int $value): array { $arr['mykey'] = $value; - assertType("hasOffsetValue('mykey', int)&non-empty-array", $arr); // should preserve T + assertType("non-empty-array&hasOffsetValue('mykey', int)", $arr); // should preserve T return $arr; } diff --git a/tests/PHPStan/Rules/Functions/data/bug-7156.php b/tests/PHPStan/Rules/Functions/data/bug-7156.php index 96dd5ee26c2..209a9decf54 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-7156.php +++ b/tests/PHPStan/Rules/Functions/data/bug-7156.php @@ -21,7 +21,7 @@ function foobar(array $data): void throw new \RuntimeException(); } - assertType("array&hasOffsetValue('value', string)", $data); + assertType("non-empty-array&hasOffsetValue('value', string)", $data); foo($data); } @@ -32,7 +32,7 @@ function foobar2(mixed $data): void throw new \RuntimeException(); } - assertType("array&hasOffsetValue('value', string)", $data); + assertType("non-empty-array&hasOffsetValue('value', string)", $data); foo($data); } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 5ebfb238e7f..41200b9ad51 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -865,7 +865,7 @@ public function testBug8146bErrors(): void $this->checkBenevolentUnionTypes = true; $this->analyse([__DIR__ . '/data/bug-8146b-errors.php'], [ [ - "Method Bug8146bError\LocationFixtures::getData() should return array, coordinates: array{lat: float, lng: float}}>> but returns array{Bács-Kiskun: array{Ágasegyháza: array{constituencies: array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}, coordinates: array{lat: 46.8386043, lng: 19.4502899}}, Akasztó: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.6898175, lng: 19.205086}}, Apostag: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.8812652, lng: 18.9648478}}, Bácsalmás: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1250396, lng: 19.3357509}}, Bácsbokod: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.1234737, lng: 19.155708}}, Bácsborsód: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.0989373, lng: 19.1566725}}, Bácsszentgyörgy: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 45.9746039, lng: 19.0398066}}, Bácsszőlős: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1352003, lng: 19.4215997}}, ...}, Baranya: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Békés: array{Almáskamarás: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4617785, lng: 21.092448}}, Battonya: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.2902462, lng: 21.0199215}}, Békés: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.6704899, lng: 21.0434996}}, Békéscsaba: array{constituencies: array{'Békés 1.'}, coordinates: array{lat: 46.6735939, lng: 21.0877309}}, Békéssámson: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4208677, lng: 20.6176498}}, Békésszentandrás: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.8715996, lng: 20.48336}}, Bélmegyer: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.8726019, lng: 21.1832832}}, Biharugra: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.9691009, lng: 21.5987651}}, ...}, Borsod-Abaúj-Zemplén: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Budapest: array{Budapest I. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.4968219, lng: 19.037458}}, Budapest II. ker.: array{constituencies: array{'Budapest 03.', 'Budapest 04.'}, coordinates: array{lat: 47.5393329, lng: 18.986934}}, Budapest III. ker.: array{constituencies: array{'Budapest 04.', 'Budapest 10.'}, coordinates: array{lat: 47.5671768, lng: 19.0368517}}, Budapest IV. ker.: array{constituencies: array{'Budapest 11.', 'Budapest 12.'}, coordinates: array{lat: 47.5648915, lng: 19.0913149}}, Budapest V. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.5002319, lng: 19.0520181}}, Budapest VI. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.509863, lng: 19.0625813}}, Budapest VII. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.5027289, lng: 19.073376}}, Budapest VIII. ker.: array{constituencies: array{'Budapest 01.', 'Budapest 06.'}, coordinates: array{lat: 47.4894184, lng: 19.070668}}, ...}, Csongrád-Csanád: array{Algyő: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3329625, lng: 20.207889}}, Ambrózfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3501417, lng: 20.7313995}}, Apátfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.173317, lng: 20.5800472}}, Árpádhalom: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.6158286, lng: 20.547733}}, Ásotthalom: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.1995983, lng: 19.7833756}}, Baks: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.5518708, lng: 20.1064166}}, Balástya: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.4261828, lng: 20.004933}}, Bordány: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.3194213, lng: 19.9227063}}, ...}, Fejér: array{Aba: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 47.0328193, lng: 18.522359}}, Adony: array{constituencies: array{'Fejér 4.'}, coordinates: array{lat: 47.119831, lng: 18.8612469}}, Alap: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.8075763, lng: 18.684028}}, Alcsútdoboz: array{constituencies: array{'Fejér 3.'}, coordinates: array{lat: 47.4277067, lng: 18.6030325}}, Alsószentiván: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.7910573, lng: 18.732161}}, Bakonycsernye: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.321719, lng: 18.0907379}}, Bakonykúti: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.2458464, lng: 18.195769}}, Balinka: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.3135736, lng: 18.1907168}}, ...}, Győr-Moson-Sopron: array{Abda: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.6962149, lng: 17.5445786}}, Acsalag: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.676095, lng: 17.1977771}}, Ágfalva: array{constituencies: array{'Győr-Moson-Sopron 4.'}, coordinates: array{lat: 47.688862, lng: 16.5110233}}, Agyagosszergény: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.608545, lng: 16.9409912}}, Árpás: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5134127, lng: 17.3931579}}, Ásványráró: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.8287695, lng: 17.499195}}, Babót: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5752269, lng: 17.0758604}}, Bágyogszovát: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5866036, lng: 17.3617273}}, ...}, ...}.", + "Method Bug8146bError\LocationFixtures::getData() should return array, coordinates: array{lat: float, lng: float}}>> but returns array{Bács-Kiskun: array{Ágasegyháza: array{constituencies: array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}, coordinates: array{lat: 46.8386043, lng: 19.4502899}}, Akasztó: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.6898175, lng: 19.205086}}, Apostag: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.8812652, lng: 18.9648478}}, Bácsalmás: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1250396, lng: 19.3357509}}, Bácsbokod: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.1234737, lng: 19.155708}}, Bácsborsód: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.0989373, lng: 19.1566725}}, Bácsszentgyörgy: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 45.9746039, lng: 19.0398066}}, Bácsszőlős: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1352003, lng: 19.4215997}}, ...}, Baranya: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Békés: array{Almáskamarás: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4617785, lng: 21.092448}}, Battonya: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.2902462, lng: 21.0199215}}, Békés: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.6704899, lng: 21.0434996}}, Békéscsaba: array{constituencies: array{'Békés 1.'}, coordinates: array{lat: 46.6735939, lng: 21.0877309}}, Békéssámson: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4208677, lng: 20.6176498}}, Békésszentandrás: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.8715996, lng: 20.48336}}, Bélmegyer: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.8726019, lng: 21.1832832}}, Biharugra: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.9691009, lng: 21.5987651}}, ...}, Borsod-Abaúj-Zemplén: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Budapest: array{Budapest I. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.4968219, lng: 19.037458}}, Budapest II. ker.: array{constituencies: array{'Budapest 03.', 'Budapest 04.'}, coordinates: array{lat: 47.5393329, lng: 18.986934}}, Budapest III. ker.: array{constituencies: array{'Budapest 04.', 'Budapest 10.'}, coordinates: array{lat: 47.5671768, lng: 19.0368517}}, Budapest IV. ker.: array{constituencies: array{'Budapest 11.', 'Budapest 12.'}, coordinates: array{lat: 47.5648915, lng: 19.0913149}}, Budapest V. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.5002319, lng: 19.0520181}}, Budapest VI. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.509863, lng: 19.0625813}}, Budapest VII. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.5027289, lng: 19.073376}}, Budapest VIII. ker.: array{constituencies: array{'Budapest 01.', 'Budapest 06.'}, coordinates: array{lat: 47.4894184, lng: 19.070668}}, ...}, Csongrád-Csanád: array{Algyő: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3329625, lng: 20.207889}}, Ambrózfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3501417, lng: 20.7313995}}, Apátfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.173317, lng: 20.5800472}}, Árpádhalom: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.6158286, lng: 20.547733}}, Ásotthalom: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.1995983, lng: 19.7833756}}, Baks: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.5518708, lng: 20.1064166}}, Balástya: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.4261828, lng: 20.004933}}, Bordány: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.3194213, lng: 19.9227063}}, ...}, Fejér: array{Aba: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 47.0328193, lng: 18.522359}}, Adony: array{constituencies: array{'Fejér 4.'}, coordinates: array{lat: 47.119831, lng: 18.8612469}}, Alap: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.8075763, lng: 18.684028}}, Alcsútdoboz: array{constituencies: array{'Fejér 3.'}, coordinates: array{lat: 47.4277067, lng: 18.6030325}}, Alsószentiván: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.7910573, lng: 18.732161}}, Bakonycsernye: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.321719, lng: 18.0907379}}, Bakonykúti: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.2458464, lng: 18.195769}}, Balinka: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.3135736, lng: 18.1907168}}, ...}, Győr-Moson-Sopron: array{Abda: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.6962149, lng: 17.5445786}}, Acsalag: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.676095, lng: 17.1977771}}, Ágfalva: array{constituencies: array{'Győr-Moson-Sopron 4.'}, coordinates: array{lat: 47.688862, lng: 16.5110233}}, Agyagosszergény: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.608545, lng: 16.9409912}}, Árpás: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5134127, lng: 17.3931579}}, Ásványráró: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.8287695, lng: 17.499195}}, Babót: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5752269, lng: 17.0758604}}, Bágyogszovát: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5866036, lng: 17.3617273}}, ...}, ...}.", 12, "Offset 'constituencies' (non-empty-list) does not accept type array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}.", ], diff --git a/tests/PHPStan/Rules/Variables/data/bug-3391.php b/tests/PHPStan/Rules/Variables/data/bug-3391.php index bfe756a0201..2d578436221 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-3391.php +++ b/tests/PHPStan/Rules/Variables/data/bug-3391.php @@ -22,11 +22,11 @@ public function test() $data['foo'] = 'a'; $data['bar'] = 'b'; - assertType("hasOffsetValue('bar', 'b')&hasOffsetValue('foo', 'a')&non-empty-array", $data); + assertType("non-empty-array&hasOffsetValue('bar', 'b')&hasOffsetValue('foo', 'a')", $data); unset($data['id']); - assertType("array&hasOffsetValue('bar', 'b')&hasOffsetValue('foo', 'a')", $data); + assertType("non-empty-array&hasOffsetValue('bar', 'b')&hasOffsetValue('foo', 'a')", $data); return $data; } } diff --git a/tests/PHPStan/Rules/Variables/data/bug-7417.php b/tests/PHPStan/Rules/Variables/data/bug-7417.php index 24fb2c4a7f5..fcf698a9ecb 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-7417.php +++ b/tests/PHPStan/Rules/Variables/data/bug-7417.php @@ -20,9 +20,9 @@ function doFoo() { // in core.extension so this will come before it's base theme. $extensions['theme']['test_subtheme'] = 0; $extensions['theme']['test_subsubtheme'] = 0; - assertType("hasOffsetValue('theme', mixed)&non-empty-array", $extensions); + assertType("non-empty-array&hasOffsetValue('theme', mixed)", $extensions); unset($extensions['theme']['test_basetheme']); unset($extensions['theme']['test_subsubtheme']); unset($extensions['theme']['test_subtheme']); - assertType("hasOffsetValue('theme', mixed)&non-empty-array", $extensions); + assertType("non-empty-array&hasOffsetValue('theme', mixed)", $extensions); } diff --git a/tests/PHPStan/Rules/Variables/data/bug-8113.php b/tests/PHPStan/Rules/Variables/data/bug-8113.php index 97b58419413..27ebe729ae9 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-8113.php +++ b/tests/PHPStan/Rules/Variables/data/bug-8113.php @@ -26,23 +26,23 @@ function () { array_key_exists('review', $review['SurveyInvitation']) && $review['SurveyInvitation']['review'] === null ) { - assertType("array>&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); + assertType("non-empty-array>&hasOffsetValue('SurveyInvitation', non-empty-array&hasOffsetValue('review', null))", $review); $review['Review'] = [ 'id' => null, 'text' => null, 'answer' => null, ]; - assertType("non-empty-array>&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', array&hasOffsetValue('review', null))", $review); + assertType("non-empty-array>&hasOffsetValue('Review', array{id: null, text: null, answer: null})&hasOffsetValue('SurveyInvitation', non-empty-array&hasOffsetValue('review', null))", $review); unset($review['SurveyInvitation']['review']); assertType("non-empty-array>&hasOffsetValue('Review', array)&hasOffsetValue('SurveyInvitation', array)", $review); } assertType('array>', $review); if (array_key_exists('User', $review['Review'])) { - assertType("array>&hasOffsetValue('Review', array&hasOffset('User'))", $review); + assertType("non-empty-array>&hasOffsetValue('Review', non-empty-array&hasOffset('User'))", $review); $review['User'] = $review['Review']['User']; - assertType("hasOffsetValue('Review', array&hasOffset('User'))&hasOffsetValue('User', mixed)&non-empty-array", $review); + assertType("non-empty-array&hasOffsetValue('Review', non-empty-array&hasOffset('User'))&hasOffsetValue('User', mixed)", $review); unset($review['Review']['User']); - assertType("hasOffsetValue('Review', array)&hasOffsetValue('User', array)&non-empty-array", $review); + assertType("non-empty-array&hasOffsetValue('Review', array)&hasOffsetValue('User', array)", $review); } - assertType("array&hasOffsetValue('Review', array)", $review); + assertType("non-empty-array&hasOffsetValue('Review', array)", $review); }; diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 7648a80ae90..c5111cbeae9 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -436,7 +436,7 @@ public function dataDescribe(): iterable new NonEmptyArrayType(), ]), VerbosityLevel::value(), - 'non-empty-list', + 'non-empty-list', ]; yield [ @@ -491,6 +491,221 @@ public function dataDescribe(): iterable VerbosityLevel::value(), 'non-empty-array', ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new IntegerType()), + new AccessoryArrayListType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new IntegerType()), + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new AccessoryArrayListType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new AccessoryArrayListType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new NonEmptyArrayType(), + ]), + VerbosityLevel::typeOnly(), + 'array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array', + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new NonEmptyArrayType(), + new OversizedArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array', + ]; + + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new NonEmptyArrayType(), + new OversizedArrayType(), + ]), + VerbosityLevel::precise(), + 'non-empty-array&oversized-array', + ]; + + $constantArrayWithOptionalKeys = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ], [ + new StringType(), + new StringType(), + new StringType(), + new StringType(), + ], [3], [2, 3], TrinaryLogic::createMaybe()); + + yield [ + new IntersectionType([ + $constantArrayWithOptionalKeys, + new AccessoryArrayListType(), + ]), + VerbosityLevel::typeOnly(), + 'list', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithOptionalKeys, + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'list{0: string, 1: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithOptionalKeys, + new AccessoryArrayListType(), + ]), + VerbosityLevel::precise(), + 'list{0: string, 1: string, 2?: string, 3?: string}', + ]; + + $constantArrayWithAllOptionalKeys = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ], [ + new StringType(), + new StringType(), + new StringType(), + new StringType(), + ], [3], [0, 1, 2, 3], TrinaryLogic::createMaybe()); + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'list{0?: string, 1?: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new NonEmptyArrayType(), + new AccessoryArrayListType(), + ]), + VerbosityLevel::value(), + 'non-empty-list{0?: string, 1?: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new NonEmptyArrayType(), + ]), + VerbosityLevel::value(), + 'non-empty-array{0?: string, 1?: string, 2?: string, 3?: string}', + ]; } /** diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 6084574e73a..2694d0f3ebe 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -965,7 +965,7 @@ public function dataUnion(): iterable ]), ], IntersectionType::class, - 'array&hasOffsetValue(\'foo\', mixed)', + 'non-empty-array&hasOffsetValue(\'foo\', mixed)', ], [ [ @@ -2267,7 +2267,7 @@ public function dataUnion(): iterable TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new HasOffsetValueType(new ConstantStringType('a'), new IntegerType())), ], IntersectionType::class, - 'array&hasOffsetValue(\'a\', int)', + 'non-empty-array&hasOffsetValue(\'a\', int)', ]; yield [ @@ -2288,7 +2288,7 @@ public function dataUnion(): iterable ]), ], IntersectionType::class, - "array&hasOffsetValue('a', mixed)", + "non-empty-array&hasOffsetValue('a', mixed)", ]; yield [ @@ -2315,7 +2315,7 @@ public function dataUnion(): iterable ]), ], IntersectionType::class, - "array&hasOffsetValue(0, array&hasOffsetValue('code', mixed))", + "non-empty-array&hasOffsetValue(0, non-empty-array&hasOffsetValue('code', mixed))", ]; yield [ @@ -2513,7 +2513,7 @@ public function dataUnion(): iterable ]), ], UnionType::class, - 'array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', + 'array{a?: true, b: true}|non-empty-array{a?: true, c?: true}', ]; yield [ @@ -2600,7 +2600,7 @@ public function dataUnion(): iterable ]), ], IntersectionType::class, - 'array&hasOffsetValue(\'thing\', mixed)', + 'non-empty-array&hasOffsetValue(\'thing\', mixed)', ]; } @@ -3109,7 +3109,7 @@ public function dataIntersect(): iterable new HasOffsetType(new ConstantStringType('a')), ], IntersectionType::class, - 'array&hasOffset(\'a\')', + 'non-empty-array&hasOffset(\'a\')', ], [ [ @@ -3118,7 +3118,7 @@ public function dataIntersect(): iterable new HasOffsetType(new ConstantStringType('a')), ], IntersectionType::class, - 'array&hasOffset(\'a\')', + 'non-empty-array&hasOffset(\'a\')', ], [ [ @@ -3267,7 +3267,7 @@ public function dataIntersect(): iterable ]), ], IntersectionType::class, - 'array&hasOffset(\'bar\')&hasOffset(\'foo\')', + 'non-empty-array&hasOffset(\'bar\')&hasOffset(\'foo\')', ], [ [ @@ -4047,7 +4047,7 @@ public function dataIntersect(): iterable TypeCombinator::intersect(new ArrayType(new MixedType(), new MixedType()), new HasOffsetValueType(new ConstantStringType('a'), new IntegerType())), ], IntersectionType::class, - 'array&hasOffsetValue(\'a\', 1)', + 'non-empty-array&hasOffsetValue(\'a\', 1)', ]; yield [ [ @@ -4207,7 +4207,7 @@ public function dataIntersect(): iterable new NonEmptyArrayType(), ], UnionType::class, - 'array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', + 'array{a?: true, b: true}|non-empty-array{a?: true, c?: true}', ]; yield [ [ @@ -4243,7 +4243,7 @@ public function dataIntersect(): iterable new NonEmptyArrayType(), ], IntersectionType::class, - 'array{a?: true, c?: true}&non-empty-array', + 'non-empty-array{a?: true, c?: true}', ]; yield [ [ diff --git a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php index eebdb080142..ffb42f1edf7 100644 --- a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php +++ b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php @@ -4,6 +4,7 @@ use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -265,7 +266,7 @@ public function dataToPhpDocNode(): iterable yield [ new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), new AccessoryArrayListType()]), - 'list', + 'list', ]; yield [ @@ -274,7 +275,7 @@ public function dataToPhpDocNode(): iterable new NonEmptyArrayType(), new AccessoryArrayListType(), ]), - 'non-empty-list', + 'non-empty-list', ]; yield [ @@ -309,6 +310,122 @@ public function dataToPhpDocNode(): iterable ], [2], [1]), "array{0: 'foo', 1?: 'bar'}", ]; + + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new IntegerType()), + new AccessoryArrayListType(), + ]), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), + new AccessoryArrayListType(), + ]), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType(true)), + new AccessoryArrayListType(), + ]), + 'list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + 'non-empty-list', + ]; + yield [ + new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType(true)), + new AccessoryArrayListType(), + new NonEmptyArrayType(), + ]), + 'non-empty-list', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new NonEmptyArrayType(), + ]), + 'non-empty-array', + ]; + yield [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType(true)), + new NonEmptyArrayType(), + ]), + 'non-empty-array', + ]; + $constantArrayWithOptionalKeys = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ], [ + new StringType(), + new StringType(), + new StringType(), + new StringType(), + ], [3], [2, 3], TrinaryLogic::createMaybe()); + + yield [ + new IntersectionType([ + $constantArrayWithOptionalKeys, + new AccessoryArrayListType(), + ]), + 'list{0: string, 1: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithOptionalKeys, + new AccessoryArrayListType(), + ]), + 'list{0: string, 1: string, 2?: string, 3?: string}', + ]; + + $constantArrayWithAllOptionalKeys = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + new ConstantIntegerType(3), + ], [ + new StringType(), + new StringType(), + new StringType(), + new StringType(), + ], [3], [0, 1, 2, 3], TrinaryLogic::createMaybe()); + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new AccessoryArrayListType(), + ]), + 'list{0?: string, 1?: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new NonEmptyArrayType(), + new AccessoryArrayListType(), + ]), + 'non-empty-list{0?: string, 1?: string, 2?: string, 3?: string}', + ]; + + yield [ + new IntersectionType([ + $constantArrayWithAllOptionalKeys, + new NonEmptyArrayType(), + ]), + 'non-empty-array{0?: string, 1?: string, 2?: string, 3?: string}', + ]; } /** From 70f319a25cc75cbb22288674108cf3212b6fe735 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:01:59 +0200 Subject: [PATCH 0698/3097] StubParser --- conf/config.stubValidator.neon | 6 ++++ src/Parser/StubParser.php | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/Parser/StubParser.php diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index 07390c7b8fd..74e943d911a 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -11,6 +11,12 @@ services: arguments: php8Parser: @php8PhpParser + defaultAnalysisParser: + class: PHPStan\Parser\StubParser + arguments!: + parser: @php8PhpParser + autowired: false + nodeScopeResolverReflector: factory: @stubReflector diff --git a/src/Parser/StubParser.php b/src/Parser/StubParser.php new file mode 100644 index 00000000000..d98a2cc7215 --- /dev/null +++ b/src/Parser/StubParser.php @@ -0,0 +1,56 @@ +parseString(FileReader::read($file)); + } catch (ParserErrorsException $e) { + throw new ParserErrorsException($e->getErrors(), $file); + } + } + + /** + * @return Node\Stmt[] + */ + public function parseString(string $sourceCode): array + { + $errorHandler = new Collecting(); + $nodes = $this->parser->parse($sourceCode, $errorHandler); + if ($errorHandler->hasErrors()) { + throw new ParserErrorsException($errorHandler->getErrors(), null); + } + if ($nodes === null) { + throw new ShouldNotHappenException(); + } + + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->nameResolver); + + /** @var array */ + return $nodeTraverser->traverse($nodes); + } + +} From b651a22caedaf13cfa29fe62ad38d7cc41ba0d88 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:07:10 +0200 Subject: [PATCH 0699/3097] Fix tests --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/nsrt/array-column.php | 8 ++++---- tests/PHPStan/Analyser/nsrt/array-fill-keys-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array-flip-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array-intersect-key-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array-search-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array_keys-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array_values-php7.php | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index e97a5f4a063..8338e6f13c3 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -4264,7 +4264,7 @@ public function dataAnonymousFunction(): array '$str', ], [ - PHP_VERSION_ID < 80000 ? 'list' : 'array', + PHP_VERSION_ID < 80000 ? 'list' : 'array', '$arr', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/array-column.php b/tests/PHPStan/Analyser/nsrt/array-column.php index 2455d6ace9d..017490d5344 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column.php +++ b/tests/PHPStan/Analyser/nsrt/array-column.php @@ -189,7 +189,7 @@ public function testImprecise5(array $array): void assertType('list', array_column($array, 'nodeName')); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -201,7 +201,7 @@ public function testObjects1(array $array): void assertType('non-empty-list', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -213,7 +213,7 @@ public function testObjects2(array $array): void assertType('array{string}', array_column($array, 'nodeName')); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); - assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -227,7 +227,7 @@ final class Foo /** @param array $a */ public function doFoo(array $a): void { - assertType('list', array_column($a, 'nodeName')); + assertType('list', array_column($a, 'nodeName')); assertType('array', array_column($a, 'nodeName', 'tagName')); } diff --git a/tests/PHPStan/Analyser/nsrt/array-fill-keys-php7.php b/tests/PHPStan/Analyser/nsrt/array-fill-keys-php7.php index 7bbc485b5a2..c0a4e8dd7a7 100644 --- a/tests/PHPStan/Analyser/nsrt/array-fill-keys-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array-fill-keys-php7.php @@ -7,7 +7,7 @@ function mixedAndSubtractedArray($mixed): void { if (is_array($mixed)) { - assertType("array<'b'>", array_fill_keys($mixed, 'b')); + assertType("array", array_fill_keys($mixed, 'b')); } else { assertType("null", array_fill_keys($mixed, 'b')); } diff --git a/tests/PHPStan/Analyser/nsrt/array-flip-php7.php b/tests/PHPStan/Analyser/nsrt/array-flip-php7.php index 0b7058de010..f62e1a6628e 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip-php7.php @@ -9,7 +9,7 @@ function mixedAndSubtractedArray($mixed) if (is_array($mixed)) { assertType('array', array_flip($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('null', array_flip($mixed)); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-intersect-key-php7.php b/tests/PHPStan/Analyser/nsrt/array-intersect-key-php7.php index 79b7af7b183..14dd7b5d0df 100644 --- a/tests/PHPStan/Analyser/nsrt/array-intersect-key-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array-intersect-key-php7.php @@ -16,7 +16,7 @@ public function mixedAndSubtractedArray($mixed, array $otherArrs): void /** @var array $otherArrs */ assertType('array', array_intersect_key($mixed, $otherArrs)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); /** @var array $otherArrs */ assertType('null', array_intersect_key($mixed, $otherArrs)); /** @var array $otherArrs */ diff --git a/tests/PHPStan/Analyser/nsrt/array-search-php7.php b/tests/PHPStan/Analyser/nsrt/array-search-php7.php index 2cd24b7c9d9..5816daf659f 100644 --- a/tests/PHPStan/Analyser/nsrt/array-search-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array-search-php7.php @@ -16,7 +16,7 @@ public function mixedAndSubtractedArray($mixed, string $string): void assertType('int|string|false', array_search('foo', $mixed)); assertType('int|string|false', array_search($string, $mixed, true)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('null', array_search('foo', $mixed, true)); assertType('null', array_search('foo', $mixed)); assertType('null', array_search($string, $mixed, true)); diff --git a/tests/PHPStan/Analyser/nsrt/array_keys-php7.php b/tests/PHPStan/Analyser/nsrt/array_keys-php7.php index 103cb0a0033..9f5ca43682b 100644 --- a/tests/PHPStan/Analyser/nsrt/array_keys-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array_keys-php7.php @@ -12,7 +12,7 @@ public function sayHello($mixed): void if (is_array($mixed)) { assertType('list<(int|string)>', array_keys($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('null', array_keys($mixed)); } } diff --git a/tests/PHPStan/Analyser/nsrt/array_values-php7.php b/tests/PHPStan/Analyser/nsrt/array_values-php7.php index 10de8239808..db378c95aac 100644 --- a/tests/PHPStan/Analyser/nsrt/array_values-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array_values-php7.php @@ -12,7 +12,7 @@ public function foo1($mixed): void if (is_array($mixed)) { assertType('list', array_values($mixed)); } else { - assertType('mixed~array', $mixed); + assertType('mixed~array', $mixed); assertType('null', array_values($mixed)); } } From 7ed2d67fa4db81323e4957894416be84a1022115 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:14:22 +0200 Subject: [PATCH 0700/3097] Fix tests --- tests/PHPStan/Levels/data/acceptTypes-5.json | 4 ++-- tests/PHPStan/Levels/data/arrayDestructuring-8.json | 4 ++-- tests/PHPStan/Levels/data/arrayDimFetches-8.json | 4 ++-- tests/PHPStan/Levels/data/casts-7.json | 6 +++--- tests/PHPStan/Levels/data/echo_-2.json | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Levels/data/acceptTypes-5.json b/tests/PHPStan/Levels/data/acceptTypes-5.json index 88d4749a458..76aac5c080d 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-5.json +++ b/tests/PHPStan/Levels/data/acceptTypes-5.json @@ -180,7 +180,7 @@ "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array{} given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array{} given.", "line": 733, "ignorable": true }, @@ -189,4 +189,4 @@ "line": 763, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/arrayDestructuring-8.json b/tests/PHPStan/Levels/data/arrayDestructuring-8.json index 7842bce8068..3b033abd6d1 100644 --- a/tests/PHPStan/Levels/data/arrayDestructuring-8.json +++ b/tests/PHPStan/Levels/data/arrayDestructuring-8.json @@ -1,7 +1,7 @@ [ { - "message": "Cannot use array destructuring on array|null.", + "message": "Cannot use array destructuring on array|null.", "line": 15, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-8.json b/tests/PHPStan/Levels/data/arrayDimFetches-8.json index 07568e3768a..e7e6efcd138 100644 --- a/tests/PHPStan/Levels/data/arrayDimFetches-8.json +++ b/tests/PHPStan/Levels/data/arrayDimFetches-8.json @@ -1,6 +1,6 @@ [ { - "message": "Offset 0 might not exist on array|null.", + "message": "Offset 0 might not exist on array|null.", "line": 15, "ignorable": true }, @@ -9,4 +9,4 @@ "line": 50, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/casts-7.json b/tests/PHPStan/Levels/data/casts-7.json index d9b7a85b781..e2810b0e424 100644 --- a/tests/PHPStan/Levels/data/casts-7.json +++ b/tests/PHPStan/Levels/data/casts-7.json @@ -1,12 +1,12 @@ [ { - "message": "Cannot cast array|(callable(): mixed) to int.", + "message": "Cannot cast array|(callable(): mixed) to int.", "line": 20, "ignorable": true }, { - "message": "Cannot cast array|float|int to string.", + "message": "Cannot cast array|float|int to string.", "line": 21, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/echo_-2.json b/tests/PHPStan/Levels/data/echo_-2.json index 1c35f8ef7c3..1e30f3b562b 100644 --- a/tests/PHPStan/Levels/data/echo_-2.json +++ b/tests/PHPStan/Levels/data/echo_-2.json @@ -1,6 +1,6 @@ [ { - "message": "Parameter #1 (array) of echo cannot be converted to string.", + "message": "Parameter #1 (array) of echo cannot be converted to string.", "line": 21, "ignorable": true }, @@ -9,4 +9,4 @@ "line": 21, "ignorable": true } -] \ No newline at end of file +] From 0ce1ce53ff55d9ab0a0bae7f28326167bcc5aebb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:22:20 +0200 Subject: [PATCH 0701/3097] Use StubParser more --- conf/config.neon | 8 +++++++- conf/config.stubValidator.neon | 7 ++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index d71fbfbdfde..4823b1c90ee 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1944,7 +1944,7 @@ services: stubPhpDocProvider: class: PHPStan\PhpDoc\StubPhpDocProvider arguments: - parser: @defaultAnalysisParser + parser: @stubParser # Reflection providers @@ -2045,6 +2045,12 @@ services: php8Parser: @php8Parser autowired: false + stubParser: + class: PHPStan\Parser\StubParser + arguments: + parser: @php8PhpParser + autowired: false + phpstanDiagnoseExtension: class: PHPStan\Diagnose\PHPStanDiagnoseExtension arguments: diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index 74e943d911a..52eac72312b 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -11,11 +11,8 @@ services: arguments: php8Parser: @php8PhpParser - defaultAnalysisParser: - class: PHPStan\Parser\StubParser - arguments!: - parser: @php8PhpParser - autowired: false + defaultAnalysisParser!: + factory: @stubParser nodeScopeResolverReflector: factory: @stubReflector From 8285a25d90f242bde0aaed94f215713446c4a664 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:24:36 +0200 Subject: [PATCH 0702/3097] Fix tests --- tests/PHPStan/Levels/data/acceptTypes-7.json | 8 ++++---- tests/PHPStan/Levels/data/echo_-2.json | 2 +- tests/PHPStan/Levels/data/iterable-7.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/PHPStan/Levels/data/acceptTypes-7.json b/tests/PHPStan/Levels/data/acceptTypes-7.json index 9e0afc2f644..92912a0f9e4 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-7.json +++ b/tests/PHPStan/Levels/data/acceptTypes-7.json @@ -85,7 +85,7 @@ "ignorable": true }, { - "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", + "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", "line": 531, "ignorable": true }, @@ -95,7 +95,7 @@ "ignorable": true }, { - "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", + "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", "line": 542, "ignorable": true }, @@ -160,7 +160,7 @@ "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array given.", "line": 735, "ignorable": true }, @@ -169,4 +169,4 @@ "line": 756, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/echo_-2.json b/tests/PHPStan/Levels/data/echo_-2.json index 1e30f3b562b..330e326e8e2 100644 --- a/tests/PHPStan/Levels/data/echo_-2.json +++ b/tests/PHPStan/Levels/data/echo_-2.json @@ -5,7 +5,7 @@ "ignorable": true }, { - "message": "Parameter #2 (array|(callable(): mixed)) of echo cannot be converted to string.", + "message": "Parameter #2 (array|(callable(): mixed)) of echo cannot be converted to string.", "line": 21, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/iterable-7.json b/tests/PHPStan/Levels/data/iterable-7.json index 74440d64d82..5c3c24924df 100644 --- a/tests/PHPStan/Levels/data/iterable-7.json +++ b/tests/PHPStan/Levels/data/iterable-7.json @@ -1,7 +1,7 @@ [ { - "message": "Argument of an invalid type array|false supplied for foreach, only iterables are supported.", + "message": "Argument of an invalid type array|false supplied for foreach, only iterables are supported.", "line": 35, "ignorable": true } -] \ No newline at end of file +] From 3128f266461cdcb559672e8519db7643666c669a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 13 Oct 2024 17:25:36 +0200 Subject: [PATCH 0703/3097] Improve lowercase string verbosity level --- src/Type/VerbosityLevel.php | 8 +-- tests/PHPStan/Type/VerbosityLevelTest.php | 61 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Type/VerbosityLevelTest.php diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 52b66c154fb..47b0d2477c5 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -131,11 +131,11 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc } if ($moreVerbose) { - return self::value(); + $verbosity = self::value(); } if ($acceptedType === null) { - return self::typeOnly(); + return $verbosity ?? self::typeOnly(); } $containsInvariantTemplateType = false; @@ -163,7 +163,7 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc }); if (!$containsInvariantTemplateType) { - return self::typeOnly(); + return $verbosity ?? self::typeOnly(); } /** @var bool $moreVerbose */ @@ -176,7 +176,7 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc return self::precise(); } - return $moreVerbose ? self::value() : self::typeOnly(); + return $moreVerbose ? self::value() : $verbosity ?? self::typeOnly(); } /** diff --git a/tests/PHPStan/Type/VerbosityLevelTest.php b/tests/PHPStan/Type/VerbosityLevelTest.php new file mode 100644 index 00000000000..0ff71d50257 --- /dev/null +++ b/tests/PHPStan/Type/VerbosityLevelTest.php @@ -0,0 +1,61 @@ +assertSame($expected->getLevelValue(), $level->getLevelValue()); + } + +} From 1aec4300794594d8f87a6e018070ccdf955fd0ef Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 13 Oct 2024 17:29:24 +0200 Subject: [PATCH 0704/3097] Fixed types --- tests/PHPStan/Levels/data/acceptTypes-7.json | 6 +++--- tests/PHPStan/Levels/data/echo_-7.json | 6 +++--- tests/PHPStan/Levels/data/iterable-8.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/PHPStan/Levels/data/acceptTypes-7.json b/tests/PHPStan/Levels/data/acceptTypes-7.json index 92912a0f9e4..ba67e6a9d68 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-7.json +++ b/tests/PHPStan/Levels/data/acceptTypes-7.json @@ -95,12 +95,12 @@ "ignorable": true }, { - "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", + "message": "Parameter #1 $array of method Levels\\AcceptTypes\\Baz::requireArray() expects array, array|Levels\\AcceptTypes\\Foo given.", "line": 542, "ignorable": true }, { - "message": "Parameter #1 $foo of method Levels\\AcceptTypes\\Baz::requireFoo() expects Levels\\AcceptTypes\\Foo, array|Levels\\AcceptTypes\\Foo given.", + "message": "Parameter #1 $foo of method Levels\\AcceptTypes\\Baz::requireFoo() expects Levels\\AcceptTypes\\Foo, array|Levels\\AcceptTypes\\Foo given.", "line": 543, "ignorable": true }, @@ -160,7 +160,7 @@ "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array given.", "line": 735, "ignorable": true }, diff --git a/tests/PHPStan/Levels/data/echo_-7.json b/tests/PHPStan/Levels/data/echo_-7.json index 3f32efd7746..275583f027c 100644 --- a/tests/PHPStan/Levels/data/echo_-7.json +++ b/tests/PHPStan/Levels/data/echo_-7.json @@ -1,12 +1,12 @@ [ { - "message": "Parameter #3 (array|float|int) of echo cannot be converted to string.", + "message": "Parameter #3 (array|float|int) of echo cannot be converted to string.", "line": 21, "ignorable": true }, { - "message": "Parameter #4 (array|string) of echo cannot be converted to string.", + "message": "Parameter #4 (array|string) of echo cannot be converted to string.", "line": 21, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Levels/data/iterable-8.json b/tests/PHPStan/Levels/data/iterable-8.json index 17e368b2766..0a46f4b75e0 100644 --- a/tests/PHPStan/Levels/data/iterable-8.json +++ b/tests/PHPStan/Levels/data/iterable-8.json @@ -1,7 +1,7 @@ [ { - "message": "Argument of an invalid type array|null supplied for foreach, only iterables are supported.", + "message": "Argument of an invalid type array|null supplied for foreach, only iterables are supported.", "line": 26, "ignorable": true } -] \ No newline at end of file +] From 428ef84a20ba1a2a2f7bfd12dc9aacb0e5c62e7d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 04:54:38 +0200 Subject: [PATCH 0705/3097] Separate FileTypeMapper for StubPhpDocProvider --- conf/config.neon | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/conf/config.neon b/conf/config.neon index 4823b1c90ee..1e64b541879 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1032,6 +1032,12 @@ services: arguments: phpParser: @defaultAnalysisParser + stubFileTypeMapper: + class: PHPStan\Type\FileTypeMapper + arguments: + phpParser: @stubParser + autowired: false + - class: PHPStan\Type\TypeAliasResolver factory: PHPStan\Type\UsefulTypeAliasResolver @@ -1945,6 +1951,7 @@ services: class: PHPStan\PhpDoc\StubPhpDocProvider arguments: parser: @stubParser + fileTypeMapper: @stubFileTypeMapper # Reflection providers @@ -2045,12 +2052,19 @@ services: php8Parser: @php8Parser autowired: false - stubParser: + freshStubParser: class: PHPStan\Parser\StubParser arguments: parser: @php8PhpParser autowired: false + stubParser: + class: PHPStan\Parser\CachedParser + arguments: + originalParser: @freshStubParser + cachedNodesByStringCountMax: %cache.nodesByStringCountMax% + autowired: false + phpstanDiagnoseExtension: class: PHPStan\Diagnose\PHPStanDiagnoseExtension arguments: From aa75de05bdb6fcccded87b6e825a32a0e19f6914 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 04:55:48 +0200 Subject: [PATCH 0706/3097] Fixed tests --- tests/PHPStan/Levels/data/print_-2.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Levels/data/print_-2.json b/tests/PHPStan/Levels/data/print_-2.json index d0a53d83623..5978bf90e09 100644 --- a/tests/PHPStan/Levels/data/print_-2.json +++ b/tests/PHPStan/Levels/data/print_-2.json @@ -1,12 +1,12 @@ [ { - "message": "Parameter array of print cannot be converted to string.", + "message": "Parameter array of print cannot be converted to string.", "line": 21, "ignorable": true }, { - "message": "Parameter array|(callable(): mixed) of print cannot be converted to string.", + "message": "Parameter array|(callable(): mixed) of print cannot be converted to string.", "line": 22, "ignorable": true } -] \ No newline at end of file +] From fba5607a664eddb0ac9c52b7f8defd665b9c5ef3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 05:08:56 +0200 Subject: [PATCH 0707/3097] Fixed tests --- tests/PHPStan/Levels/data/encapsedString-2.json | 4 ++-- tests/PHPStan/Levels/data/encapsedString-7.json | 4 ++-- tests/PHPStan/Levels/data/print_-7.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/PHPStan/Levels/data/encapsedString-2.json b/tests/PHPStan/Levels/data/encapsedString-2.json index 01ea01b977d..8035809eb67 100644 --- a/tests/PHPStan/Levels/data/encapsedString-2.json +++ b/tests/PHPStan/Levels/data/encapsedString-2.json @@ -1,11 +1,11 @@ [ { - "message": "Part $array (array) of encapsed string cannot be cast to string.", + "message": "Part $array (array) of encapsed string cannot be cast to string.", "line": 21, "ignorable": true }, { - "message": "Part $arrayOrCallable (array|(callable(): mixed)) of encapsed string cannot be cast to string.", + "message": "Part $arrayOrCallable (array|(callable(): mixed)) of encapsed string cannot be cast to string.", "line": 22, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/encapsedString-7.json b/tests/PHPStan/Levels/data/encapsedString-7.json index 577b87c127e..c468ffbc0a2 100644 --- a/tests/PHPStan/Levels/data/encapsedString-7.json +++ b/tests/PHPStan/Levels/data/encapsedString-7.json @@ -1,11 +1,11 @@ [ { - "message": "Part $arrayOrFloatOrInt (array|float|int) of encapsed string cannot be cast to string.", + "message": "Part $arrayOrFloatOrInt (array|float|int) of encapsed string cannot be cast to string.", "line": 23, "ignorable": true }, { - "message": "Part $arrayOrString (array|string) of encapsed string cannot be cast to string.", + "message": "Part $arrayOrString (array|string) of encapsed string cannot be cast to string.", "line": 24, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/print_-7.json b/tests/PHPStan/Levels/data/print_-7.json index 84c94b44c4d..532f660fad1 100644 --- a/tests/PHPStan/Levels/data/print_-7.json +++ b/tests/PHPStan/Levels/data/print_-7.json @@ -1,11 +1,11 @@ [ { - "message": "Parameter array|float|int of print cannot be converted to string.", + "message": "Parameter array|float|int of print cannot be converted to string.", "line": 23, "ignorable": true }, { - "message": "Parameter array|string of print cannot be converted to string.", + "message": "Parameter array|string of print cannot be converted to string.", "line": 24, "ignorable": true } From cf39148cd0342a76e5ebef1f7ebbdeedb831c60c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 08:46:52 +0200 Subject: [PATCH 0708/3097] Do not run integration tests on PHAR for now --- .github/workflows/phar.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 04cda78057d..28d8d8b36bb 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -103,14 +103,14 @@ jobs: - name: "Delete checksum PHAR" run: "rm tmp/phpstan.phar" - - integration-tests: - if: github.event_name == 'pull_request' - needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x - with: - ref: 2.0.x - phar-checksum: ${{needs.compiler-tests.outputs.checksum}} +# +# integration-tests: +# if: github.event_name == 'pull_request' +# needs: compiler-tests +# uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x +# with: +# ref: 2.0.x +# phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' From bbf44d93d852ea1b60486b9b5541851467cdbacb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 08:46:52 +0200 Subject: [PATCH 0709/3097] Do not run integration tests on PHAR for now --- .github/workflows/phar.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index c6c4934b44b..f94cc442445 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -104,13 +104,14 @@ jobs: - name: "Delete checksum PHAR" run: "rm tmp/phpstan.phar" - integration-tests: - if: github.event_name == 'pull_request' - needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.12.x - with: - ref: 1.12.x - phar-checksum: ${{needs.compiler-tests.outputs.checksum}} +# +# integration-tests: +# if: github.event_name == 'pull_request' +# needs: compiler-tests +# uses: phpstan/phpstan/.github/workflows/integration-tests.yml@1.12.x +# with: +# ref: 1.12.x +# phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' From 40e461d2e8dff171cdf588e380eb20e782e94eb1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 10:42:49 +0200 Subject: [PATCH 0710/3097] react/http PHP 8.4 patch --- composer.json | 3 +++ composer.lock | 2 +- patches/Sender.patch | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 patches/Sender.patch diff --git a/composer.json b/composer.json index 5b816f87463..2614dcf5840 100644 --- a/composer.json +++ b/composer.json @@ -123,6 +123,9 @@ "nette/neon": [ "patches/NetteNeonStringNode.patch", "patches/NeonParser.patch" + ], + "react/http": [ + "patches/Sender.patch" ] } }, diff --git a/composer.lock b/composer.lock index 91ca3e19318..c0f104e2b7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07635a5e2bdedfa64a641735fba09971", + "content-hash": "674f9ec5e66603e465b9ef2aaa90189a", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/Sender.patch b/patches/Sender.patch new file mode 100644 index 00000000000..e01d1ff81cb --- /dev/null +++ b/patches/Sender.patch @@ -0,0 +1,11 @@ +--- src/Io/Sender.php 2024-03-27 18:20:46 ++++ src/Io/Sender.php 2024-10-14 10:19:28 +@@ -48,7 +48,7 @@ + * @param ConnectorInterface|null $connector + * @return self + */ +- public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector = null) ++ public static function createFromLoop(LoopInterface $loop, ?ConnectorInterface $connector = null) + { + if ($connector === null) { + $connector = new Connector(array(), $loop); From c90d966cfaff4818a65c5b1d2a2e4bb6e2dda0e0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Oct 2024 11:02:49 +0200 Subject: [PATCH 0711/3097] Upgrading notes about 1st party extensions --- UPGRADING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 5aae2eeb291..bc65ba774e2 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -101,6 +101,17 @@ parameters: Appending `(?)` in `ignoreErrors` is not supported. +### Changes in 1st party PHPStan extensions + +* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) + * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`) + * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`) +* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony) + * Removed legacy options with `_` in the name + * `container_xml_path` -> use `containerXmlPath` + * `constant_hassers` -> use `constantHassers` + * `console_application_loader` -> use `consoleApplicationLoader` + ### Docker images no longer tagged without a PHP version Tags without a PHP version are no longer published - `nightly`, `2`, `latest` are no longer updated. Instead, use `nightly-php8.3`, `2-php8.3`, `latest-php8.3`. You can replace `8.3` with PHP versions `8.0`-`8.3`. From 6ac62d3b00f64e72c02b7aa477e425e90f46fa65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:40:45 +0000 Subject: [PATCH 0712/3097] chore(deps): update crate-ci/typos action to v1.26.0 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 4fdafdcbd92..41282ea0695 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.25.0" + uses: "crate-ci/typos@v1.26.0" with: files: "README.md src/" From 87b0dcc44c032a74c271b62dc0940f3ab241f743 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:03:46 +0000 Subject: [PATCH 0713/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 80c2c2cd1c2..aac23594f01 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#a45eab9318f66864e9840379d3a1976ffe9b8d63", + "jetbrains/phpstorm-stubs": "dev-master#345cde8b2bdc981a07030c18fe7236153c824a05", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index bef8714c74b..c3ff064825d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bcacbb78aee0b072323001df71bce3ba", + "content-hash": "82fbdb85736d80091ff20ba37d78546d", "packages": [ { "name": "clue/ndjson-react", @@ -1442,17 +1442,17 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "a45eab9318f66864e9840379d3a1976ffe9b8d63" + "reference": "345cde8b2bdc981a07030c18fe7236153c824a05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/a45eab9318f66864e9840379d3a1976ffe9b8d63", - "reference": "a45eab9318f66864e9840379d3a1976ffe9b8d63", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/345cde8b2bdc981a07030c18fe7236153c824a05", + "reference": "345cde8b2bdc981a07030c18fe7236153c824a05", "shasum": "" }, "require-dev": { "friendsofphp/php-cs-fixer": "v3.61.1", - "nikic/php-parser": "v5.1.0", + "nikic/php-parser": "v5.3.1", "phpdocumentor/reflection-docblock": "5.4.1", "phpunit/phpunit": "11.3.0" }, @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-10-05T19:50:06+00:00" + "time": "2024-10-14T12:35:31+00:00" }, { "name": "nette/bootstrap", From 2f0fd8578fda941dabb90b820e2169b9bdfcfb2e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 16 Oct 2024 10:33:00 +0200 Subject: [PATCH 0714/3097] Remove dead code --- bin/phpstan | 9 --------- 1 file changed, 9 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index 978d755ac19..ba27551db69 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -13,15 +13,6 @@ use Symfony\Component\Console\Helper\ProgressBar; (function () { error_reporting(E_ALL); ini_set('display_errors', 'stderr'); - if (version_compare(PHP_VERSION, '7.4.0', '<')) { - // PHP earlier than 7.4.x with OpCache triggers a bug when we intercept - // custom autoloaders' reads to discover file paths. See PHPStan #4881. - ini_set('opcache.enable', 'Off'); - } - - if (PHP_VERSION_ID < 70300) { - gc_disable(); // performance boost - } define('__PHPSTAN_RUNNING__', true); From 79319b3af98491bbbfe76851753de59ad63f75c4 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:19:43 +0000 Subject: [PATCH 0715/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index aac23594f01..b57ec8dd52a 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.11", - "phpstan/php-8-stubs": "0.4.2", + "phpstan/php-8-stubs": "0.4.3", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index c3ff064825d..271b9c01923 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "82fbdb85736d80091ff20ba37d78546d", + "content-hash": "66d7fc0ee35d6ebdf822a59598a3c9f1", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.2", + "version": "0.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "64fbb357f86728a3d0a06d57178bf968bcf82206" + "reference": "b30c6975205b4b51e7d8c635f57d29b869220a9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/64fbb357f86728a3d0a06d57178bf968bcf82206", - "reference": "64fbb357f86728a3d0a06d57178bf968bcf82206", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/b30c6975205b4b51e7d8c635f57d29b869220a9e", + "reference": "b30c6975205b4b51e7d8c635f57d29b869220a9e", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.2" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.3" }, - "time": "2024-10-09T07:25:55+00:00" + "time": "2024-10-18T00:19:10+00:00" }, { "name": "phpstan/phpdoc-parser", From a815d575dc10616e510713e01627369b10980d31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 17 Oct 2024 10:36:01 +0200 Subject: [PATCH 0716/3097] Introduce TypeResult --- src/Analyser/MutatingScope.php | 4 +- .../InitializerExprTypeResolver.php | 55 +++++++++++-------- src/Type/TypeResult.php | 22 ++++++++ 3 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 src/Type/TypeResult.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5d6636683fb..401f011dc52 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -799,7 +799,7 @@ private function resolveType(string $exprString, Expr $node): Type $leftType = $this->getType($node->left); $rightType = $this->getType($node->right); - return $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType); + return $this->initializerExprTypeResolver->resolveEqualType($leftType, $rightType)->type; } if ($node instanceof Expr\BinaryOp\NotEqual) { @@ -959,7 +959,7 @@ private function resolveType(string $exprString, Expr $node): Type return new BooleanType(); } - return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType); + return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType)->type; } if ($node instanceof Expr\BinaryOp\NotIdentical) { diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8b015c91ab4..178c16b4a81 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -65,6 +65,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; +use PHPStan\Type\TypeResult; use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use PHPStan\Type\TypeWithClassName; @@ -298,7 +299,7 @@ public function getType(Expr $expr, InitializerExprContext $context): Type return $this->resolveIdenticalType( $this->getType($expr->left, $context), $this->getType($expr->right, $context), - ); + )->type; } if ($expr instanceof BinaryOp\NotIdentical) { @@ -309,7 +310,7 @@ public function getType(Expr $expr, InitializerExprContext $context): Type return $this->resolveEqualType( $this->getType($expr->left, $context), $this->getType($expr->right, $context), - ); + )->type; } if ($expr instanceof BinaryOp\NotEqual) { @@ -1349,34 +1350,42 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType); } - public function resolveIdenticalType(Type $leftType, Type $rightType): BooleanType + /** + * @return TypeResult + */ + public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResult { if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new ConstantBooleanType(false); + return new TypeResult(new ConstantBooleanType(false), []); } if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) { - return new ConstantBooleanType($leftType->getValue() === $rightType->getValue()); + return new TypeResult(new ConstantBooleanType($leftType->getValue() === $rightType->getValue()), []); } $leftTypeFiniteTypes = $leftType->getFiniteTypes(); $rightTypeFiniteType = $rightType->getFiniteTypes(); if (count($leftTypeFiniteTypes) === 1 && count($rightTypeFiniteType) === 1) { - return new ConstantBooleanType($leftTypeFiniteTypes[0]->equals($rightTypeFiniteType[0])); + return new TypeResult(new ConstantBooleanType($leftTypeFiniteTypes[0]->equals($rightTypeFiniteType[0])), []); } - if ($leftType->isSuperTypeOf($rightType)->no() && $rightType->isSuperTypeOf($leftType)->no()) { - return new ConstantBooleanType(false); + $leftIsSuperTypeOfRight = $leftType->isSuperTypeOfWithReason($rightType); + $rightIsSuperTypeOfLeft = $rightType->isSuperTypeOfWithReason($leftType); + if ($leftIsSuperTypeOfRight->no() && $rightIsSuperTypeOfLeft->no()) { + return new TypeResult(new ConstantBooleanType(false), array_merge($leftIsSuperTypeOfRight->reasons, $rightIsSuperTypeOfLeft->reasons)); } if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) { - return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveIdenticalType($leftValueType, $rightValueType)); + return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveIdenticalType($leftValueType, $rightValueType)); } - return new BooleanType(); + return new TypeResult(new BooleanType(), []); } - public function resolveEqualType(Type $leftType, Type $rightType): BooleanType + /** + * @return TypeResult + */ + public function resolveEqualType(Type $leftType, Type $rightType): TypeResult { if ( ($leftType->isEnum()->yes() && $rightType->isTrue()->no()) @@ -1386,16 +1395,17 @@ public function resolveEqualType(Type $leftType, Type $rightType): BooleanType } if ($leftType instanceof ConstantArrayType && $rightType instanceof ConstantArrayType) { - return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): BooleanType => $this->resolveEqualType($leftValueType, $rightValueType)); + return $this->resolveConstantArrayTypeComparison($leftType, $rightType, fn ($leftValueType, $rightValueType): TypeResult => $this->resolveEqualType($leftValueType, $rightValueType)); } - return $leftType->looseCompare($rightType, $this->phpVersion); + return new TypeResult($leftType->looseCompare($rightType, $this->phpVersion), []); } /** - * @param callable(Type, Type): BooleanType $valueComparisonCallback + * @param callable(Type, Type): TypeResult $valueComparisonCallback + * @return TypeResult */ - private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): BooleanType + private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, ConstantArrayType $rightType, callable $valueComparisonCallback): TypeResult { $leftKeyTypes = $leftType->getKeyTypes(); $rightKeyTypes = $rightType->getKeyTypes(); @@ -1412,7 +1422,7 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, if (count($rightKeyTypes) === 0) { if (!$leftOptional) { - return new ConstantBooleanType(false); + return new TypeResult(new ConstantBooleanType(false), []); } continue; } @@ -1425,13 +1435,13 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, $found = true; break; } elseif (!$rightType->isOptionalKey($j)) { - return new ConstantBooleanType(false); + return new TypeResult(new ConstantBooleanType(false), []); } } if (!$found) { if (!$leftOptional) { - return new ConstantBooleanType(false); + return new TypeResult(new ConstantBooleanType(false), []); } continue; } @@ -1448,21 +1458,22 @@ private function resolveConstantArrayTypeComparison(ConstantArrayType $leftType, } } - $leftIdenticalToRight = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]); + $leftIdenticalToRightResult = $valueComparisonCallback($leftValueTypes[$i], $rightValueTypes[$j]); + $leftIdenticalToRight = $leftIdenticalToRightResult->type; if ($leftIdenticalToRight->isFalse()->yes()) { - return new ConstantBooleanType(false); + return $leftIdenticalToRightResult; } $resultType = TypeCombinator::union($resultType, $leftIdenticalToRight); } foreach (array_keys($rightKeyTypes) as $j) { if (!$rightType->isOptionalKey($j)) { - return new ConstantBooleanType(false); + return new TypeResult(new ConstantBooleanType(false), []); } $resultType = new BooleanType(); } - return $resultType->toBoolean(); + return new TypeResult($resultType->toBoolean(), []); } private function callOperatorTypeSpecifyingExtensions(Expr\BinaryOp $expr, Type $leftType, Type $rightType): ?Type diff --git a/src/Type/TypeResult.php b/src/Type/TypeResult.php new file mode 100644 index 00000000000..76d79326e8a --- /dev/null +++ b/src/Type/TypeResult.php @@ -0,0 +1,22 @@ + $reasons + */ + public function __construct( + public readonly Type $type, + public readonly array $reasons, + ) + { + } + +} From 34bacd74410573cf79754348231849b474b7312e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 17 Oct 2024 10:36:12 +0200 Subject: [PATCH 0717/3097] Show TypeResult reasons in StrictComparisonOfDifferentTypesRule --- conf/config.neon | 3 + src/Analyser/DirectInternalScopeFactory.php | 2 + src/Analyser/LazyInternalScopeFactory.php | 1 + src/Analyser/MutatingScope.php | 42 +--------- src/Analyser/RicherScopeGetTypeHelper.php | 78 +++++++++++++++++++ .../StrictComparisonOfDifferentTypesRule.php | 18 ++++- src/Testing/PHPStanTestCase.php | 20 +++-- src/Type/MixedType.php | 13 +++- ...rictComparisonOfDifferentTypesRuleTest.php | 20 +++++ .../Comparison/data/strict-comparison.php | 20 +++++ .../Rules/Methods/CallMethodsRuleTest.php | 6 ++ 11 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 src/Analyser/RicherScopeGetTypeHelper.php diff --git a/conf/config.neon b/conf/config.neon index 3146a8fd3f9..5ad99397a9c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -582,6 +582,9 @@ services: arguments: cacheFilePath: %resultCachePath% + - + class: PHPStan\Analyser\RicherScopeGetTypeHelper + - class: PHPStan\Cache\Cache arguments: diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 49a3395d2e6..cbec53d0d16 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -34,6 +34,7 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, private Parser $parser, private NodeScopeResolver $nodeScopeResolver, + private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private PhpVersion $phpVersion, private bool $explicitMixedInUnknownGenericNew, private bool $explicitMixedForGlobalVariables, @@ -85,6 +86,7 @@ public function create( $this->propertyReflectionFinder, $this->parser, $this->nodeScopeResolver, + $this->richerScopeGetTypeHelper, $this->constantResolver, $context, $this->phpVersion, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index fec15217689..1e2e9386783 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -79,6 +79,7 @@ public function create( $this->container->getByType(PropertyReflectionFinder::class), $this->container->getService('currentPhpVersionSimpleParser'), $this->container->getByType(NodeScopeResolver::class), + $this->container->getByType(RicherScopeGetTypeHelper::class), $this->container->getByType(ConstantResolver::class), $context, $this->container->getByType(PhpVersion::class), diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 401f011dc52..90b3fdd4653 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -204,6 +204,7 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, private Parser $parser, private NodeScopeResolver $nodeScopeResolver, + private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private ConstantResolver $constantResolver, private ScopeContext $context, private PhpVersion $phpVersion, @@ -924,46 +925,11 @@ private function resolveType(string $exprString, Expr $node): Type } if ($node instanceof Expr\BinaryOp\Identical) { - if ( - $node->left instanceof Variable - && is_string($node->left->name) - && $node->right instanceof Variable - && is_string($node->right->name) - && $node->left->name === $node->right->name - ) { - return new ConstantBooleanType(true); - } - - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - - if ( - ( - $node->left instanceof Node\Expr\PropertyFetch - || $node->left instanceof Node\Expr\StaticPropertyFetch - ) - && $rightType->isNull()->yes() - && !$this->hasPropertyNativeType($node->left) - ) { - return new BooleanType(); - } - - if ( - ( - $node->right instanceof Node\Expr\PropertyFetch - || $node->right instanceof Node\Expr\StaticPropertyFetch - ) - && $leftType->isNull()->yes() - && !$this->hasPropertyNativeType($node->right) - ) { - return new BooleanType(); - } - - return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType)->type; + return $this->richerScopeGetTypeHelper->getIdenticalResult($this, $node)->type; } if ($node instanceof Expr\BinaryOp\NotIdentical) { - return $this->getType(new Expr\BooleanNot(new BinaryOp\Identical($node->left, $node->right))); + return $this->richerScopeGetTypeHelper->getNotIdenticalResult($this, $node)->type; } if ($node instanceof Expr\Instanceof_) { @@ -2654,7 +2620,7 @@ private function promoteNativeTypes(): self /** * @param Node\Expr\PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch */ - private function hasPropertyNativeType($propertyFetch): bool + public function hasPropertyNativeType($propertyFetch): bool { $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyFetch, $this); if ($propertyReflection === null) { diff --git a/src/Analyser/RicherScopeGetTypeHelper.php b/src/Analyser/RicherScopeGetTypeHelper.php new file mode 100644 index 00000000000..e60612f8205 --- /dev/null +++ b/src/Analyser/RicherScopeGetTypeHelper.php @@ -0,0 +1,78 @@ + + */ + public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult + { + if ( + $expr->left instanceof Variable + && is_string($expr->left->name) + && $expr->right instanceof Variable + && is_string($expr->right->name) + && $expr->left->name === $expr->right->name + ) { + return new TypeResult(new ConstantBooleanType(true), []); + } + + $leftType = $scope->getType($expr->left); + $rightType = $scope->getType($expr->right); + + if ( + ( + $expr->left instanceof Node\Expr\PropertyFetch + || $expr->left instanceof Node\Expr\StaticPropertyFetch + ) + && $rightType->isNull()->yes() + && !$scope->hasPropertyNativeType($expr->left) + ) { + return new TypeResult(new BooleanType(), []); + } + + if ( + ( + $expr->right instanceof Node\Expr\PropertyFetch + || $expr->right instanceof Node\Expr\StaticPropertyFetch + ) + && $leftType->isNull()->yes() + && !$scope->hasPropertyNativeType($expr->right) + ) { + return new TypeResult(new BooleanType(), []); + } + + return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType); + } + + /** + * @return TypeResult + */ + public function getNotIdenticalResult(Scope $scope, Node\Expr\BinaryOp\NotIdentical $expr): TypeResult + { + $identicalResult = $this->getIdenticalResult($scope, new Identical($expr->left, $expr->right)); + $identicalType = $identicalResult->type; + if ($identicalType instanceof ConstantBooleanType) { + return new TypeResult(new ConstantBooleanType(!$identicalType->getValue()), $identicalResult->reasons); + } + + return new TypeResult(new BooleanType(), []); + } + +} diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index 0db119501fd..7d9c7641325 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Comparison; use PhpParser\Node; +use PHPStan\Analyser\RicherScopeGetTypeHelper; use PHPStan\Analyser\Scope; use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; @@ -10,6 +11,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\VerbosityLevel; +use function count; use function sprintf; /** @@ -19,6 +21,7 @@ final class StrictComparisonOfDifferentTypesRule implements Rule { public function __construct( + private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private bool $checkAlwaysTrueStrictComparison, private bool $treatPhpDocTypesAsCertain, private bool $reportAlwaysTrueInLastCondition, @@ -34,11 +37,15 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node instanceof Node\Expr\BinaryOp\Identical && !$node instanceof Node\Expr\BinaryOp\NotIdentical) { + if ($node instanceof Node\Expr\BinaryOp\Identical) { + $nodeTypeResult = $this->richerScopeGetTypeHelper->getIdenticalResult($this->treatPhpDocTypesAsCertain ? $scope : $scope->doNotTreatPhpDocTypesAsCertain(), $node); + } elseif ($node instanceof Node\Expr\BinaryOp\NotIdentical) { + $nodeTypeResult = $this->richerScopeGetTypeHelper->getNotIdenticalResult($this->treatPhpDocTypesAsCertain ? $scope : $scope->doNotTreatPhpDocTypesAsCertain(), $node); + } else { return []; } - $nodeType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node); + $nodeType = $nodeTypeResult->type; if (!$nodeType instanceof ConstantBooleanType) { return []; } @@ -46,7 +53,12 @@ public function processNode(Node $node, Scope $scope): array $leftType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->left) : $scope->getNativeType($node->left); $rightType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node->right) : $scope->getNativeType($node->right); - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $nodeTypeResult): RuleErrorBuilder { + $reasons = $nodeTypeResult->reasons; + if (count($reasons) > 0) { + return $ruleErrorBuilder->acceptsReasonsTip($reasons); + } + if (!$this->treatPhpDocTypesAsCertain) { return $ruleErrorBuilder; } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 14efcc7480e..8f9fdfd44fb 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\Error; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; +use PHPStan\Analyser\RicherScopeGetTypeHelper; use PHPStan\Analyser\ScopeFactory; use PHPStan\Analyser\TypeSpecifier; use PHPStan\BetterReflection\Reflector\ClassReflector; @@ -168,18 +169,20 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames); + $initializerExprTypeResolver = new InitializerExprTypeResolver( + $constantResolver, + $reflectionProviderProvider, + $container->getByType(PhpVersion::class), + $container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), + new OversizedArrayBuilder(), + $container->getParameter('usePathConstantsAsConstantString'), + ); + return new ScopeFactory( new DirectInternalScopeFactory( MutatingScope::class, $reflectionProvider, - new InitializerExprTypeResolver( - $constantResolver, - $reflectionProviderProvider, - $container->getByType(PhpVersion::class), - $container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), - new OversizedArrayBuilder(), - $container->getParameter('usePathConstantsAsConstantString'), - ), + $initializerExprTypeResolver, $container->getByType(DynamicReturnTypeExtensionRegistryProvider::class), $container->getByType(ExpressionTypeResolverExtensionRegistryProvider::class), $container->getByType(ExprPrinter::class), @@ -187,6 +190,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider new PropertyReflectionFinder(), self::getParser(), $container->getByType(NodeScopeResolver::class), + new RicherScopeGetTypeHelper($initializerExprTypeResolver), $container->getByType(PhpVersion::class), $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], $container->getParameter('featureToggles')['explicitMixedForGlobalVariables'], diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 20117d68b32..0b9d87394ab 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -159,7 +159,18 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createMaybe(); } - return $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); + $result = $this->subtractedType->isSuperTypeOfWithReason($type)->negate(); + if ($result->no()) { + return IsSuperTypeOfResult::createNo([ + sprintf( + 'Type %s has already been eliminated from %s.', + $this->subtractedType->describe(VerbosityLevel::precise()), + $this->describe(VerbosityLevel::typeOnly()), + ), + ]); + } + + return $result; } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4721b4e78bc..96712b66b9d 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Comparison; +use PHPStan\Analyser\RicherScopeGetTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_INT_SIZE; @@ -22,6 +23,7 @@ class StrictComparisonOfDifferentTypesRuleTest extends RuleTestCase protected function getRule(): Rule { return new StrictComparisonOfDifferentTypesRule( + self::getContainer()->getByType(RicherScopeGetTypeHelper::class), $this->checkAlwaysTrueStrictComparison, $this->treatPhpDocTypesAsCertain, $this->reportAlwaysTrueInLastCondition, @@ -216,10 +218,12 @@ public function testStrictComparison(): void [ 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', 808, + 'Type 1|string has already been eliminated from mixed.', ], [ 'Strict comparison using !== between mixed and 1 will always evaluate to true.', 812, + 'Type 1|string has already been eliminated from mixed.', ], [ 'Strict comparison using === between \'foo\' and \'foo\' will always evaluate to true.', @@ -275,6 +279,16 @@ public function testStrictComparison(): void 1014, $tipText, ], + [ + 'Strict comparison using === between mixed and null will always evaluate to false.', + 1030, + 'Type null has already been eliminated from mixed.', + ], + [ + 'Strict comparison using !== between mixed and null will always evaluate to true.', + 1034, + 'Type null has already been eliminated from mixed.', + ], ], ); } @@ -419,6 +433,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void [ 'Strict comparison using === between mixed and \'foo\' will always evaluate to false.', 808, + 'Type 1|string has already been eliminated from mixed.', ], [ 'Strict comparison using === between NAN and NAN will always evaluate to false.', @@ -433,6 +448,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 1014, $tipText, ], + [ + 'Strict comparison using === between mixed and null will always evaluate to false.', + 1030, + 'Type null has already been eliminated from mixed.', + ], ], ); } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 3e05dd8b831..b6389bd5ee2 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -1017,3 +1017,23 @@ public function doFoo($a): void } } + +class SubtractedMixedAgainstNull +{ + + public function doFoo($m): void + { + if ($m === null) { + return; + } + + if ($m === null) { + + } + + if ($m !== null) { + + } + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 7a46d849d32..b9c196420d8 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -457,14 +457,17 @@ public function testCallMethods(): void [ 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', 1277, + 'Type int has already been eliminated from mixed.', ], [ 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', 1284, + 'Type int|string has already been eliminated from mixed.', ], [ 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', 1285, + 'Type int|string has already been eliminated from mixed.', ], [ 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', @@ -793,14 +796,17 @@ public function testCallMethodsOnThisOnly(): void [ 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', 1277, + 'Type int has already been eliminated from mixed.', ], [ 'Parameter #1 $i of method Test\SubtractedMixed::requireInt() expects int, mixed given.', 1284, + 'Type int|string has already been eliminated from mixed.', ], [ 'Parameter #1 $parameter of method Test\SubtractedMixed::requireIntOrString() expects int|string, mixed given.', 1285, + 'Type int|string has already been eliminated from mixed.', ], [ 'Parameter #2 $b of method Test\ExpectsExceptionGenerics::expectsExceptionUpperBound() expects Exception, Throwable given.', From dc5d8f4d3eef18b1d80b8ffb3a1adfe8de6d7268 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Oct 2024 09:20:02 +0200 Subject: [PATCH 0718/3097] Decorate reasons when comparing ConstantArrayType --- phpstan-baseline.neon | 13 +++++++++++++ src/Analyser/RicherScopeGetTypeHelper.php | 4 ++++ src/Type/Constant/ConstantArrayType.php | 2 +- .../StrictComparisonOfDifferentTypesRuleTest.php | 5 +++++ .../Rules/Comparison/data/strict-comparison.php | 14 ++++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 197663fec6b..73144c4ac14 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -66,6 +66,11 @@ parameters: count: 1 path: src/Analyser/NodeScopeResolver.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" + count: 1 + path: src/Analyser/RicherScopeGetTypeHelper.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 2 @@ -487,6 +492,14 @@ parameters: count: 2 path: src/Rules/Comparison/NumberComparisonOperatorsConstantConditionRule.php + - + message: """ + #^Call to deprecated method doNotTreatPhpDocTypesAsCertain\\(\\) of interface PHPStan\\\\Analyser\\\\Scope\\: + Use getNativeType\\(\\)$# + """ + count: 2 + path: src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 2 diff --git a/src/Analyser/RicherScopeGetTypeHelper.php b/src/Analyser/RicherScopeGetTypeHelper.php index e60612f8205..ba7c6c618a1 100644 --- a/src/Analyser/RicherScopeGetTypeHelper.php +++ b/src/Analyser/RicherScopeGetTypeHelper.php @@ -36,6 +36,10 @@ public function getIdenticalResult(Scope $scope, Identical $expr): TypeResult $leftType = $scope->getType($expr->left); $rightType = $scope->getType($expr->right); + if (!$scope instanceof MutatingScope) { + return $this->initializerExprTypeResolver->resolveIdenticalType($leftType, $rightType); + } + if ( ( $expr->left instanceof Node\Expr\PropertyFetch diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 74786c7e89f..1b770d68897 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -390,7 +390,7 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult $isValueSuperType = $this->valueTypes[$i]->isSuperTypeOfWithReason($type->getOffsetValueType($keyType)); if ($isValueSuperType->no()) { - return $isValueSuperType; + return $isValueSuperType->decorateReasons(static fn (string $reason) => sprintf('Offset %s: %s', $keyType->describe(VerbosityLevel::value()), $reason)); } $results[] = $isValueSuperType; } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 96712b66b9d..01157d82e1d 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -289,6 +289,11 @@ public function testStrictComparison(): void 1034, 'Type null has already been eliminated from mixed.', ], + [ + 'Strict comparison using !== between array{1, mixed, 3} and array{int, null, int} will always evaluate to true.', + 1048, + 'Offset 1: Type null has already been eliminated from mixed.', + ], ], ); } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index b6389bd5ee2..70882c4ca47 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -1036,4 +1036,18 @@ public function doFoo($m): void } } + public function doBar($m, int $i, int $j): void + { + if ($m === null) { + return; + } + + $a = [1, $m, 3]; + $b = [$i, null, $j]; + + if ($a !== $b) { + + } + } + } From e802b85aa2cecf243410c41464e632ca58eaa658 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Oct 2024 09:39:51 +0200 Subject: [PATCH 0719/3097] Fix --- src/Type/TypeResult.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Type/TypeResult.php b/src/Type/TypeResult.php index 76d79326e8a..18942d7080e 100644 --- a/src/Type/TypeResult.php +++ b/src/Type/TypeResult.php @@ -8,15 +8,21 @@ final class TypeResult { + public readonly Type $type; + + public readonly array $reasons; + /** * @param T $type * @param list $reasons */ public function __construct( - public readonly Type $type, - public readonly array $reasons, + Type $type, + array $reasons, ) { + $this->type = $type; + $this->reasons = $reasons; } } From 247ae64eaa4126e6fd9bca563d71ac95080392cf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Oct 2024 09:45:25 +0200 Subject: [PATCH 0720/3097] Fix --- src/Type/TypeResult.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/TypeResult.php b/src/Type/TypeResult.php index 18942d7080e..5d10bced1f1 100644 --- a/src/Type/TypeResult.php +++ b/src/Type/TypeResult.php @@ -10,6 +10,7 @@ final class TypeResult public readonly Type $type; + /** @var list */ public readonly array $reasons; /** From c875e8309c08b8ff05043acbcb16344883786f35 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Oct 2024 09:50:36 +0200 Subject: [PATCH 0721/3097] Work around simple-downgrader bug --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index 8a8a89df389..7ec0a21da25 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -202,6 +202,7 @@ + src/Type/TypeResult.php compiler/tests/*/data/ tests/*/Fixture/ From 4b3406e420e0a3bfc796de0e62226d7cbcbb6785 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 22 Oct 2024 00:03:32 +0000 Subject: [PATCH 0722/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index b57ec8dd52a..fd100caeb68 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#345cde8b2bdc981a07030c18fe7236153c824a05", + "jetbrains/phpstorm-stubs": "dev-master#f8625adce08b146bf481a0f1bbee06e82a488059", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 271b9c01923..88147fa23b1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66d7fc0ee35d6ebdf822a59598a3c9f1", + "content-hash": "b46b210764567d0dae89d02afb1c9b92", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "345cde8b2bdc981a07030c18fe7236153c824a05" + "reference": "f8625adce08b146bf481a0f1bbee06e82a488059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/345cde8b2bdc981a07030c18fe7236153c824a05", - "reference": "345cde8b2bdc981a07030c18fe7236153c824a05", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/f8625adce08b146bf481a0f1bbee06e82a488059", + "reference": "f8625adce08b146bf481a0f1bbee06e82a488059", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-10-14T12:35:31+00:00" + "time": "2024-10-20T18:41:15+00:00" }, { "name": "nette/bootstrap", From d73acef87f9d3ebd04bb6671d6b83755a8e62e8e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 22 Oct 2024 09:51:15 +0200 Subject: [PATCH 0723/3097] Fix build --- build/deprecated-8.4.neon | 7 +++++++ build/ignore-by-php-version.neon.php | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 build/deprecated-8.4.neon diff --git a/build/deprecated-8.4.neon b/build/deprecated-8.4.neon new file mode 100644 index 00000000000..6b3bd6db5e8 --- /dev/null +++ b/build/deprecated-8.4.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: '#^Use of constant E_STRICT is deprecated\.$#' + identifier: constant.deprecated + count: 1 + path: ../src/Analyser/FileAnalyser.php diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 2c808909bca..c250ea9eec5 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -29,6 +29,10 @@ $includes[] = __DIR__ . '/datetime-php-83.neon'; } +if (PHP_VERSION_ID >= 80400) { + $includes[] = __DIR__ . '/deprecated-8.4.neon'; +} + $config = []; $config['includes'] = $includes; From 11e0ab61e187127b82f24a2e88b1e137513e1744 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Oct 2024 21:37:48 +0200 Subject: [PATCH 0724/3097] Remove unnecessary `instanceof ConstantBooleanType` in loop analysis --- phpstan-baseline.neon | 2 +- src/Analyser/NodeScopeResolver.php | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 73144c4ac14..050d8a1d2c0 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -58,7 +58,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 3 + count: 2 path: src/Analyser/NodeScopeResolver.php - diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 92eae571e54..969d0be6b4e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1320,12 +1320,7 @@ private function processStmtNode( $initScope = $condResult->getScope(); $condResultScope = $condResult->getScope(); $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); - if ($condTruthiness instanceof ConstantBooleanType) { - $condTruthinessTrinary = TrinaryLogic::createFromBoolean($condTruthiness->getValue()); - } else { - $condTruthinessTrinary = TrinaryLogic::createMaybe(); - } - $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthinessTrinary); + $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); $hasYield = $hasYield || $condResult->hasYield(); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); From a07996a9cad15c0c6e6e8fd57338236734a5c0dd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 11:37:39 +0200 Subject: [PATCH 0725/3097] OffsetAccessValueAssignmentRule optimization for huge arrays --- src/Rules/Arrays/OffsetAccessValueAssignmentRule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php index 45f7b490b13..9145005d201 100644 --- a/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessValueAssignmentRule.php @@ -46,6 +46,10 @@ public function processNode(Node $node, Scope $scope): array } $arrayDimFetch = $node->var; + $varType = $scope->getType($arrayDimFetch->var); + if ($varType->isObject()->no()) { + return []; + } if ($node instanceof Assign || $node instanceof Expr\AssignRef) { $assignedValueType = $scope->getType($node->expr); From 1a0099dc61674ff1eb0ef8d68c90011f2206a64b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 11:47:14 +0200 Subject: [PATCH 0726/3097] NodeScopeResolver - refactoring before optimization --- src/Analyser/NodeScopeResolver.php | 104 ++++++++++++----------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 969d0be6b4e..afe1373704c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5055,7 +5055,7 @@ private function processAssignVar( $valueToWrite = $scope->getType($assignedExpr); $nativeValueToWrite = $scope->getNativeType($assignedExpr); $originalValueToWrite = $valueToWrite; - $originalNativeValueToWrite = $valueToWrite; + $originalNativeValueToWrite = $nativeValueToWrite; // 3. eval assigned expr $result = $processExprCallback($scope); @@ -5076,67 +5076,9 @@ private function processAssignVar( } $offsetValueType = $varType; $offsetNativeValueType = $varNativeType; - $offsetValueTypeStack = [$offsetValueType]; - $offsetValueNativeTypeStack = [$offsetNativeValueType]; - foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { - if ($offsetType === null) { - $offsetValueType = new ConstantArrayType([], []); - - } else { - $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); - if ($offsetValueType instanceof ErrorType) { - $offsetValueType = new ConstantArrayType([], []); - } - } - $offsetValueTypeStack[] = $offsetValueType; - } - foreach (array_slice($offsetNativeTypes, 0, -1) as $offsetNativeType) { - if ($offsetNativeType === null) { - $offsetNativeValueType = new ConstantArrayType([], []); - - } else { - $offsetNativeValueType = $offsetNativeValueType->getOffsetValueType($offsetNativeType); - if ($offsetNativeValueType instanceof ErrorType) { - $offsetNativeValueType = new ConstantArrayType([], []); - } - } - - $offsetValueNativeTypeStack[] = $offsetNativeValueType; - } - - foreach (array_reverse($offsetTypes) as $i => $offsetType) { - /** @var Type $offsetValueType */ - $offsetValueType = array_pop($offsetValueTypeStack); - if (!$offsetValueType instanceof MixedType) { - $types = [ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ]; - if ($offsetType !== null && $offsetType->isInteger()->yes()) { - $types[] = new StringType(); - } - $offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types)); - } - $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); - } - foreach (array_reverse($offsetNativeTypes) as $i => $offsetNativeType) { - /** @var Type $offsetNativeValueType */ - $offsetNativeValueType = array_pop($offsetValueNativeTypeStack); - if (!$offsetNativeValueType instanceof MixedType) { - $types = [ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ]; - if ($offsetNativeType !== null && $offsetNativeType->isInteger()->yes()) { - $types[] = new StringType(); - } - $offsetNativeValueType = TypeCombinator::intersect($offsetNativeValueType, TypeCombinator::union(...$types)); - } - $nativeValueToWrite = $offsetNativeValueType->setOffsetValueType($offsetNativeType, $nativeValueToWrite, $i === 0); - } + $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { @@ -5409,6 +5351,46 @@ static function (): void { return new ExpressionResult($scope, $hasYield, $throwPoints, $impurePoints); } + /** + * @param list $offsetTypes + */ + private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type $offsetValueType, Type $valueToWrite): Type + { + $offsetValueTypeStack = [$offsetValueType]; + foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { + if ($offsetType === null) { + $offsetValueType = new ConstantArrayType([], []); + + } else { + $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); + if ($offsetValueType instanceof ErrorType) { + $offsetValueType = new ConstantArrayType([], []); + } + } + + $offsetValueTypeStack[] = $offsetValueType; + } + + foreach (array_reverse($offsetTypes) as $i => $offsetType) { + /** @var Type $offsetValueType */ + $offsetValueType = array_pop($offsetValueTypeStack); + if (!$offsetValueType instanceof MixedType) { + $types = [ + new ArrayType(new MixedType(), new MixedType()), + new ObjectType(ArrayAccess::class), + new NullType(), + ]; + if ($offsetType !== null && $offsetType->isInteger()->yes()) { + $types[] = new StringType(); + } + $offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types)); + } + $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); + } + + return $valueToWrite; + } + private function unwrapAssign(Expr $expr): Expr { if ($expr instanceof Assign) { From 53a15542683e5e0fd4e8f32cb9e94290edb7c39c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 11:51:55 +0200 Subject: [PATCH 0727/3097] processAssignVar optimization for arrays --- src/Analyser/NodeScopeResolver.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index afe1373704c..f4f60df2aed 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5078,7 +5078,30 @@ private function processAssignVar( $offsetNativeValueType = $varNativeType; $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + + $nativeValueToWrite = $valueToWrite; + if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + } else { + foreach ($offsetTypes as $i => $offsetType) { + $offsetNativeType = $offsetNativeTypes[$i]; + if ($offsetType === null) { + if ($offsetNativeType !== null) { + throw new ShouldNotHappenException(); + } + + continue; + } elseif ($offsetNativeType === null) { + throw new ShouldNotHappenException(); + } + if ($offsetType->equals($offsetNativeType)) { + continue; + } + + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + break; + } + } if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { From 537c12c0c3f14371ceaf59051fc5445339857a97 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 11:15:13 +0200 Subject: [PATCH 0728/3097] Fix performance issue with big appended arrays --- .../Analyser/AnalyserIntegrationTest.php | 6 + tests/PHPStan/Analyser/data/bug-11913.php | 274 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-11913.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index b237cdd33ad..e5a45b8a102 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1448,6 +1448,12 @@ public function testBug11640(): void $this->assertNoErrors($errors); } + public function testBug11913(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-11913.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-11913.php b/tests/PHPStan/Analyser/data/bug-11913.php new file mode 100644 index 00000000000..865023c7252 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-11913.php @@ -0,0 +1,274 @@ + "AF", "name" => "Afghanistan", "d_code" => "0093"); + $countries[] = array("code" => "AL", "name" => "Albania", "d_code" => "00355"); + $countries[] = array("code" => "DZ", "name" => "Algeria", "d_code" => "00213"); + $countries[] = array("code" => "AS", "name" => "American Samoa", "d_code" => "001"); + $countries[] = array("code" => "AD", "name" => "Andorra", "d_code" => "00376"); + $countries[] = array("code" => "AO", "name" => "Angola", "d_code" => "00244"); + $countries[] = array("code" => "AI", "name" => "Anguilla", "d_code" => "001"); + $countries[] = array("code" => "AG", "name" => "Antigua", "d_code" => "001"); + $countries[] = array("code" => "AR", "name" => "Argentina", "d_code" => "0054"); + $countries[] = array("code" => "AM", "name" => "Armenia", "d_code" => "00374"); + $countries[] = array("code" => "AW", "name" => "Aruba", "d_code" => "00297"); + $countries[] = array("code" => "AU", "name" => "Australia", "d_code" => "0061"); + $countries[] = array("code" => "AT", "name" => "Austria", "d_code" => "0043"); + $countries[] = array("code" => "AZ", "name" => "Azerbaijan", "d_code" => "00994"); + $countries[] = array("code" => "BH", "name" => "Bahrain", "d_code" => "00973"); + $countries[] = array("code" => "BD", "name" => "Bangladesh", "d_code" => "00880"); + $countries[] = array("code" => "BB", "name" => "Barbados", "d_code" => "001"); + $countries[] = array("code" => "BY", "name" => "Belarus", "d_code" => "00375"); + $countries[] = array("code" => "BE", "name" => "Belgium", "d_code" => "0032"); + $countries[] = array("code" => "BZ", "name" => "Belize", "d_code" => "00501"); + $countries[] = array("code" => "BJ", "name" => "Benin", "d_code" => "00229"); + $countries[] = array("code" => "BM", "name" => "Bermuda", "d_code" => "001"); + $countries[] = array("code" => "BT", "name" => "Bhutan", "d_code" => "00975"); + $countries[] = array("code" => "BO", "name" => "Bolivia", "d_code" => "00591"); + $countries[] = array("code" => "BA", "name" => "Bosnia and Herzegovina", "d_code" => "00387"); + $countries[] = array("code" => "BW", "name" => "Botswana", "d_code" => "00267"); + $countries[] = array("code" => "BR", "name" => "Brazil", "d_code" => "0055"); + $countries[] = array("code" => "IO", "name" => "British Indian Ocean Territory", "d_code" => "00246"); + $countries[] = array("code" => "VG", "name" => "British Virgin Islands", "d_code" => "001"); + $countries[] = array("code" => "BN", "name" => "Brunei", "d_code" => "00673"); + $countries[] = array("code" => "BG", "name" => "Bulgaria", "d_code" => "00359"); + $countries[] = array("code" => "BF", "name" => "Burkina Faso", "d_code" => "00226"); + $countries[] = array("code" => "MM", "name" => "Burma Myanmar", "d_code" => "0095"); + $countries[] = array("code" => "BI", "name" => "Burundi", "d_code" => "00257"); + $countries[] = array("code" => "KH", "name" => "Cambodia", "d_code" => "00855"); + $countries[] = array("code" => "CM", "name" => "Cameroon", "d_code" => "00237"); + $countries[] = array("code" => "CA", "name" => "Canada", "d_code" => "001"); + $countries[] = array("code" => "CV", "name" => "Cape Verde", "d_code" => "00238"); + $countries[] = array("code" => "KY", "name" => "Cayman Islands", "d_code" => "001"); + $countries[] = array("code" => "CF", "name" => "Central African Republic", "d_code" => "00236"); + $countries[] = array("code" => "TD", "name" => "Chad", "d_code" => "00235"); + $countries[] = array("code" => "CL", "name" => "Chile", "d_code" => "0056"); + $countries[] = array("code" => "CN", "name" => "China", "d_code" => "0086"); + $countries[] = array("code" => "CO", "name" => "Colombia", "d_code" => "0057"); + $countries[] = array("code" => "KM", "name" => "Comoros", "d_code" => "00269"); + $countries[] = array("code" => "CK", "name" => "Cook Islands", "d_code" => "00682"); + $countries[] = array("code" => "CR", "name" => "Costa Rica", "d_code" => "00506"); + $countries[] = array("code" => "CI", "name" => "Côte d'Ivoire", "d_code" => "00225"); + $countries[] = array("code" => "HR", "name" => "Croatia", "d_code" => "00385"); + $countries[] = array("code" => "CU", "name" => "Cuba", "d_code" => "0053"); + $countries[] = array("code" => "CY", "name" => "Cyprus", "d_code" => "00357"); + $countries[] = array("code" => "CZ", "name" => "Czech Republic", "d_code" => "00420"); + $countries[] = array("code" => "CD", "name" => "Democratic Republic of Congo", "d_code" => "00243"); + $countries[] = array("code" => "DK", "name" => "Denmark", "d_code" => "0045"); + $countries[] = array("code" => "DJ", "name" => "Djibouti", "d_code" => "00253"); + $countries[] = array("code" => "DM", "name" => "Dominica", "d_code" => "001"); + $countries[] = array("code" => "DO", "name" => "Dominican Republic", "d_code" => "001"); + $countries[] = array("code" => "EC", "name" => "Ecuador", "d_code" => "00593"); + $countries[] = array("code" => "EG", "name" => "Egypt", "d_code" => "0020"); + $countries[] = array("code" => "SV", "name" => "El Salvador", "d_code" => "00503"); + $countries[] = array("code" => "GQ", "name" => "Equatorial Guinea", "d_code" => "00240"); + $countries[] = array("code" => "ER", "name" => "Eritrea", "d_code" => "00291"); + $countries[] = array("code" => "EE", "name" => "Estonia", "d_code" => "00372"); + $countries[] = array("code" => "ET", "name" => "Ethiopia", "d_code" => "00251"); + $countries[] = array("code" => "FK", "name" => "Falkland Islands", "d_code" => "00500"); + $countries[] = array("code" => "FO", "name" => "Faroe Islands", "d_code" => "00298"); + $countries[] = array("code" => "FM", "name" => "Federated States of Micronesia", "d_code" => "00691"); + $countries[] = array("code" => "FJ", "name" => "Fiji", "d_code" => "00679"); + $countries[] = array("code" => "FI", "name" => "Finland", "d_code" => "00358"); + $countries[] = array("code" => "FR", "name" => "France", "d_code" => "0033"); + $countries[] = array("code" => "GF", "name" => "French Guiana", "d_code" => "00594"); + $countries[] = array("code" => "PF", "name" => "French Polynesia", "d_code" => "00689"); + $countries[] = array("code" => "GA", "name" => "Gabon", "d_code" => "00241"); + $countries[] = array("code" => "GE", "name" => "Georgia", "d_code" => "00995"); + $countries[] = array("code" => "DE", "name" => "Germany", "d_code" => "0049"); + $countries[] = array("code" => "GH", "name" => "Ghana", "d_code" => "00233"); + $countries[] = array("code" => "GI", "name" => "Gibraltar", "d_code" => "00350"); + $countries[] = array("code" => "GR", "name" => "Greece", "d_code" => "0030"); + $countries[] = array("code" => "GL", "name" => "Greenland", "d_code" => "00299"); + $countries[] = array("code" => "GD", "name" => "Grenada", "d_code" => "001"); + $countries[] = array("code" => "GP", "name" => "Guadeloupe", "d_code" => "00590"); + $countries[] = array("code" => "GU", "name" => "Guam", "d_code" => "001"); + $countries[] = array("code" => "GT", "name" => "Guatemala", "d_code" => "00502"); + $countries[] = array("code" => "GN", "name" => "Guinea", "d_code" => "00224"); + $countries[] = array("code" => "GW", "name" => "Guinea-Bissau", "d_code" => "00245"); + $countries[] = array("code" => "GY", "name" => "Guyana", "d_code" => "00592"); + $countries[] = array("code" => "HT", "name" => "Haiti", "d_code" => "00509"); + $countries[] = array("code" => "HN", "name" => "Honduras", "d_code" => "00504"); + $countries[] = array("code" => "HK", "name" => "Hong Kong", "d_code" => "00852"); + $countries[] = array("code" => "HU", "name" => "Hungary", "d_code" => "0036"); + $countries[] = array("code" => "IS", "name" => "Iceland", "d_code" => "00354"); + $countries[] = array("code" => "IN", "name" => "India", "d_code" => "0091"); + $countries[] = array("code" => "ID", "name" => "Indonesia", "d_code" => "0062"); + $countries[] = array("code" => "IR", "name" => "Iran", "d_code" => "0098"); + $countries[] = array("code" => "IQ", "name" => "Iraq", "d_code" => "00964"); + $countries[] = array("code" => "IE", "name" => "Ireland", "d_code" => "00353"); + $countries[] = array("code" => "IL", "name" => "Israel", "d_code" => "00972"); + $countries[] = array("code" => "IT", "name" => "Italy", "d_code" => "0039"); + $countries[] = array("code" => "JM", "name" => "Jamaica", "d_code" => "001"); + $countries[] = array("code" => "JP", "name" => "Japan", "d_code" => "0081"); + $countries[] = array("code" => "JO", "name" => "Jordan", "d_code" => "00962"); + $countries[] = array("code" => "KZ", "name" => "Kazakhstan", "d_code" => "007"); + $countries[] = array("code" => "KE", "name" => "Kenya", "d_code" => "00254"); + $countries[] = array("code" => "KI", "name" => "Kiribati", "d_code" => "00686"); + $countries[] = array("code" => "XK", "name" => "Kosovo", "d_code" => "00381"); + $countries[] = array("code" => "KW", "name" => "Kuwait", "d_code" => "00965"); + $countries[] = array("code" => "KG", "name" => "Kyrgyzstan", "d_code" => "00996"); + $countries[] = array("code" => "LA", "name" => "Laos", "d_code" => "00856"); + $countries[] = array("code" => "LV", "name" => "Latvia", "d_code" => "00371"); + $countries[] = array("code" => "LB", "name" => "Lebanon", "d_code" => "00961"); + $countries[] = array("code" => "LS", "name" => "Lesotho", "d_code" => "00266"); + $countries[] = array("code" => "LR", "name" => "Liberia", "d_code" => "00231"); + $countries[] = array("code" => "LY", "name" => "Libya", "d_code" => "00218"); + $countries[] = array("code" => "LI", "name" => "Liechtenstein", "d_code" => "00423"); + $countries[] = array("code" => "LT", "name" => "Lithuania", "d_code" => "00370"); + $countries[] = array("code" => "LU", "name" => "Luxembourg", "d_code" => "00352"); + $countries[] = array("code" => "MO", "name" => "Macau", "d_code" => "00853"); + $countries[] = array("code" => "MK", "name" => "Macedonia", "d_code" => "00389"); + $countries[] = array("code" => "MG", "name" => "Madagascar", "d_code" => "00261"); + $countries[] = array("code" => "MW", "name" => "Malawi", "d_code" => "00265"); + $countries[] = array("code" => "MY", "name" => "Malaysia", "d_code" => "0060"); + $countries[] = array("code" => "MV", "name" => "Maldives", "d_code" => "00960"); + $countries[] = array("code" => "ML", "name" => "Mali", "d_code" => "00223"); + $countries[] = array("code" => "MT", "name" => "Malta", "d_code" => "00356"); + $countries[] = array("code" => "MH", "name" => "Marshall Islands", "d_code" => "00692"); + $countries[] = array("code" => "MQ", "name" => "Martinique", "d_code" => "00596"); + $countries[] = array("code" => "MR", "name" => "Mauritania", "d_code" => "00222"); + $countries[] = array("code" => "MU", "name" => "Mauritius", "d_code" => "00230"); + $countries[] = array("code" => "YT", "name" => "Mayotte", "d_code" => "00262"); + $countries[] = array("code" => "MX", "name" => "Mexico", "d_code" => "0052"); + $countries[] = array("code" => "MD", "name" => "Moldova", "d_code" => "00373"); + $countries[] = array("code" => "MC", "name" => "Monaco", "d_code" => "00377"); + $countries[] = array("code" => "MN", "name" => "Mongolia", "d_code" => "00976"); + $countries[] = array("code" => "ME", "name" => "Montenegro", "d_code" => "00382"); + $countries[] = array("code" => "MS", "name" => "Montserrat", "d_code" => "001"); + $countries[] = array("code" => "MA", "name" => "Morocco", "d_code" => "00212"); + $countries[] = array("code" => "MZ", "name" => "Mozambique", "d_code" => "00258"); + $countries[] = array("code" => "NA", "name" => "Namibia", "d_code" => "00264"); + $countries[] = array("code" => "NR", "name" => "Nauru", "d_code" => "00674"); + $countries[] = array("code" => "NP", "name" => "Nepal", "d_code" => "00977"); + $countries[] = array("code" => "NL", "name" => "Netherlands", "d_code" => "0031"); + $countries[] = array("code" => "AN", "name" => "Netherlands Antilles", "d_code" => "00599"); + $countries[] = array("code" => "NC", "name" => "New Caledonia", "d_code" => "00687"); + $countries[] = array("code" => "NZ", "name" => "New Zealand", "d_code" => "0064"); + $countries[] = array("code" => "NI", "name" => "Nicaragua", "d_code" => "00505"); + $countries[] = array("code" => "NE", "name" => "Niger", "d_code" => "00227"); + $countries[] = array("code" => "NG", "name" => "Nigeria", "d_code" => "00234"); + $countries[] = array("code" => "NU", "name" => "Niue", "d_code" => "00683"); + $countries[] = array("code" => "NF", "name" => "Norfolk Island", "d_code" => "00672"); + $countries[] = array("code" => "KP", "name" => "North Korea", "d_code" => "00850"); + $countries[] = array("code" => "MP", "name" => "Northern Mariana Islands", "d_code" => "001"); + $countries[] = array("code" => "NO", "name" => "Norway", "d_code" => "0047"); + $countries[] = array("code" => "OM", "name" => "Oman", "d_code" => "00968"); + $countries[] = array("code" => "PK", "name" => "Pakistan", "d_code" => "0092"); + $countries[] = array("code" => "PW", "name" => "Palau", "d_code" => "00680"); + $countries[] = array("code" => "PS", "name" => "Palestine", "d_code" => "00970"); + $countries[] = array("code" => "PA", "name" => "Panama", "d_code" => "00507"); + $countries[] = array("code" => "PG", "name" => "Papua New Guinea", "d_code" => "00675"); + $countries[] = array("code" => "PY", "name" => "Paraguay", "d_code" => "00595"); + $countries[] = array("code" => "PE", "name" => "Peru", "d_code" => "0051"); + $countries[] = array("code" => "PH", "name" => "Philippines", "d_code" => "0063"); + $countries[] = array("code" => "PL", "name" => "Poland", "d_code" => "0048"); + $countries[] = array("code" => "PT", "name" => "Portugal", "d_code" => "00351"); + $countries[] = array("code" => "PR", "name" => "Puerto Rico", "d_code" => "001"); + $countries[] = array("code" => "QA", "name" => "Qatar", "d_code" => "00974"); + $countries[] = array("code" => "CG", "name" => "Republic of the Congo", "d_code" => "00242"); + $countries[] = array("code" => "RE", "name" => "Réunion", "d_code" => "00262"); + $countries[] = array("code" => "RO", "name" => "Romania", "d_code" => "0040"); + $countries[] = array("code" => "RU", "name" => "Russia", "d_code" => "007"); + $countries[] = array("code" => "RW", "name" => "Rwanda", "d_code" => "00250"); + $countries[] = array("code" => "BL", "name" => "Saint Barthélemy", "d_code" => "00590"); + $countries[] = array("code" => "SH", "name" => "Saint Helena", "d_code" => "00290"); + $countries[] = array("code" => "KN", "name" => "Saint Kitts and Nevis", "d_code" => "001"); + $countries[] = array("code" => "MF", "name" => "Saint Martin", "d_code" => "00590"); + $countries[] = array("code" => "PM", "name" => "Saint Pierre and Miquelon", "d_code" => "00508"); + $countries[] = array("code" => "VC", "name" => "Saint Vincent and the Grenadines", "d_code" => "001"); + $countries[] = array("code" => "WS", "name" => "Samoa", "d_code" => "00685"); + $countries[] = array("code" => "SM", "name" => "San Marino", "d_code" => "00378"); + $countries[] = array("code" => "ST", "name" => "São Tomé and Príncipe", "d_code" => "00239"); + $countries[] = array("code" => "SA", "name" => "Saudi Arabia", "d_code" => "00966"); + $countries[] = array("code" => "SN", "name" => "Senegal", "d_code" => "00221"); + $countries[] = array("code" => "RS", "name" => "Serbia", "d_code" => "00381"); + $countries[] = array("code" => "SC", "name" => "Seychelles", "d_code" => "00248"); + $countries[] = array("code" => "SL", "name" => "Sierra Leone", "d_code" => "00232"); + $countries[] = array("code" => "SG", "name" => "Singapore", "d_code" => "0065"); + $countries[] = array("code" => "SK", "name" => "Slovakia", "d_code" => "00421"); + $countries[] = array("code" => "SI", "name" => "Slovenia", "d_code" => "00386"); + $countries[] = array("code" => "SB", "name" => "Solomon Islands", "d_code" => "00677"); + $countries[] = array("code" => "SO", "name" => "Somalia", "d_code" => "00252"); + $countries[] = array("code" => "ZA", "name" => "South Africa", "d_code" => "0027"); + $countries[] = array("code" => "KR", "name" => "South Korea", "d_code" => "0082"); + $countries[] = array("code" => "ES", "name" => "Spain", "d_code" => "0034"); + $countries[] = array("code" => "LK", "name" => "Sri Lanka", "d_code" => "0094"); + $countries[] = array("code" => "LC", "name" => "St. Lucia", "d_code" => "001"); + $countries[] = array("code" => "SD", "name" => "Sudan", "d_code" => "00249"); + $countries[] = array("code" => "SR", "name" => "Suriname", "d_code" => "00597"); + $countries[] = array("code" => "SZ", "name" => "Swaziland", "d_code" => "00268"); + $countries[] = array("code" => "SE", "name" => "Sweden", "d_code" => "0046"); + $countries[] = array("code" => "CH", "name" => "Switzerland", "d_code" => "0041"); + $countries[] = array("code" => "SY", "name" => "Syria", "d_code" => "00963"); + $countries[] = array("code" => "TW", "name" => "Taiwan", "d_code" => "00886"); + $countries[] = array("code" => "TJ", "name" => "Tajikistan", "d_code" => "00992"); + $countries[] = array("code" => "TZ", "name" => "Tanzania", "d_code" => "00255"); + $countries[] = array("code" => "TH", "name" => "Thailand", "d_code" => "0066"); + $countries[] = array("code" => "BS", "name" => "The Bahamas", "d_code" => "001"); + $countries[] = array("code" => "GM", "name" => "The Gambia", "d_code" => "00220"); + $countries[] = array("code" => "TL", "name" => "Timor-Leste", "d_code" => "00670"); + $countries[] = array("code" => "TG", "name" => "Togo", "d_code" => "00228"); + $countries[] = array("code" => "TK", "name" => "Tokelau", "d_code" => "00690"); + $countries[] = array("code" => "TO", "name" => "Tonga", "d_code" => "00676"); + $countries[] = array("code" => "TT", "name" => "Trinidad and Tobago", "d_code" => "001"); + $countries[] = array("code" => "TN", "name" => "Tunisia", "d_code" => "00216"); + $countries[] = array("code" => "TR", "name" => "Turkey", "d_code" => "0090"); + $countries[] = array("code" => "TM", "name" => "Turkmenistan", "d_code" => "00993"); + $countries[] = array("code" => "TC", "name" => "Turks and Caicos Islands", "d_code" => "001"); + $countries[] = array("code" => "TV", "name" => "Tuvalu", "d_code" => "00688"); + $countries[] = array("code" => "UG", "name" => "Uganda", "d_code" => "00256"); + $countries[] = array("code" => "UA", "name" => "Ukraine", "d_code" => "00380"); + $countries[] = array("code" => "AE", "name" => "United Arab Emirates", "d_code" => "00971"); + $countries[] = array("code" => "GB", "name" => "United Kingdom", "d_code" => "0044"); + $countries[] = array("code" => "US", "name" => "United States", "d_code" => "001"); + $countries[] = array("code" => "UY", "name" => "Uruguay", "d_code" => "00598"); + $countries[] = array("code" => "VI", "name" => "US Virgin Islands", "d_code" => "001"); + $countries[] = array("code" => "UZ", "name" => "Uzbekistan", "d_code" => "00998"); + $countries[] = array("code" => "VU", "name" => "Vanuatu", "d_code" => "00678"); + $countries[] = array("code" => "VA", "name" => "Vatican City", "d_code" => "0039"); + $countries[] = array("code" => "VE", "name" => "Venezuela", "d_code" => "0058"); + $countries[] = array("code" => "VN", "name" => "Vietnam", "d_code" => "0084"); + $countries[] = array("code" => "WF", "name" => "Wallis and Futuna", "d_code" => "00681"); + $countries[] = array("code" => "YE", "name" => "Yemen", "d_code" => "00967"); + $countries[] = array("code" => "ZM", "name" => "Zambia", "d_code" => "00260"); + $countries[] = array("code" => "ZW", "name" => "Zimbabwe", "d_code" => "00263"); + return $countries; + } +} From c04555b9387dea448a5649348546e9629063dda8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 12:15:51 +0200 Subject: [PATCH 0729/3097] Another micro optimization --- src/Rules/RuleLevelHelper.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 0f156836060..ce937ac0d0d 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -14,6 +14,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; +use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StaticType; use PHPStan\Type\StrictMixedType; @@ -497,7 +498,15 @@ private function findTypeToCheckImplementation( } $tip = null; - if (str_contains($type->describe(VerbosityLevel::typeOnly()), 'PhpParser\\Node\\Arg|PhpParser\\Node\\VariadicPlaceholder') && !$unionTypeCriteriaCallback($type)) { + if ( + $type instanceof UnionType + && count($type->getTypes()) === 2 + && $type->getTypes()[0] instanceof ObjectType + && $type->getTypes()[1] instanceof ObjectType + && $type->getTypes()[0]->getClassName() === 'PhpParser\\Node\\Arg' + && $type->getTypes()[1]->getClassName() === 'PhpParser\\Node\\VariadicPlaceholder' + && !$unionTypeCriteriaCallback($type) + ) { $tip = 'Use ->getArgs() instead of ->args.'; } From 4dfbe16ed9cdf0808027f3bcbdb26980ec39df3f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 12:36:22 +0200 Subject: [PATCH 0730/3097] Optimization of huge unions of oversized arrays --- src/Type/TypeCombinator.php | 40 ++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index d27fd6fbdb6..f1860d1abbe 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -780,8 +780,10 @@ private static function optimizeConstantArrays(array $types): array } $results = []; + $eachIsOversized = true; foreach ($types as $type) { - $results[] = TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + $isOversized = false; + $result = TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$isOversized): Type { if (!$type instanceof ConstantArrayType) { return $traverse($type); } @@ -790,6 +792,8 @@ private static function optimizeConstantArrays(array $types): array return $type; } + $isOversized = true; + $isList = true; $valueTypes = []; $keyTypes = []; @@ -808,8 +812,8 @@ private static function optimizeConstantArrays(array $types): array $keyTypes[$generalizedKeyType->describe(VerbosityLevel::precise())] = $generalizedKeyType; $innerValueType = $type->getValueTypes()[$i]; - $generalizedValueType = TypeTraverser::map($innerValueType, static function (Type $type, callable $innerTraverse) use ($traverse): Type { - if ($type instanceof ArrayType) { + $generalizedValueType = TypeTraverser::map($innerValueType, static function (Type $type) use ($traverse): Type { + if ($type instanceof ArrayType || $type instanceof ConstantArrayType) { return TypeCombinator::intersect($type, new OversizedArrayType()); } @@ -828,6 +832,36 @@ private static function optimizeConstantArrays(array $types): array return TypeCombinator::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType()); }); + + if (!$isOversized) { + $eachIsOversized = false; + } + + $results[] = $result; + } + + if ($eachIsOversized) { + $eachIsList = true; + $keyTypes = []; + $valueTypes = []; + foreach ($results as $result) { + $keyTypes[] = $result->getIterableKeyType(); + $valueTypes[] = $result->getLastIterableValueType(); + if ($result->isList()->yes()) { + continue; + } + $eachIsList = false; + } + + $keyType = self::union(...array_values($keyTypes)); + $valueType = self::union(...array_values($valueTypes)); + + $arrayType = new ArrayType($keyType, $valueType); + if ($eachIsList) { + $arrayType = self::intersect($arrayType, new AccessoryArrayListType()); + } + + return [self::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType())]; } return $results; From 61c1c85751676b30b15594c97a4fafee7da77ee7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 12:38:20 +0200 Subject: [PATCH 0731/3097] Fix CS --- src/Rules/RuleLevelHelper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index ce937ac0d0d..71c348f7eca 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -27,7 +27,6 @@ use function array_merge; use function count; use function sprintf; -use function str_contains; final class RuleLevelHelper { From 34fb83ddbecc9cd4ce5fcad661011846bfb185ee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 13:02:21 +0200 Subject: [PATCH 0732/3097] Revert "processAssignVar optimization for arrays" This reverts commit 5d2b6244dd2e17fb078037b56a91cf3982e974ea. --- src/Analyser/NodeScopeResolver.php | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f4f60df2aed..afe1373704c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5078,30 +5078,7 @@ private function processAssignVar( $offsetNativeValueType = $varNativeType; $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); - - $nativeValueToWrite = $valueToWrite; - if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); - } else { - foreach ($offsetTypes as $i => $offsetType) { - $offsetNativeType = $offsetNativeTypes[$i]; - if ($offsetType === null) { - if ($offsetNativeType !== null) { - throw new ShouldNotHappenException(); - } - - continue; - } elseif ($offsetNativeType === null) { - throw new ShouldNotHappenException(); - } - if ($offsetType->equals($offsetNativeType)) { - continue; - } - - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); - break; - } - } + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { From c54b257dc9c85bc3fba732068a4dcfe16d65996b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 13:08:01 +0200 Subject: [PATCH 0733/3097] Fix build --- phpstan-baseline.neon | 5 +++++ src/Type/TypeCombinator.php | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 050d8a1d2c0..1bdc95b954b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -635,6 +635,11 @@ parameters: count: 2 path: src/Rules/RuleLevelHelper.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + count: 2 + path: src/Rules/RuleLevelHelper.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectWithoutClassType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) instead\\.$#" count: 1 diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index f1860d1abbe..ccbb29f97b9 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -813,7 +813,7 @@ private static function optimizeConstantArrays(array $types): array $innerValueType = $type->getValueTypes()[$i]; $generalizedValueType = TypeTraverser::map($innerValueType, static function (Type $type) use ($traverse): Type { - if ($type instanceof ArrayType || $type instanceof ConstantArrayType) { + if ($type instanceof ArrayType) { return TypeCombinator::intersect($type, new OversizedArrayType()); } @@ -853,8 +853,8 @@ private static function optimizeConstantArrays(array $types): array $eachIsList = false; } - $keyType = self::union(...array_values($keyTypes)); - $valueType = self::union(...array_values($valueTypes)); + $keyType = self::union(...$keyTypes); + $valueType = self::union(...$valueTypes); $arrayType = new ArrayType($keyType, $valueType); if ($eachIsList) { From 16f63b3a48105ddce37d816e157f004fe9164cf9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 11:51:55 +0200 Subject: [PATCH 0734/3097] processAssignVar optimization for arrays --- src/Analyser/NodeScopeResolver.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index afe1373704c..f4f60df2aed 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5078,7 +5078,30 @@ private function processAssignVar( $offsetNativeValueType = $varNativeType; $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + + $nativeValueToWrite = $valueToWrite; + if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + } else { + foreach ($offsetTypes as $i => $offsetType) { + $offsetNativeType = $offsetNativeTypes[$i]; + if ($offsetType === null) { + if ($offsetNativeType !== null) { + throw new ShouldNotHappenException(); + } + + continue; + } elseif ($offsetNativeType === null) { + throw new ShouldNotHappenException(); + } + if ($offsetType->equals($offsetNativeType)) { + continue; + } + + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + break; + } + } if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { From b0df8498353291d982553306005b4669d50a8ff4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 13:10:49 +0200 Subject: [PATCH 0735/3097] Fix CS --- src/Rules/RuleLevelHelper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 81d166902fa..416583546f2 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -21,7 +21,6 @@ use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; -use PHPStan\Type\VerbosityLevel; use function count; use function sprintf; From deef91983766dd61c18d4f9d819ffc94fb701cd5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 15:31:26 +0200 Subject: [PATCH 0736/3097] Avoid new HasOffsetValueType being intersected with oversized array --- phpstan-baseline.neon | 10 ++++++++ .../Constant/ConstantArrayTypeBuilder.php | 2 ++ src/Type/IntersectionType.php | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1bdc95b954b..1521bbb1a04 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1170,6 +1170,11 @@ parameters: count: 3 path: src/Type/IntersectionType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\ArrayType is error\\-prone and deprecated\\. Use Type\\:\\:isArray\\(\\) or Type\\:\\:getArrays\\(\\) instead\\.$#" + count: 1 + path: src/Type/IntersectionType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\BooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isBoolean\\(\\) instead\\.$#" count: 1 @@ -1180,6 +1185,11 @@ parameters: count: 2 path: src/Type/IntersectionType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" + count: 1 + path: src/Type/IntersectionType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 4 diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index a306833e8d0..e5caa1117ad 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -128,6 +128,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt if (count($this->keyTypes) > self::ARRAY_COUNT_LIMIT) { $this->degradeToGeneralArray = true; + $this->oversized = true; } return; @@ -194,6 +195,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt if (count($this->keyTypes) > self::ARRAY_COUNT_LIMIT) { $this->degradeToGeneralArray = true; + $this->oversized = true; } return; diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 140e45a8edd..6a8d2ab329d 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -28,6 +28,7 @@ use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -722,6 +723,30 @@ public function getOffsetValueType(Type $offsetType): Type public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { + if ($this->isOversizedArray()->yes()) { + return $this->intersectTypes(static function (Type $type) use ($offsetType, $valueType, $unionValues): Type { + // avoid new HasOffsetValueType being intersected with oversized array + if (!$type instanceof ArrayType) { + return $type->setOffsetValueType($offsetType, $valueType, $unionValues); + } + + if (!$offsetType instanceof ConstantStringType && !$offsetType instanceof ConstantIntegerType) { + return $type->setOffsetValueType($offsetType, $valueType, $unionValues); + } + + if (!$offsetType->isSuperTypeOf($type->getKeyType())->yes()) { + return $type->setOffsetValueType($offsetType, $valueType, $unionValues); + } + + return TypeCombinator::intersect( + new ArrayType( + TypeCombinator::union($type->getKeyType(), $offsetType), + TypeCombinator::union($type->getItemType(), $valueType), + ), + new NonEmptyArrayType(), + ); + }); + } return $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues)); } From a960f74d4f7796b241b50dbc6553b14449fe21bc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 15:34:18 +0200 Subject: [PATCH 0737/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/10847 --- .../Analyser/AnalyserIntegrationTest.php | 6 + tests/PHPStan/Analyser/data/bug-10847.php | 880 ++++++++++++++++++ 2 files changed, 886 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-10847.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e5a45b8a102..f80796bce58 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1333,6 +1333,12 @@ public function testBug10538(): void $this->assertNoErrors($errors); } + public function testBug10847(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-10847.php'); + $this->assertNoErrors($errors); + } + public function testBug10772(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/PHPStan/Analyser/data/bug-10847.php b/tests/PHPStan/Analyser/data/bug-10847.php new file mode 100644 index 00000000000..6a3dd0bbb0c --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-10847.php @@ -0,0 +1,880 @@ +): Value>, url: string}> + */ + private const BUILTIN_FUNCTIONS = [ + // sass:color + 'red' => ['overloads' => ['$color' => [ColorFunctions::class, 'red']], 'url' => 'sass:color'], + 'green' => ['overloads' => ['$color' => [ColorFunctions::class, 'green']], 'url' => 'sass:color'], + 'blue' => ['overloads' => ['$color' => [ColorFunctions::class, 'blue']], 'url' => 'sass:color'], + 'mix' => ['overloads' => ['$color1, $color2, $weight: 50%' => [ColorFunctions::class, 'mix']], 'url' => 'sass:color'], + 'rgb' => ['overloads' => [ + '$red, $green, $blue, $alpha' => [ColorFunctions::class, 'rgb'], + '$red, $green, $blue' => [ColorFunctions::class, 'rgb'], + '$color, $alpha' => [ColorFunctions::class, 'rgbTwoArgs'], + '$channels' => [ColorFunctions::class, 'rgbOneArgs'], + ], 'url' => 'sass:color'], + 'rgba' => ['overloads' => [ + '$red, $green, $blue, $alpha' => [ColorFunctions::class, 'rgba'], + '$red, $green, $blue' => [ColorFunctions::class, 'rgba'], + '$color, $alpha' => [ColorFunctions::class, 'rgbaTwoArgs'], + '$channels' => [ColorFunctions::class, 'rgbaOneArgs'], + ], 'url' => 'sass:color'], + 'invert' => ['overloads' => ['$color, $weight: 100%' => [ColorFunctions::class, 'invert']], 'url' => 'sass:color'], + 'hue' => ['overloads' => ['$color' => [ColorFunctions::class, 'hue']], 'url' => 'sass:color'], + 'saturation' => ['overloads' => ['$color' => [ColorFunctions::class, 'saturation']], 'url' => 'sass:color'], + 'lightness' => ['overloads' => ['$color' => [ColorFunctions::class, 'lightness']], 'url' => 'sass:color'], + 'complement' => ['overloads' => ['$color' => [ColorFunctions::class, 'complement']], 'url' => 'sass:color'], + 'hsl' => ['overloads' => [ + '$hue, $saturation, $lightness, $alpha' => [ColorFunctions::class, 'hsl'], + '$hue, $saturation, $lightness' => [ColorFunctions::class, 'hsl'], + '$hue, $saturation' => [ColorFunctions::class, 'hslTwoArgs'], + '$channels' => [ColorFunctions::class, 'hslOneArgs'], + ], 'url' => 'sass:color'], + 'hsla' => ['overloads' => [ + '$hue, $saturation, $lightness, $alpha' => [ColorFunctions::class, 'hsla'], + '$hue, $saturation, $lightness' => [ColorFunctions::class, 'hsla'], + '$hue, $saturation' => [ColorFunctions::class, 'hslaTwoArgs'], + '$channels' => [ColorFunctions::class, 'hslaOneArgs'], + ], 'url' => 'sass:color'], + 'grayscale' => ['overloads' => ['$color' => [ColorFunctions::class, 'grayscale']], 'url' => 'sass:color'], + 'adjust-hue' => ['overloads' => ['$color, $degrees' => [ColorFunctions::class, 'adjustHue']], 'url' => 'sass:color'], + 'lighten' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'lighten']], 'url' => 'sass:color'], + 'darken' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'darken']], 'url' => 'sass:color'], + 'saturate' => ['overloads' => [ + '$amount' => [ColorFunctions::class, 'saturateCss'], + '$color, $amount' => [ColorFunctions::class, 'saturate'], + ], 'url' => 'sass:color'], + 'desaturate' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'desaturate']], 'url' => 'sass:color'], + 'opacify' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'opacify']], 'url' => 'sass:color'], + 'fade-in' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'opacify']], 'url' => 'sass:color'], + 'transparentize' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'transparentize']], 'url' => 'sass:color'], + 'fade-out' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'transparentize']], 'url' => 'sass:color'], + 'alpha' => ['overloads' => [ + '$color' => [ColorFunctions::class, 'alpha'], + '$args...' => [ColorFunctions::class, 'alphaMicrosoft'], + ], 'url' => 'sass:color'], + 'opacity' => ['overloads' => ['$color' => [ColorFunctions::class, 'opacity']], 'url' => 'sass:color'], + 'ie-hex-str' => ['overloads' => ['$color' => [ColorFunctions::class, 'ieHexStr']], 'url' => 'sass:color'], + 'adjust-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'adjust']], 'url' => 'sass:color'], + 'scale-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'scale']], 'url' => 'sass:color'], + 'change-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'change']], 'url' => 'sass:color'], + // sass:list + 'length' => ['overloads' => ['$list' => [ListFunctions::class, 'length']], 'url' => 'sass:list'], + 'nth' => ['overloads' => ['$list, $n' => [ListFunctions::class, 'nth']], 'url' => 'sass:list'], + 'set-nth' => ['overloads' => ['$list, $n, $value' => [ListFunctions::class, 'setNth']], 'url' => 'sass:list'], + 'join' => ['overloads' => ['$list1, $list2, $separator: auto, $bracketed: auto' => [ListFunctions::class, 'join']], 'url' => 'sass:list'], + 'append' => ['overloads' => ['$list, $val, $separator: auto' => [ListFunctions::class, 'append']], 'url' => 'sass:list'], + 'zip' => ['overloads' => ['$lists...' => [ListFunctions::class, 'zip']], 'url' => 'sass:list'], + 'index' => ['overloads' => ['$list, $value' => [ListFunctions::class, 'index']], 'url' => 'sass:list'], + 'is-bracketed' => ['overloads' => ['$list' => [ListFunctions::class, 'isBracketed']], 'url' => 'sass:list'], + 'list-separator' => ['overloads' => ['$list' => [ListFunctions::class, 'separator']], 'url' => 'sass:list'], + // sass:map + 'map-get' => ['overloads' => ['$map, $key, $keys...' => [MapFunctions::class, 'get']], 'url' => 'sass:map'], + 'map-merge' => ['overloads' => [ + '$map1, $map2' => [MapFunctions::class, 'mergeTwoArgs'], + '$map1, $args...' => [MapFunctions::class, 'mergeVariadic'], + ], 'url' => 'sass:map'], + 'map-remove' => ['overloads' => [ + // Because the signature below has an explicit `$key` argument, it doesn't + // allow zero keys to be passed. We want to allow that case, so we add an + // explicit overload for it. + '$map' => [MapFunctions::class, 'removeNoKeys'], + // The first argument has special handling so that the $key parameter can be + // passed by name. + '$map, $key, $keys...' => [MapFunctions::class, 'remove'], + ], 'url' => 'sass:map'], + 'map-keys' => ['overloads' => ['$map' => [MapFunctions::class, 'keys']], 'url' => 'sass:map'], + 'map-values' => ['overloads' => ['$map' => [MapFunctions::class, 'values']], 'url' => 'sass:map'], + 'map-has-key' => ['overloads' => ['map, $key, $keys...' => [MapFunctions::class, 'hasKey']], 'url' => 'sass:map'], + // sass:math + 'abs' => ['overloads' => ['$number' => [MathFunctions::class, 'abs']], 'url' => 'sass:math'], + 'ceil' => ['overloads' => ['$number' => [MathFunctions::class, 'ceil']], 'url' => 'sass:math'], + 'floor' => ['overloads' => ['$number' => [MathFunctions::class, 'floor']], 'url' => 'sass:math'], + 'max' => ['overloads' => ['$numbers...' => [MathFunctions::class, 'max']], 'url' => 'sass:math'], + 'min' => ['overloads' => ['$numbers...' => [MathFunctions::class, 'min']], 'url' => 'sass:math'], + 'random' => ['overloads' => ['$limit: null' => [MathFunctions::class, 'random']], 'url' => 'sass:math'], + 'percentage' => ['overloads' => ['$number' => [MathFunctions::class, 'percentage']], 'url' => 'sass:math'], + 'round' => ['overloads' => ['$number' => [MathFunctions::class, 'round']], 'url' => 'sass:math'], + 'unit' => ['overloads' => ['$number' => [MathFunctions::class, 'unit']], 'url' => 'sass:math'], + 'comparable' => ['overloads' => ['$number1, $number2' => [MathFunctions::class, 'compatible']], 'url' => 'sass:math'], + 'unitless' => ['overloads' => ['$number' => [MathFunctions::class, 'isUnitless']], 'url' => 'sass:math'], + // sass:meta + 'feature-exists' => ['overloads' => ['$feature' => [MetaFunctions::class, 'featureExists']], 'url' => 'sass:meta'], + 'inspect' => ['overloads' => ['$value' => [MetaFunctions::class, 'inspect']], 'url' => 'sass:meta'], + 'type-of' => ['overloads' => ['$value' => [MetaFunctions::class, 'typeof']], 'url' => 'sass:meta'], + // sass:selector + 'is-superselector' => ['overloads' => ['$super, $sub' => [SelectorFunctions::class, 'isSuperselector']], 'url' => 'sass:selector'], + 'simple-selectors' => ['overloads' => ['$selector' => [SelectorFunctions::class, 'simpleSelectors']], 'url' => 'sass:selector'], + 'selector-parse' => ['overloads' => ['$selector' => [SelectorFunctions::class, 'parse']], 'url' => 'sass:selector'], + 'selector-nest' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'nest']], 'url' => 'sass:selector'], + 'selector-append' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'append']], 'url' => 'sass:selector'], + 'selector-extend' => ['overloads' => ['$selector, $extendee, $extender' => [SelectorFunctions::class, 'extend']], 'url' => 'sass:selector'], + 'selector-replace' => ['overloads' => ['$selector, $original, $replacement' => [SelectorFunctions::class, 'replace']], 'url' => 'sass:selector'], + 'selector-unify' => ['overloads' => ['$selector1, $selector2' => [SelectorFunctions::class, 'unify']], 'url' => 'sass:selector'], + // sass:string + 'unquote' => ['overloads' => ['$string' => [StringFunctions::class, 'unquote']], 'url' => 'sass:string'], + 'quote' => ['overloads' => ['$string' => [StringFunctions::class, 'quote']], 'url' => 'sass:string'], + 'to-upper-case' => ['overloads' => ['$string' => [StringFunctions::class, 'toUpperCase']], 'url' => 'sass:string'], + 'to-lower-case' => ['overloads' => ['$string' => [StringFunctions::class, 'toLowerCase']], 'url' => 'sass:string'], + 'uniqueId' => ['overloads' => ['' => [StringFunctions::class, 'uniqueId']], 'url' => 'sass:string'], + 'str-length' => ['overloads' => ['$string' => [StringFunctions::class, 'length']], 'url' => 'sass:string'], + 'str-insert' => ['overloads' => ['$string, $insert, $index' => [StringFunctions::class, 'insert']], 'url' => 'sass:string'], + 'str-index' => ['overloads' => ['$string, $substring' => [StringFunctions::class, 'index']], 'url' => 'sass:string'], + 'str-slice' => ['overloads' => ['$string, $start-at, $end-at: -1' => [StringFunctions::class, 'slice']], 'url' => 'sass:string'], + ]; + + public static function has(string $name): bool + { + return isset(self::BUILTIN_FUNCTIONS[$name]); + } + + public static function get(string $name): BuiltInCallable + { + if (!isset(self::BUILTIN_FUNCTIONS[$name])) { + throw new \InvalidArgumentException("There is no builtin function named $name."); + } + + return BuiltInCallable::overloadedFunction($name, self::BUILTIN_FUNCTIONS[$name]['overloads'], self::BUILTIN_FUNCTIONS[$name]['url']); + } +} + +abstract class Value {} + +class BuiltInCallable +{ + /** + * @param array): Value> $overloads + */ + public static function overloadedFunction(string $name, array $overloads, ?string $url = null): BuiltInCallable + { + $processedOverloads = []; + + foreach ($overloads as $args => $callback) { + $overloads[] = [ + $args, + $callback, + ]; + } + + return new BuiltInCallable($name, $processedOverloads, $url); + } + + /** + * @param list): Value}> $overloads + */ + private function __construct(public readonly string $name, public readonly array $overloads, public readonly ?string $url) + { + } +} + +/** + * @internal + */ +class ColorFunctions +{ + /** + * @param list $arguments + */ + public static function rgb(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function rgbTwoArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function rgbOneArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function rgba(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function rgbaTwoArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function rgbaOneArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function invert(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hsl(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hslTwoArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hslOneArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hsla(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hslaTwoArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hslaOneArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function grayscale(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function adjustHue(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function lighten(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function darken(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function saturateCss(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function saturate(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function desaturate(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function alpha(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function alphaMicrosoft(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function opacity(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function red(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function green(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function blue(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function mix(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hue(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function saturation(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function lightness(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function complement(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function adjust(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function scale(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function change(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function ieHexStr(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function opacify(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function transparentize(array $arguments): Value + { + return $arguments[0]; + } +} +class ListFunctions +{ + /** + * @param list $arguments + */ + public static function length(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function nth(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function setNth(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function join(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function append(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function zip(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function index(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function separator(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function isBracketed(array $arguments): Value + { + return $arguments[0]; + } +} +class MapFunctions +{ + /** + * @param list $arguments + */ + public static function get(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function mergeTwoArgs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function mergeVariadic(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function removeNoKeys(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function remove(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function keys(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function values(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function hasKey(array $arguments): Value + { + return $arguments[0]; + } +} +final class MathFunctions +{ + /** + * @param list $arguments + */ + public static function abs(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function ceil(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function floor(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function max(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function min(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function round(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function compatible(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function isUnitless(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function unit(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function percentage(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function random(array $arguments): Value + { + return $arguments[0]; + } +} +final class MetaFunctions +{ + /** + * @param list $arguments + */ + public static function featureExists(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function inspect(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function typeof(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function keywords(array $arguments): Value + { + return $arguments[0]; + } +} +final class SelectorFunctions +{ + /** + * @param list $arguments + */ + public static function nest(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function append(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function extend(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function replace(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function unify(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function isSuperselector(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function simpleSelectors(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function parse(array $arguments): Value + { + return $arguments[0]; + } +} +final class StringFunctions +{ + /** + * @param list $arguments + */ + public static function unquote(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function quote(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function length(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function insert(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function index(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function slice(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function toUpperCase(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function toLowerCase(array $arguments): Value + { + return $arguments[0]; + } + + /** + * @param list $arguments + */ + public static function uniqueId(array $arguments): Value + { + return $arguments[0]; + } +} From 5d2512ae1e570d4961b76cf1449dba6d75aa039b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 15:40:27 +0200 Subject: [PATCH 0738/3097] Fix build --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index f80796bce58..ebdf3a03e41 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1335,6 +1335,10 @@ public function testBug10538(): void public function testBug10847(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-10847.php'); $this->assertNoErrors($errors); } From 78690539f0590860dd4f1ffb35f5f524e877a3ad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Oct 2024 16:16:44 +0200 Subject: [PATCH 0739/3097] Fix --- src/Analyser/NodeScopeResolver.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index f4f60df2aed..5831cdaaaf8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5079,10 +5079,10 @@ private function processAssignVar( $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); - $nativeValueToWrite = $valueToWrite; if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); } else { + $rewritten = false; foreach ($offsetTypes as $i => $offsetType) { $offsetNativeType = $offsetNativeTypes[$i]; if ($offsetType === null) { @@ -5099,8 +5099,13 @@ private function processAssignVar( } $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + $rewritten = true; break; } + + if (!$rewritten) { + $nativeValueToWrite = $valueToWrite; + } } if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { From f361e354a91cad1b05f8e196172cf6245aace77f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 02:01:36 +0000 Subject: [PATCH 0740/3097] Update crate-ci/typos action to v1.26.8 --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 41282ea0695..8dadf24aba7 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.26.0" + uses: "crate-ci/typos@v1.26.8" with: files: "README.md src/" From 5797c27e0d7e943b67b717f023e2ac2fd8a25140 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:03:56 +0000 Subject: [PATCH 0741/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index fd100caeb68..a14b805eaaf 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#f8625adce08b146bf481a0f1bbee06e82a488059", + "jetbrains/phpstorm-stubs": "dev-master#08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 88147fa23b1..caf98eda55f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b46b210764567d0dae89d02afb1c9b92", + "content-hash": "092bad73652f5dc799eca247aaeb21db", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "f8625adce08b146bf481a0f1bbee06e82a488059" + "reference": "08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/f8625adce08b146bf481a0f1bbee06e82a488059", - "reference": "f8625adce08b146bf481a0f1bbee06e82a488059", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", + "reference": "08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-10-20T18:41:15+00:00" + "time": "2024-10-28T16:40:57+00:00" }, { "name": "nette/bootstrap", From 277e34b23a43b7ff0cca0c141789d0a786690e9b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 30 Oct 2024 12:58:38 +0100 Subject: [PATCH 0742/3097] Exclude E_DEPRECATED from error_reporting This error is filtered in https://github.com/phpstan/phpstan-src/blob/5797c27e0d7e943b67b717f023e2ac2fd8a25140/src/Analyser/FileAnalyser.php#L329 anyway. But it might still be seen when the deprecation is triggered by one of autoload.files in included Composer autoloaders. --- bin/phpstan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/phpstan b/bin/phpstan index 8d6cd25cd5d..03a1a8aad74 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -11,7 +11,7 @@ use PHPStan\Internal\ComposerHelper; use Symfony\Component\Console\Helper\ProgressBar; (function () { - error_reporting(E_ALL); + error_reporting(E_ALL & ~E_DEPRECATED); ini_set('display_errors', 'stderr'); if (version_compare(PHP_VERSION, '7.4.0', '<')) { // PHP earlier than 7.4.x with OpCache triggers a bug when we intercept From 8fbcf5bdc6250cb4da57ad5945f371bb0c65f931 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 30 Oct 2024 20:18:04 +0100 Subject: [PATCH 0743/3097] More precise types in immediately invoked callables --- conf/config.neon | 5 +++ src/Analyser/MutatingScope.php | 6 ++- src/Analyser/NodeScopeResolver.php | 6 +-- .../ImmediatelyInvokedClosureVisitor.php | 22 ++++++++++ tests/PHPStan/Analyser/nsrt/bug-11561.php | 40 +++++++++++++++++++ 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/Parser/ImmediatelyInvokedClosureVisitor.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11561.php diff --git a/conf/config.neon b/conf/config.neon index 5ad99397a9c..7c88f87a31c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -733,6 +733,11 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\ImmediatelyInvokedClosureVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Parallel\ParallelAnalyser arguments: diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 90b3fdd4653..2724ba1fcab 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -46,6 +46,7 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\PropertyAssignNode; use PHPStan\Parser\ArrayMapArgVisitor; +use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; @@ -4794,6 +4795,7 @@ private function processFinallyScopeVariableTypeHolders( * @param Expr\ClosureUse[] $byRefUses */ public function processClosureScope( + Expr\Closure $expr, self $closureScope, ?self $prevScope, array $byRefUses, @@ -4826,7 +4828,9 @@ public function processClosureScope( $prevVariableType = $prevScope->getVariableType($variableName); if (!$variableType->equals($prevVariableType)) { $variableType = TypeCombinator::union($variableType, $prevVariableType); - $variableType = self::generalizeType($variableType, $prevVariableType, 0); + if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true) { + $variableType = self::generalizeType($variableType, $prevVariableType, 0); + } } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 5831cdaaaf8..fd6ac13fc3b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4232,7 +4232,7 @@ private function processClosureNode( } $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); + $closureScope = $closureScope->processClosureScope($expr, $scope, null, $byRefUses); $closureType = $closureScope->getAnonymousFunctionReflection(); if (!$closureType instanceof ClosureType) { throw new ShouldNotHappenException(); @@ -4302,7 +4302,7 @@ private function processClosureNode( $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); } $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); + $closureScope = $closureScope->processClosureScope($expr, $intermediaryClosureScope, $prevScope, $byRefUses); if ($closureScope->equals($prevScope)) { break; } @@ -4322,7 +4322,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope->processClosureScope($closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); + return new ProcessClosureResult($scope->processClosureScope($expr, $closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); } /** diff --git a/src/Parser/ImmediatelyInvokedClosureVisitor.php b/src/Parser/ImmediatelyInvokedClosureVisitor.php new file mode 100644 index 00000000000..c77059e2145 --- /dev/null +++ b/src/Parser/ImmediatelyInvokedClosureVisitor.php @@ -0,0 +1,22 @@ +name instanceof Node\Expr\Closure) { + $node->name->setAttribute(self::ATTRIBUTE_NAME, true); + } + + return null; + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-11561.php b/tests/PHPStan/Analyser/nsrt/bug-11561.php new file mode 100644 index 00000000000..1a01e5f97ad --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11561.php @@ -0,0 +1,40 @@ += 8.0 + +namespace Bug11561; + +use function PHPStan\Testing\assertType; +use DateTime; + +/** @param array{date: DateTime} $c */ +function main(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + assertType('array{date: DateTime, id: 1}', $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); + $c['name'] = 'ruud'; + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); + return 'x'; + })(); + + assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); +} + + +/** @param array{date: DateTime} $c */ +function main2(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + $c['name'] = 'staabm'; + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); + $c['name'] = 'ruud'; + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); + return 'x'; + })(); + + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); +} From 6bd0a5fc3054326a2134ab8bd4d28814b3da16c7 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:22:40 +0000 Subject: [PATCH 0744/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index a14b805eaaf..28752bbba8f 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.11", - "phpstan/php-8-stubs": "0.4.3", + "phpstan/php-8-stubs": "0.4.4", "phpstan/phpdoc-parser": "^2.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index caf98eda55f..2c31e7e3dbd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "092bad73652f5dc799eca247aaeb21db", + "content-hash": "35d6da6b01195515a7b8fcee8a2f952b", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.3", + "version": "0.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "b30c6975205b4b51e7d8c635f57d29b869220a9e" + "reference": "c42f6e278d600b219b76d20f80f8455259bcd593" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/b30c6975205b4b51e7d8c635f57d29b869220a9e", - "reference": "b30c6975205b4b51e7d8c635f57d29b869220a9e", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/c42f6e278d600b219b76d20f80f8455259bcd593", + "reference": "c42f6e278d600b219b76d20f80f8455259bcd593", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.3" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.4" }, - "time": "2024-10-18T00:19:10+00:00" + "time": "2024-11-01T00:22:02+00:00" }, { "name": "phpstan/phpdoc-parser", From 88d5b8cf0cb2da8873dc33222b5f8b4727f0756a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 2 Nov 2024 14:34:39 +0100 Subject: [PATCH 0745/3097] Remove dead code in ConstantConditionRuleHelper --- src/Rules/Comparison/ConstantConditionRuleHelper.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Rules/Comparison/ConstantConditionRuleHelper.php b/src/Rules/Comparison/ConstantConditionRuleHelper.php index dc3167a38d3..36b2c569d8a 100644 --- a/src/Rules/Comparison/ConstantConditionRuleHelper.php +++ b/src/Rules/Comparison/ConstantConditionRuleHelper.php @@ -18,16 +18,6 @@ public function __construct( { } - public function shouldReportAlwaysTrueByDefault(Expr $expr): bool - { - return $expr instanceof Expr\BooleanNot - || $expr instanceof Expr\BinaryOp\BooleanOr - || $expr instanceof Expr\BinaryOp\BooleanAnd - || $expr instanceof Expr\Ternary - || $expr instanceof Expr\Isset_ - || $expr instanceof Expr\Empty_; - } - public function shouldSkip(Scope $scope, Expr $expr): bool { if ( From d5486cd84e1b59d21bbacca6c91fb0cb3b15cfb8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 01:19:59 +0000 Subject: [PATCH 0746/3097] Update github-actions --- .github/workflows/issue-bot.yml | 2 +- .github/workflows/reflection-golden-test.yml | 2 +- .github/workflows/spelling.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 530886afff3..eca615b3ce9 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -97,7 +97,7 @@ jobs: working-directory: "issue-bot" run: "composer install --no-interaction --no-progress" - - uses: Wandalen/wretry.action@v3.5.0 + - uses: Wandalen/wretry.action@v3.7.0 with: action: actions/download-artifact@v4 with: | diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 8b84920a73e..b4a0ac5fd5d 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -73,7 +73,7 @@ jobs: - "8.4" steps: - - uses: Wandalen/wretry.action@v3.5.0 + - uses: Wandalen/wretry.action@v3.7.0 with: action: actions/download-artifact@v4 with: | diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 8dadf24aba7..81d852e90bf 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.26.8" + uses: "crate-ci/typos@v1.27.0" with: files: "README.md src/" From 595091012739d03fb364a40a51a8d30671fe9e80 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 4 Nov 2024 09:39:15 +0100 Subject: [PATCH 0747/3097] Add `@api` to TypeExpr See https://github.com/phpstan/phpstan/discussions/11960 --- src/Node/Expr/TypeExpr.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Node/Expr/TypeExpr.php b/src/Node/Expr/TypeExpr.php index 35e364f15cf..c21a2099c31 100644 --- a/src/Node/Expr/TypeExpr.php +++ b/src/Node/Expr/TypeExpr.php @@ -6,9 +6,13 @@ use PHPStan\Node\VirtualNode; use PHPStan\Type\Type; +/** + * @api + */ final class TypeExpr extends Expr implements VirtualNode { + /** @api */ public function __construct(private Type $exprType) { parent::__construct(); From 1b4997efa65974b5a7827fe764ca14f0768a7afa Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:03:15 +0000 Subject: [PATCH 0748/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 28752bbba8f..1afe40674c9 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", + "jetbrains/phpstorm-stubs": "dev-master#1f0dca06d54cf187adb3481a9c3e7d74af01743b", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 2c31e7e3dbd..4a1e51a351f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "35d6da6b01195515a7b8fcee8a2f952b", + "content-hash": "350cc72e1a307581ffc8228f105bd273", "packages": [ { "name": "clue/ndjson-react", @@ -1442,19 +1442,19 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab" + "reference": "1f0dca06d54cf187adb3481a9c3e7d74af01743b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", - "reference": "08ee6c06d3d6021399c02ae1e4e91ae2ceaf90ab", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/1f0dca06d54cf187adb3481a9c3e7d74af01743b", + "reference": "1f0dca06d54cf187adb3481a9c3e7d74af01743b", "shasum": "" }, "require-dev": { - "friendsofphp/php-cs-fixer": "v3.61.1", + "friendsofphp/php-cs-fixer": "v3.64.0", "nikic/php-parser": "v5.3.1", "phpdocumentor/reflection-docblock": "5.4.1", - "phpunit/phpunit": "11.3.0" + "phpunit/phpunit": "11.4.3" }, "default-branch": true, "type": "library", @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-10-28T16:40:57+00:00" + "time": "2024-11-04T21:28:48+00:00" }, { "name": "nette/bootstrap", From 5f064ddc5d2e998689ea0846b8d3eea46fca4921 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 5 Nov 2024 12:27:48 +0100 Subject: [PATCH 0749/3097] More precise types in immediately invoked callables --- src/Analyser/MutatingScope.php | 6 +-- src/Analyser/NodeScopeResolver.php | 20 ++++++++-- tests/PHPStan/Analyser/nsrt/bug-11561.php | 46 +++++++++++++++++++++-- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2724ba1fcab..90b3fdd4653 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -46,7 +46,6 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\PropertyAssignNode; use PHPStan\Parser\ArrayMapArgVisitor; -use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; @@ -4795,7 +4794,6 @@ private function processFinallyScopeVariableTypeHolders( * @param Expr\ClosureUse[] $byRefUses */ public function processClosureScope( - Expr\Closure $expr, self $closureScope, ?self $prevScope, array $byRefUses, @@ -4828,9 +4826,7 @@ public function processClosureScope( $prevVariableType = $prevScope->getVariableType($variableName); if (!$variableType->equals($prevVariableType)) { $variableType = TypeCombinator::union($variableType, $prevVariableType); - if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) !== true) { - $variableType = self::generalizeType($variableType, $prevVariableType, 0); - } + $variableType = self::generalizeType($variableType, $prevVariableType, 0); } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fd6ac13fc3b..fc7b1e9df77 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -119,6 +119,7 @@ use PHPStan\Node\VarTagChangedExpressionTypeNode; use PHPStan\Parser\ArrowFunctionArgVisitor; use PHPStan\Parser\ClosureArgVisitor; +use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; @@ -4232,7 +4233,7 @@ private function processClosureNode( } $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($expr, $scope, null, $byRefUses); + $closureScope = $closureScope->processClosureScope($scope, null, $byRefUses); $closureType = $closureScope->getAnonymousFunctionReflection(); if (!$closureType instanceof ClosureType) { throw new ShouldNotHappenException(); @@ -4277,6 +4278,7 @@ private function processClosureNode( $gatheredReturnStatements[] = new ReturnStatement($scope, $node); }; + if (count($byRefUses) === 0) { $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); $nodeCallback(new ClosureReturnStatementsNode( @@ -4292,6 +4294,7 @@ private function processClosureNode( } $count = 0; + $closureResultScope = null; do { $prevScope = $closureScope; @@ -4301,8 +4304,15 @@ private function processClosureNode( foreach ($intermediaryClosureScopeResult->getExitPoints() as $exitPoint) { $intermediaryClosureScope = $intermediaryClosureScope->mergeWith($exitPoint->getScope()); } + + if ($expr->getAttribute(ImmediatelyInvokedClosureVisitor::ATTRIBUTE_NAME) === true) { + $closureResultScope = $intermediaryClosureScope; + break; + } + $closureScope = $scope->enterAnonymousFunction($expr, $callableParameters); - $closureScope = $closureScope->processClosureScope($expr, $intermediaryClosureScope, $prevScope, $byRefUses); + $closureScope = $closureScope->processClosureScope($intermediaryClosureScope, $prevScope, $byRefUses); + if ($closureScope->equals($prevScope)) { break; } @@ -4312,6 +4322,10 @@ private function processClosureNode( $count++; } while ($count < self::LOOP_SCOPE_ITERATIONS); + if ($closureResultScope === null) { + $closureResultScope = $closureScope; + } + $statementResult = $this->processStmtNodes($expr, $expr->stmts, $closureScope, $closureStmtsCallback, StatementContext::createTopLevel()); $nodeCallback(new ClosureReturnStatementsNode( $expr, @@ -4322,7 +4336,7 @@ private function processClosureNode( array_merge($statementResult->getImpurePoints(), $closureImpurePoints), ), $closureScope); - return new ProcessClosureResult($scope->processClosureScope($expr, $closureScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); + return new ProcessClosureResult($scope->processClosureScope($closureResultScope, null, $byRefUses), $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), $invalidateExpressions); } /** diff --git a/tests/PHPStan/Analyser/nsrt/bug-11561.php b/tests/PHPStan/Analyser/nsrt/bug-11561.php index 1a01e5f97ad..f6894d4724e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11561.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11561.php @@ -12,13 +12,13 @@ function main(mixed $c): void{ assertType('array{date: DateTime, id: 1}', $c); $x = (function() use (&$c) { - assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); + assertType("array{date: DateTime, id: 1}", $c); $c['name'] = 'ruud'; assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); return 'x'; })(); - assertType("array{date: DateTime, id: 1, name?: 'ruud'}", $c); + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); } @@ -30,11 +30,51 @@ function main2(mixed $c): void{ assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); $x = (function() use (&$c) { - assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); $c['name'] = 'ruud'; assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); return 'x'; })(); + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); +} + +/** @param array{date: DateTime} $c */ +function main3(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + $c['name'] = 'staabm'; + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + if (rand(0,1)) { + $c['name'] = 'ruud'; + } + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); + return 'x'; + })(); + + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); +} + +/** @param array{date: DateTime} $c */ +function main4(mixed $c): void{ + assertType('array{date: DateTime}', $c); + $c['id']=1; + $c['name'] = 'staabm'; + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + + $x = (function() use (&$c) { + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + if (rand(0,1)) { + $c['name'] = 'ruud'; + assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c); + return 'y'; + } + assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c); + return 'x'; + })(); + assertType("array{date: DateTime, id: 1, name: 'ruud'|'staabm'}", $c); } From 801924357745759a86d9118f1ccf04f9114d554d Mon Sep 17 00:00:00 2001 From: Jonathan Goode Date: Fri, 1 Nov 2024 16:31:47 +0000 Subject: [PATCH 0750/3097] Support returning an array or a string in `count_chars()` --- conf/config.neon | 7 ++- ...harsFunctionDynamicReturnTypeExtension.php | 59 +++++++++++++++++++ .../PHPStan/Analyser/nsrt/count-chars-7.4.php | 21 +++++++ .../PHPStan/Analyser/nsrt/count-chars-8.0.php | 21 +++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/count-chars-7.4.php create mode 100644 tests/PHPStan/Analyser/nsrt/count-chars-8.0.php diff --git a/conf/config.neon b/conf/config.neon index e307d1380b6..24156dc09bb 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1262,13 +1262,18 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ConstantHelper + class: PHPStan\Type\Php\ConstantHelper - class: PHPStan\Type\Php\CountFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\CountCharsFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension tags: diff --git a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..cddb7e79898 --- /dev/null +++ b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php @@ -0,0 +1,59 @@ +getName() === 'count_chars'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + if (count($functionCall->getArgs()) < 1) { + return null; + } + + $modeType = $scope->getType($functionCall->getArgs()[1]->value); + + if (IntegerRangeType::fromInterval(0, 2)->isSuperTypeOf($modeType)->yes()) { + $arrayType = new ArrayType(new IntegerType(), new IntegerType()); + + return $this->phpVersion->throwsValueErrorForInternalFunctions() + ? $arrayType + : TypeUtils::toBenevolentUnion(new UnionType([$arrayType, new ConstantBooleanType(false)])); + } + + $stringType = new StringType(); + + return $this->phpVersion->throwsValueErrorForInternalFunctions() + ? $stringType + : TypeUtils::toBenevolentUnion(new UnionType([$stringType, new ConstantBooleanType(false)])); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php new file mode 100644 index 00000000000..713d73a5e14 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php @@ -0,0 +1,21 @@ +|false', count_chars(self::ABC, 0)); + assertType('array|false', count_chars(self::ABC, 1)); + assertType('array|false', count_chars(self::ABC, 2)); + + assertType('string|false', count_chars(self::ABC, 3)); + assertType('string|false', count_chars(self::ABC, 4)); + + assertType('string|false', count_chars(self::ABC, -1)); + assertType('string|false', count_chars(self::ABC, 5)); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php new file mode 100644 index 00000000000..88a165e931c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php @@ -0,0 +1,21 @@ += 8.0 + +namespace CountChars; + +use function PHPStan\Testing\assertType; + +class Y { + const ABC = 'abcdef'; + + function doFoo(): void { + assertType('array', count_chars(self::ABC, 0)); + assertType('array', count_chars(self::ABC, 1)); + assertType('array', count_chars(self::ABC, 2)); + + assertType('string', count_chars(self::ABC, 3)); + assertType('string', count_chars(self::ABC, 4)); + + assertType('string', count_chars(self::ABC, -1)); + assertType('string', count_chars(self::ABC, 5)); + } +} From a7628e250fb78c19692d020cbfb098d8003d6891 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 6 Nov 2024 00:35:30 +0900 Subject: [PATCH 0751/3097] Split ArrayFilterFunctionReturnTypeExtension to Helper --- conf/config.neon | 3 + phpstan-baseline.neon | 2 +- ...ArrayFilterFunctionReturnTypeExtension.php | 323 +---------------- .../ArrayFilterFunctionReturnTypeHelper.php | 340 ++++++++++++++++++ 4 files changed, 346 insertions(+), 322 deletions(-) create mode 100644 src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php diff --git a/conf/config.neon b/conf/config.neon index 7c88f87a31c..f9d8ca018e7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1207,6 +1207,9 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeHelper + - class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeExtension tags: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1521bbb1a04..498207dbe7b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1305,7 +1305,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" count: 1 - path: src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php + path: src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php index 4672fd1a96d..62dc52abf0a 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php @@ -2,52 +2,16 @@ namespace PHPStan\Type\Php; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ArrowFunction; -use PhpParser\Node\Expr\Closure; -use PhpParser\Node\Expr\Error; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Name; -use PhpParser\Node\Stmt\Return_; -use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\ShouldNotHappenException; -use PHPStan\Type\ArrayType; -use PHPStan\Type\BenevolentUnionType; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\ErrorType; -use PHPStan\Type\MixedType; -use PHPStan\Type\NeverType; -use PHPStan\Type\NullType; -use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; -use PHPStan\Type\TypeUtils; -use function array_map; -use function count; -use function in_array; -use function is_string; -use function sprintf; -use function substr; final class ArrayFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - private const USE_BOTH = 1; - private const USE_KEY = 2; - private const USE_ITEM = 3; - - public function __construct(private ReflectionProvider $reflectionProvider) + public function __construct(private ArrayFilterFunctionReturnTypeHelper $arrayFilterFunctionReturnTypeHelper) { } @@ -62,290 +26,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $callbackArg = $functionCall->getArgs()[1]->value ?? null; $flagArg = $functionCall->getArgs()[2]->value ?? null; - if ($arrayArg === null) { - return new ArrayType(new MixedType(), new MixedType()); - } - - $arrayArgType = $scope->getType($arrayArg); - $arrayArgType = TypeUtils::toBenevolentUnion($arrayArgType); - $keyType = $arrayArgType->getIterableKeyType(); - $itemType = $arrayArgType->getIterableValueType(); - - if ($itemType instanceof NeverType || $keyType instanceof NeverType) { - return new ConstantArrayType([], []); - } - - if ($arrayArgType instanceof MixedType) { - return new BenevolentUnionType([ - new ArrayType(new MixedType(), new MixedType()), - new NullType(), - ]); - } - - if ($callbackArg === null || $scope->getType($callbackArg)->isNull()->yes()) { - return TypeCombinator::union( - ...array_map([$this, 'removeFalsey'], $arrayArgType->getArrays()), - ); - } - - $mode = $this->determineMode($flagArg, $scope); - if ($mode === null) { - return new ArrayType($keyType, $itemType); - } - - if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { - $statement = $callbackArg->stmts[0]; - if ($statement instanceof Return_ && $statement->expr !== null) { - if ($mode === self::USE_ITEM) { - $keyVar = null; - $itemVar = $callbackArg->params[0]->var; - } elseif ($mode === self::USE_KEY) { - $keyVar = $callbackArg->params[0]->var; - $itemVar = null; - } elseif ($mode === self::USE_BOTH) { - $keyVar = $callbackArg->params[1]->var ?? null; - $itemVar = $callbackArg->params[0]->var; - } - return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $statement->expr); - } - } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { - if ($mode === self::USE_ITEM) { - $keyVar = null; - $itemVar = $callbackArg->params[0]->var; - } elseif ($mode === self::USE_KEY) { - $keyVar = $callbackArg->params[0]->var; - $itemVar = null; - } elseif ($mode === self::USE_BOTH) { - $keyVar = $callbackArg->params[1]->var ?? null; - $itemVar = $callbackArg->params[0]->var; - } - return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $callbackArg->expr); - } elseif ( - ($callbackArg instanceof FuncCall || $callbackArg instanceof MethodCall || $callbackArg instanceof StaticCall) - && $callbackArg->isFirstClassCallable() - ) { - [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); - $expr = clone $callbackArg; - $expr->args = $args; - return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); - } else { - $constantStrings = $scope->getType($callbackArg)->getConstantStrings(); - if (count($constantStrings) > 0) { - $results = []; - [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); - - foreach ($constantStrings as $constantString) { - $funcName = self::createFunctionName($constantString->getValue()); - if ($funcName === null) { - $results[] = new ErrorType(); - continue; - } - - $expr = new FuncCall($funcName, $args); - $results[] = $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); - } - return TypeCombinator::union(...$results); - } - } - - return new ArrayType($keyType, $itemType); - } - - public function removeFalsey(Type $type): Type - { - $falseyTypes = StaticTypeFactory::falsey(); - - if (count($type->getConstantArrays()) > 0) { - $result = []; - foreach ($type->getConstantArrays() as $constantArray) { - $keys = $constantArray->getKeyTypes(); - $values = $constantArray->getValueTypes(); - - $builder = ConstantArrayTypeBuilder::createEmpty(); - - foreach ($values as $offset => $value) { - $isFalsey = $falseyTypes->isSuperTypeOf($value); - - if ($isFalsey->maybe()) { - $builder->setOffsetValueType($keys[$offset], TypeCombinator::remove($value, $falseyTypes), true); - } elseif ($isFalsey->no()) { - $builder->setOffsetValueType($keys[$offset], $value, $constantArray->isOptionalKey($offset)); - } - } - - $result[] = $builder->getArray(); - } - - return TypeCombinator::union(...$result); - } - - $keyType = $type->getIterableKeyType(); - $valueType = $type->getIterableValueType(); - - $valueType = TypeCombinator::remove($valueType, $falseyTypes); - - if ($valueType instanceof NeverType) { - return new ConstantArrayType([], []); - } - - return new ArrayType($keyType, $valueType); - } - - private function filterByTruthyValue(Scope $scope, Error|Variable|null $itemVar, Type $arrayType, Error|Variable|null $keyVar, Expr $expr): Type - { - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - $constantArrays = $arrayType->getConstantArrays(); - if (count($constantArrays) > 0) { - $results = []; - foreach ($constantArrays as $constantArray) { - $builder = ConstantArrayTypeBuilder::createEmpty(); - $optionalKeys = $constantArray->getOptionalKeys(); - foreach ($constantArray->getKeyTypes() as $i => $keyType) { - $itemType = $constantArray->getValueTypes()[$i]; - [$newKeyType, $newItemType, $optional] = $this->processKeyAndItemType($scope, $keyType, $itemType, $itemVar, $keyVar, $expr); - $optional = $optional || in_array($i, $optionalKeys, true); - if ($newKeyType instanceof NeverType || $newItemType instanceof NeverType) { - continue; - } - if ($itemType->equals($newItemType) && $keyType->equals($newKeyType)) { - $builder->setOffsetValueType($keyType, $itemType, $optional); - continue; - } - - $builder->setOffsetValueType($newKeyType, $newItemType, true); - } - - $results[] = $builder->getArray(); - } - - return TypeCombinator::union(...$results); - } - - [$newKeyType, $newItemType] = $this->processKeyAndItemType($scope, $arrayType->getIterableKeyType(), $arrayType->getIterableValueType(), $itemVar, $keyVar, $expr); - - if ($newItemType instanceof NeverType || $newKeyType instanceof NeverType) { - return new ConstantArrayType([], []); - } - - return new ArrayType($newKeyType, $newItemType); - } - - /** - * @return array{Type, Type, bool} - */ - private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type $itemType, Error|Variable|null $itemVar, Error|Variable|null $keyVar, Expr $expr): array - { - $itemVarName = null; - if ($itemVar !== null) { - if (!$itemVar instanceof Variable || !is_string($itemVar->name)) { - throw new ShouldNotHappenException(); - } - $itemVarName = $itemVar->name; - $scope = $scope->assignVariable($itemVarName, $itemType, new MixedType()); - } - - $keyVarName = null; - if ($keyVar !== null) { - if (!$keyVar instanceof Variable || !is_string($keyVar->name)) { - throw new ShouldNotHappenException(); - } - $keyVarName = $keyVar->name; - $scope = $scope->assignVariable($keyVarName, $keyType, new MixedType()); - } - - $booleanResult = $scope->getType($expr)->toBoolean(); - if ($booleanResult->isFalse()->yes()) { - return [new NeverType(), new NeverType(), false]; - } - - $scope = $scope->filterByTruthyValue($expr); - - return [ - $keyVarName !== null ? $scope->getVariableType($keyVarName) : $keyType, - $itemVarName !== null ? $scope->getVariableType($itemVarName) : $itemType, - !$booleanResult instanceof ConstantBooleanType, - ]; - } - - private static function createFunctionName(string $funcName): ?Name - { - if ($funcName === '') { - return null; - } - - if ($funcName[0] === '\\') { - $funcName = substr($funcName, 1); - - if ($funcName === '') { - return null; - } - - return new Name\FullyQualified($funcName); - } - - return new Name($funcName); - } - - /** - * @param self::USE_* $mode - * @return array{list, ?Variable, ?Variable} - */ - private function createDummyArgs(int $mode): array - { - if ($mode === self::USE_ITEM) { - $itemVar = new Variable('item'); - $keyVar = null; - $args = [new Arg($itemVar)]; - } elseif ($mode === self::USE_KEY) { - $itemVar = null; - $keyVar = new Variable('key'); - $args = [new Arg($keyVar)]; - } elseif ($mode === self::USE_BOTH) { - $itemVar = new Variable('item'); - $keyVar = new Variable('key'); - $args = [new Arg($itemVar), new Arg($keyVar)]; - } - return [$args, $itemVar, $keyVar]; - } - - /** - * @param non-empty-string $constantName - */ - private function getConstant(string $constantName): int - { - $constant = $this->reflectionProvider->getConstant(new Name($constantName), null); - $valueType = $constant->getValueType(); - if (!$valueType instanceof ConstantIntegerType) { - throw new ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); - } - - return $valueType->getValue(); - } - - /** - * @return self::USE_*|null - */ - private function determineMode(?Expr $flagArg, Scope $scope): ?int - { - if ($flagArg === null) { - return self::USE_ITEM; - } - - $flagValues = $scope->getType($flagArg)->getConstantScalarValues(); - if (count($flagValues) !== 1) { - return null; - } - - if ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_KEY')) { - return self::USE_KEY; - } elseif ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_BOTH')) { - return self::USE_BOTH; - } - - return null; + return $this->arrayFilterFunctionReturnTypeHelper->getType($scope, $arrayArg, $callbackArg, $flagArg); } } diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php new file mode 100644 index 00000000000..52916521295 --- /dev/null +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php @@ -0,0 +1,340 @@ +getType($arrayArg); + $arrayArgType = TypeUtils::toBenevolentUnion($arrayArgType); + $keyType = $arrayArgType->getIterableKeyType(); + $itemType = $arrayArgType->getIterableValueType(); + + if ($itemType instanceof NeverType || $keyType instanceof NeverType) { + return new ConstantArrayType([], []); + } + + if ($arrayArgType instanceof MixedType) { + return new BenevolentUnionType([ + new ArrayType(new MixedType(), new MixedType()), + new NullType(), + ]); + } + + if ($callbackArg === null || $scope->getType($callbackArg)->isNull()->yes()) { + return TypeCombinator::union( + ...array_map([$this, 'removeFalsey'], $arrayArgType->getArrays()), + ); + } + + $mode = $this->determineMode($flagArg, $scope); + if ($mode === null) { + return new ArrayType($keyType, $itemType); + } + + if ($callbackArg instanceof Closure && count($callbackArg->stmts) === 1 && count($callbackArg->params) > 0) { + $statement = $callbackArg->stmts[0]; + if ($statement instanceof Return_ && $statement->expr !== null) { + if ($mode === self::USE_ITEM) { + $keyVar = null; + $itemVar = $callbackArg->params[0]->var; + } elseif ($mode === self::USE_KEY) { + $keyVar = $callbackArg->params[0]->var; + $itemVar = null; + } elseif ($mode === self::USE_BOTH) { + $keyVar = $callbackArg->params[1]->var ?? null; + $itemVar = $callbackArg->params[0]->var; + } + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $statement->expr); + } + } elseif ($callbackArg instanceof ArrowFunction && count($callbackArg->params) > 0) { + if ($mode === self::USE_ITEM) { + $keyVar = null; + $itemVar = $callbackArg->params[0]->var; + } elseif ($mode === self::USE_KEY) { + $keyVar = $callbackArg->params[0]->var; + $itemVar = null; + } elseif ($mode === self::USE_BOTH) { + $keyVar = $callbackArg->params[1]->var ?? null; + $itemVar = $callbackArg->params[0]->var; + } + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $callbackArg->expr); + } elseif ( + ($callbackArg instanceof FuncCall || $callbackArg instanceof MethodCall || $callbackArg instanceof StaticCall) + && $callbackArg->isFirstClassCallable() + ) { + [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); + $expr = clone $callbackArg; + $expr->args = $args; + return $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); + } else { + $constantStrings = $scope->getType($callbackArg)->getConstantStrings(); + if (count($constantStrings) > 0) { + $results = []; + [$args, $itemVar, $keyVar] = $this->createDummyArgs($mode); + + foreach ($constantStrings as $constantString) { + $funcName = self::createFunctionName($constantString->getValue()); + if ($funcName === null) { + $results[] = new ErrorType(); + continue; + } + + $expr = new FuncCall($funcName, $args); + $results[] = $this->filterByTruthyValue($scope, $itemVar, $arrayArgType, $keyVar, $expr); + } + return TypeCombinator::union(...$results); + } + } + + return new ArrayType($keyType, $itemType); + } + + private function removeFalsey(Type $type): Type + { + $falseyTypes = StaticTypeFactory::falsey(); + + if (count($type->getConstantArrays()) > 0) { + $result = []; + foreach ($type->getConstantArrays() as $constantArray) { + $keys = $constantArray->getKeyTypes(); + $values = $constantArray->getValueTypes(); + + $builder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($values as $offset => $value) { + $isFalsey = $falseyTypes->isSuperTypeOf($value); + + if ($isFalsey->maybe()) { + $builder->setOffsetValueType($keys[$offset], TypeCombinator::remove($value, $falseyTypes), true); + } elseif ($isFalsey->no()) { + $builder->setOffsetValueType($keys[$offset], $value, $constantArray->isOptionalKey($offset)); + } + } + + $result[] = $builder->getArray(); + } + + return TypeCombinator::union(...$result); + } + + $keyType = $type->getIterableKeyType(); + $valueType = $type->getIterableValueType(); + + $valueType = TypeCombinator::remove($valueType, $falseyTypes); + + if ($valueType instanceof NeverType) { + return new ConstantArrayType([], []); + } + + return new ArrayType($keyType, $valueType); + } + + private function filterByTruthyValue(Scope $scope, Error|Variable|null $itemVar, Type $arrayType, Error|Variable|null $keyVar, Expr $expr): Type + { + if (!$scope instanceof MutatingScope) { + throw new ShouldNotHappenException(); + } + + $constantArrays = $arrayType->getConstantArrays(); + if (count($constantArrays) > 0) { + $results = []; + foreach ($constantArrays as $constantArray) { + $builder = ConstantArrayTypeBuilder::createEmpty(); + $optionalKeys = $constantArray->getOptionalKeys(); + foreach ($constantArray->getKeyTypes() as $i => $keyType) { + $itemType = $constantArray->getValueTypes()[$i]; + [$newKeyType, $newItemType, $optional] = $this->processKeyAndItemType($scope, $keyType, $itemType, $itemVar, $keyVar, $expr); + $optional = $optional || in_array($i, $optionalKeys, true); + if ($newKeyType instanceof NeverType || $newItemType instanceof NeverType) { + continue; + } + if ($itemType->equals($newItemType) && $keyType->equals($newKeyType)) { + $builder->setOffsetValueType($keyType, $itemType, $optional); + continue; + } + + $builder->setOffsetValueType($newKeyType, $newItemType, true); + } + + $results[] = $builder->getArray(); + } + + return TypeCombinator::union(...$results); + } + + [$newKeyType, $newItemType] = $this->processKeyAndItemType($scope, $arrayType->getIterableKeyType(), $arrayType->getIterableValueType(), $itemVar, $keyVar, $expr); + + if ($newItemType instanceof NeverType || $newKeyType instanceof NeverType) { + return new ConstantArrayType([], []); + } + + return new ArrayType($newKeyType, $newItemType); + } + + /** + * @return array{Type, Type, bool} + */ + private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type $itemType, Error|Variable|null $itemVar, Error|Variable|null $keyVar, Expr $expr): array + { + $itemVarName = null; + if ($itemVar !== null) { + if (!$itemVar instanceof Variable || !is_string($itemVar->name)) { + throw new ShouldNotHappenException(); + } + $itemVarName = $itemVar->name; + $scope = $scope->assignVariable($itemVarName, $itemType, new MixedType()); + } + + $keyVarName = null; + if ($keyVar !== null) { + if (!$keyVar instanceof Variable || !is_string($keyVar->name)) { + throw new ShouldNotHappenException(); + } + $keyVarName = $keyVar->name; + $scope = $scope->assignVariable($keyVarName, $keyType, new MixedType()); + } + + $booleanResult = $scope->getType($expr)->toBoolean(); + if ($booleanResult->isFalse()->yes()) { + return [new NeverType(), new NeverType(), false]; + } + + $scope = $scope->filterByTruthyValue($expr); + + return [ + $keyVarName !== null ? $scope->getVariableType($keyVarName) : $keyType, + $itemVarName !== null ? $scope->getVariableType($itemVarName) : $itemType, + !$booleanResult instanceof ConstantBooleanType, + ]; + } + + private static function createFunctionName(string $funcName): ?Name + { + if ($funcName === '') { + return null; + } + + if ($funcName[0] === '\\') { + $funcName = substr($funcName, 1); + + if ($funcName === '') { + return null; + } + + return new Name\FullyQualified($funcName); + } + + return new Name($funcName); + } + + /** + * @param self::USE_* $mode + * @return array{list, ?Variable, ?Variable} + */ + private function createDummyArgs(int $mode): array + { + if ($mode === self::USE_ITEM) { + $itemVar = new Variable('item'); + $keyVar = null; + $args = [new Arg($itemVar)]; + } elseif ($mode === self::USE_KEY) { + $itemVar = null; + $keyVar = new Variable('key'); + $args = [new Arg($keyVar)]; + } elseif ($mode === self::USE_BOTH) { + $itemVar = new Variable('item'); + $keyVar = new Variable('key'); + $args = [new Arg($itemVar), new Arg($keyVar)]; + } + return [$args, $itemVar, $keyVar]; + } + + /** + * @param non-empty-string $constantName + */ + private function getConstant(string $constantName): int + { + $constant = $this->reflectionProvider->getConstant(new Name($constantName), null); + $valueType = $constant->getValueType(); + if (!$valueType instanceof ConstantIntegerType) { + throw new ShouldNotHappenException(sprintf('Constant %s does not have integer type.', $constantName)); + } + + return $valueType->getValue(); + } + + /** + * @return self::USE_*|null + */ + private function determineMode(?Expr $flagArg, Scope $scope): ?int + { + if ($flagArg === null) { + return self::USE_ITEM; + } + + $flagValues = $scope->getType($flagArg)->getConstantScalarValues(); + if (count($flagValues) !== 1) { + return null; + } + + if ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_KEY')) { + return self::USE_KEY; + } elseif ($flagValues[0] === $this->getConstant('ARRAY_FILTER_USE_BOTH')) { + return self::USE_BOTH; + } + + return null; + } + +} From 88b12f88ea625d6e70d2a5469c97d339eecc86a4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 5 Nov 2024 18:51:34 +0100 Subject: [PATCH 0752/3097] Introduce `UnionType::filterTypes` --- src/Analyser/MutatingScope.php | 67 +++++----------------------------- src/Type/UnionType.php | 17 +++++++++ 2 files changed, 26 insertions(+), 58 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 90b3fdd4653..c24defb1f8d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5594,18 +5594,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type { if ($typeWithMethod instanceof UnionType) { - $newTypes = []; - foreach ($typeWithMethod->getTypes() as $innerType) { - if (!$innerType->hasMethod($methodName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithMethod = TypeCombinator::union(...$newTypes); + $typeWithMethod = $typeWithMethod->filterTypes(static fn (Type $innerType) => $innerType->hasMethod($methodName)->yes()); } if (!$typeWithMethod->hasMethod($methodName)->yes()) { @@ -5709,18 +5698,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection { if ($typeWithProperty instanceof UnionType) { - $newTypes = []; - foreach ($typeWithProperty->getTypes() as $innerType) { - if (!$innerType->hasProperty($propertyName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithProperty = TypeCombinator::union(...$newTypes); + $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasProperty($propertyName)->yes()); } if (!$typeWithProperty->hasProperty($propertyName)->yes()) { return null; @@ -5749,18 +5727,7 @@ private function propertyFetchType(Type $fetchedOnType, string $propertyName, Ex public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ConstantReflection { if ($typeWithConstant instanceof UnionType) { - $newTypes = []; - foreach ($typeWithConstant->getTypes() as $innerType) { - if (!$innerType->hasConstant($constantName)->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return null; - } - $typeWithConstant = TypeCombinator::union(...$newTypes); + $typeWithConstant = $typeWithConstant->filterTypes(static fn (Type $innerType) => $innerType->hasConstant($constantName)->yes()); } if (!$typeWithConstant->hasConstant($constantName)->yes()) { return null; @@ -5804,18 +5771,10 @@ private function getNativeConstantTypes(): array public function getIterableKeyType(Type $iteratee): Type { if ($iteratee instanceof UnionType) { - $newTypes = []; - foreach ($iteratee->getTypes() as $innerType) { - if (!$innerType->isIterable()->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return $iteratee->getIterableKeyType(); + $filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes()); + if (!$filtered instanceof NeverType) { + $iteratee = $filtered; } - $iteratee = TypeCombinator::union(...$newTypes); } return $iteratee->getIterableKeyType(); @@ -5824,18 +5783,10 @@ public function getIterableKeyType(Type $iteratee): Type public function getIterableValueType(Type $iteratee): Type { if ($iteratee instanceof UnionType) { - $newTypes = []; - foreach ($iteratee->getTypes() as $innerType) { - if (!$innerType->isIterable()->yes()) { - continue; - } - - $newTypes[] = $innerType; - } - if (count($newTypes) === 0) { - return $iteratee->getIterableValueType(); + $filtered = $iteratee->filterTypes(static fn (Type $innerType) => $innerType->isIterable()->yes()); + if (!$filtered instanceof NeverType) { + $iteratee = $filtered; } - $iteratee = TypeCombinator::union(...$newTypes); } return $iteratee->getIterableValueType(); diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 6b828625b6b..b7e794378af 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -86,6 +86,23 @@ public function getTypes(): array return $this->types; } + /** + * @param callable(Type $type): bool $filterCb + */ + public function filterTypes(callable $filterCb): Type + { + $newTypes = []; + foreach ($this->getTypes() as $innerType) { + if (!$filterCb($innerType)) { + continue; + } + + $newTypes[] = $innerType; + } + + return TypeCombinator::union(...$newTypes); + } + public function isNormalized(): bool { return $this->normalized; From 3f8c27d2d5fbe6b65fa56f997191db46cfc9aeb7 Mon Sep 17 00:00:00 2001 From: JiaJia Ji Date: Tue, 5 Nov 2024 18:52:52 +0100 Subject: [PATCH 0753/3097] Imagick::writeImage(s)File supporting `format` parameter --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index c9eba8373a8..b678f48e5bf 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5016,9 +5016,9 @@ 'Imagick::waveImage' => ['bool', 'amplitude'=>'float', 'length'=>'float'], 'Imagick::whiteThresholdImage' => ['bool', 'threshold'=>'mixed'], 'Imagick::writeImage' => ['bool', 'filename='=>'string'], -'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource'], +'Imagick::writeImageFile' => ['bool', 'filehandle'=>'resource', 'format='=>'?string'], 'Imagick::writeImages' => ['bool', 'filename'=>'string', 'adjoin'=>'bool'], -'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource'], +'Imagick::writeImagesFile' => ['bool', 'filehandle'=>'resource', 'format='=>'?string'], 'ImagickDraw::__construct' => ['void'], 'ImagickDraw::affine' => ['bool', 'affine'=>'array'], 'ImagickDraw::annotation' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], From 9798bd44f083ed57d79b609b827c0f9cf7b00d43 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 6 Nov 2024 05:21:06 +0900 Subject: [PATCH 0754/3097] Add ArrayFindFunctionReturnTypeExtension --- conf/config.neon | 15 ++++ src/Parser/ArrayFindArgVisitor.php | 28 +++++++ src/Reflection/ParametersAcceptorSelector.php | 32 ++++++++ .../ArrayFindFunctionReturnTypeExtension.php | 46 +++++++++++ ...rrayFindKeyFunctionReturnTypeExtension.php | 36 +++++++++ .../PHPStan/Analyser/nsrt/array-find-key.php | 62 +++++++++++++++ tests/PHPStan/Analyser/nsrt/array-find.php | 79 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 52 ++++++++++++ .../Rules/Functions/data/array_find.php | 53 +++++++++++++ .../Rules/Functions/data/array_find_key.php | 53 +++++++++++++ 10 files changed, 456 insertions(+) create mode 100644 src/Parser/ArrayFindArgVisitor.php create mode 100644 src/Type/Php/ArrayFindFunctionReturnTypeExtension.php create mode 100644 src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/array-find-key.php create mode 100644 tests/PHPStan/Analyser/nsrt/array-find.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_find.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_find_key.php diff --git a/conf/config.neon b/conf/config.neon index f9d8ca018e7..11e845136d5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -325,6 +325,11 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\ArrayFindArgVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Parser\ArrayMapArgVisitor tags: @@ -1220,6 +1225,16 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayFindFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + + - + class: PHPStan\Type\Php\ArrayFindKeyFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension tags: diff --git a/src/Parser/ArrayFindArgVisitor.php b/src/Parser/ArrayFindArgVisitor.php new file mode 100644 index 00000000000..8b25b364919 --- /dev/null +++ b/src/Parser/ArrayFindArgVisitor.php @@ -0,0 +1,28 @@ +name instanceof Node\Name) { + $functionName = $node->name->toLowerString(); + if (in_array($functionName, ['array_find', 'array_find_key'], true)) { + $args = $node->getRawArgs(); + if (isset($args[0])) { + $args[0]->setAttribute(self::ATTRIBUTE_NAME, true); + } + } + } + return null; + } + +} diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 619ee2aa819..07a7c280801 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; use PHPStan\Parser\ArrayFilterArgVisitor; +use PHPStan\Parser\ArrayFindArgVisitor; use PHPStan\Parser\ArrayMapArgVisitor; use PHPStan\Parser\ArrayWalkArgVisitor; use PHPStan\Parser\ClosureBindArgVisitor; @@ -257,6 +258,37 @@ public static function selectFromArgs( ]; } + if (isset($args[0]) && (bool) $args[0]->getAttribute(ArrayFindArgVisitor::ATTRIBUTE_NAME)) { + $acceptor = $parametersAcceptors[0]; + $parameters = $acceptor->getParameters(); + $argType = $scope->getType($args[0]->value); + $parameters[1] = new NativeParameterReflection( + $parameters[1]->getName(), + $parameters[1]->isOptional(), + new CallableType( + [ + new DummyParameter('value', $scope->getIterableValueType($argType), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', $scope->getIterableKeyType($argType), false, PassedByReference::createNo(), false, null), + ], + new BooleanType(), + false, + ), + $parameters[1]->passedByReference(), + $parameters[1]->isVariadic(), + $parameters[1]->getDefaultValue(), + ); + $parametersAcceptors = [ + new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + $parameters, + $acceptor->isVariadic(), + $acceptor->getReturnType(), + $acceptor instanceof ParametersAcceptorWithPhpDocs ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + ), + ]; + } + if (isset($args[0])) { $closureBindToVar = $args[0]->getAttribute(ClosureBindToVarVisitor::ATTRIBUTE_NAME); if ( diff --git a/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php new file mode 100644 index 00000000000..220d8fa0efa --- /dev/null +++ b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php @@ -0,0 +1,46 @@ +getName() === 'array_find'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + if (count($functionCall->getArgs()) < 2) { + return null; + } + + $arrayType = $scope->getType($functionCall->getArgs()[0]->value); + if (count($arrayType->getArrays()) < 1) { + return null; + } + + $arrayArg = $functionCall->getArgs()[0]->value ?? null; + $callbackArg = $functionCall->getArgs()[1]->value ?? null; + + $resultTypes = $this->arrayFilterFunctionReturnTypeHelper->getType($scope, $arrayArg, $callbackArg, null); + $resultType = TypeCombinator::union(...array_map(static fn ($type) => $type->getIterableValueType(), $resultTypes->getArrays())); + + return $resultTypes->isIterableAtLeastOnce()->yes() ? $resultType : TypeCombinator::addNull($resultType); + } + +} diff --git a/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php new file mode 100644 index 00000000000..97c514f427f --- /dev/null +++ b/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php @@ -0,0 +1,36 @@ +getName() === 'array_find_key'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + if (count($functionCall->getArgs()) < 2) { + return null; + } + + $arrayType = $scope->getType($functionCall->getArgs()[0]->value); + if (count($arrayType->getArrays()) < 1) { + return null; + } + + return TypeCombinator::union($arrayType->getIterableKeyType(), new NullType()); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/array-find-key.php b/tests/PHPStan/Analyser/nsrt/array-find-key.php new file mode 100644 index 00000000000..5caf828f531 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-find-key.php @@ -0,0 +1,62 @@ + $array + * @param callable(mixed, array-key=): mixed $callback + * @return ?array-key + */ + function array_find_key(array $array, callable $callback) + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { // @phpstan-ignore if.condNotBoolean + return $key; + } + } + + return null; + } + } + +} + +namespace ArrayFindKey +{ + + use function PHPStan\Testing\assertType; + + /** + * @param array $array + * @phpstan-ignore missingType.callable + */ + function testMixed(array $array, callable $callback): void + { + assertType('int|string|null', array_find_key($array, $callback)); + assertType('int|string|null', array_find_key($array, 'is_int')); + } + + /** + * @param array{1, 'foo', \DateTime} $array + * @phpstan-ignore missingType.callable + */ + function testConstant(array $array, callable $callback): void + { + assertType("0|1|2|null", array_find_key($array, $callback)); + assertType("0|1|2|null", array_find_key($array, 'is_int')); + } + + function testCallback(): void + { + $subject = ['foo' => 1, 'bar' => null, 'buz' => '']; + $result = array_find_key($subject, function ($value, $key) { + assertType("array{value: 1|''|null, key: 'bar'|'buz'|'foo'}", compact('value', 'key')); + + return is_int($value); + }); + + assertType("'bar'|'buz'|'foo'|null", $result); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/array-find.php b/tests/PHPStan/Analyser/nsrt/array-find.php new file mode 100644 index 00000000000..f3b5b0b8222 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-find.php @@ -0,0 +1,79 @@ + $array + * @param callable(mixed, array-key=): mixed $callback + * @return mixed + */ + function array_find(array $array, callable $callback) + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { // @phpstan-ignore if.condNotBoolean + return $value; + } + } + + return null; + } + } + +} + +namespace ArrayFind +{ + + use function PHPStan\Testing\assertType; + + /** + * @param array $array + * @param non-empty-array $non_empty_array + * @phpstan-ignore missingType.callable + */ + function testMixed(array $array, array $non_empty_array, callable $callback): void + { + assertType('mixed', array_find($array, $callback)); + assertType('int|null', array_find($array, 'is_int')); + assertType('mixed', array_find($non_empty_array, $callback)); + assertType('int|null', array_find($non_empty_array, 'is_int')); + } + + /** + * @param array{1, 'foo', \DateTime} $array + * @phpstan-ignore missingType.callable + */ + function testConstant(array $array, callable $callback): void + { + assertType("1|'foo'|DateTime|null", array_find($array, $callback)); + assertType('1', array_find($array, 'is_int')); + } + + /** + * @param array $array + * @param non-empty-array $non_empty_array + * @phpstan-ignore missingType.callable + */ + function testInt(array $array, array $non_empty_array, callable $callback): void + { + assertType('int|null', array_find($array, $callback)); + assertType('int|null', array_find($array, 'is_int')); + assertType('int|null', array_find($non_empty_array, $callback)); + // should be 'int' + assertType('int|null', array_find($non_empty_array, 'is_int')); + } + + function testCallback(): void + { + $subject = ['foo' => 1, 'bar' => null, 'buz' => '']; + $result = array_find($subject, function ($value, $key) { + assertType("array{value: 1|''|null, key: 'bar'|'buz'|'foo'}", compact('value', 'key')); + + return is_int($value); + }); + + assertType("1|''|null", $result); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4b00fb720e9..cec109d5f80 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -886,6 +886,58 @@ public function testArrayFilterCallback(bool $checkExplicitMixed): void $this->analyse([__DIR__ . '/data/array_filter_callback.php'], $errors); } + public function testArrayFindCallback(): void + { + $this->analyse([__DIR__ . '/data/array_find.php'], [ + [ + 'Parameter #2 $callback of function array_find expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 22, + ], + [ + 'Parameter #2 $callback of function array_find expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 30, + ], + [ + 'Parameter #2 $callback of function array_find expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.', + 36, + ], + [ + 'Parameter #2 $callback of function array_find expects callable(mixed, int|string): bool, Closure(string, array): false given.', + 49, + ], + [ + 'Parameter #2 $callback of function array_find expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', + 52, + ], + ]); + } + + public function testArrayFindKeyCallback(): void + { + $this->analyse([__DIR__ . '/data/array_find_key.php'], [ + [ + 'Parameter #2 $callback of function array_find_key expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 22, + ], + [ + 'Parameter #2 $callback of function array_find_key expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 30, + ], + [ + 'Parameter #2 $callback of function array_find_key expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.', + 36, + ], + [ + 'Parameter #2 $callback of function array_find_key expects callable(mixed, int|string): bool, Closure(string, array): false given.', + 49, + ], + [ + 'Parameter #2 $callback of function array_find_key expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', + 52, + ], + ]); + } + public function testBug5356(): void { $this->analyse([__DIR__ . '/data/bug-5356.php'], [ diff --git a/tests/PHPStan/Rules/Functions/data/array_find.php b/tests/PHPStan/Rules/Functions/data/array_find.php new file mode 100644 index 00000000000..a749bd82949 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_find.php @@ -0,0 +1,53 @@ += 8.4 + +// ok +array_find( + ['foo' => 1, 'bar' => 2], + function($value, $key) { + return $key === 0; + } +); + +// ok +array_find( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key === 0; + } +); + +// bad parameters +array_find( + ['foo' => 1, 'bar' => 2], + function(string $value, int $key): bool { + return $key === 0; + } +); + +// bad parameters +array_find( + ['foo' => 1, 'bar' => 2], + fn (string $item, int $key) => $key === 0, +); + +// bad return type +array_find( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key; + }, +); + +if (is_array($array)) { + // ok + array_find($array, fn ($value, $key) => $key === 0); + + // ok + array_find($array, fn (string $value, int $key) => $key === 0); + + // bad parameters + array_find($array, fn (string $item, array $key) => $key === 0); + + // bad return type + array_find($array, fn (string $value, int $key): array => []); +} diff --git a/tests/PHPStan/Rules/Functions/data/array_find_key.php b/tests/PHPStan/Rules/Functions/data/array_find_key.php new file mode 100644 index 00000000000..393468d42c2 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_find_key.php @@ -0,0 +1,53 @@ += 8.4 + +// ok +array_find_key( + ['foo' => 1, 'bar' => 2], + function($value, $key) { + return $key === 0; + } +); + +// ok +array_find_key( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key === 0; + } +); + +// bad parameters +array_find_key( + ['foo' => 1, 'bar' => 2], + function(string $value, int $key): bool { + return $key === 0; + } +); + +// bad parameters +array_find_key( + ['foo' => 1, 'bar' => 2], + fn (string $item, int $key) => $key === 0, +); + +// bad return type +array_find_key( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key; + }, +); + +if (is_array($array)) { + // ok + array_find_key($array, fn ($value, $key) => $key === 0); + + // ok + array_find_key($array, fn (string $value, int $key) => $key === 0); + + // bad parameters + array_find_key($array, fn (string $item, array $key) => $key === 0); + + // bad return type + array_find_key($array, fn (string $value, int $key): array => []); +} From 13798c2b9941a97dea9842756663de953c644b93 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Oct 2024 22:04:10 +0200 Subject: [PATCH 0755/3097] Only use last for condition to filter scope --- src/Analyser/NodeScopeResolver.php | 21 ++++++++++++------- .../PHPStan/Analyser/nsrt/for-loop-i-type.php | 8 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fc7b1e9df77..4a8264b6749 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1315,13 +1315,18 @@ private function processStmtNode( $bodyScope = $initScope; $isIterableAtLeastOnce = TrinaryLogic::createYes(); + $lastCondExpr = $stmt->cond[count($stmt->cond) - 1] ?? null; foreach ($stmt->cond as $condExpr) { $condResult = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { }, ExpressionContext::createDeep()); $initScope = $condResult->getScope(); $condResultScope = $condResult->getScope(); - $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); - $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); + + if ($condExpr === $lastCondExpr) { + $condTruthiness = ($this->treatPhpDocTypesAsCertain ? $condResultScope->getType($condExpr) : $condResultScope->getNativeType($condExpr))->toBoolean(); + $isIterableAtLeastOnce = $isIterableAtLeastOnce->and($condTruthiness->isTrue()); + } + $hasYield = $hasYield || $condResult->hasYield(); $throwPoints = array_merge($throwPoints, $condResult->getThrowPoints()); $impurePoints = array_merge($impurePoints, $condResult->getImpurePoints()); @@ -1333,8 +1338,8 @@ private function processStmtNode( do { $prevScope = $bodyScope; $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, static function (): void { + if ($lastCondExpr !== null) { + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, static function (): void { }, ExpressionContext::createDeep())->getTruthyScope(); } $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { @@ -1364,8 +1369,8 @@ private function processStmtNode( } $bodyScope = $bodyScope->mergeWith($initScope); - foreach ($stmt->cond as $condExpr) { - $bodyScope = $this->processExprNode($stmt, $condExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); + if ($lastCondExpr !== null) { + $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); } $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback, $context)->filterOutLoopExitPoints(); @@ -1379,8 +1384,8 @@ private function processStmtNode( $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); } $finalScope = $finalScope->generalizeWith($loopScope); - foreach ($stmt->cond as $condExpr) { - $finalScope = $finalScope->filterByFalseyValue($condExpr); + if ($lastCondExpr !== null) { + $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } foreach ($finalScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { diff --git a/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php b/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php index 011d15d6c75..1317b3695cc 100644 --- a/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php +++ b/tests/PHPStan/Analyser/nsrt/for-loop-i-type.php @@ -94,4 +94,12 @@ public static function groupCapacities(array $startTimes): array return $capacities; } + + public function lastConditionResult(): void + { + for ($i = 0, $j = 5; $i < 10, $j > 0; $i++, $j--) { + assertType('int<0, max>', $i); // int<0,4> would be more precise, see https://github.com/phpstan/phpstan/issues/11872 + assertType('int<1, 5>', $j); + } + } } From d9b383fb9f4b4efd95e139df43cefe33ae1d3733 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 6 Nov 2024 18:41:50 +0900 Subject: [PATCH 0756/3097] Make ArrayFindArgVisitor supports array_any() and array_all() --- src/Parser/ArrayFindArgVisitor.php | 2 +- .../CallToFunctionParametersRuleTest.php | 68 +++++++++++++++++-- .../Rules/Functions/data/array_all.php | 56 +++++++++++++++ .../Rules/Functions/data/array_any.php | 56 +++++++++++++++ .../Rules/Functions/data/array_find.php | 3 + .../Rules/Functions/data/array_find_key.php | 3 + 6 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/array_all.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_any.php diff --git a/src/Parser/ArrayFindArgVisitor.php b/src/Parser/ArrayFindArgVisitor.php index 8b25b364919..0e798eb5c0b 100644 --- a/src/Parser/ArrayFindArgVisitor.php +++ b/src/Parser/ArrayFindArgVisitor.php @@ -15,7 +15,7 @@ public function enterNode(Node $node): ?Node { if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { $functionName = $node->name->toLowerString(); - if (in_array($functionName, ['array_find', 'array_find_key'], true)) { + if (in_array($functionName, ['array_all', 'array_any', 'array_find', 'array_find_key'], true)) { $args = $node->getRawArgs(); if (isset($args[0])) { $args[0]->setAttribute(self::ATTRIBUTE_NAME, true); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index cec109d5f80..123fa4259f5 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -886,6 +886,66 @@ public function testArrayFilterCallback(bool $checkExplicitMixed): void $this->analyse([__DIR__ . '/data/array_filter_callback.php'], $errors); } + public function testArrayAllCallback(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test skipped on lower version than 8.4 (needs array_all function)'); + } + + $this->analyse([__DIR__ . '/data/array_all.php'], [ + [ + 'Parameter #2 $callback of function array_all expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 22, + ], + [ + 'Parameter #2 $callback of function array_all expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 30, + ], + [ + 'Parameter #2 $callback of function array_all expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.', + 36, + ], + [ + 'Parameter #2 $callback of function array_all expects callable(mixed, int|string): bool, Closure(string, array): false given.', + 52, + ], + [ + 'Parameter #2 $callback of function array_all expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', + 55, + ], + ]); + } + + public function testArrayAnyCallback(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test skipped on lower version than 8.4 (needs array_any function)'); + } + + $this->analyse([__DIR__ . '/data/array_any.php'], [ + [ + 'Parameter #2 $callback of function array_any expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 22, + ], + [ + 'Parameter #2 $callback of function array_any expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(string, int): bool given.', + 30, + ], + [ + 'Parameter #2 $callback of function array_any expects callable(1|2, \'bar\'|\'foo\'): bool, Closure(int, string): (\'bar\'|\'foo\') given.', + 36, + ], + [ + 'Parameter #2 $callback of function array_any expects callable(mixed, int|string): bool, Closure(string, array): false given.', + 52, + ], + [ + 'Parameter #2 $callback of function array_any expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', + 55, + ], + ]); + } + public function testArrayFindCallback(): void { $this->analyse([__DIR__ . '/data/array_find.php'], [ @@ -903,11 +963,11 @@ public function testArrayFindCallback(): void ], [ 'Parameter #2 $callback of function array_find expects callable(mixed, int|string): bool, Closure(string, array): false given.', - 49, + 52, ], [ 'Parameter #2 $callback of function array_find expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', - 52, + 55, ], ]); } @@ -929,11 +989,11 @@ public function testArrayFindKeyCallback(): void ], [ 'Parameter #2 $callback of function array_find_key expects callable(mixed, int|string): bool, Closure(string, array): false given.', - 49, + 52, ], [ 'Parameter #2 $callback of function array_find_key expects callable(mixed, int|string): bool, Closure(string, int): array{} given.', - 52, + 55, ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/array_all.php b/tests/PHPStan/Rules/Functions/data/array_all.php new file mode 100644 index 00000000000..e4a3348eb3c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_all.php @@ -0,0 +1,56 @@ += 8.4 + +// ok +array_all( + ['foo' => 1, 'bar' => 2], + function($value, $key) { + return $key === 0; + } +); + +// ok +array_all( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key === 0; + } +); + +// bad parameters +array_all( + ['foo' => 1, 'bar' => 2], + function(string $value, int $key): bool { + return $key === 0; + } +); + +// bad parameters +array_all( + ['foo' => 1, 'bar' => 2], + fn (string $item, int $key) => $key === 0, +); + +// bad return type +array_all( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key; + }, +); + +if (is_array($array)) { + // ok + array_all($array, fn ($value, $key) => $key === 0); + + // ok + array_all($array, fn (string $value, int $key) => $key === 0); + + // ok + array_all($array, fn (string $value) => $value === 'foo'); + + // bad parameters + array_all($array, fn (string $item, array $key) => $key === 0); + + // bad return type + array_all($array, fn (string $value, int $key): array => []); +} diff --git a/tests/PHPStan/Rules/Functions/data/array_any.php b/tests/PHPStan/Rules/Functions/data/array_any.php new file mode 100644 index 00000000000..1c267ffc62d --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_any.php @@ -0,0 +1,56 @@ += 8.4 + +// ok +array_any( + ['foo' => 1, 'bar' => 2], + function($value, $key) { + return $key === 0; + } +); + +// ok +array_any( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key === 0; + } +); + +// bad parameters +array_any( + ['foo' => 1, 'bar' => 2], + function(string $value, int $key): bool { + return $key === 0; + } +); + +// bad parameters +array_any( + ['foo' => 1, 'bar' => 2], + fn (string $item, int $key) => $key === 0, +); + +// bad return type +array_any( + ['foo' => 1, 'bar' => 2], + function(int $value, string $key) { + return $key; + }, +); + +if (is_array($array)) { + // ok + array_any($array, fn ($value, $key) => $key === 0); + + // ok + array_any($array, fn (string $value, int $key) => $key === 0); + + // ok + array_any($array, fn (string $value) => $value === 'foo'); + + // bad parameters + array_any($array, fn (string $item, array $key) => $key === 0); + + // bad return type + array_any($array, fn (string $value, int $key): array => []); +} diff --git a/tests/PHPStan/Rules/Functions/data/array_find.php b/tests/PHPStan/Rules/Functions/data/array_find.php index a749bd82949..3b7d7f9e13a 100644 --- a/tests/PHPStan/Rules/Functions/data/array_find.php +++ b/tests/PHPStan/Rules/Functions/data/array_find.php @@ -45,6 +45,9 @@ function(int $value, string $key) { // ok array_find($array, fn (string $value, int $key) => $key === 0); + // ok + array_find($array, fn (string $value) => $key === 0); + // bad parameters array_find($array, fn (string $item, array $key) => $key === 0); diff --git a/tests/PHPStan/Rules/Functions/data/array_find_key.php b/tests/PHPStan/Rules/Functions/data/array_find_key.php index 393468d42c2..ab2b7df3fb1 100644 --- a/tests/PHPStan/Rules/Functions/data/array_find_key.php +++ b/tests/PHPStan/Rules/Functions/data/array_find_key.php @@ -45,6 +45,9 @@ function(int $value, string $key) { // ok array_find_key($array, fn (string $value, int $key) => $key === 0); + // ok + array_find_key($array, fn (string $value) => $value === 'foo'); + // bad parameters array_find_key($array, fn (string $item, array $key) => $key === 0); From 71d01d661a5602d19f0a313a95ff8d66fd00798b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 6 Nov 2024 11:41:43 +0100 Subject: [PATCH 0757/3097] xdebug_get_function_stack: fix signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 275eea976fd..cb8b4c58700 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -13272,7 +13272,7 @@ 'xdebug_get_declared_vars' => ['array'], 'xdebug_get_formatted_function_stack' => [''], 'xdebug_get_function_count' => ['int'], -'xdebug_get_function_stack' => ['array', 'message='=>'string', 'options='=>'int'], +'xdebug_get_function_stack' => ['array', 'options='=>'array{local_vars?: bool, params_as_values?: bool, from_exception?: Throwable}'], 'xdebug_get_headers' => ['array'], 'xdebug_get_monitored_functions' => ['array'], 'xdebug_get_profiler_filename' => ['string'], From d999117006c777fa14ad1f07d1eded3ff00af7ea Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 6 Nov 2024 14:45:03 +0100 Subject: [PATCH 0758/3097] Preserve correct UnionType subclass in `filterTypes()` --- src/Type/BenevolentUnionType.php | 10 +++ .../Generic/TemplateBenevolentUnionType.php | 17 ++++ src/Type/Generic/TemplateUnionType.php | 17 ++++ src/Type/UnionType.php | 6 ++ tests/PHPStan/Analyser/nsrt/bug-6609-83.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6609.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 5 ++ .../PHPStan/Rules/Methods/data/bug-11663.php | 79 +++++++++++++++++++ 8 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11663.php diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index bb4a47761bc..d6b43fbd852 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -19,6 +19,16 @@ public function __construct(array $types, bool $normalized = false) parent::__construct($types, $normalized); } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof self && $result instanceof UnionType) { + return TypeUtils::toBenevolentUnion($result); + } + + return $result; + } + public function describe(VerbosityLevel $level): string { return '(' . parent::describe($level) . ')'; diff --git a/src/Type/Generic/TemplateBenevolentUnionType.php b/src/Type/Generic/TemplateBenevolentUnionType.php index cc630fd0dde..aea8573131f 100644 --- a/src/Type/Generic/TemplateBenevolentUnionType.php +++ b/src/Type/Generic/TemplateBenevolentUnionType.php @@ -47,4 +47,21 @@ public function withTypes(array $types): self ); } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof TemplateType) { + return TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + $this->getStrategy(), + $this->getDefault(), + ); + } + + return $result; + } + } diff --git a/src/Type/Generic/TemplateUnionType.php b/src/Type/Generic/TemplateUnionType.php index cc196a07f4c..dc58af565aa 100644 --- a/src/Type/Generic/TemplateUnionType.php +++ b/src/Type/Generic/TemplateUnionType.php @@ -34,4 +34,21 @@ public function __construct( $this->default = $default; } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof TemplateType) { + return TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + $this->getStrategy(), + $this->getDefault(), + ); + } + + return $result; + } + } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index b7e794378af..f0bef45903b 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -92,14 +92,20 @@ public function getTypes(): array public function filterTypes(callable $filterCb): Type { $newTypes = []; + $changed = false; foreach ($this->getTypes() as $innerType) { if (!$filterCb($innerType)) { + $changed = true; continue; } $newTypes[] = $innerType; } + if (!$changed) { + return $this; + } + return TypeCombinator::union(...$newTypes); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6609-83.php b/tests/PHPStan/Analyser/nsrt/bug-6609-83.php index 65d7f22f1d2..4a5f5bb7812 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6609-83.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6609-83.php @@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) { */ function modify3(\DateTimeInterface $date, string $s) { $date = $date->modify($s); - assertType('DateTime|DateTimeImmutable', $date); + assertType('T of DateTime|DateTimeImmutable (method Bug6609Php83\Foo::modify3(), argument)', $date); return $date; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6609.php b/tests/PHPStan/Analyser/nsrt/bug-6609.php index 046f9c8403a..571f97d9887 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6609.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6609.php @@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) { */ function modify3(\DateTimeInterface $date, string $s) { $date = $date->modify($s); - assertType('(DateTime|DateTimeImmutable|false)', $date); + assertType('((T of DateTime|DateTimeImmutable (method Bug6609\Foo::modify3(), argument))|false)', $date); return $date; } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6d374a6f1c9..fcbc0643c96 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1054,4 +1054,9 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug11663(): void + { + $this->analyse([__DIR__ . '/data/bug-11663.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11663.php b/tests/PHPStan/Rules/Methods/data/bug-11663.php new file mode 100644 index 00000000000..ac2ed03a93f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11663.php @@ -0,0 +1,79 @@ +where('test'); + } + + /** + * @param __benevolent $template + * @return __benevolent + */ + public function test2($template) + { + return $template->where('test'); + } + + + /** + * @template T of A|B + * @param T $ab + * @return T + */ + function foo(A|B $ab): A|B + { + return $ab->doFoo(); + } + + /** + * @template T of __benevolent + * @param T $ab + * @return T + */ + function foo2(A|B $ab): A|B + { + return $ab->doFoo(); + } +} From 11998ed4eb36ef036877e14e9ce3eb8a7cb8fc76 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 6 Nov 2024 12:01:44 +0100 Subject: [PATCH 0759/3097] Update changelog --- changelog-2.0.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index b4967463c65..1e2d866a1fd 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -134,7 +134,12 @@ Improvements 🔧 * Collected PHP errors cannot be ignored (https://github.com/phpstan/phpstan-src/commit/1d3f4313955dc6fa5c6ce60fa58afe765964e5b0) * Added missing rules to StubValidator (https://github.com/phpstan/phpstan-src/commit/bf19914cac1682d0eab8bf65a874ba368522311c) * Report precise offsets in errors ([#3504](https://github.com/phpstan/phpstan-src/pull/3504)), thanks @ruudk! - +* IntersectionType - always describe list as list (https://github.com/phpstan/phpstan-src/commit/f680629bc92e4dd5d7acd3bc60c9539fb047452b) +* ArrayType::describe - explicit mixed should be stated explicitly (https://github.com/phpstan/phpstan-src/commit/6cf223840f89c972551f373ade9eea16d12e143b) +* Refactor IntersectionType::describe() (https://github.com/phpstan/phpstan-src/commit/67fbfaee6585c2d47485dc2a159ee76d3ed02b35) +* Remove inefficient caching from `PhpMethodReflection` and `PhpFunctionReflection::isVariadic()` ([#3534](https://github.com/phpstan/phpstan-src/pull/3534)), thanks @staabm! +* Clean file cache from unused items (https://github.com/phpstan/phpstan-src/commit/466ad51740d629c9137a77dac28a676b71ef7197) +* Journal for used generated containers (https://github.com/phpstan/phpstan-src/commit/57c65888e6372a4056afbbacc8207d411ea8559a) Bugfixes 🐛 ===================== @@ -165,6 +170,8 @@ Function signature fixes 🤖 * Update `Locale` signatures ([#2880](https://github.com/phpstan/phpstan-src/pull/2880)), thanks @devnix! * Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3!* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! * Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! +* Support returning an array or a string in `count_chars()` ([#3596](https://github.com/phpstan/phpstan-src/pull/3596)), thanks @u01jmg3! +* xdebug_get_function_stack: fix signature ([#3605](https://github.com/phpstan/phpstan-src/pull/3605)), thanks @janedbal! Internals 🔍 @@ -194,3 +201,8 @@ Internals 🔍 * More interfaces that are not supposed to be implemented in userland (https://github.com/phpstan/phpstan-src/commit/778af2ed74ba59bfb2a69fd5b45821ccdb1107c9, https://github.com/phpstan/phpstan-src/commit/cb6ab5544a016c52f931fc390bcdf9c627819d8f) * Refactored `FunctionCallParametersCheck::check()` parameters (https://github.com/phpstan/phpstan-src/commit/710e09c41698efb1d8d3ae31791944077dbb9cc1) * Spread list usages in Reflection, Scope, Type ([#3530](https://github.com/phpstan/phpstan-src/pull/3530)), thanks @janedbal! +* Remove $isFinal dead-code in PhpFunctionReflection ([#3545](https://github.com/phpstan/phpstan-src/pull/3545)), thanks @staabm! +* Get rid of unnecessary `instanceof self` in `ConstantArrayType` ([#3552](https://github.com/phpstan/phpstan-src/pull/3552)), thanks @herndlm! +* test: use `bashunit -a` exit_code to check for errors ([#3533](https://github.com/phpstan/phpstan-src/pull/3533)), thanks @Chemaclass! +* Remove dead code ([#3575](https://github.com/phpstan/phpstan-src/pull/3575)), thanks @staabm! +* Remove dead code in ConstantConditionRuleHelper ([#3597](https://github.com/phpstan/phpstan-src/pull/3597)), thanks @staabm! From 6924e46f4af313fc70403120ff7a08a62594bb55 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 6 Nov 2024 17:35:43 +0100 Subject: [PATCH 0760/3097] Utilize PHP version constraint from composer.json to narrow `PHP_*` constants --- .github/workflows/e2e-tests.yml | 45 +++++ conf/config.neon | 8 +- conf/parametersSchema.neon | 8 +- e2e/composer-max-version/.gitignore | 2 + e2e/composer-max-version/composer.json | 5 + e2e/composer-max-version/test.php | 10 ++ e2e/composer-min-max-version/.gitignore | 2 + e2e/composer-min-max-version/composer.json | 5 + e2e/composer-min-max-version/test.php | 10 ++ e2e/composer-min-open-end-version/.gitignore | 2 + .../composer.json | 5 + e2e/composer-min-open-end-version/test.php | 6 + e2e/composer-min-version-v5/.gitignore | 2 + e2e/composer-min-version-v5/composer.json | 5 + e2e/composer-min-version-v5/test.php | 6 + e2e/composer-min-version-v7/.gitignore | 2 + e2e/composer-min-version-v7/composer.json | 5 + e2e/composer-min-version-v7/test.php | 6 + e2e/composer-min-version/.gitignore | 2 + e2e/composer-min-version/composer.json | 5 + e2e/composer-min-version/test.php | 6 + e2e/composer-no-versions/.gitignore | 2 + e2e/composer-no-versions/composer.json | 2 + e2e/composer-no-versions/test.php | 6 + .../phpstan.neon | 5 + e2e/composer-version-config-patch/.gitignore | 2 + .../composer.json | 5 + e2e/composer-version-config-patch/test.php | 7 + e2e/composer-version-config/.gitignore | 2 + e2e/composer-version-config/composer.json | 5 + e2e/composer-version-config/phpstan.neon | 4 + e2e/composer-version-config/test.php | 11 ++ src/Analyser/ConstantResolver.php | 69 +++++++- src/Analyser/ConstantResolverFactory.php | 5 + src/DependencyInjection/ContainerFactory.php | 13 ++ .../InvalidPhpVersionException.php | 10 ++ .../ValidateIgnoredErrorsExtension.php | 2 +- src/Php/ComposerPhpVersionFactory.php | 158 ++++++++++++++++++ src/Php/PhpVersion.php | 21 ++- src/Php/PhpVersionFactory.php | 9 +- src/Php/PhpVersionFactoryFactory.php | 17 +- src/Testing/PHPStanTestCase.php | 2 +- ...pareFunctionDynamicReturnTypeExtension.php | 38 ++++- 43 files changed, 521 insertions(+), 21 deletions(-) create mode 100644 e2e/composer-max-version/.gitignore create mode 100644 e2e/composer-max-version/composer.json create mode 100644 e2e/composer-max-version/test.php create mode 100644 e2e/composer-min-max-version/.gitignore create mode 100644 e2e/composer-min-max-version/composer.json create mode 100644 e2e/composer-min-max-version/test.php create mode 100644 e2e/composer-min-open-end-version/.gitignore create mode 100644 e2e/composer-min-open-end-version/composer.json create mode 100644 e2e/composer-min-open-end-version/test.php create mode 100644 e2e/composer-min-version-v5/.gitignore create mode 100644 e2e/composer-min-version-v5/composer.json create mode 100644 e2e/composer-min-version-v5/test.php create mode 100644 e2e/composer-min-version-v7/.gitignore create mode 100644 e2e/composer-min-version-v7/composer.json create mode 100644 e2e/composer-min-version-v7/test.php create mode 100644 e2e/composer-min-version/.gitignore create mode 100644 e2e/composer-min-version/composer.json create mode 100644 e2e/composer-min-version/test.php create mode 100644 e2e/composer-no-versions/.gitignore create mode 100644 e2e/composer-no-versions/composer.json create mode 100644 e2e/composer-no-versions/test.php create mode 100644 e2e/composer-version-config-invalid/phpstan.neon create mode 100644 e2e/composer-version-config-patch/.gitignore create mode 100644 e2e/composer-version-config-patch/composer.json create mode 100644 e2e/composer-version-config-patch/test.php create mode 100644 e2e/composer-version-config/.gitignore create mode 100644 e2e/composer-version-config/composer.json create mode 100644 e2e/composer-version-config/phpstan.neon create mode 100644 e2e/composer-version-config/test.php create mode 100644 src/DependencyInjection/InvalidPhpVersionException.php create mode 100644 src/Php/ComposerPhpVersionFactory.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9c134d199cf..7577e5c04fe 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -292,6 +292,48 @@ jobs: - script: | cd e2e/bug-11819 ../../bin/phpstan + - script: | + cd e2e/composer-max-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-max-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-open-end-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version-v5 + composer install --ignore-platform-reqs + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version-v7 + composer install --ignore-platform-reqs + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-min-version + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-no-versions + composer install + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-config-invalid + OUTPUT=$(../bashunit -a exit_code "1" ../../bin/phpstan) + echo "$OUTPUT" + ../bashunit -a contains 'Invalid configuration' "$OUTPUT" + ../bashunit -a contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT" + - script: | + cd e2e/composer-version-config-patch + composer install --ignore-platform-reqs + ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-config + composer install + ../../bin/phpstan analyze test.php --level=0 steps: - name: "Checkout" @@ -308,5 +350,8 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" + - name: "Install bashunit" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.17.0" + - name: "Test" run: ${{ matrix.script }} diff --git a/conf/config.neon b/conf/config.neon index 7d1bf16616b..df5c15ddec7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -339,7 +339,13 @@ services: - class: PHPStan\Php\PhpVersionFactoryFactory arguments: - versionId: %phpVersion% + phpVersion: %phpVersion% + composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% + + - + class: PHPStan\Php\ComposerPhpVersionFactory + arguments: + phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d6e4a348828..6c7f9c40e61 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -79,7 +79,13 @@ parametersSchema: minimumNumberOfJobsPerProcess: int(), buffer: int() ]) - phpVersion: schema(anyOf(schema(int(), min(70100), max(80499))), nullable()) + phpVersion: schema(anyOf( + schema(int(), min(70100), max(80499)), + structure([ + min: schema(int(), min(70100), max(80499)), + max: schema(int(), min(70100), max(80499)) + ]) + ), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() polluteScopeWithBlock: bool() diff --git a/e2e/composer-max-version/.gitignore b/e2e/composer-max-version/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-max-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-max-version/composer.json b/e2e/composer-max-version/composer.json new file mode 100644 index 00000000000..4d4ca141ef4 --- /dev/null +++ b/e2e/composer-max-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "<=8.3" + } +} diff --git a/e2e/composer-max-version/test.php b/e2e/composer-max-version/test.php new file mode 100644 index 00000000000..038f5591224 --- /dev/null +++ b/e2e/composer-max-version/test.php @@ -0,0 +1,10 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('-1|0|1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('bool', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/e2e/composer-min-max-version/.gitignore b/e2e/composer-min-max-version/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-min-max-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-max-version/composer.json b/e2e/composer-min-max-version/composer.json new file mode 100644 index 00000000000..869fd2ce42e --- /dev/null +++ b/e2e/composer-min-max-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">=8.1, <=8.2.99" + } +} diff --git a/e2e/composer-min-max-version/test.php b/e2e/composer-min-max-version/test.php new file mode 100644 index 00000000000..28d770f3bbf --- /dev/null +++ b/e2e/composer-min-max-version/test.php @@ -0,0 +1,10 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 2>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/e2e/composer-min-open-end-version/.gitignore b/e2e/composer-min-open-end-version/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-min-open-end-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-open-end-version/composer.json b/e2e/composer-min-open-end-version/composer.json new file mode 100644 index 00000000000..b6303c6b770 --- /dev/null +++ b/e2e/composer-min-open-end-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">= 8.1" + } +} diff --git a/e2e/composer-min-open-end-version/test.php b/e2e/composer-min-open-end-version/test.php new file mode 100644 index 00000000000..d4d06a34eb9 --- /dev/null +++ b/e2e/composer-min-open-end-version/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 4>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version-v5/.gitignore b/e2e/composer-min-version-v5/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-min-version-v5/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version-v5/composer.json b/e2e/composer-min-version-v5/composer.json new file mode 100644 index 00000000000..b73464d219d --- /dev/null +++ b/e2e/composer-min-version-v5/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^5.6" + } +} diff --git a/e2e/composer-min-version-v5/test.php b/e2e/composer-min-version-v5/test.php new file mode 100644 index 00000000000..2f652079a65 --- /dev/null +++ b/e2e/composer-min-version-v5/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('5', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('6', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, 99>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version-v7/.gitignore b/e2e/composer-min-version-v7/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-min-version-v7/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version-v7/composer.json b/e2e/composer-min-version-v7/composer.json new file mode 100644 index 00000000000..9f9b2638713 --- /dev/null +++ b/e2e/composer-min-version-v7/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^7" + } +} diff --git a/e2e/composer-min-version-v7/test.php b/e2e/composer-min-version-v7/test.php new file mode 100644 index 00000000000..e8876bd78ff --- /dev/null +++ b/e2e/composer-min-version-v7/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('7', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, 4>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-min-version/.gitignore b/e2e/composer-min-version/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-min-version/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-min-version/composer.json b/e2e/composer-min-version/composer.json new file mode 100644 index 00000000000..9be64619f16 --- /dev/null +++ b/e2e/composer-min-version/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8.1" + } +} diff --git a/e2e/composer-min-version/test.php b/e2e/composer-min-version/test.php new file mode 100644 index 00000000000..d4d06a34eb9 --- /dev/null +++ b/e2e/composer-min-version/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 4>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-no-versions/.gitignore b/e2e/composer-no-versions/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-no-versions/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-no-versions/composer.json b/e2e/composer-no-versions/composer.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/e2e/composer-no-versions/composer.json @@ -0,0 +1,2 @@ +{ +} diff --git a/e2e/composer-no-versions/test.php b/e2e/composer-no-versions/test.php new file mode 100644 index 00000000000..28c8a3183bb --- /dev/null +++ b/e2e/composer-no-versions/test.php @@ -0,0 +1,6 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('int<5, 8>', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-version-config-invalid/phpstan.neon b/e2e/composer-version-config-invalid/phpstan.neon new file mode 100644 index 00000000000..96977def5fe --- /dev/null +++ b/e2e/composer-version-config-invalid/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + phpVersion: + min: 80303 + max: 80104 + diff --git a/e2e/composer-version-config-patch/.gitignore b/e2e/composer-version-config-patch/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-version-config-patch/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-version-config-patch/composer.json b/e2e/composer-version-config-patch/composer.json new file mode 100644 index 00000000000..d6103988c85 --- /dev/null +++ b/e2e/composer-version-config-patch/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": ">=8.0.2, <8.0.15" + } +} diff --git a/e2e/composer-version-config-patch/test.php b/e2e/composer-version-config-patch/test.php new file mode 100644 index 00000000000..3f201eadabe --- /dev/null +++ b/e2e/composer-version-config-patch/test.php @@ -0,0 +1,7 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('0', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<2, 15>', PHP_RELEASE_VERSION); diff --git a/e2e/composer-version-config/.gitignore b/e2e/composer-version-config/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-version-config/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-version-config/composer.json b/e2e/composer-version-config/composer.json new file mode 100644 index 00000000000..2da0adaf1cc --- /dev/null +++ b/e2e/composer-version-config/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8.0" + } +} diff --git a/e2e/composer-version-config/phpstan.neon b/e2e/composer-version-config/phpstan.neon new file mode 100644 index 00000000000..003e5e1484e --- /dev/null +++ b/e2e/composer-version-config/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + phpVersion: + min: 80103 + max: 80304 diff --git a/e2e/composer-version-config/test.php b/e2e/composer-version-config/test.php new file mode 100644 index 00000000000..a9afaa4b658 --- /dev/null +++ b/e2e/composer-version-config/test.php @@ -0,0 +1,11 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 3>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index b209533fac8..8176fe8abcb 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PhpParser\Node\Name; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; @@ -20,6 +21,7 @@ use PHPStan\Type\UnionType; use function array_key_exists; use function in_array; +use function max; use function sprintf; use const INF; use const NAN; @@ -34,7 +36,12 @@ final class ConstantResolver /** * @param string[] $dynamicConstantNames */ - public function __construct(private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames) + public function __construct( + private ReflectionProviderProvider $reflectionProviderProvider, + private array $dynamicConstantNames, + private ?PhpVersion $composerMinPhpVersion, + private ?PhpVersion $composerMaxPhpVersion, + ) { } @@ -77,16 +84,60 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type ]); } if ($resolvedConstantName === 'PHP_MAJOR_VERSION') { - return IntegerRangeType::fromInterval(5, null); + $minMajor = 5; + $maxMajor = null; + + if ($this->composerMinPhpVersion !== null) { + $minMajor = max($minMajor, $this->composerMinPhpVersion->getMajorVersionId()); + } + if ($this->composerMaxPhpVersion !== null) { + $maxMajor = $this->composerMaxPhpVersion->getMajorVersionId(); + } + + return $this->createInteger($minMajor, $maxMajor); } if ($resolvedConstantName === 'PHP_MINOR_VERSION') { - return IntegerRangeType::fromInterval(0, null); + $minMinor = 0; + $maxMinor = null; + + if ( + $this->composerMinPhpVersion !== null + && $this->composerMaxPhpVersion !== null + && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() + ) { + $minMinor = $this->composerMinPhpVersion->getMinorVersionId(); + $maxMinor = $this->composerMaxPhpVersion->getMinorVersionId(); + } + + return $this->createInteger($minMinor, $maxMinor); } if ($resolvedConstantName === 'PHP_RELEASE_VERSION') { - return IntegerRangeType::fromInterval(0, null); + $minRelease = 0; + $maxRelease = null; + + if ( + $this->composerMinPhpVersion !== null + && $this->composerMaxPhpVersion !== null + && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() + && $this->composerMaxPhpVersion->getMinorVersionId() === $this->composerMinPhpVersion->getMinorVersionId() + ) { + $minRelease = $this->composerMinPhpVersion->getPatchVersionId(); + $maxRelease = $this->composerMaxPhpVersion->getPatchVersionId(); + } + + return $this->createInteger($minRelease, $maxRelease); } if ($resolvedConstantName === 'PHP_VERSION_ID') { - return IntegerRangeType::fromInterval(50207, null); + $minVersion = 50207; + $maxVersion = null; + if ($this->composerMinPhpVersion !== null) { + $minVersion = max($minVersion, $this->composerMinPhpVersion->getVersionId()); + } + if ($this->composerMaxPhpVersion !== null) { + $maxVersion = $this->composerMaxPhpVersion->getVersionId(); + } + + return $this->createInteger($minVersion, $maxVersion); } if ($resolvedConstantName === 'PHP_ZTS') { return new UnionType([ @@ -325,6 +376,14 @@ public function resolveClassConstantType(string $className, string $constantName return $constantType; } + private function createInteger(?int $min, ?int $max): Type + { + if ($min !== null && $min === $max) { + return new ConstantIntegerType($min); + } + return IntegerRangeType::fromInterval($min, $max); + } + private function getReflectionProvider(): ReflectionProvider { return $this->reflectionProviderProvider->getReflectionProvider(); diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index 67a98408a03..f111da14ec7 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PHPStan\DependencyInjection\Container; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; final class ConstantResolverFactory @@ -17,9 +18,13 @@ public function __construct( public function create(): ConstantResolver { + $composerFactory = $this->container->getByType(ComposerPhpVersionFactory::class); + return new ConstantResolver( $this->reflectionProviderProvider, $this->container->getParameter('dynamicConstantNames'), + $composerFactory->getMinVersion(), + $composerFactory->getMaxVersion(), ); } diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 7ac72d4fc49..75c79d6678a 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -32,6 +32,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; use function array_diff_key; +use function array_key_exists; use function array_map; use function array_merge; use function array_unique; @@ -310,6 +311,18 @@ private function validateParameters(array $parameters, array $parametersSchema): $context->path = ['parameters']; }; $processor->process($schema, $parameters); + + if ( + !array_key_exists('phpVersion', $parameters) + || !is_array($parameters['phpVersion'])) { + return; + } + + $phpVersion = $parameters['phpVersion']; + + if ($phpVersion['max'] < $phpVersion['min']) { + throw new InvalidPhpVersionException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); + } } /** diff --git a/src/DependencyInjection/InvalidPhpVersionException.php b/src/DependencyInjection/InvalidPhpVersionException.php new file mode 100644 index 00000000000..f9b41690a39 --- /dev/null +++ b/src/DependencyInjection/InvalidPhpVersionException.php @@ -0,0 +1,10 @@ +initialized = true; + + $phpVersion = $this->phpVersion; + + if (is_int($phpVersion)) { + throw new ShouldNotHappenException(); + } + + if (is_array($phpVersion)) { + if ($phpVersion['max'] < $phpVersion['min']) { + throw new ShouldNotHappenException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); + } + + $this->minVersion = new PhpVersion($phpVersion['min']); + $this->maxVersion = new PhpVersion($phpVersion['max']); + + return; + } + + // don't limit minVersion... PHPStan can analyze even PHP5 + $this->maxVersion = new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION); + + // fallback to composer.json based php-version constraint + $composerPhpVersion = $this->getComposerRequireVersion(); + if ($composerPhpVersion === null) { + return; + } + + $parser = new VersionParser(); + $constraint = $parser->parseConstraints($composerPhpVersion); + + if (!$constraint->getLowerBound()->isZero()) { + $minVersion = $this->buildVersion($constraint->getLowerBound()->getVersion(), false); + + if ($minVersion !== null) { + $this->minVersion = new PhpVersion($minVersion->getVersionId()); + } + } + if ($constraint->getUpperBound()->isPositiveInfinity()) { + return; + } + + $this->maxVersion = $this->buildVersion($constraint->getUpperBound()->getVersion(), true); + } + + public function getMinVersion(): ?PhpVersion + { + if (is_int($this->phpVersion)) { + return null; + } + + if ($this->initialized === false) { + $this->initializeVersions(); + } + + return $this->minVersion; + } + + public function getMaxVersion(): ?PhpVersion + { + if (is_int($this->phpVersion)) { + return null; + } + + if ($this->initialized === false) { + $this->initializeVersions(); + } + + return $this->maxVersion; + } + + private function getComposerRequireVersion(): ?string + { + $composerPhpVersion = null; + if (count($this->composerAutoloaderProjectPaths) > 0) { + $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; + if (is_file($composerJsonPath)) { + try { + $composerJsonContents = FileReader::read($composerJsonPath); + $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); + $requiredVersion = $composer['require']['php'] ?? null; + if (is_string($requiredVersion)) { + $composerPhpVersion = $requiredVersion; + } + } catch (CouldNotReadFileException | JsonException) { + // pass + } + } + } + return $composerPhpVersion; + } + + private function buildVersion(string $version, bool $isMaxVersion): ?PhpVersion + { + $matches = Strings::match($version, '#^(\d+)\.(\d+)(?:\.(\d+))?#'); + if ($matches === null) { + return null; + } + + $major = $matches[1]; + $minor = $matches[2]; + $patch = $matches[3] ?? 0; + $versionId = (int) sprintf('%d%02d%02d', $major, $minor, $patch); + + if ($isMaxVersion && $version === '6.0.0.0-dev') { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP5_VERSION); + } elseif ($isMaxVersion && $version === '8.0.0.0-dev') { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP7_VERSION); + } else { + $versionId = min($versionId, PhpVersionFactory::MAX_PHP_VERSION); + } + + return new PhpVersion($versionId); + } + +} diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index fcd6871c398..83ca1245b5f 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -41,11 +41,26 @@ public function getVersionId(): int return $this->versionId; } + public function getMajorVersionId(): int + { + return (int) floor($this->versionId / 10000); + } + + public function getMinorVersionId(): int + { + return (int) floor(($this->versionId % 10000) / 100); + } + + public function getPatchVersionId(): int + { + return (int) floor($this->versionId % 100); + } + public function getVersionString(): string { - $first = (int) floor($this->versionId / 10000); - $second = (int) floor(($this->versionId % 10000) / 100); - $third = (int) floor($this->versionId % 100); + $first = $this->getMajorVersionId(); + $second = $this->getMinorVersionId(); + $third = $this->getPatchVersionId(); return $first . '.' . $second . ($third !== 0 ? '.' . $third : ''); } diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index d926420e773..bd1bfcabf54 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -10,6 +10,11 @@ final class PhpVersionFactory { + public const MIN_PHP_VERSION = 70100; + public const MAX_PHP_VERSION = 80499; + public const MAX_PHP5_VERSION = 50699; + public const MAX_PHP7_VERSION = 70499; + public function __construct( private ?int $versionId, private ?string $composerPhpVersion, @@ -25,8 +30,8 @@ public function create(): PhpVersion } elseif ($this->composerPhpVersion !== null) { $parts = explode('.', $this->composerPhpVersion); $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); - $tmp = max($tmp, 70100); - $versionId = min($tmp, 80499); + $tmp = max($tmp, self::MIN_PHP_VERSION); + $versionId = min($tmp, self::MAX_PHP_VERSION); $source = PhpVersion::SOURCE_COMPOSER_PLATFORM_PHP; } else { $versionId = PHP_VERSION_ID; diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index c578ef06fce..0190ca0e82d 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -8,17 +8,20 @@ use PHPStan\File\FileReader; use function count; use function end; +use function is_array; use function is_file; +use function is_int; use function is_string; final class PhpVersionFactoryFactory { /** + * @param int|array{min: int, max: int}|null $phpVersion * @param string[] $composerAutoloaderProjectPaths */ public function __construct( - private ?int $versionId, + private int|array|null $phpVersion, private array $composerAutoloaderProjectPaths, ) { @@ -43,7 +46,17 @@ public function create(): PhpVersionFactory } } - return new PhpVersionFactory($this->versionId, $composerPhpVersion); + $versionId = null; + + if (is_int($this->phpVersion)) { + $versionId = $this->phpVersion; + } + + if (is_array($this->phpVersion)) { + $versionId = $this->phpVersion['min']; + } + + return new PhpVersionFactory($versionId, $composerPhpVersion); } } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 87d5bab299c..aee824bfbc7 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -137,7 +137,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider } $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); - $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, null); $initializerExprTypeResolver = new InitializerExprTypeResolver( $constantResolver, diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index 9a5592bf406..d79ebf07064 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -2,12 +2,15 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -18,6 +21,10 @@ final class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private ComposerPhpVersionFactory $composerPhpVersionFactory) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'version_compare'; @@ -29,19 +36,20 @@ public function getTypeFromFunctionCall( Scope $scope, ): ?Type { - if (count($functionCall->getArgs()) < 2) { + $args = $functionCall->getArgs(); + if (count($args) < 2) { return null; } - $version1Strings = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings(); - $version2Strings = $scope->getType($functionCall->getArgs()[1]->value)->getConstantStrings(); + $version1Strings = $this->getVersionStrings($args[0]->value, $scope); + $version2Strings = $this->getVersionStrings($args[1]->value, $scope); $counts = [ count($version1Strings), count($version2Strings), ]; - if (isset($functionCall->getArgs()[2])) { - $operatorStrings = $scope->getType($functionCall->getArgs()[2]->value)->getConstantStrings(); + if (isset($args[2])) { + $operatorStrings = $scope->getType($args[2]->value)->getConstantStrings(); $counts[] = count($operatorStrings); $returnType = new BooleanType(); } else { @@ -77,4 +85,24 @@ public function getTypeFromFunctionCall( return TypeCombinator::union(...$types); } + /** + * @return ConstantStringType[] + */ + private function getVersionStrings(Expr $expr, Scope $scope): array + { + if ( + $expr instanceof Expr\ConstFetch + && $expr->name->toString() === 'PHP_VERSION' + && $this->composerPhpVersionFactory->getMinVersion() !== null + && $this->composerPhpVersionFactory->getMaxVersion() !== null + ) { + return [ + new ConstantStringType($this->composerPhpVersionFactory->getMinVersion()->getVersionString()), + new ConstantStringType($this->composerPhpVersionFactory->getMaxVersion()->getVersionString()), + ]; + } + + return $scope->getType($expr)->getConstantStrings(); + } + } From 6dcda722c3ced237547e2beac127470cb1791fb7 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Wed, 6 Nov 2024 12:51:52 -0600 Subject: [PATCH 0761/3097] Do not transform `$this` in return type even in final classes --- .github/workflows/e2e-tests.yml | 4 ++ e2e/bug-11857/.gitignore | 1 + e2e/bug-11857/composer.json | 5 ++ e2e/bug-11857/composer.lock | 18 +++++ e2e/bug-11857/phpstan.neon | 10 +++ e2e/bug-11857/src/extension.php | 36 ++++++++++ e2e/bug-11857/src/test.php | 70 +++++++++++++++++++ src/Analyser/MutatingScope.php | 2 +- src/Analyser/NodeScopeResolver.php | 2 +- src/Type/StaticType.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 5 ++ .../Rules/Methods/data/bug-11857-builder.php | 49 +++++++++++++ 12 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 e2e/bug-11857/.gitignore create mode 100644 e2e/bug-11857/composer.json create mode 100644 e2e/bug-11857/composer.lock create mode 100644 e2e/bug-11857/phpstan.neon create mode 100644 e2e/bug-11857/src/extension.php create mode 100644 e2e/bug-11857/src/test.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11857-builder.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8914cca9048..4b90be1ec3c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -255,6 +255,10 @@ jobs: echo "$OUTPUT" ../bashunit -a contains 'Child process error (exit code 255): PHP Fatal error' "$OUTPUT" ../bashunit -a contains 'Result is incomplete because of severe errors.' "$OUTPUT" + - script: | + cd e2e/bug-11857 + composer install + ../../bin/phpstan steps: - name: "Checkout" diff --git a/e2e/bug-11857/.gitignore b/e2e/bug-11857/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/e2e/bug-11857/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/bug-11857/composer.json b/e2e/bug-11857/composer.json new file mode 100644 index 00000000000..a072011fe86 --- /dev/null +++ b/e2e/bug-11857/composer.json @@ -0,0 +1,5 @@ +{ + "autoload-dev": { + "classmap": ["src/"] + } +} diff --git a/e2e/bug-11857/composer.lock b/e2e/bug-11857/composer.lock new file mode 100644 index 00000000000..b383d88ac57 --- /dev/null +++ b/e2e/bug-11857/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d751713988987e9331980363e24189ce", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/e2e/bug-11857/phpstan.neon b/e2e/bug-11857/phpstan.neon new file mode 100644 index 00000000000..306701cd6f6 --- /dev/null +++ b/e2e/bug-11857/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + level: 8 + paths: + - src + +services: + - + class: Bug11857\RelationDynamicMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/e2e/bug-11857/src/extension.php b/e2e/bug-11857/src/extension.php new file mode 100644 index 00000000000..c24d0f36535 --- /dev/null +++ b/e2e/bug-11857/src/extension.php @@ -0,0 +1,36 @@ +getName() === 'belongsTo'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { + $returnType = $methodReflection->getVariants()[0]->getReturnType(); + $argType = $scope->getType($methodCall->getArgs()[0]->value); + $modelClass = $argType->getClassStringObjectType()->getObjectClassNames()[0]; + + return new GenericObjectType($returnType->getObjectClassNames()[0], [ + new ObjectType($modelClass), + $scope->getType($methodCall->var), + ]); + } +} + diff --git a/e2e/bug-11857/src/test.php b/e2e/bug-11857/src/test.php new file mode 100644 index 00000000000..5c237f25e83 --- /dev/null +++ b/e2e/bug-11857/src/test.php @@ -0,0 +1,70 @@ + */ + public function belongsTo(string $related): BelongsTo + { + return new BelongsTo(); + } +} + +/** + * @template TRelatedModel of Model + * @template TDeclaringModel of Model + */ +class BelongsTo {} + +class User extends Model {} + +class Post extends Model +{ + /** @return BelongsTo */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** @return BelongsTo */ + public function userSelf(): BelongsTo + { + /** @phpstan-ignore return.type */ + return $this->belongsTo(User::class); + } +} + +class ChildPost extends Post {} + +final class Comment extends Model +{ + // This model is final, so either of these + // two methods would work. It seems that + // PHPStan is automatically converting the + // `$this` to a `self` type in the user docblock, + // but it is not doing so likewise for the `$this` + // that is returned by the dynamic return extension. + + /** @return BelongsTo */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** @return BelongsTo */ + public function user2(): BelongsTo + { + /** @phpstan-ignore return.type */ + return $this->belongsTo(User::class); + } +} + +function test(ChildPost $child): void +{ + assertType('Bug11857\BelongsTo', $child->user()); + // This demonstrates why `$this` is needed in non-final models + assertType('Bug11857\BelongsTo', $child->userSelf()); // should be: Bug11857\BelongsTo +} diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c24defb1f8d..df85c5fa35d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3015,7 +3015,7 @@ private function transformStaticType(Type $type): Type if ($type instanceof StaticType) { $classReflection = $this->getClassReflection(); $changedType = $type->changeBaseClass($classReflection); - if ($classReflection->isFinal()) { + if ($classReflection->isFinal() && !$type instanceof ThisType) { $changedType = $changedType->getStaticObjectType(); } return $traverse($changedType); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 4a8264b6749..dda335f5669 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6159,7 +6159,7 @@ private function transformStaticType(ClassReflection $declaringClass, Type $type return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { if ($type instanceof StaticType) { $changedType = $type->changeBaseClass($declaringClass); - if ($declaringClass->isFinal()) { + if ($declaringClass->isFinal() && !$type instanceof ThisType) { $changedType = $changedType->getStaticObjectType(); } return $traverse($changedType); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 9cf4da4dc8d..5fd6f7e358f 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -299,7 +299,7 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop $isFinal = $classReflection->isFinal(); } $type = $type->changeBaseClass($classReflection); - if (!$isFinal) { + if (!$isFinal || $type instanceof ThisType) { return $type; } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index fcbc0643c96..eaa0465a428 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1059,4 +1059,9 @@ public function testBug11663(): void $this->analyse([__DIR__ . '/data/bug-11663.php'], []); } + public function testBug11857(): void + { + $this->analyse([__DIR__ . '/data/bug-11857-builder.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11857-builder.php b/tests/PHPStan/Rules/Methods/data/bug-11857-builder.php new file mode 100644 index 00000000000..89209ffe373 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11857-builder.php @@ -0,0 +1,49 @@ += 8.0 + +namespace Bug11857Builder; + +class Foo +{ + + /** + * @param array $attributes + * @return $this + */ + public function filter(array $attributes): static + { + return $this; + } + + /** + * @param array $attributes + * @return $this + */ + public function filterUsingRequest(array $attributes): static + { + return $this->filter($attributes); + } + +} + +final class FinalFoo +{ + + /** + * @param array $attributes + * @return $this + */ + public function filter(array $attributes): static + { + return $this; + } + + /** + * @param array $attributes + * @return $this + */ + public function filterUsingRequest(array $attributes): static + { + return $this->filter($attributes); + } + +} From e3ee89952f9ab129c3fc5a96af840501edf9c6bb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 27 Oct 2024 00:36:50 +0200 Subject: [PATCH 0762/3097] Fix too early inference --- src/Analyser/MutatingScope.php | 3 --- .../Rules/Comparison/MatchExpressionRuleTest.php | 9 +++++++++ tests/PHPStan/Rules/Comparison/data/bug-11852.php | 13 +++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-11852.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index df85c5fa35d..5659a107bcf 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1129,9 +1129,6 @@ private function resolveType(string $exprString, Expr $node): Type } $resultType = $this->initializerExprTypeResolver->resolveConcatType($resultType, $partType); - if (count($resultType->getConstantStrings()) === 0) { - return $resultType; - } } return $resultType ?? new ConstantStringType(''); diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index b727ba76e29..dd583aa13be 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -599,4 +599,13 @@ public function testBug9436(): void $this->analyse([__DIR__ . '/data/bug-9436.php'], []); } + public function testBug11852(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/bug-11852.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11852.php b/tests/PHPStan/Rules/Comparison/data/bug-11852.php new file mode 100644 index 00000000000..690def3bc48 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11852.php @@ -0,0 +1,13 @@ += 8.0 + +namespace Bug11852; + +function sayHello(int $type, string $activity): int +{ + return match("$type:$activity") { + '159:Work' => 12, + '159:education' => 19, + + default => throw new \InvalidArgumentException("unknown values $type:$activity"), + }; +} From bf85e9d774fed9422d565521a13a2e21552fcb4d Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 2 Oct 2024 14:42:17 +0200 Subject: [PATCH 0763/3097] Support `@readonly` PHPDoc on the class as alternative to `@immutable` --- src/Reflection/ClassReflection.php | 2 +- ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 18 ++++++++ .../Rules/Properties/data/feature-11775.php | 45 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/feature-11775.php diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 0254204a639..c79f2a6bed5 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1238,7 +1238,7 @@ public function isImmutable(): bool { if ($this->isImmutable === null) { $resolvedPhpDoc = $this->getResolvedPhpDoc(); - $this->isImmutable = $resolvedPhpDoc !== null && $resolvedPhpDoc->isImmutable(); + $this->isImmutable = $resolvedPhpDoc !== null && ($resolvedPhpDoc->isImmutable() || $resolvedPhpDoc->isReadOnly()); $parentClass = $this->getParentClass(); if ($parentClass !== null && !$this->isImmutable) { diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php index e5496fc6463..70d53797015 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -160,4 +160,22 @@ public function testFeature7648(): void $this->analyse([__DIR__ . '/data/feature-7648.php'], []); } + public function testFeature11775(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/feature-11775.php'], [ + [ + '@readonly property Feature11775\FooImmutable::$i is assigned outside of the constructor.', + 22, + ], + [ + '@readonly property Feature11775\FooReadonly::$i is assigned outside of the constructor.', + 43, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/feature-11775.php b/tests/PHPStan/Rules/Properties/data/feature-11775.php new file mode 100644 index 00000000000..a8a4bb8555b --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/feature-11775.php @@ -0,0 +1,45 @@ += 7.4 + +namespace Feature11775; + +/** @immutable */ +class FooImmutable +{ + private int $i; + + public function __construct(int $i) + { + $this->i = $i; + } + + public function getId(): int + { + return $this->i; + } + + public function setId(): void + { + $this->i = 5; + } +} + +/** @readonly */ +class FooReadonly +{ + private int $i; + + public function __construct(int $i) + { + $this->i = $i; + } + + public function getId(): int + { + return $this->i; + } + + public function setId(): void + { + $this->i = 5; + } +} From 3dd5a01b2f6ca0c82a5f79f5aa9ba01aa4b4c69f Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 6 Nov 2024 20:35:51 +0100 Subject: [PATCH 0764/3097] Support `for` endless loops --- src/Analyser/NodeScopeResolver.php | 13 +- .../PHPStan/Analyser/StatementResultTest.php | 126 +++++++++++++++++- ...rictComparisonOfDifferentTypesRuleTest.php | 10 +- .../Comparison/data/strict-comparison.php | 14 +- .../Rules/Missing/MissingReturnRuleTest.php | 18 +++ tests/PHPStan/Rules/Missing/data/bug-6807.php | 16 +++ tests/PHPStan/Rules/Missing/data/bug-8463.php | 26 ++++ tests/PHPStan/Rules/Missing/data/bug-9374.php | 8 ++ ...fined-variables-anonymous-function-use.php | 2 +- .../Variables/data/defined-variables.php | 6 +- tests/e2e/baseline.neon | 5 + 11 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 tests/PHPStan/Rules/Missing/data/bug-6807.php create mode 100644 tests/PHPStan/Rules/Missing/data/bug-8463.php create mode 100644 tests/PHPStan/Rules/Missing/data/bug-9374.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index dda335f5669..1a6a069d28f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1384,7 +1384,10 @@ private function processStmtNode( $loopScope = $this->processExprNode($stmt, $loopExpr, $loopScope, $nodeCallback, ExpressionContext::createTopLevel())->getScope(); } $finalScope = $finalScope->generalizeWith($loopScope); + + $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { + $alwaysIterates = $alwaysIterates->and($finalScope->getType($lastCondExpr)->toBoolean()->isTrue()); $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } @@ -1411,10 +1414,18 @@ private function processStmtNode( } } + if ($alwaysIterates->yes()) { + $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; + } elseif ($isIterableAtLeastOnce->yes()) { + $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); + } else { + $isAlwaysTerminating = false; + } + return new StatementResult( $finalScope, $finalScopeResult->hasYield() || $hasYield, - false/* $finalScopeResult->isAlwaysTerminating() && $isAlwaysIterable*/, + $isAlwaysTerminating, $finalScopeResult->getExitPointsForOuterLoop(), array_merge($throwPoints, $finalScopeResult->getThrowPoints()), array_merge($impurePoints, $finalScopeResult->getImpurePoints()), diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index fb3a9dd5b1a..b86de8617ed 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -173,6 +173,130 @@ public function dataIsAlwaysTerminating(): array 'while (true) { break; }', false, ], + [ + 'while (true) { exit; }', + true, + ], + [ + 'while (true) { while (true) { } }', + true, + ], + [ + 'while (true) { while (true) { return; } }', + true, + ], + [ + 'while (true) { while (true) { break; } }', + true, + ], + [ + 'while (true) { while (true) { exit; } }', + true, + ], + [ + 'while (true) { while (true) { break 2; } }', + false, + ], + [ + 'while (true) { while ($x) { } }', + true, + ], + [ + 'while (true) { while ($x) { return; } }', + true, + ], + [ + 'while (true) { while ($x) { break; } }', + true, + ], + [ + 'while (true) { while ($x) { exit; } }', + true, + ], + [ + 'while (true) { while ($x) { break 2; } }', + false, + ], + [ + 'for (;;) { }', + true, + ], + [ + 'for (;;) { return; }', + true, + ], + [ + 'for (;;) { break; }', + false, + ], + [ + 'for (;;) { exit; }', + true, + ], + [ + 'for (;;) { for (;;) { } }', + true, + ], + [ + 'for (;;) { for (;;) { return; } }', + true, + ], + [ + 'for (;;) { for (;;) { break; } }', + true, + ], + [ + 'for (;;) { for (;;) { exit; } }', + true, + ], + [ + 'for (;;) { for (;;) { break 2; } }', + false, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { return; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { break; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { exit; } }', + true, + ], + [ + 'for (;;) { for ($i = 0; $i< 5; $i++) { break 2; } }', + false, + ], + [ + 'for ($i = 0; $i < 5;) { }', + true, + ], + [ + 'for ($i = 0; $i < 5; $i--) { }', + true, + ], + [ + 'for (; 0, 1;) { }', + true, + ], + [ + 'for (; 1, 0;) { }', + false, + ], + [ + 'for (; "", "a";) { }', + true, + ], + [ + 'for (; "a", "";) { }', + false, + ], [ 'do { } while (doFoo());', false, @@ -231,7 +355,7 @@ public function dataIsAlwaysTerminating(): array ], [ 'for ($i = 0; $i < 10; $i++) { return; }', - false, // will be true with range types + true, ], [ 'for ($i = 0; $i < 0; $i++) { return; }', diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 01157d82e1d..48de5a95b4d 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -109,12 +109,12 @@ public function testStrictComparison(): void 140, ], [ - 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', - 154, + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', + 150, ], [ - 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 164, + 'Strict comparison using !== between StrictComparison\Foo|null and 1 will always evaluate to true.', + 161, ], [ 'Strict comparison using !== between StrictComparison\Node|null and false will always evaluate to true.', @@ -352,7 +352,7 @@ public function testStrictComparisonWithoutAlwaysTrue(): void ], [ 'Strict comparison using === between non-empty-array and null will always evaluate to false.', - 164, + 150, ], [ 'Strict comparison using === between 1 and 2 will always evaluate to false.', diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 70882c4ca47..f98e7e11aa9 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -146,6 +146,13 @@ public function whileWithTypeChange() public function forWithTypeChange() { + for (; $val = $this->returnArray();) { + if ($val === null) { + + } + $val = null; + } + $foo = null; for (;;) { if ($foo !== null) { @@ -159,13 +166,6 @@ public function forWithTypeChange() $foo = new self(); } } - - for (; $val = $this->returnArray();) { - if ($val === null) { - - } - $val = null; - } } private function returnArray(): array diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index ad9fc94be5d..f99d9352d9f 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -325,4 +325,22 @@ public function testBug9309(): void $this->analyse([__DIR__ . '/data/bug-9309.php'], []); } + public function testBug6807(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-6807.php'], []); + } + + public function testBug8463(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-8463.php'], []); + } + + public function testBug9374(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-9374.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Missing/data/bug-6807.php b/tests/PHPStan/Rules/Missing/data/bug-6807.php new file mode 100644 index 00000000000..d7ffdde9dd2 --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-6807.php @@ -0,0 +1,16 @@ + 5) + throw new Exception(); + + if (rand() == 1) + return 5; + } +} diff --git a/tests/PHPStan/Rules/Missing/data/bug-8463.php b/tests/PHPStan/Rules/Missing/data/bug-8463.php new file mode 100644 index 00000000000..c27592782f1 --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-8463.php @@ -0,0 +1,26 @@ + Date: Thu, 7 Nov 2024 09:23:17 +0100 Subject: [PATCH 0765/3097] Reorganize new rules in changelog --- changelog-2.0.md | 120 +++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/changelog-2.0.md b/changelog-2.0.md index 1e2d866a1fd..1756a96aa07 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -8,72 +8,78 @@ Major new features 🚀 * **Level 10** - level 9 on steroids, treats all `mixed` types strictly, not just explicit `mixed` * **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! * Lists are arrays with sequential integer keys starting at 0 -* **Validate inline PHPDoc `@var` tag** type against native type (level 2) (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) - * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones - * Use config option `reportAnyTypeWideningInVarTag: true` for stricter behaviour ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! * **Lower memory consumption** thanks to breaking up of reference cycles * [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) * In testing the memory consumption was reduced by 50–70 %. * **Enhancements in handling parameters passed by reference** * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! -* Check too wide private property type (level 4) (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) -* Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) -* Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule - * Because "always true" is always reported, these are no longer needed +* New rules (level 0): + * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! + * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! + * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! + * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! + * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! + * Rule about `@phpstan-consistent-constructor` ([#1296](https://github.com/phpstan/phpstan-src/pull/1296)), thanks @canvural! + * Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) + * Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) + * ApiInstanceofRule + * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) + * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) + * Check that PHPStan class in class constant fetch is covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) + * Previously absent type checks: + * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) + * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) + * Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) + * Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) + * Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 + * Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 +* New rules (level 2): + * **Validate inline PHPDoc `@var` tag** type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) + * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones + * Use config option `reportAnyTypeWideningInVarTag: true` for stricter behaviour ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! + * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) + * Checking truthiness of `@phpstan-pure` above functions and methods + * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! + * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 + * Previously absent type checks: + * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) + * Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 + * Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) + * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) + * Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 +* New rule (level 3): + * ArrayUnpackingRule ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! +* New rules (level 4): + * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) + * LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 + * Check that each trait is used and analysed at least once (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) + * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! + * ConstantLooseComparisonRule (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) + * Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side + * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 + * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! + * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! + * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! + * Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) + * Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule + * Because "always true" is always reported, these are no longer needed +* New rules (level 5): + * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! + * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! + * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! + * Report useless `array_values()` calls ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! + * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! + * Check unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! + * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 +* New rules (level 6): + * Previously absent type checks: + * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) + * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) + * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) * New option: `polluteScopeWithBlock` (defaults to `true`, `false` in `phpstan-strict-rules`) (https://github.com/phpstan/phpstan-src/commit/946cf180c960930c2c42075d0f28ff9090507272) -* Checking truthiness of `@phpstan-pure` above functions and methods -* Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side - * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 - * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! - * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! - * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! -* LogicalXorConstantConditionRule (level 4) (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 -* Check that each trait is used and analysed at least once (level 4) (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) -* Check preg_quote delimiter sanity (level 5) ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! -* MagicConstantContextRule (level 0) ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! -* MissingMagicSerializationMethodsRule (level 0) ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! -* Check vprintf/vsprintf arguments against placeholder count (level 0) ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! -* Report useless return values of function calls like `var_export` without `$return=true` (level 4) ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! -* Rule for `call_user_func()` (level 5) ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! -* Report useless `array_filter()` calls (level 5) ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! -* Report useless `array_values()` calls (level 5) ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! -* Check if required file exists (level 0) ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! -* ConstantLooseComparisonRule - level 4 (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) -* Check array functions which require stringish values (level 5) ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! -* Check variance of template types in properties (level 2) ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! -* ArrayUnpackingRule (level 3) ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! -* Check unresolvable parameters (level 5) ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! -* Enforce `@no-named-arguments` (level 5) (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 * Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! -* Add `@readonly` rule that disallows default values (level 0) ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! -* IncompatibleDefaultParameterTypeRule for closures (level 2) (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) -* Added previously absent type checks (level 0) - * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) - * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) - * Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) - * Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) - * Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 - * Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 -* Added previously absent type checks (level 2) - * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) - * Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 - * Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) - * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) - * Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 -* Added previously absent type checks (level 6) - * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) - * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) - * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) -* Rule about `@phpstan-consistent-constructor` (level 0) ([#1296](https://github.com/phpstan/phpstan-src/pull/1296)), thanks @canvural! -* Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (level 0) (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) -* Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (level 0) (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) -* ApiInstanceofRule (level 0) - * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) - * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) -* Check that PHPStan class in class constant fetch is covered by backward compatibility promise (level 0) (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) * Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) -* Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 Improvements 🔧 From 3dc64a29f1fb003630bb7164dfcce12f5bb40fad Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:00:06 +0000 Subject: [PATCH 0766/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 1afe40674c9..23c5c46c7a9 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.11", "phpstan/php-8-stubs": "0.4.4", - "phpstan/phpdoc-parser": "^2.0", + "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 4a1e51a351f..3197c29134b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "350cc72e1a307581ffc8228f105bd273", + "content-hash": "8793a11aa08550fce8d98648609fda3b", "packages": [ { "name": "clue/ndjson-react", @@ -2290,7 +2290,7 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", @@ -2316,7 +2316,6 @@ "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -2332,7 +2331,7 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.x" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" }, "time": "2024-10-13T11:29:49+00:00" }, From b1b778280207c076b4e92ad0b5fea385102ea9b1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 8 Nov 2024 14:53:02 +0100 Subject: [PATCH 0767/3097] More precise types for `preg_match` greater than `0` --- src/Analyser/TypeSpecifier.php | 20 +++++++- tests/PHPStan/Analyser/nsrt/bug-11293.php | 62 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11293.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fb8b5985e75..68864c18b6c 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -242,11 +242,11 @@ public function specifyTypesInCondition( $expr->left instanceof FuncCall && count($expr->left->getArgs()) >= 1 && $expr->left->name instanceof Name - && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen', 'mb_strlen'], true) + && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen', 'mb_strlen', 'preg_match'], true) && ( !$expr->right instanceof FuncCall || !$expr->right->name instanceof Name - || !in_array(strtolower((string) $expr->right->name), ['count', 'sizeof', 'strlen', 'mb_strlen'], true) + || !in_array(strtolower((string) $expr->right->name), ['count', 'sizeof', 'strlen', 'mb_strlen', 'preg_match'], true) ) ) { $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller @@ -336,6 +336,22 @@ public function specifyTypesInCondition( } } + if ( + !$context->null() + && $expr->right instanceof FuncCall + && count($expr->right->getArgs()) >= 3 + && $expr->right->name instanceof Name + && in_array(strtolower((string) $expr->right->name), ['preg_match'], true) + && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes() + ) { + return $this->specifyTypesInCondition( + $scope, + new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))), + $context, + $rootExpr, + ); + } + if ( !$context->null() && $expr->right instanceof FuncCall diff --git a/tests/PHPStan/Analyser/nsrt/bug-11293.php b/tests/PHPStan/Analyser/nsrt/bug-11293.php new file mode 100644 index 00000000000..0c190b23fc4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -0,0 +1,62 @@ += 7.4 + +namespace Bug11293; + +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + public function sayHello(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) > 0) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello2(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) === 1) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello3(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) >= 1) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello4(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) <= 0) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + + public function sayHello5(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) < 1) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + + public function sayHello6(string $s): void + { + if (1 > preg_match('/data-(\d{6})\.json$/', $s, $matches)) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } +} From d44f4df6bbccf05a0913b7eb1ad0ea0f6928e824 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 8 Nov 2024 15:31:11 +0100 Subject: [PATCH 0768/3097] Fix after merge --- src/Analyser/TypeSpecifier.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 8b6e3c4541c..cee4acb5691 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -340,8 +340,7 @@ public function specifyTypesInCondition( $scope, new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))), $context, - $rootExpr, - ); + )->setRootExpr($expr); } if ( From a965e73470dd35568b19f0cbe4c366036821ab35 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 8 Nov 2024 16:06:14 +0100 Subject: [PATCH 0769/3097] Use named argument in error for variadic types When a type is variadic, and multiple named arguments are used, it's often hard to tell for which argument the error is coming. Since we have the named argument, we could use it in this situation. --- src/Rules/AttributesCheck.php | 6 +++--- src/Rules/Classes/InstantiationRule.php | 6 +++--- src/Rules/FunctionCallParametersCheck.php | 15 ++++++++++----- src/Rules/Functions/CallCallablesRule.php | 6 +++--- .../Functions/CallToFunctionParametersRule.php | 6 +++--- src/Rules/Functions/CallUserFuncRule.php | 6 +++--- src/Rules/Methods/CallMethodsRule.php | 6 +++--- src/Rules/Methods/CallStaticMethodsRule.php | 6 +++--- .../PHPStan/Rules/Methods/CallMethodsRuleTest.php | 10 +++++++++- .../Rules/Methods/data/named-arguments.php | 1 + 10 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 8633178d9fd..e12f906c005 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -144,14 +144,14 @@ public function check( 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, at least %d required.', 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d-%d required.', 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor expects %s, %s given.', + '%s of attribute class ' . $attributeClassName . ' constructor expects %s, %s given.', '', // constructor does not have a return type - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor is passed by reference, so it expects variables only', + '%s of attribute class ' . $attributeClassName . ' constructor is passed by reference, so it expects variables only', 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClassName, 'Missing parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', - 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', + '%s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', 'Attribute class ' . $attributeClassName . ' constructorinvoked with %s, but it\'s not allowed because of @no-named-arguments.', ); diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 604038d1fa8..7dd65488a0f 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -209,14 +209,14 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, at least %d required.', 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d-%d required.', 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of class ' . $classDisplayName . ' constructor expects %s, %s given.', + '%s of class ' . $classDisplayName . ' constructor expects %s, %s given.', '', // constructor does not have a return type - 'Parameter %s of class ' . $classDisplayName . ' constructor is passed by reference, so it expects variables only', + ' %s of class ' . $classDisplayName . ' constructor is passed by reference, so it expects variables only', 'Unable to resolve the template type %s in instantiation of class ' . $classDisplayName, 'Missing parameter $%s in call to ' . $classDisplayName . ' constructor.', 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', - 'Parameter %s of class ' . $classDisplayName . ' constructor contains unresolvable type.', + '%s of class ' . $classDisplayName . ' constructor contains unresolvable type.', 'Class ' . $classDisplayName . ' constructor invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index eb714ab1717..10e4e502127 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -30,6 +30,7 @@ use function array_key_exists; use function count; use function implode; +use function is_int; use function is_string; use function max; use function sprintf; @@ -331,7 +332,7 @@ public function check( $verbosityLevel = VerbosityLevel::getRecommendedLevelByType($parameterType, $argumentValueType); $errors[] = RuleErrorBuilder::message(sprintf( $wrongArgumentTypeMessage, - $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), + $this->describeParameter($parameter, $argumentName ?? $i + 1), $parameterType->describe($verbosityLevel), $argumentValueType->describe($verbosityLevel), )) @@ -409,7 +410,7 @@ public function check( } $errors[] = RuleErrorBuilder::message(sprintf( - 'Parameter %s is passed by reference so it does not accept %s.', + '%s is passed by reference so it does not accept %s.', $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), $propertyDescription, ))->identifier('argument.byRef')->line($argumentLine)->build(); @@ -625,11 +626,15 @@ private function processArguments( return [$errors, $newArguments]; } - private function describeParameter(ParameterReflection $parameter, ?int $position): string + private function describeParameter(ParameterReflection $parameter, int|string|null $positionOrNamed): string { $parts = []; - if ($position !== null) { - $parts[] = '#' . $position; + if (is_int($positionOrNamed)) { + $parts[] = 'Parameter #' . $positionOrNamed; + } elseif ($parameter->isVariadic() && is_string($positionOrNamed)) { + $parts[] = 'Named argument ' . $positionOrNamed . ' for variadic parameter'; + } else { + $parts[] = 'Parameter'; } $name = $parameter->getName(); diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index 15c0bfb9cad..162894e2668 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -126,14 +126,14 @@ public function processNode( ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', + '%s of ' . $callableDescription . ' expects %s, %s given.', 'Result of ' . $callableDescription . ' (void) is used.', - 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', + '%s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', 'Unable to resolve the template type %s in call to ' . $callableDescription, 'Missing parameter $%s in call to ' . $callableDescription . '.', 'Unknown parameter $%s in call to ' . $callableDescription . '.', 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', - 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', + '%s of ' . $callableDescription . ' contains unresolvable type.', ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ), ); diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 39b4ee650fe..63e1b93c7a2 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -57,14 +57,14 @@ public function processNode(Node $node, Scope $scope): array 'Function ' . $functionName . ' invoked with %d parameters, at least %d required.', 'Function ' . $functionName . ' invoked with %d parameter, %d-%d required.', 'Function ' . $functionName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of function ' . $functionName . ' expects %s, %s given.', + '%s of function ' . $functionName . ' expects %s, %s given.', 'Result of function ' . $functionName . ' (void) is used.', - 'Parameter %s of function ' . $functionName . ' is passed by reference, so it expects variables only.', + '%s of function ' . $functionName . ' is passed by reference, so it expects variables only.', 'Unable to resolve the template type %s in call to function ' . $functionName, 'Missing parameter $%s in call to function ' . $functionName . '.', 'Unknown parameter $%s in call to function ' . $functionName . '.', 'Return type of call to function ' . $functionName . ' contains unresolvable type.', - 'Parameter %s of function ' . $functionName . ' contains unresolvable type.', + '%s of function ' . $functionName . ' contains unresolvable type.', 'Function ' . $functionName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ); } diff --git a/src/Rules/Functions/CallUserFuncRule.php b/src/Rules/Functions/CallUserFuncRule.php index 2ea1fb27771..5eb21c2128b 100644 --- a/src/Rules/Functions/CallUserFuncRule.php +++ b/src/Rules/Functions/CallUserFuncRule.php @@ -73,14 +73,14 @@ public function processNode(Node $node, Scope $scope): array ucfirst($callableDescription) . ' invoked with %d parameters, at least %d required.', ucfirst($callableDescription) . ' invoked with %d parameter, %d-%d required.', ucfirst($callableDescription) . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $callableDescription . ' expects %s, %s given.', + '%s of ' . $callableDescription . ' expects %s, %s given.', 'Result of ' . $callableDescription . ' (void) is used.', - 'Parameter %s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', + '%s of ' . $callableDescription . ' is passed by reference, so it expects variables only.', 'Unable to resolve the template type %s in call to ' . $callableDescription, 'Missing parameter $%s in call to ' . $callableDescription . '.', 'Unknown parameter $%s in call to ' . $callableDescription . '.', 'Return type of call to ' . $callableDescription . ' contains unresolvable type.', - 'Parameter %s of ' . $callableDescription . ' contains unresolvable type.', + '%s of ' . $callableDescription . ' contains unresolvable type.', ucfirst($callableDescription) . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', ); } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index b0d522f439a..19bc52fe80b 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -63,14 +63,14 @@ public function processNode(Node $node, Scope $scope): array 'Method ' . $messagesMethodName . ' invoked with %d parameters, at least %d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameter, %d-%d required.', 'Method ' . $messagesMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of method ' . $messagesMethodName . ' expects %s, %s given.', + '%s of method ' . $messagesMethodName . ' expects %s, %s given.', 'Result of method ' . $messagesMethodName . ' (void) is used.', - 'Parameter %s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', + '%s of method ' . $messagesMethodName . ' is passed by reference, so it expects variables only.', 'Unable to resolve the template type %s in call to method ' . $messagesMethodName, 'Missing parameter $%s in call to method ' . $messagesMethodName . '.', 'Unknown parameter $%s in call to method ' . $messagesMethodName . '.', 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', - 'Parameter %s of method ' . $messagesMethodName . ' contains unresolvable type.', + '%s of method ' . $messagesMethodName . ' contains unresolvable type.', 'Method ' . $messagesMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 0f98eca2d9c..e6b2c9dbb57 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -71,14 +71,14 @@ public function processNode(Node $node, Scope $scope): array $displayMethodName . ' invoked with %d parameters, at least %d required.', $displayMethodName . ' invoked with %d parameter, %d-%d required.', $displayMethodName . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of ' . $lowercasedMethodName . ' expects %s, %s given.', + '%s of ' . $lowercasedMethodName . ' expects %s, %s given.', 'Result of ' . $lowercasedMethodName . ' (void) is used.', - 'Parameter %s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', + '%s of ' . $lowercasedMethodName . ' is passed by reference, so it expects variables only.', 'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName, 'Missing parameter $%s in call to ' . $lowercasedMethodName . '.', 'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.', 'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.', - 'Parameter %s of ' . $lowercasedMethodName . ' contains unresolvable type.', + '%s of ' . $lowercasedMethodName . ' contains unresolvable type.', $displayMethodName . ' invoked with %s, but it\'s not allowed because of @no-named-arguments.', )); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index cc614cef956..a883c768d98 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1907,7 +1907,7 @@ public function testNamedArguments(): void 91, ], [ - 'Parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', + 'Named argument foo for variadic parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', 91, ], [ @@ -1922,6 +1922,14 @@ public function testNamedArguments(): void 'Unpacked argument (...) cannot be followed by a non-unpacked argument.', 94, ], + [ + 'Named argument foo for variadic parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', + 95, + ], + [ + 'Named argument bar for variadic parameter ...$args of method NamedArgumentsMethod\Foo::doIpsum() expects string, int given.', + 95, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/named-arguments.php b/tests/PHPStan/Rules/Methods/data/named-arguments.php index f5c779f1716..25d9ef362b3 100644 --- a/tests/PHPStan/Rules/Methods/data/named-arguments.php +++ b/tests/PHPStan/Rules/Methods/data/named-arguments.php @@ -92,6 +92,7 @@ public function doDolor(): void $this->doIpsum(...['a' => 1, 'foo' => 'foo']); $this->doIpsum(...['b' => 1, 'foo' => 'foo']); $this->doIpsum(...[1, 2], 'foo'); + $this->doIpsum(1, 2, foo: 1, bar: 2); } } From 5f0b1ccfa47060c209ead7116005214183c0e56f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 9 Nov 2024 10:05:14 +0100 Subject: [PATCH 0770/3097] Too-wide return type - do not report void in PHPDoc union type --- .../TooWideFunctionReturnTypehintRule.php | 6 +-- .../TooWideMethodReturnTypehintRule.php | 6 +-- .../TooWideFunctionReturnTypehintRuleTest.php | 10 ++++ .../TooWideMethodReturnTypehintRuleTest.php | 12 +++++ .../data/bug-11980-function.php | 49 +++++++++++++++++ .../Rules/TooWideTypehints/data/bug-11980.php | 53 +++++++++++++++++++ 6 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php create mode 100644 tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 217d8576cdc..b0b11a10aec 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -11,6 +11,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use PHPStan\Type\VoidType; use function count; use function sprintf; @@ -48,16 +49,13 @@ public function processNode(Node $node, Scope $scope): array foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { + $returnTypes[] = new VoidType(); continue; } $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } - if (count($returnTypes) === 0) { - return []; - } - $returnType = TypeCombinator::union(...$returnTypes); $messages = []; diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index ebb22df8ae5..b73fa09641f 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -12,6 +12,7 @@ use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use PHPStan\Type\VoidType; use function count; use function sprintf; @@ -74,16 +75,13 @@ public function processNode(Node $node, Scope $scope): array foreach ($returnStatements as $returnStatement) { $returnNode = $returnStatement->getReturnNode(); if ($returnNode->expr === null) { + $returnTypes[] = new VoidType(); continue; } $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } - if (count($returnTypes) === 0) { - return []; - } - $returnType = TypeCombinator::union(...$returnTypes); if ( !$method->isPrivate() diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index b914db31415..9c86812e92c 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -51,4 +51,14 @@ public function testRule(): void ]); } + public function testBug11980(): void + { + $this->analyse([__DIR__ . '/data/bug-11980-function.php'], [ + [ + 'Function Bug11980Function\process2() never returns void so it can be removed from the return type.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 9e9588d219e..cad3ca757fa 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -248,4 +248,16 @@ public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, bool $this->analyse([__DIR__ . '/data/method-too-wide-return-always-check-final.php'], $expectedErrors); } + public function testBug11980(): void + { + $this->checkProtectedAndPublicMethods = true; + $this->alwaysCheckFinal = true; + $this->analyse([__DIR__ . '/data/bug-11980.php'], [ + [ + 'Method Bug11980\Demo::process2() never returns void so it can be removed from the return type.', + 37, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php new file mode 100644 index 00000000000..0db93a1cfb7 --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php @@ -0,0 +1,49 @@ +> $tokens + * @param int $stackPtr + * + * @return int|void + */ +function process($tokens, $stackPtr) +{ + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } +} + +/** + * @param array> $tokens + * @param int $stackPtr + * + * @return int|void + */ +function process2($tokens, $stackPtr) +{ + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return null; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php new file mode 100644 index 00000000000..99ab186f62e --- /dev/null +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php @@ -0,0 +1,53 @@ +> $tokens + * @param int $stackPtr + * + * @return int|void + */ + public function process($tokens, $stackPtr) + { + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } + } + + /** + * @param array> $tokens + * @param int $stackPtr + * + * @return int|void + */ + public function process2($tokens, $stackPtr) + { + if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) { + // Not a stand-alone statement. + return null; + } + + $end = 10; + + if ($tokens[$end]['code'] !== 10 + && $tokens[$end]['code'] !== 20 + ) { + // Not a stand-alone statement. + return $end; + } + } +} From ac1f53936d37c04da80023b7c88c9cfed9f0e890 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 9 Nov 2024 10:20:22 +0100 Subject: [PATCH 0771/3097] Fix after merge --- .../TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index f3aad9f7da5..a98c638d24b 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -192,7 +192,6 @@ public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, array public function testBug11980(): void { $this->checkProtectedAndPublicMethods = true; - $this->alwaysCheckFinal = true; $this->analyse([__DIR__ . '/data/bug-11980.php'], [ [ 'Method Bug11980\Demo::process2() never returns void so it can be removed from the return type.', From ccfb4ab7a19151925b9434e3245892006b3d9dcd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 9 Nov 2024 18:52:57 +0100 Subject: [PATCH 0772/3097] Results - make reasons unique --- src/Type/AcceptsResult.php | 4 ++-- src/Type/IsSuperTypeOfResult.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/AcceptsResult.php b/src/Type/AcceptsResult.php index 4cfecb05f60..2548f3dd44e 100644 --- a/src/Type/AcceptsResult.php +++ b/src/Type/AcceptsResult.php @@ -108,7 +108,7 @@ public static function extremeIdentity(self ...$operands): self } } - return new self($result, $reasons); + return new self($result, array_values(array_unique($reasons))); } public static function maxMin(self ...$operands): self @@ -125,7 +125,7 @@ public static function maxMin(self ...$operands): self } } - return new self($result, $reasons); + return new self($result, array_values(array_unique($reasons))); } } diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php index 30e9fe6acc6..dd28cec78f2 100644 --- a/src/Type/IsSuperTypeOfResult.php +++ b/src/Type/IsSuperTypeOfResult.php @@ -153,7 +153,7 @@ private static function mergeReasons(array $operands): array } } - return $reasons; + return array_values(array_unique($reasons)); } } From a0b6b3ba95e9a0e22a47259066890c35b1c15749 Mon Sep 17 00:00:00 2001 From: "Jose M. Valera Reales" Date: Sat, 9 Nov 2024 18:55:02 +0100 Subject: [PATCH 0773/3097] Upgrade bashunit:0.18.0 for e2e tests --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 21071b8ed6d..6d7233d38a3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -253,7 +253,7 @@ jobs: run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" - name: "Install bashunit" - run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.17.0" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.18.0" - name: "Test" run: "${{ matrix.script }}" @@ -355,7 +355,7 @@ jobs: run: "composer install --no-interaction --no-progress" - name: "Install bashunit" - run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.17.0" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.18.0" - name: "Test" run: ${{ matrix.script }} From c55aa0586bf08b8e029576ba4a78782418b4a932 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 10 Nov 2024 13:56:27 +0100 Subject: [PATCH 0774/3097] Improve `for` loop initial statement scope pollution --- src/Analyser/MutatingScope.php | 62 ++++++++++ src/Analyser/NodeScopeResolver.php | 2 +- .../Analyser/ForLoopNoScopePollutionTest.php | 36 ++++++ .../data/for-loop-no-scope-pollution.php | 107 ++++++++++++++++++ .../Analyser/forLoopNoScopePollution.neon | 2 + tests/PHPStan/Analyser/nsrt/for-loop.php | 107 ++++++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 9 ++ .../PHPStan/Rules/Variables/data/bug-9550.php | 32 ++++++ 8 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php create mode 100644 tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php create mode 100644 tests/PHPStan/Analyser/forLoopNoScopePollution.neon create mode 100644 tests/PHPStan/Analyser/nsrt/for-loop.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-9550.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5659a107bcf..39930858948 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4900,6 +4900,68 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope ); } + public function processAlwaysIterableForScopeWithoutPollute(self $finalScope, self $initScope): self + { + $expressionTypes = $this->expressionTypes; + $initScopeExpressionTypes = $initScope->expressionTypes; + foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) { + if (!isset($expressionTypes[$variableExprString])) { + if (isset($initScopeExpressionTypes[$variableExprString])) { + $expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); + continue; + } + + $expressionTypes[$variableExprString] = $variableTypeHolder; + continue; + } + + $expressionTypes[$variableExprString] = new ExpressionTypeHolder( + $variableTypeHolder->getExpr(), + $variableTypeHolder->getType(), + $variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()), + ); + } + + $nativeTypes = $this->nativeExpressionTypes; + $initScopeNativeExpressionTypes = $initScope->nativeExpressionTypes; + foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) { + if (!isset($nativeTypes[$variableExprString])) { + if (isset($initScopeNativeExpressionTypes[$variableExprString])) { + $nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); + continue; + } + + $nativeTypes[$variableExprString] = $variableTypeHolder; + continue; + } + + $nativeTypes[$variableExprString] = new ExpressionTypeHolder( + $variableTypeHolder->getExpr(), + $variableTypeHolder->getType(), + $variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()), + ); + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->getFunction(), + $this->getNamespace(), + $expressionTypes, + $nativeTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClasses, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + [], + [], + $this->afterExtractCall, + $this->parentScope, + $this->nativeTypesPromoted, + ); + } + public function generalizeWith(self $otherScope): self { $variableTypeHolders = $this->generalizeVariableTypeHolders( diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1a6a069d28f..31dde8718b1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1410,7 +1410,7 @@ private function processStmtNode( } } else { if (!$this->polluteScopeWithLoopInitialAssignments) { - $finalScope = $finalScope->mergeWith($scope); + $finalScope = $scope->processAlwaysIterableForScopeWithoutPollute($finalScope, $initScope); } } diff --git a/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php b/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php new file mode 100644 index 00000000000..9b07f9ab2bb --- /dev/null +++ b/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php @@ -0,0 +1,36 @@ +> */ + public function dataFileAsserts(): iterable + { + yield from $this->gatherAssertTypes(__DIR__ . '/data/for-loop-no-scope-pollution.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/forLoopNoScopePollution.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php b/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php new file mode 100644 index 00000000000..e7c0bef9ff5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php @@ -0,0 +1,107 @@ +', $i); + assertNativeType('int<10, max>', $i); + assertVariableCertainty(TrinaryLogic::createMaybe(), $i); + + assertType('int', $j); + assertNativeType('int', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int<0, 1>', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int<0, 1>', $b); + assertNativeType('int', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('int<0, 1>', $c); + assertNativeType('int', $c); + assertVariableCertainty(TrinaryLogic::createYes(), $c); + } + + /** @param int $b */ + public function loopThatMightIterateAtLeastOnce(int $a, $b): void + { + $j = 0; + for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) { + $a = rand(0, 1); + $b = rand(0, 1); + $c = rand(0, 1); + } + + assertType('int<0, max>', $i); + assertNativeType('int<0, max>', $i); + assertVariableCertainty(TrinaryLogic::createMaybe(), $i); + + assertType('int', $j); + assertNativeType('int', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int', $b); + assertNativeType('mixed', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('int<0, 1>', $c); + assertNativeType('int', $c); + assertVariableCertainty(TrinaryLogic::createMaybe(), $c); + } + + /** @param int $b */ + public function loopThatNeverIterates(int $a, $b): void + { + $j = 0; + for ($i = 0, $j = 10; $i > 10; $i++, $j--) { + $a = rand(0, 1); + $b = rand(0, 1); + $c = rand(0, 1); + } + + assertType('*ERROR*', $i); + assertNativeType('*ERROR*', $i); + assertVariableCertainty(TrinaryLogic::createNo(), $i); + + assertType('0', $j); + assertNativeType('0', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int', $b); + assertNativeType('mixed', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('*ERROR*', $c); + assertNativeType('*ERROR*', $c); + assertVariableCertainty(TrinaryLogic::createNo(), $c); + } + +} diff --git a/tests/PHPStan/Analyser/forLoopNoScopePollution.neon b/tests/PHPStan/Analyser/forLoopNoScopePollution.neon new file mode 100644 index 00000000000..47811f500ec --- /dev/null +++ b/tests/PHPStan/Analyser/forLoopNoScopePollution.neon @@ -0,0 +1,2 @@ +parameters: + polluteScopeWithLoopInitialAssignments: false diff --git a/tests/PHPStan/Analyser/nsrt/for-loop.php b/tests/PHPStan/Analyser/nsrt/for-loop.php new file mode 100644 index 00000000000..f15cdeb8110 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/for-loop.php @@ -0,0 +1,107 @@ +', $i); + assertNativeType('int<10, max>', $i); + assertVariableCertainty(TrinaryLogic::createYes(), $i); + + assertType('int', $j); + assertNativeType('int', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int<0, 1>', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int<0, 1>', $b); + assertNativeType('int', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('int<0, 1>', $c); + assertNativeType('int', $c); + assertVariableCertainty(TrinaryLogic::createYes(), $c); + } + + /** @param int $b */ + public function loopThatMightIterateAtLeastOnce(int $a, $b): void + { + $j = 0; + for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) { + $a = rand(0, 1); + $b = rand(0, 1); + $c = rand(0, 1); + } + + assertType('int<0, max>', $i); + assertNativeType('int<0, max>', $i); + assertVariableCertainty(TrinaryLogic::createYes(), $i); + + assertType('int', $j); + assertNativeType('int', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int', $b); + assertNativeType('mixed', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('int<0, 1>', $c); + assertNativeType('int', $c); + assertVariableCertainty(TrinaryLogic::createMaybe(), $c); + } + + /** @param int $b */ + public function loopThatNeverIterates(int $a, $b): void + { + $j = 0; + for ($i = 0, $j = 10; $i > 10; $i++, $j--) { + $a = rand(0, 1); + $b = rand(0, 1); + $c = rand(0, 1); + } + + assertType('0', $i); + assertNativeType('0', $i); + assertVariableCertainty(TrinaryLogic::createYes(), $i); + + assertType('10', $j); + assertNativeType('10', $j); + assertVariableCertainty(TrinaryLogic::createYes(), $j); + + assertType('int', $a); + assertNativeType('int', $a); + assertVariableCertainty(TrinaryLogic::createYes(), $a); + + assertType('int', $b); + assertNativeType('mixed', $b); + assertVariableCertainty(TrinaryLogic::createYes(), $b); + + assertType('*ERROR*', $c); + assertNativeType('*ERROR*', $c); + assertVariableCertainty(TrinaryLogic::createNo(), $c); + } + +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 94f0b0ffebf..9a2346eb772 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1068,4 +1068,13 @@ public function testBug10228(): void $this->analyse([__DIR__ . '/data/bug-10228.php'], []); } + public function testBug9550(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-9550.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-9550.php b/tests/PHPStan/Rules/Variables/data/bug-9550.php new file mode 100644 index 00000000000..ab260ad44c7 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-9550.php @@ -0,0 +1,32 @@ + 0; --$l) { + $vStr = mb_substr($vStrLonger, 0, $l); + if ($vStr !== $vStrLonger) { + $vStrLonger = $vStr; + $vStr = mb_substr($vStr, 0, $l - 3); + $withThreeDots = true; + } else { + $vStrLonger = $vStr; + } + $vStr = str_replace(["\0", "\t", "\n", "\r"], ['\0', '\t', '\n', '\r'], $vStr); + if (mb_strlen($vStr) <= $maxUtf8Length - ($withThreeDots ? 3 : 0)) { + break; + } + } + + return '\'' . $vStr . '\'' . ($withThreeDots ? '...' : ''); + } +} From 381c1370e7ce3b1c2d8de6c5b30913908c362eb8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 10 Nov 2024 14:57:32 +0100 Subject: [PATCH 0775/3097] RichParser - fix `@phpstan-ignore` with trait in the same file --- src/Parser/RichParser.php | 7 ++++- tests/PHPStan/Parser/RichParserTest.php | 39 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index b5863a4b800..fb481b9923b 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -102,7 +102,12 @@ public function parseString(string $sourceCode): array } foreach ($traitCollectingVisitor->traits as $trait) { - $trait->setAttribute('linesToIgnore', array_filter($linesToIgnore, static fn (int $line): bool => $line >= $trait->getStartLine() && $line <= $trait->getEndLine(), ARRAY_FILTER_USE_KEY)); + $preexisting = $trait->getAttribute('linesToIgnore', []); + $filteredLinesToIgnore = array_filter($linesToIgnore, static fn (int $line): bool => $line >= $trait->getStartLine() && $line <= $trait->getEndLine(), ARRAY_FILTER_USE_KEY); + foreach ($preexisting as $line => $ignores) { + $filteredLinesToIgnore[$line] = $ignores; + } + $trait->setAttribute('linesToIgnore', $filteredLinesToIgnore); } return $nodes; diff --git a/tests/PHPStan/Parser/RichParserTest.php b/tests/PHPStan/Parser/RichParserTest.php index 103eb129b42..5fa112a57ff 100644 --- a/tests/PHPStan/Parser/RichParserTest.php +++ b/tests/PHPStan/Parser/RichParserTest.php @@ -261,6 +261,45 @@ public function dataLinesToIgnore(): iterable 2 => ['identifier'], ], ]; + + yield [ + 'myProperty = $b;' . PHP_EOL . + ' }' . PHP_EOL . + '}', + [ + 10 => ['variable.undefined'], + ], + ]; + + yield [ + 'myProperty = $b;' . PHP_EOL . + ' }' . PHP_EOL . + '}', + [ + 13 => ['variable.undefined'], + ], + ]; } /** From f2dfd5debf73336a7d5c7071b42d43db975470b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 10 Nov 2024 15:09:45 +0100 Subject: [PATCH 0776/3097] Revert "Improve `for` loop initial statement scope pollution" This reverts commit c55aa0586bf08b8e029576ba4a78782418b4a932. --- src/Analyser/MutatingScope.php | 62 ---------- src/Analyser/NodeScopeResolver.php | 2 +- .../Analyser/ForLoopNoScopePollutionTest.php | 36 ------ .../data/for-loop-no-scope-pollution.php | 107 ------------------ .../Analyser/forLoopNoScopePollution.neon | 2 - tests/PHPStan/Analyser/nsrt/for-loop.php | 107 ------------------ .../Variables/DefinedVariableRuleTest.php | 9 -- .../PHPStan/Rules/Variables/data/bug-9550.php | 32 ------ 8 files changed, 1 insertion(+), 356 deletions(-) delete mode 100644 tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php delete mode 100644 tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php delete mode 100644 tests/PHPStan/Analyser/forLoopNoScopePollution.neon delete mode 100644 tests/PHPStan/Analyser/nsrt/for-loop.php delete mode 100644 tests/PHPStan/Rules/Variables/data/bug-9550.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 39930858948..5659a107bcf 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4900,68 +4900,6 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope ); } - public function processAlwaysIterableForScopeWithoutPollute(self $finalScope, self $initScope): self - { - $expressionTypes = $this->expressionTypes; - $initScopeExpressionTypes = $initScope->expressionTypes; - foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) { - if (!isset($expressionTypes[$variableExprString])) { - if (isset($initScopeExpressionTypes[$variableExprString])) { - $expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - continue; - } - - $expressionTypes[$variableExprString] = $variableTypeHolder; - continue; - } - - $expressionTypes[$variableExprString] = new ExpressionTypeHolder( - $variableTypeHolder->getExpr(), - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()), - ); - } - - $nativeTypes = $this->nativeExpressionTypes; - $initScopeNativeExpressionTypes = $initScope->nativeExpressionTypes; - foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) { - if (!isset($nativeTypes[$variableExprString])) { - if (isset($initScopeNativeExpressionTypes[$variableExprString])) { - $nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); - continue; - } - - $nativeTypes[$variableExprString] = $variableTypeHolder; - continue; - } - - $nativeTypes[$variableExprString] = new ExpressionTypeHolder( - $variableTypeHolder->getExpr(), - $variableTypeHolder->getType(), - $variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()), - ); - } - - return $this->scopeFactory->create( - $this->context, - $this->isDeclareStrictTypes(), - $this->getFunction(), - $this->getNamespace(), - $expressionTypes, - $nativeTypes, - $this->conditionalExpressions, - $this->inClosureBindScopeClasses, - $this->anonymousFunctionReflection, - $this->inFirstLevelStatement, - [], - [], - [], - $this->afterExtractCall, - $this->parentScope, - $this->nativeTypesPromoted, - ); - } - public function generalizeWith(self $otherScope): self { $variableTypeHolders = $this->generalizeVariableTypeHolders( diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 31dde8718b1..1a6a069d28f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1410,7 +1410,7 @@ private function processStmtNode( } } else { if (!$this->polluteScopeWithLoopInitialAssignments) { - $finalScope = $scope->processAlwaysIterableForScopeWithoutPollute($finalScope, $initScope); + $finalScope = $finalScope->mergeWith($scope); } } diff --git a/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php b/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php deleted file mode 100644 index 9b07f9ab2bb..00000000000 --- a/tests/PHPStan/Analyser/ForLoopNoScopePollutionTest.php +++ /dev/null @@ -1,36 +0,0 @@ -> */ - public function dataFileAsserts(): iterable - { - yield from $this->gatherAssertTypes(__DIR__ . '/data/for-loop-no-scope-pollution.php'); - } - - /** - * @dataProvider dataFileAsserts - * @param mixed ...$args - */ - public function testFileAsserts( - string $assertType, - string $file, - ...$args, - ): void - { - $this->assertFileAsserts($assertType, $file, ...$args); - } - - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__ . '/forLoopNoScopePollution.neon', - ]; - } - -} diff --git a/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php b/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php deleted file mode 100644 index e7c0bef9ff5..00000000000 --- a/tests/PHPStan/Analyser/data/for-loop-no-scope-pollution.php +++ /dev/null @@ -1,107 +0,0 @@ -', $i); - assertNativeType('int<10, max>', $i); - assertVariableCertainty(TrinaryLogic::createMaybe(), $i); - - assertType('int', $j); - assertNativeType('int', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int<0, 1>', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int<0, 1>', $b); - assertNativeType('int', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('int<0, 1>', $c); - assertNativeType('int', $c); - assertVariableCertainty(TrinaryLogic::createYes(), $c); - } - - /** @param int $b */ - public function loopThatMightIterateAtLeastOnce(int $a, $b): void - { - $j = 0; - for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) { - $a = rand(0, 1); - $b = rand(0, 1); - $c = rand(0, 1); - } - - assertType('int<0, max>', $i); - assertNativeType('int<0, max>', $i); - assertVariableCertainty(TrinaryLogic::createMaybe(), $i); - - assertType('int', $j); - assertNativeType('int', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int', $b); - assertNativeType('mixed', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('int<0, 1>', $c); - assertNativeType('int', $c); - assertVariableCertainty(TrinaryLogic::createMaybe(), $c); - } - - /** @param int $b */ - public function loopThatNeverIterates(int $a, $b): void - { - $j = 0; - for ($i = 0, $j = 10; $i > 10; $i++, $j--) { - $a = rand(0, 1); - $b = rand(0, 1); - $c = rand(0, 1); - } - - assertType('*ERROR*', $i); - assertNativeType('*ERROR*', $i); - assertVariableCertainty(TrinaryLogic::createNo(), $i); - - assertType('0', $j); - assertNativeType('0', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int', $b); - assertNativeType('mixed', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('*ERROR*', $c); - assertNativeType('*ERROR*', $c); - assertVariableCertainty(TrinaryLogic::createNo(), $c); - } - -} diff --git a/tests/PHPStan/Analyser/forLoopNoScopePollution.neon b/tests/PHPStan/Analyser/forLoopNoScopePollution.neon deleted file mode 100644 index 47811f500ec..00000000000 --- a/tests/PHPStan/Analyser/forLoopNoScopePollution.neon +++ /dev/null @@ -1,2 +0,0 @@ -parameters: - polluteScopeWithLoopInitialAssignments: false diff --git a/tests/PHPStan/Analyser/nsrt/for-loop.php b/tests/PHPStan/Analyser/nsrt/for-loop.php deleted file mode 100644 index f15cdeb8110..00000000000 --- a/tests/PHPStan/Analyser/nsrt/for-loop.php +++ /dev/null @@ -1,107 +0,0 @@ -', $i); - assertNativeType('int<10, max>', $i); - assertVariableCertainty(TrinaryLogic::createYes(), $i); - - assertType('int', $j); - assertNativeType('int', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int<0, 1>', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int<0, 1>', $b); - assertNativeType('int', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('int<0, 1>', $c); - assertNativeType('int', $c); - assertVariableCertainty(TrinaryLogic::createYes(), $c); - } - - /** @param int $b */ - public function loopThatMightIterateAtLeastOnce(int $a, $b): void - { - $j = 0; - for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) { - $a = rand(0, 1); - $b = rand(0, 1); - $c = rand(0, 1); - } - - assertType('int<0, max>', $i); - assertNativeType('int<0, max>', $i); - assertVariableCertainty(TrinaryLogic::createYes(), $i); - - assertType('int', $j); - assertNativeType('int', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int', $b); - assertNativeType('mixed', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('int<0, 1>', $c); - assertNativeType('int', $c); - assertVariableCertainty(TrinaryLogic::createMaybe(), $c); - } - - /** @param int $b */ - public function loopThatNeverIterates(int $a, $b): void - { - $j = 0; - for ($i = 0, $j = 10; $i > 10; $i++, $j--) { - $a = rand(0, 1); - $b = rand(0, 1); - $c = rand(0, 1); - } - - assertType('0', $i); - assertNativeType('0', $i); - assertVariableCertainty(TrinaryLogic::createYes(), $i); - - assertType('10', $j); - assertNativeType('10', $j); - assertVariableCertainty(TrinaryLogic::createYes(), $j); - - assertType('int', $a); - assertNativeType('int', $a); - assertVariableCertainty(TrinaryLogic::createYes(), $a); - - assertType('int', $b); - assertNativeType('mixed', $b); - assertVariableCertainty(TrinaryLogic::createYes(), $b); - - assertType('*ERROR*', $c); - assertNativeType('*ERROR*', $c); - assertVariableCertainty(TrinaryLogic::createNo(), $c); - } - -} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 9a2346eb772..94f0b0ffebf 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1068,13 +1068,4 @@ public function testBug10228(): void $this->analyse([__DIR__ . '/data/bug-10228.php'], []); } - public function testBug9550(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = false; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/bug-9550.php'], []); - } - } diff --git a/tests/PHPStan/Rules/Variables/data/bug-9550.php b/tests/PHPStan/Rules/Variables/data/bug-9550.php deleted file mode 100644 index ab260ad44c7..00000000000 --- a/tests/PHPStan/Rules/Variables/data/bug-9550.php +++ /dev/null @@ -1,32 +0,0 @@ - 0; --$l) { - $vStr = mb_substr($vStrLonger, 0, $l); - if ($vStr !== $vStrLonger) { - $vStrLonger = $vStr; - $vStr = mb_substr($vStr, 0, $l - 3); - $withThreeDots = true; - } else { - $vStrLonger = $vStr; - } - $vStr = str_replace(["\0", "\t", "\n", "\r"], ['\0', '\t', '\n', '\r'], $vStr); - if (mb_strlen($vStr) <= $maxUtf8Length - ($withThreeDots ? 3 : 0)) { - break; - } - } - - return '\'' . $vStr . '\'' . ($withThreeDots ? '...' : ''); - } -} From 80c1df2d73210227776db5443dbc28c2d71fa289 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 10 Nov 2024 15:12:19 +0100 Subject: [PATCH 0777/3097] Too-wide return type - allow `void` return type in a union when the returned expr is originally `void` --- .../TooWideFunctionReturnTypehintRule.php | 4 ++++ .../TooWideMethodReturnTypehintRule.php | 4 ++++ .../data/bug-11980-function.php | 21 +++++++++++++++++++ .../Rules/TooWideTypehints/data/bug-11980.php | 21 +++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index b0b11a10aec..3b52a47aad8 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -56,6 +56,10 @@ public function processNode(Node $node, Scope $scope): array $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } + if (!$statementResult->isAlwaysTerminating()) { + $returnTypes[] = new VoidType(); + } + $returnType = TypeCombinator::union(...$returnTypes); $messages = []; diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index b73fa09641f..92084ea3a05 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -82,6 +82,10 @@ public function processNode(Node $node, Scope $scope): array $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); } + if (!$statementResult->isAlwaysTerminating()) { + $returnTypes[] = new VoidType(); + } + $returnType = TypeCombinator::union(...$returnTypes); if ( !$method->isPrivate() diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php index 0db93a1cfb7..aaafea889d6 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980-function.php @@ -46,4 +46,25 @@ function process2($tokens, $stackPtr) // Not a stand-alone statement. return $end; } + + return 1; +} + +/** @return int|void */ +function process3( int $code ) { + + if ( $code === \T_CLASS ) { + return process_class( $code ); + } + + process_function( $code ); +} + +/** @return int */ +function process_class(int $code) { + return $code; +} + +/** @return void */ +function process_function(int $code) { } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php index 99ab186f62e..d45bb085761 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/bug-11980.php @@ -49,5 +49,26 @@ public function process2($tokens, $stackPtr) // Not a stand-alone statement. return $end; } + + return 1; + } + + /** @return int|void */ + public function process3( int $code ) { + + if ( $code === \T_CLASS ) { + return $this->process_class( $code ); + } + + $this->process_function( $code ); + } + + /** @return int */ + public function process_class(int $code) { + return $code; + } + + /** @return void */ + public function process_function(int $code) { } } From 40be73a94ba6eff3e1f080826b66c99c2ec16465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Sun, 10 Nov 2024 18:19:58 +0100 Subject: [PATCH 0778/3097] Update UPGRADING.md --- UPGRADING.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index bc65ba774e2..1b76768ec46 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -112,10 +112,6 @@ Appending `(?)` in `ignoreErrors` is not supported. * `constant_hassers` -> use `constantHassers` * `console_application_loader` -> use `consoleApplicationLoader` -### Docker images no longer tagged without a PHP version - -Tags without a PHP version are no longer published - `nightly`, `2`, `latest` are no longer updated. Instead, use `nightly-php8.3`, `2-php8.3`, `latest-php8.3`. You can replace `8.3` with PHP versions `8.0`-`8.3`. - ### Minor backward compatibility breaks * Removed unused config parameter `cache.nodesByFileCountMax` From eb82cb2ad17807a062fea3cd8cf31a446991a23c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 08:01:50 +0100 Subject: [PATCH 0779/3097] Update changelog one last time --- changelog-2.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog-2.0.md b/changelog-2.0.md index 1756a96aa07..b504a3281d5 100644 --- a/changelog-2.0.md +++ b/changelog-2.0.md @@ -146,6 +146,7 @@ Improvements 🔧 * Remove inefficient caching from `PhpMethodReflection` and `PhpFunctionReflection::isVariadic()` ([#3534](https://github.com/phpstan/phpstan-src/pull/3534)), thanks @staabm! * Clean file cache from unused items (https://github.com/phpstan/phpstan-src/commit/466ad51740d629c9137a77dac28a676b71ef7197) * Journal for used generated containers (https://github.com/phpstan/phpstan-src/commit/57c65888e6372a4056afbbacc8207d411ea8559a) +* Use named argument in error for variadic types ([#3611](https://github.com/phpstan/phpstan-src/pull/3611)), thanks @ruudk! Bugfixes 🐛 ===================== @@ -210,5 +211,6 @@ Internals 🔍 * Remove $isFinal dead-code in PhpFunctionReflection ([#3545](https://github.com/phpstan/phpstan-src/pull/3545)), thanks @staabm! * Get rid of unnecessary `instanceof self` in `ConstantArrayType` ([#3552](https://github.com/phpstan/phpstan-src/pull/3552)), thanks @herndlm! * test: use `bashunit -a` exit_code to check for errors ([#3533](https://github.com/phpstan/phpstan-src/pull/3533)), thanks @Chemaclass! +* Upgrade bashunit:0.18.0 for e2e tests ([#3614](https://github.com/phpstan/phpstan-src/pull/3614)), thanks @Chemaclass! * Remove dead code ([#3575](https://github.com/phpstan/phpstan-src/pull/3575)), thanks @staabm! * Remove dead code in ConstantConditionRuleHelper ([#3597](https://github.com/phpstan/phpstan-src/pull/3597)), thanks @staabm! From 91d1b11c8740cdadea3d5f7b96af4f64c5a1f6c8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 08:44:29 +0100 Subject: [PATCH 0780/3097] Update PHPStan extensions --- composer.lock | 53 ++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/composer.lock b/composer.lock index 3197c29134b..4a377463ad2 100644 --- a/composer.lock +++ b/composer.lock @@ -4672,16 +4672,16 @@ }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "392bbe7be54b00fbe945fede6a8ef543216f3b9c" + "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/392bbe7be54b00fbe945fede6a8ef543216f3b9c", - "reference": "392bbe7be54b00fbe945fede6a8ef543216f3b9c", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/81833b5787e2e8f451b31218875e29e4ed600ab2", + "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2", "shasum": "" }, "require": { @@ -4693,7 +4693,6 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4714,22 +4713,22 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.0" }, - "time": "2024-09-26T12:14:06+00:00" + "time": "2024-10-26T16:04:11+00:00" }, { "name": "phpstan/phpstan-nette", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "c903386c4e3d1d25a57f66458476bfb6347f1c66" + "reference": "cacb6983bbdf44d5c3a7222e5ca74f61f8531806" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/c903386c4e3d1d25a57f66458476bfb6347f1c66", - "reference": "c903386c4e3d1d25a57f66458476bfb6347f1c66", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/cacb6983bbdf44d5c3a7222e5ca74f61f8531806", + "reference": "cacb6983bbdf44d5c3a7222e5ca74f61f8531806", "shasum": "" }, "require": { @@ -4746,6 +4745,7 @@ }, "require-dev": { "nette/application": "^3.0", + "nette/di": "^3.1.10", "nette/forms": "^3.0", "nette/utils": "^2.3.0 || ^3.0.0", "php-parallel-lint/php-parallel-lint": "^1.2", @@ -4753,7 +4753,6 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4775,22 +4774,22 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.x" + "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.0" }, - "time": "2024-09-24T16:09:34+00:00" + "time": "2024-10-26T16:03:48+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "09e2d3b470bdda02824c626735153dfd962e3f29" + "reference": "3cc855474263ad6220dfa49167cbea34ca1dd300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/09e2d3b470bdda02824c626735153dfd962e3f29", - "reference": "09e2d3b470bdda02824c626735153dfd962e3f29", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/3cc855474263ad6220dfa49167cbea34ca1dd300", + "reference": "3cc855474263ad6220dfa49167cbea34ca1dd300", "shasum": "" }, "require": { @@ -4805,7 +4804,6 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4827,22 +4825,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.x" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.0" }, - "time": "2024-09-24T16:07:03+00:00" + "time": "2024-10-14T03:16:27+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e208c9311872047b903511e2e03cb0df795014b0" + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e208c9311872047b903511e2e03cb0df795014b0", - "reference": "e208c9311872047b903511e2e03cb0df795014b0", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", "shasum": "" }, "require": { @@ -4855,7 +4853,6 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4876,9 +4873,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.0" }, - "time": "2024-09-30T19:35:25+00:00" + "time": "2024-10-26T16:04:33+00:00" }, { "name": "phpunit/php-code-coverage", From ae38fe5857f403ca466907d16e1265fead5f46d0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 08:44:41 +0100 Subject: [PATCH 0781/3097] Remove changelog --- changelog-2.0.md | 216 ----------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 changelog-2.0.md diff --git a/changelog-2.0.md b/changelog-2.0.md deleted file mode 100644 index b504a3281d5..00000000000 --- a/changelog-2.0.md +++ /dev/null @@ -1,216 +0,0 @@ -When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](https://github.com/phpstan/phpstan/releases). - -Check out the [**UPGRADING guide**](https://github.com/phpstan/phpstan-src/blob/2.0.x/UPGRADING.md)!. - -Major new features 🚀 -===================== - -* **Level 10** - level 9 on steroids, treats all `mixed` types strictly, not just explicit `mixed` -* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen! - * Lists are arrays with sequential integer keys starting at 0 -* **Lower memory consumption** thanks to breaking up of reference cycles - * [Learn more »](https://phpstan.org/blog/preprocessing-ast-for-custom-rules) - * In testing the memory consumption was reduced by 50–70 %. -* **Enhancements in handling parameters passed by reference** - * [Learn more on phpstan.org](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) - * [#2941](https://github.com/phpstan/phpstan-src/pull/2941), thanks @ljmaskey! -* New rules (level 0): - * MagicConstantContextRule ([#2741](https://github.com/phpstan/phpstan-src/pull/2741)), #10099, thanks @staabm! - * MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm! - * Check vprintf/vsprintf arguments against placeholder count ([#3126](https://github.com/phpstan/phpstan-src/pull/3126)), thanks @staabm! - * Check if required file exists ([#3294](https://github.com/phpstan/phpstan-src/pull/3294)), #3397, thanks @Bellangelo! - * Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm! - * Rule about `@phpstan-consistent-constructor` ([#1296](https://github.com/phpstan/phpstan-src/pull/1296)), thanks @canvural! - * Check code in custom PHPStan extensions for runtime reflection concepts like `is_a()` or `class_parents()` (https://github.com/phpstan/phpstan-src/commit/c4a662ac6c3ec63f063238880b243b5399c34fcc) - * Check code in custom PHPStan extensions for runtime reflection concepts like `new ReflectionMethod()` (https://github.com/phpstan/phpstan-src/commit/536306611cbb5877b6565755cd07b87f9ccfdf08) - * ApiInstanceofRule - * Report `instanceof` of classes not covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/ff4d02d62a7a2e2c4d928d48d31d49246dba7139) - * Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef) - * Check that PHPStan class in class constant fetch is covered by backward compatibility promise (https://github.com/phpstan/phpstan-src/commit/9e007251ce61788f6a0319a53f1de6cf801ed233) - * Previously absent type checks: - * Check existing classes in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/6838669976bf20232abde36ecdd52b1770fa50c9) - * Check nonexistent classes in local type aliases (https://github.com/phpstan/phpstan-src/commit/2485b2e9c129e789ec3b2d7db81ca30f87c63911) - * Check unresolvable types in local type aliases (https://github.com/phpstan/phpstan-src/commit/5f7d12b2fb2809525ab0e96eeae95093204ea4d3) - * Check generics in local type aliases (https://github.com/phpstan/phpstan-src/commit/5a2d4416d94ab77a2a2e7e1bfaba4c5ed2a13c25) - * Check existing classes in `@param-out` (https://github.com/phpstan/phpstan-src/commit/30c4b9e80f51af8b5f166ba3aae93d8409c9c0ea), #10260 - * Check existing classes in `@param-closure-this` (https://github.com/phpstan/phpstan-src/commit/2fa539a39e06bcc3155b109fd8d246703ceb176d), #10933 -* New rules (level 2): - * **Validate inline PHPDoc `@var` tag** type against native type (https://github.com/phpstan/phpstan-src/commit/a69e3bc2f1e87f6da1e65d7935f1cc36bd5c42fe) - * Set [`reportWrongPhpDocTypeInVarTag`](https://phpstan.org/config-reference#reportwrongphpdoctypeinvartag) to `true` to have all types validated, not just native ones - * Use config option `reportAnyTypeWideningInVarTag: true` for stricter behaviour ([#2840](https://github.com/phpstan/phpstan-src/pull/2840)), thanks @janedbal! - * IncompatibleDefaultParameterTypeRule for closures (https://github.com/phpstan/phpstan-src/commit/0264f5bc48448c7e02a23b82eef4177d0617a82f) - * Checking truthiness of `@phpstan-pure` above functions and methods - * Check variance of template types in properties ([#2314](https://github.com/phpstan/phpstan-src/pull/2314)), thanks @jiripudil! - * Report narrowing `PHPStan\Type\Type` interface via `@var` (https://github.com/phpstan/phpstan-src/commit/713b98fb107213c28e3d8c8b4b43c5f5fc47c144), https://github.com/nunomaduro/larastan/issues/1567#issuecomment-1460445389 - * Previously absent type checks: - * Check `@mixin` PHPDoc tag above traits (https://github.com/phpstan/phpstan-src/commit/0d0de946900adf4eb3c799b1b547567536e23147) - * Check `@extends`, `@implements`, `@use` for unresolvable types (https://github.com/phpstan/phpstan-src/commit/2bb528233edb75312614166e282776f279cf2018), #11552 - * Check types in `@method` tags (https://github.com/phpstan/phpstan-src/commit/5b7e474680eaf33874b7ed6a227677adcbed9ca5) - * Check generics `@method` `@template` tags above traits (https://github.com/phpstan/phpstan-src/commit/aadbf62d3ae4517fc7a212b07130bedcef8d13ac) - * Check types in `@property` tags (https://github.com/phpstan/phpstan-src/commit/55ea2ae516df22a071ab873fdd6f748a3af0520e), #10752, #9356 -* New rule (level 3): - * ArrayUnpackingRule ([#856](https://github.com/phpstan/phpstan-src/pull/856)), thanks @canvural! -* New rules (level 4): - * Check too wide private property type (https://github.com/phpstan/phpstan-src/commit/7453f4f75fae3d635063589467842aae29d88b54) - * LogicalXorConstantConditionRule (https://github.com/phpstan/phpstan-src/commit/3a12724fd636b1bcf36c22b36e8f765d97150895, https://github.com/phpstan/phpstan-src/commit/3b011f6524254dad0f16840fdcfdbe7421548617), #7539 - * Check that each trait is used and analysed at least once (https://github.com/phpstan/phpstan-src/commit/c4d05276fb8605d6ac20acbe1cc5df31cd6c10b0) - * Report useless return values of function calls like `var_export` without `$return=true` ([#3225](https://github.com/phpstan/phpstan-src/pull/3225)), #11320, thanks @staabm! - * ConstantLooseComparisonRule (https://github.com/phpstan/phpstan-src/commit/6ebf2361a3c831dd105a815521889428c295dc9f) - * Check `new`/function call/method call/static method call on a separate line without any side effects even without `@phpstan-pure` PHPDoc tag on the declaration side - * https://github.com/phpstan/phpstan-src/commit/281a87d1ab61809076ecfa6dfc2cc86e3babe235 - * [#3020](https://github.com/phpstan/phpstan-src/pull/3020), thanks @staabm! - * [#3022](https://github.com/phpstan/phpstan-src/pull/3022), thanks @staabm! - * [#3023](https://github.com/phpstan/phpstan-src/pull/3023), thanks @staabm! - * Always report always true conditions, except for last elseif and match arm (https://github.com/phpstan/phpstan-src/commit/565fb0f6da9cdc58e8686598015561a848693972) - * Remove "unreachable branches" rules: UnreachableIfBranchesRule, UnreachableTernaryElseBranchRule, unreachable arm error in MatchExpressionRule - * Because "always true" is always reported, these are no longer needed -* New rules (level 5): - * Check preg_quote delimiter sanity ([#3252](https://github.com/phpstan/phpstan-src/pull/3252)), #11338, thanks @staabm! - * Rule for `call_user_func()` ([#2479](https://github.com/phpstan/phpstan-src/pull/2479)), thanks @staabm! - * Report useless `array_filter()` calls ([#1077](https://github.com/phpstan/phpstan-src/pull/1077)), #6840, thanks @leongersen! - * Report useless `array_values()` calls ([#2917](https://github.com/phpstan/phpstan-src/pull/2917)), thanks @kamil-zacek! - * Check array functions which require stringish values ([#3132](https://github.com/phpstan/phpstan-src/pull/3132)), #11141, #5848, #3694, #11111, thanks @schlndh! - * Check unresolvable parameters ([#1319](https://github.com/phpstan/phpstan-src/pull/1319)), thanks @rvanvelzen! - * Enforce `@no-named-arguments` (https://github.com/phpstan/phpstan-src/commit/74ba8c23696948f2647d880df72f375346f41010), #5968 -* New rules (level 6): - * Previously absent type checks: - * Check missing types in `@phpstan-self-out` (https://github.com/phpstan/phpstan-src/commit/892b319f25f04bc1b55c3d0063b607909612fe6d) - * Check missing types in local type aliases (https://github.com/phpstan/phpstan-src/commit/ce7ffaf02d624a7fb9d38f8e5dffc9739f1233fc) - * Check missing types in `@mixin` (https://github.com/phpstan/phpstan-src/commit/3175c81f26fd5bcb4a161b24e774921870ed2533) -* New option: `polluteScopeWithBlock` (defaults to `true`, `false` in `phpstan-strict-rules`) (https://github.com/phpstan/phpstan-src/commit/946cf180c960930c2c42075d0f28ff9090507272) -* Support `@readonly` property and `@immutable` class PHPDoc ([#1295](https://github.com/phpstan/phpstan-src/pull/1295), [#1335](https://github.com/phpstan/phpstan-src/pull/1335)), #4082, thanks @herndlm! -* Deprecate various `instanceof *Type` in favour of new methods on `Type` interface, (https://github.com/phpstan/phpstan-src/commit/436e6d3015cbeba4645d38bc7a6a865b9c6d7c74), learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) - - -Improvements 🔧 -===================== - -* TableErrorFormatter - always output identifiers (https://github.com/phpstan/phpstan-src/commit/fc66c24113e9fe88c3155703224eb03768846fdd) -* Config option `exceptions.check.tooWideThrowType` made true by default (https://github.com/phpstan/phpstan-src/commit/1b1da3e2ce3acf10dde03d9656638cda4f7389a4) -* Use `implicitThrows` to only look for explicit throw points in too-wide `@throws` rules when set to `false` (https://github.com/phpstan/phpstan-src/commit/a0e688c1d1e4c5e82f989b26485eb9162f47aa97) -* Rules about tooWideThrowType moved to level 4 (https://github.com/phpstan/phpstan-src/commit/d7798d7f2c47f426efe91c566e6cafd5a4e2410c) -* Both .php and .neon baselines now include error identifiers (https://github.com/phpstan/phpstan-src/commit/f38addda2b151b6e41a746a37659c0bbe9e2293b, https://github.com/phpstan/phpstan-src/commit/c8b7ea9e8f51c8bbc38dfa6b04f9a0172f5cfea0) -* PHPDoc parser: Require whitespace before description with limited start tokens (https://github.com/phpstan/phpdoc-parser/pull/128), https://github.com/phpstan/phpdoc-parser/issues/125, thanks @rvanvelzen! -* Unescape strings in PHPDoc parser (https://github.com/phpstan/phpstan-src/commit/97786ed8376b478ec541ea9df1c450c1fbfe7461) -* PHPDoc parser: add config for lines in its AST & enable ignoring errors within PHPDocs ([#2807](https://github.com/phpstan/phpstan-src/pull/2807)), thanks @janedbal! -* InvalidPhpDocTagValueRule: include PHPDoc line number in the error message (https://github.com/phpstan/phpstan-src/commit/a04e0be832900749b5b4ba22e2de21db8bfa09a0) -* No implicit wildcard in FileExcluder (https://github.com/phpstan/phpstan-src/commit/e19e6e5f8cfa706cc30e44a17276a6bc269f995c), #10299 -* Report invalid exclude paths in PHP config (https://github.com/phpstan/phpstan-src/commit/9718c14f1ffac81ba3d2bf331b4e8b4041a4d004) -* Do not generalize template types, except when in `GenericObjectType` ([#2818](https://github.com/phpstan/phpstan-src/pull/2818), [#2821](https://github.com/phpstan/phpstan-src/pull/2821)) - * This fixes following **20 issues**: #8166, #8127, #7944, #7283, #6653, #6196, #9084, #8683, #8074, #7984, #7301, #7087, #5594, #5592, #9472, #9764, #10092, #11126, #11032, #10653 -* Non-static methods cannot be used as static callables in PHP 8+ ([#2420](https://github.com/phpstan/phpstan-src/pull/2420)), thanks @staabm! -* Analysis with zero files results in non-zero exit code (https://github.com/phpstan/phpstan-src/commit/46ff440648e62617df86aa74ba905ffa99897737), #9410 -* Fail build when project config uses custom extensions outside of analysed paths - * This will only occur after a run that uses already present and valid result cache -* Returning plain strings as errors no longer supported, use RuleErrorBuilder - * Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) -* Require identifier in custom rules (https://github.com/phpstan/phpstan-src/commit/969e6fa31d5484d42dab902703cfc6820a983cfd) -* New `RuleLevelHelper::accepts()` behaviour (https://github.com/phpstan/phpstan-src/commit/941fc815db49315b8783dc466cf593e0d8a85d23), #11119, #4174 -* Infer explicit mixed when instantiating generic class with unknown template types (https://github.com/phpstan/phpstan-src/commit/089d4c6fb6eb709c44123548d33990113d174b86), #6398 -* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), #7082, thanks @herndlm! -* Consider implicit throw points when the only explicit one is `Throw_` (https://github.com/phpstan/phpstan-src/commit/22eef6d5ab9a4afafb2305258fea273be6cc06e4), #4912 -* Run missing type check on `@param-out` (https://github.com/phpstan/phpstan-src/commit/56b20024386d983927c64dfa895ff026bed2798c) -* Report "missing return" error closer to where the return is missing (https://github.com/phpstan/phpstan-src/commit/04f8636e6577cbcaefc944725eed74c0d7865ead) -* Report dead types even in multi-exception catch ([#2399](https://github.com/phpstan/phpstan-src/pull/2399)), thanks @JanTvrdik! -* MethodSignatureRule - look at abstract trait method (https://github.com/phpstan/phpstan-src/commit/5fd8cee591ce1b07daa5f98a1ddcdfc723f1b5eb) -* OverridingMethodRule - include template types in prototype declaring class description (https://github.com/phpstan/phpstan-src/commit/ca2c66cc4dff59ba44d52b82cb9e0aa3256240f3) -* Detect overriding `@final` method in OverridingMethodRule, #9135 -* Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet! -* Stricter ++/-- operator check ([#3255](https://github.com/phpstan/phpstan-src/pull/3255)), thanks @schlndh! -* Check mixed in binary operator ([#3231](https://github.com/phpstan/phpstan-src/pull/3231)), #7538, #10440, thanks @schlndh! -* Check mixed in unary operator ([#3253](https://github.com/phpstan/phpstan-src/pull/3253)), thanks @schlndh! -* Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4) -* NoopRule - take advantage of impure points (https://github.com/phpstan/phpstan-src/commit/a6470521b65d7424f552633c1f3827704c6262c3), #10389 -* Improve impossible type checker for void-returning functions ([#1857](https://github.com/phpstan/phpstan-src/pull/1857)), #8169, thanks @rvanvelzen! -* Check template type variance in `@param-out` (https://github.com/phpstan/phpstan-src/commit/7ceb19d3b42cf4632d10c2babb0fc5a21b6c8352), https://github.com/phpstan/phpstan/issues/8880#issuecomment-1426971473 -* Fix position variance of static method parameters ([#2313](https://github.com/phpstan/phpstan-src/pull/2313)), thanks @jiripudil! -* Empty `skipCheckGenericClasses` (https://github.com/phpstan/phpstan-src/commit/28c2c79b16cac6ba6b01f1b4d211541dd49d8a77) -* Report unnecessary nullsafe property fetch inside `??` / `isset` / `empty` with different message ([#1253](https://github.com/phpstan/phpstan-src/pull/1253)), thanks @rajyan! -* Specify explicit mixed array type via `is_array` ([#1191](https://github.com/phpstan/phpstan-src/pull/1191)), thanks @herndlm! -* TooWideMethodReturnTypehintRule - always report for final methods (https://github.com/phpstan/phpstan-src/commit/c30e9a484c8245b8126cd63444607ca74d2af761) -* Move IllegalConstructorMethodCallRule and IllegalConstructorStaticCallRule to phpstan-strict-rules (https://github.com/phpstan/phpstan-src/commit/124b30f98c182193187be0b9c2e151e477429b7a, https://github.com/phpstan/phpstan-strict-rules/commit/0c82c96f2a55d8b91bbc7ee6512c94f68a206b43) -* Check invalid PHPDocs in previously unchecked statement types (https://github.com/phpstan/phpstan-src/commit/9780d352f3264aac09ac7954f691de1877db8e01) -* InvalidPHPStanDocTagRule in StubValidator (https://github.com/phpstan/phpstan-src/commit/9c2552b7e744926d1a74c1ba8fd32c64079eed61) -* CallToConstructorStatementWithoutSideEffectsRule - report class with no constructor (https://github.com/phpstan/phpstan-src/commit/b116d25a6e4ba6c09f59af6569d9e6f6fd20aff4) -* ContainerFactory - always check duplicate files (https://github.com/phpstan/phpstan-src/commit/939a715a0636ed05752659dbe7646c1f1a574765) -* Display parent class name for anonymous class like native PHP does ([#3362](https://github.com/phpstan/phpstan-src/pull/3362)), thanks @mvorisek! -* Always report static property fetch in `isset()`, not just on PHP 8.2+ ([#3476](https://github.com/phpstan/phpstan-src/pull/3476)), thanks @ondrejmirtes! -* Revert "Dumb down parameter types in some recently added stubs" (https://github.com/phpstan/phpstan-src/commit/950a491485c46068074ca3f4f6dc5b970d41465a) -* Do not apply heuristics of `Collection<...>|Foo[]` being resolved to Collection of Foo (https://github.com/phpstan/phpstan-src/commit/fff8f095988a66f298aa4037fe8e6ba98266063c) -* Collected PHP errors cannot be ignored (https://github.com/phpstan/phpstan-src/commit/1d3f4313955dc6fa5c6ce60fa58afe765964e5b0) -* Added missing rules to StubValidator (https://github.com/phpstan/phpstan-src/commit/bf19914cac1682d0eab8bf65a874ba368522311c) -* Report precise offsets in errors ([#3504](https://github.com/phpstan/phpstan-src/pull/3504)), thanks @ruudk! -* IntersectionType - always describe list as list (https://github.com/phpstan/phpstan-src/commit/f680629bc92e4dd5d7acd3bc60c9539fb047452b) -* ArrayType::describe - explicit mixed should be stated explicitly (https://github.com/phpstan/phpstan-src/commit/6cf223840f89c972551f373ade9eea16d12e143b) -* Refactor IntersectionType::describe() (https://github.com/phpstan/phpstan-src/commit/67fbfaee6585c2d47485dc2a159ee76d3ed02b35) -* Remove inefficient caching from `PhpMethodReflection` and `PhpFunctionReflection::isVariadic()` ([#3534](https://github.com/phpstan/phpstan-src/pull/3534)), thanks @staabm! -* Clean file cache from unused items (https://github.com/phpstan/phpstan-src/commit/466ad51740d629c9137a77dac28a676b71ef7197) -* Journal for used generated containers (https://github.com/phpstan/phpstan-src/commit/57c65888e6372a4056afbbacc8207d411ea8559a) -* Use named argument in error for variadic types ([#3611](https://github.com/phpstan/phpstan-src/pull/3611)), thanks @ruudk! - -Bugfixes 🐛 -===================== - -* Fix invariance composition ([#2054](https://github.com/phpstan/phpstan-src/pull/2054)), thanks @jiripudil! -* Fix checking generic `mixed` type based on config ([#2885](https://github.com/phpstan/phpstan-src/pull/2885)), thanks @schlndh! - - -Function signature fixes 🤖 -======================= - -* Countable stub with `0|positive-int` ([#1027](https://github.com/phpstan/phpstan-src/pull/1027)), thanks @staabm! -* More precise types for bcmath function parameters ([#2217](https://github.com/phpstan/phpstan-src/pull/2217)), thanks @Warxcell! -* Specify `Imagick` parameter types ([#2334](https://github.com/phpstan/phpstan-src/pull/2334)), thanks @zonuexe! -* `max()`/`min()` should expect non-empty-array ([#2163](https://github.com/phpstan/phpstan-src/pull/2163)), thanks @staabm! -* Narrow `Closure::bind` `$newScope` param ([#2817](https://github.com/phpstan/phpstan-src/pull/2817)), thanks @mvorisek! -* `error_log` errors with `message_type=2` ([#2428](https://github.com/phpstan/phpstan-src/pull/2428)), #9380, thanks @staabm! -* Update functionMap ([#2699](https://github.com/phpstan/phpstan-src/pull/2699), [#2783](https://github.com/phpstan/phpstan-src/pull/2783)), thanks @zonuexe! -* Improve image related functions signature ([#3127](https://github.com/phpstan/phpstan-src/pull/3127)), thanks @thg2k! -* Support `FILE_NO_DEFAULT_CONTEXT` in `file()` ([#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! -* Fix ftp related function signatures ([#2551](https://github.com/phpstan/phpstan-src/pull/2551)), thanks @thg2k! -* More precise `file()` flags args ([#2476](https://github.com/phpstan/phpstan-src/pull/2476), [#2482](https://github.com/phpstan/phpstan-src/pull/2482)), thanks @staabm! -* More precise `flock()` operation flags ([#2477](https://github.com/phpstan/phpstan-src/pull/2477)), thanks @staabm! -* More precise `stream_socket_client()` signature ([#2519](https://github.com/phpstan/phpstan-src/pull/2519)), thanks @staabm! -* More precise `scandir()` signature ([#2518](https://github.com/phpstan/phpstan-src/pull/2518)), thanks @staabm! -* More precise `extract()` signature ([#2517](https://github.com/phpstan/phpstan-src/pull/2517)), thanks @staabm! -* More precise `RecursiveIteratorIterator::__construct()` parameter types ([#2835](https://github.com/phpstan/phpstan-src/pull/2835)), thanks @staabm! -* Update `Locale` signatures ([#2880](https://github.com/phpstan/phpstan-src/pull/2880)), thanks @devnix! -* Improved the type of the `$mode` parameter for the `count()` ([#3190](https://github.com/phpstan/phpstan-src/pull/3190)), thanks @kuma3!* Check `filter_input*` type param type ([#2271](https://github.com/phpstan/phpstan-src/pull/2271)), thanks @herndlm! -* Change `curl_setopt` function signature based on 2nd arg ([#1719](https://github.com/phpstan/phpstan-src/pull/1719)), thanks @staabm! -* Support returning an array or a string in `count_chars()` ([#3596](https://github.com/phpstan/phpstan-src/pull/3596)), thanks @u01jmg3! -* xdebug_get_function_stack: fix signature ([#3605](https://github.com/phpstan/phpstan-src/pull/3605)), thanks @janedbal! - - -Internals 🔍 -===================== - -* Tool to make optional parameters required across the codebase (https://github.com/phpstan/phpstan-src/commit/7e366e08f96e2e4095b3f02b5487e8f9531f37bf) -* A few more MutatingScope method parameters made required (https://github.com/phpstan/phpstan-src/commit/2c4c0cde75e637ac323e81def57d4a2ace952429) -* CommandHelper::begin() parameters made required (https://github.com/phpstan/phpstan-src/commit/f17cf9ec43111cb29dd50d620fb6259c0ab0d373) -* MethodTag - constructor parameter `$templateTags` is required (https://github.com/phpstan/phpstan-src/commit/5b58f83e6d8b5044d742caed9729d00178c4a9de) -* InitializerExprTypeResolver - constructor parameter `$usePathConstantsAsConstantString` made required (https://github.com/phpstan/phpstan-src/commit/f88d9ba7f56ef6c3b783aee1c909a3422c0ef3c3) -* `PhpMethodReflectionFactory::create()` - all parameters are required (https://github.com/phpstan/phpstan-src/commit/8bfbf8f254a68e4f1b15419eb950ea677fc2916e) -* FunctionCallParametersCheck - parameters `$nodeType` and `$acceptsNamedArguments` made required (https://github.com/phpstan/phpstan-src/commit/493752737c32eb878de4dfb91817761b952348e4) -* MethodParameterComparisonHelper - parameter `$ignorable` of `compare()` method made required (https://github.com/phpstan/phpstan-src/commit/f85a500288b0b8ef9a19d405c0e3d99ab57ce797) -* Parameter `$dateTimeClass` of DateTimeModifyReturnTypeExtension constructor made required (https://github.com/phpstan/phpstan-src/commit/a8cd423e842deaa7d924580665207a4b1a373115) -* NativeFunctionReflection construct parameters made required (https://github.com/phpstan/phpstan-src/commit/64ff598cd42268d2178d02efd208afe637060978) -* Cover AccessoryArrayListType constructor with BC promise (https://github.com/phpstan/phpstan-src/commit/51de9032c6e98bff2d6eb0e5b7295720ec0276b9) -* Add `PhpVersion` parameter to various `Type` methods ([#3478](https://github.com/phpstan/phpstan-src/pull/3478)), thanks @VincentLanglet! -* Move ContainerDynamicReturnTypeExtension to build/PHPStan (https://github.com/phpstan/phpstan-src/commit/5651bec661582b2d62de1b4ae9d5f27e69e3c524) -* Renamed NewOptimizedDirectorySourceLocator to OptimizedDirectorySourceLocator (https://github.com/phpstan/phpstan-src/commit/db02a30ca11c7b9839c30e0321ed403dd14f6c73) -* Remove unneded abstraction (https://github.com/phpstan/phpstan-src/commit/f302c9069274afa63ec1b4f313ca72340699e9d8) -* Introduce native return types thanks to PHP 7.4 return type covariance (https://github.com/phpstan/phpstan-src/commit/392f090066bfc9946b4ad524ffecf3d420c23114) -* ReadWritePropertiesExtension - use ExtendedPropertyReflection in parameter type (https://github.com/phpstan/phpstan-src/commit/f0a629685de2202687b9f92bd0e1a516daf2443e) -* Declare more precise `getClass()` return types in extension interfaces ([#1754](https://github.com/phpstan/phpstan-src/pull/1754)), thanks @staabm! -* (https://github.com/phpstan/phpstan-src/commit/38cb5a315e5573231d8695df343c8ee87a8c3b2e) -* HasOffsetType - put constructor parameter type natively (https://github.com/phpstan/phpstan-src/commit/b5accb3f6bbcffc8a44934539b88903e09b6a174) -* Printer is covered by BC promise (https://github.com/phpstan/phpstan-src/commit/b0858332efc7aa2f2fde7544a2a821ba81bde13b) -* More interfaces that are not supposed to be implemented in userland (https://github.com/phpstan/phpstan-src/commit/778af2ed74ba59bfb2a69fd5b45821ccdb1107c9, https://github.com/phpstan/phpstan-src/commit/cb6ab5544a016c52f931fc390bcdf9c627819d8f) -* Refactored `FunctionCallParametersCheck::check()` parameters (https://github.com/phpstan/phpstan-src/commit/710e09c41698efb1d8d3ae31791944077dbb9cc1) -* Spread list usages in Reflection, Scope, Type ([#3530](https://github.com/phpstan/phpstan-src/pull/3530)), thanks @janedbal! -* Remove $isFinal dead-code in PhpFunctionReflection ([#3545](https://github.com/phpstan/phpstan-src/pull/3545)), thanks @staabm! -* Get rid of unnecessary `instanceof self` in `ConstantArrayType` ([#3552](https://github.com/phpstan/phpstan-src/pull/3552)), thanks @herndlm! -* test: use `bashunit -a` exit_code to check for errors ([#3533](https://github.com/phpstan/phpstan-src/pull/3533)), thanks @Chemaclass! -* Upgrade bashunit:0.18.0 for e2e tests ([#3614](https://github.com/phpstan/phpstan-src/pull/3614)), thanks @Chemaclass! -* Remove dead code ([#3575](https://github.com/phpstan/phpstan-src/pull/3575)), thanks @staabm! -* Remove dead code in ConstantConditionRuleHelper ([#3597](https://github.com/phpstan/phpstan-src/pull/3597)), thanks @staabm! From 6b52c1dda46ce625de4375f1a3d39c54aa5d6b4b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 08:44:51 +0100 Subject: [PATCH 0782/3097] Update issue-bot --- issue-bot/composer.lock | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/issue-bot/composer.lock b/issue-bot/composer.lock index be0c0518424..b4abdf4947e 100644 --- a/issue-bot/composer.lock +++ b/issue-bot/composer.lock @@ -1403,16 +1403,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f347f223a7235178f056f34dc104557095998614" + "reference": "72115ab2bf1e40af1f9b238938d493ba7f3221e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f347f223a7235178f056f34dc104557095998614", - "reference": "f347f223a7235178f056f34dc104557095998614", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/72115ab2bf1e40af1f9b238938d493ba7f3221e7", + "reference": "72115ab2bf1e40af1f9b238938d493ba7f3221e7", "shasum": "" }, "require": { @@ -1421,7 +1421,6 @@ "conflict": { "phpstan/phpstan-shim": "*" }, - "default-branch": true, "bin": [ "phpstan", "phpstan.phar" @@ -1458,20 +1457,20 @@ "type": "github" } ], - "time": "2024-09-30T19:33:02+00:00" + "time": "2024-11-11T07:06:55+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.x-dev", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "e208c9311872047b903511e2e03cb0df795014b0" + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/e208c9311872047b903511e2e03cb0df795014b0", - "reference": "e208c9311872047b903511e2e03cb0df795014b0", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", "shasum": "" }, "require": { @@ -1484,7 +1483,6 @@ "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -1505,9 +1503,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.x" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.0" }, - "time": "2024-09-30T19:35:25+00:00" + "time": "2024-10-26T16:04:33+00:00" }, { "name": "psr/cache", @@ -4471,12 +4469,12 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } From 6517446a99d70c75015e6fc2990adba63a662913 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 08:45:41 +0100 Subject: [PATCH 0783/3097] Upgrading guide will only be in phpstan/phpstan --- .github/workflows/phar.yml | 3 - UPGRADING.md | 338 ------------------------------------- 2 files changed, 341 deletions(-) delete mode 100644 UPGRADING.md diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 28d8d8b36bb..dcc27d6be74 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -211,9 +211,6 @@ jobs: env: GPG_ID: ${{ steps.import-gpg.outputs.fingerprint }} - - name: "cp UPGRADING.md" - run: cp phpstan-src/UPGRADING.md phpstan-dist/UPGRADING.md - - name: "Verify PHAR" working-directory: phpstan-dist run: "gpg --verify phpstan.phar.asc" diff --git a/UPGRADING.md b/UPGRADING.md deleted file mode 100644 index 1b76768ec46..00000000000 --- a/UPGRADING.md +++ /dev/null @@ -1,338 +0,0 @@ -Upgrading from PHPStan 1.x to 2.0 -================================= - -## PHP version requirements - -PHPStan now requires PHP 7.4 or newer to run. - -## Upgrading guide for end users - -The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** -and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. - -Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). - -Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: - -```json -"require-dev": { - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0", - "phpstan/phpstan-doctrine": "^2.0", - "phpstan/phpstan-nette": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpstan/phpstan-symfony": "^2.0", - "phpstan/phpstan-webmozart-assert": "^2.0", - ... -} -``` - -Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well. - -After changing your `composer.json`, run `composer update 'phpstan/*' -W`. - -It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. - -### Noteworthy changes to code analysis - -* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) -* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) -* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type) -* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. - -### Removed option `checkMissingIterableValueType` - -It's strongly recommended to add the missing array typehints. - -If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`: - -```neon -parameters: - ignoreErrors: - - - identifier: missingType.iterableValue -``` - -### Removed option `checkGenericClassInNonGenericObjectType` - -It's strongly recommended to add the missing generic typehints. - -If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`: - -```neon -parameters: - ignoreErrors: - - - identifier: missingType.generics -``` - -### Removed `checkAlwaysTrue*` options - -These options have been removed because PHPStan now always behaves as if these were set to `true`: - -* `checkAlwaysTrueCheckTypeFunctionCall` -* `checkAlwaysTrueInstanceof` -* `checkAlwaysTrueStrictComparison` -* `checkAlwaysTrueLooseComparison` - -### Removed option `excludes_analyse` - -It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). - -### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern - -If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: - -```neon -parameters: - excludePaths: - - tests/*/data/* - - src/broken - - node_modules (?) # optional path, might not exist -``` - -If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`. - -```neon -parameters: - reportUnmatchedIgnoredErrors: false -``` - -Appending `(?)` in `ignoreErrors` is not supported. - -### Changes in 1st party PHPStan extensions - -* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) - * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`) - * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`) -* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony) - * Removed legacy options with `_` in the name - * `container_xml_path` -> use `containerXmlPath` - * `constant_hassers` -> use `constantHassers` - * `console_application_loader` -> use `consoleApplicationLoader` - -### Minor backward compatibility breaks - -* Removed unused config parameter `cache.nodesByFileCountMax` -* Removed unused config parameter `memoryLimitFile` -* Removed unused feature toggle `disableRuntimeReflectionProvider` -* Removed unused config parameter `staticReflectionClassNamePatterns` -* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead -* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead -* `additionalConfigFiles` config parameter must be a list - -## Upgrading guide for extension developers - -> [!NOTE] -> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. -> -> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. - -### PHPStan now uses nikic/php-parser v5 - -See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. - -The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class. - -Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The -`Stmt\Throw_` class has been removed. - -### PHPStan now uses phpstan/phpdoc-parser v2 - -See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. - -### Returning plain strings as errors no longer supported, use RuleErrorBuilder - -Identifiers are also required in custom rules. - -Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) - -**Before**: - -```php -return ['My error']; -``` - -**After**: - -```php -return [ - RuleErrorBuilder::message('My error') - ->identifier('my.error') - ->build(), -]; -``` - -### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface - -Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) - -### Removed deprecated `ParametersAcceptorSelector::selectSingle()` - -Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions. - -**Before**: - -```php -$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); -``` - -**After**: - -```php -$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $functionCall->getArgs(), - $functionReflection->getVariants() -)->getReturnType(); -``` - -If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object: - -* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html) -* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html) -* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html) -* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html) -* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction) - -**Before**: - -```php -$function = $node->getFunctionReflection(); -$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); -``` - -**After**: - -```php -$returnType = $node->getFunctionReflection()->getReturnType(); -``` - -### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters - -[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): - -* `Expr $expr` -* `Type $type` -* `TypeSpecifierContext $context` -* `Scope $scope` - -If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable). - -[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts: - -* `array $sureTypes` -* `array $sureNotTypes` - -If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). - -### `ConstantArrayType` no longer extends `ArrayType` - -`Type::getArrays()` now returns `list`. - -Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal. - -### Changed `TypeSpecifier::specifyTypesInCondition()` - -This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). - -### Node attributes `parent`, `previous`, `next` are no longer available - -Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules - -### Removed config parameter `scopeClass` - -As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. - -### Removed `PHPStan\Broker\Broker` - -Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead. - -`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead. - -Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. - -### List type is enabled for everyone - -Removed static methods from `AccessoryArrayListType` class: - -* `isListTypeEnabled()` -* `setListTypeEnabled()` -* `intersectWith()` - -Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`. - -### Minor backward compatibility breaks - -* Classes that were previously `@final` were made `final` -* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required -* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required -* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) -* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. -* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. -* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) -* Remove `ArrayType::generalizeKeys()` -* Remove `ArrayType::count()`, use `Type::getArraySize()` instead -* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead -* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead -* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead -* Remove unused `PHPStanTestCase::$useStaticReflectionProvider` -* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead -* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead -* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead -* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` -* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead -* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead -* Rename `Type::isClassStringType()` to `Type::isClassString()` -* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead -* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead -* Remove `ConstantArrayType::getNextAutoIndex()` -* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` - * Use `getFirstIterable*Type` and `getLastIterable*Type` instead -* Remove `ConstantArrayType::generalizeToArray()` -* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead -* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead -* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead -* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead -* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead -* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead -* Made `TypeUtils` thinner by removing methods: - * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead - * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead - * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead - * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) - * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead - * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead - * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead - * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead - * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead -* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead -* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool` -* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int` -* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead -* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` -* Remove `FunctionReflection::isFinal()` -* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) -* Remove `__set_state()` on objects that should not be serialized in cache -* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` -* `LevelsTestCase::dataTopics()` data provider made static -* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint -* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) -* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) -Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) -* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) -* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) -* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) -* Changes around `ClassConstantReflection` - * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` - * Interface `ConstantReflection` renamed to `ClassConstantReflection` - * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` - * Interface `GlobalConstantReflection` renamed to `ConstantReflection` -* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` - * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor` - * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection` - * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant` -* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` -* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node - * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node From 1f411b96eb130636d3cb3f663c29e8674fc2129a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 09:17:29 +0100 Subject: [PATCH 0784/3097] Revert "Upgrading guide will only be in phpstan/phpstan" This reverts commit 6517446a99d70c75015e6fc2990adba63a662913. --- UPGRADING.md | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 UPGRADING.md diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000000..1b76768ec46 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,338 @@ +Upgrading from PHPStan 1.x to 2.0 +================================= + +## PHP version requirements + +PHPStan now requires PHP 7.4 or newer to run. + +## Upgrading guide for end users + +The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** +and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. + +Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). + +Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: + +```json +"require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-nette": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + ... +} +``` + +Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well. + +After changing your `composer.json`, run `composer update 'phpstan/*' -W`. + +It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. + +### Noteworthy changes to code analysis + +* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) +* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) +* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type) +* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. + +### Removed option `checkMissingIterableValueType` + +It's strongly recommended to add the missing array typehints. + +If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.iterableValue +``` + +### Removed option `checkGenericClassInNonGenericObjectType` + +It's strongly recommended to add the missing generic typehints. + +If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.generics +``` + +### Removed `checkAlwaysTrue*` options + +These options have been removed because PHPStan now always behaves as if these were set to `true`: + +* `checkAlwaysTrueCheckTypeFunctionCall` +* `checkAlwaysTrueInstanceof` +* `checkAlwaysTrueStrictComparison` +* `checkAlwaysTrueLooseComparison` + +### Removed option `excludes_analyse` + +It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). + +### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern + +If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: + +```neon +parameters: + excludePaths: + - tests/*/data/* + - src/broken + - node_modules (?) # optional path, might not exist +``` + +If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`. + +```neon +parameters: + reportUnmatchedIgnoredErrors: false +``` + +Appending `(?)` in `ignoreErrors` is not supported. + +### Changes in 1st party PHPStan extensions + +* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) + * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`) + * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`) +* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony) + * Removed legacy options with `_` in the name + * `container_xml_path` -> use `containerXmlPath` + * `constant_hassers` -> use `constantHassers` + * `console_application_loader` -> use `consoleApplicationLoader` + +### Minor backward compatibility breaks + +* Removed unused config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `memoryLimitFile` +* Removed unused feature toggle `disableRuntimeReflectionProvider` +* Removed unused config parameter `staticReflectionClassNamePatterns` +* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead +* `additionalConfigFiles` config parameter must be a list + +## Upgrading guide for extension developers + +> [!NOTE] +> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. +> +> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. + +### PHPStan now uses nikic/php-parser v5 + +See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. + +The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class. + +Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The +`Stmt\Throw_` class has been removed. + +### PHPStan now uses phpstan/phpdoc-parser v2 + +See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. + +### Returning plain strings as errors no longer supported, use RuleErrorBuilder + +Identifiers are also required in custom rules. + +Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) + +**Before**: + +```php +return ['My error']; +``` + +**After**: + +```php +return [ + RuleErrorBuilder::message('My error') + ->identifier('my.error') + ->build(), +]; +``` + +### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface + +Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) + +### Removed deprecated `ParametersAcceptorSelector::selectSingle()` + +Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions. + +**Before**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); +``` + +**After**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants() +)->getReturnType(); +``` + +If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object: + +* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html) +* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html) +* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html) +* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html) +* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction) + +**Before**: + +```php +$function = $node->getFunctionReflection(); +$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); +``` + +**After**: + +```php +$returnType = $node->getFunctionReflection()->getReturnType(); +``` + +### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters + +[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): + +* `Expr $expr` +* `Type $type` +* `TypeSpecifierContext $context` +* `Scope $scope` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable). + +[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts: + +* `array $sureTypes` +* `array $sureNotTypes` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). + +### `ConstantArrayType` no longer extends `ArrayType` + +`Type::getArrays()` now returns `list`. + +Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal. + +### Changed `TypeSpecifier::specifyTypesInCondition()` + +This method now longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). + +### Node attributes `parent`, `previous`, `next` are no longer available + +Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules + +### Removed config parameter `scopeClass` + +As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. + +### Removed `PHPStan\Broker\Broker` + +Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead. + +`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead. + +Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. + +### List type is enabled for everyone + +Removed static methods from `AccessoryArrayListType` class: + +* `isListTypeEnabled()` +* `setListTypeEnabled()` +* `intersectWith()` + +Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`. + +### Minor backward compatibility breaks + +* Classes that were previously `@final` were made `final` +* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required +* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required +* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) +* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) +* Remove `ArrayType::generalizeKeys()` +* Remove `ArrayType::count()`, use `Type::getArraySize()` instead +* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead +* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead +* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead +* Remove unused `PHPStanTestCase::$useStaticReflectionProvider` +* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead +* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead +* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead +* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` +* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead +* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead +* Rename `Type::isClassStringType()` to `Type::isClassString()` +* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead +* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead +* Remove `ConstantArrayType::getNextAutoIndex()` +* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` + * Use `getFirstIterable*Type` and `getLastIterable*Type` instead +* Remove `ConstantArrayType::generalizeToArray()` +* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead +* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead +* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead +* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead +* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead +* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead +* Made `TypeUtils` thinner by removing methods: + * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead + * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead + * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead + * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) + * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead + * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead + * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead + * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead + * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead +* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead +* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool` +* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int` +* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead +* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` +* Remove `FunctionReflection::isFinal()` +* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) +* Remove `__set_state()` on objects that should not be serialized in cache +* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` +* `LevelsTestCase::dataTopics()` data provider made static +* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint +* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Changes around `ClassConstantReflection` + * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` + * Interface `ConstantReflection` renamed to `ClassConstantReflection` + * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` + * Interface `GlobalConstantReflection` renamed to `ConstantReflection` +* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` + * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor` + * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection` + * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant` +* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` +* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node + * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node From edcc9e8a875990913769cc355d2a775543da16a2 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Mon, 11 Nov 2024 10:15:15 +0100 Subject: [PATCH 0785/3097] fix: check for existence of second arg in CountCharsFunctionDynamicReturnTypeExtension --- .../Php/CountCharsFunctionDynamicReturnTypeExtension.php | 7 +++++-- tests/PHPStan/Analyser/nsrt/count-chars-7.4.php | 1 + tests/PHPStan/Analyser/nsrt/count-chars-8.0.php | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php index cddb7e79898..e798af6bbd1 100644 --- a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; @@ -35,11 +36,13 @@ public function getTypeFromFunctionCall( Scope $scope, ): ?Type { - if (count($functionCall->getArgs()) < 1) { + $args = $functionCall->getArgs(); + + if (count($args) < 1) { return null; } - $modeType = $scope->getType($functionCall->getArgs()[1]->value); + $modeType = count($args) === 2 ? $scope->getType($args[1]->value) : new ConstantIntegerType(0); if (IntegerRangeType::fromInterval(0, 2)->isSuperTypeOf($modeType)->yes()) { $arrayType = new ArrayType(new IntegerType(), new IntegerType()); diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php index 713d73a5e14..76e0bea5817 100644 --- a/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php +++ b/tests/PHPStan/Analyser/nsrt/count-chars-7.4.php @@ -8,6 +8,7 @@ class X { const ABC = 'abcdef'; function doFoo(): void { + assertType('array|false', count_chars(self::ABC)); assertType('array|false', count_chars(self::ABC, 0)); assertType('array|false', count_chars(self::ABC, 1)); assertType('array|false', count_chars(self::ABC, 2)); diff --git a/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php index 88a165e931c..6e0af08f038 100644 --- a/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php +++ b/tests/PHPStan/Analyser/nsrt/count-chars-8.0.php @@ -8,6 +8,7 @@ class Y { const ABC = 'abcdef'; function doFoo(): void { + assertType('array', count_chars(self::ABC)); assertType('array', count_chars(self::ABC, 0)); assertType('array', count_chars(self::ABC, 1)); assertType('array', count_chars(self::ABC, 2)); From d6412b8f9fa7f0900df5d05ca49e28aee567d688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Mon, 11 Nov 2024 13:54:39 +0100 Subject: [PATCH 0786/3097] ClassReflection: resolve missing template type to its default (if set) rather than bound --- src/Reflection/ClassReflection.php | 2 +- src/Type/Generic/TemplateTypeHelper.php | 11 ++++++++ src/Type/GenericTypeVariableResolver.php | 2 +- src/Type/ObjectType.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-11899.php | 34 +++++++++++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11899.php diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index c79f2a6bed5..ff45172fd8f 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1374,7 +1374,7 @@ public function getActiveTemplateTypeMap(): TemplateTypeMap if ($type instanceof ErrorType) { $templateType = $templateTypeMap->getType($name); if ($templateType !== null) { - return TemplateTypeHelper::resolveToBounds($templateType); + return TemplateTypeHelper::resolveToDefaults($templateType); } } diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 58460efaee1..29ecd487206 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -68,6 +68,17 @@ public static function resolveTemplateTypes( }); } + public static function resolveToDefaults(Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof TemplateType) { + return $traverse($type->getDefault() ?? $type->getBound()); + } + + return $traverse($type); + }); + } + public static function resolveToBounds(Type $type): Type { return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { diff --git a/src/Type/GenericTypeVariableResolver.php b/src/Type/GenericTypeVariableResolver.php index 8c3f7d2da58..eee42d0f1b1 100644 --- a/src/Type/GenericTypeVariableResolver.php +++ b/src/Type/GenericTypeVariableResolver.php @@ -44,7 +44,7 @@ public static function getType( return new MixedType(false); } - return $bound; + return TemplateTypeHelper::resolveToDefaults($templateType); } return $type; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 3e8ac1371f2..7427aa167c7 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -841,7 +841,7 @@ public function getTemplateType(string $ancestorClassName, string $templateTypeN return new MixedType(false); } - return $bound; + return TemplateTypeHelper::resolveToDefaults($templateType); } return $type; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11899.php b/tests/PHPStan/Analyser/nsrt/bug-11899.php new file mode 100644 index 00000000000..c56b47dfcb8 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11899.php @@ -0,0 +1,34 @@ +test); +} + +/** + * @param UserTest $ut + */ +function acceptUserTest2(UserTest $ut) : void { + assertType('Bug11899\\UserTest', $ut); + assertType('Bug11899\\InvertedQuestions|null', $ut->test); +} From 753fc4d98fe8929aa8816f454d2f9a836ccd7a6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 15:16:38 +0100 Subject: [PATCH 0787/3097] Fix resolving tentative return type --- .../Native/NativeMethodReflection.php | 2 +- src/Reflection/Php/PhpMethodReflection.php | 2 +- .../Methods/OverridingMethodRuleTest.php | 6 ++++++ .../Methods/data/simple-xml-element-child.php | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/simple-xml-element-child.php diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 8f1e21d7c91..731d4973fe6 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -81,7 +81,7 @@ public function getPrototype(): ClassMemberReflection $tentativeReturnType = null; if ($prototypeMethod->getTentativeReturnType() !== null) { - $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType()); + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), null, $prototypeDeclaringClass); } return new MethodPrototypeReflection( diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 53f5d800544..d32c16e75eb 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -118,7 +118,7 @@ public function getPrototype(): ClassMemberReflection $tentativeReturnType = null; if ($prototypeMethod->getTentativeReturnType() !== null) { - $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType()); + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), null, $prototypeDeclaringClass); } return new MethodPrototypeReflection( diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 5a8b6772292..6b2a5a0c37e 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -812,4 +812,10 @@ public function testBug9524(): void $this->analyse([__DIR__ . '/data/bug-9524.php'], []); } + public function testSimpleXmlElementChildClass(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/simple-xml-element-child.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/simple-xml-element-child.php b/tests/PHPStan/Rules/Methods/data/simple-xml-element-child.php new file mode 100644 index 00000000000..1251bf5933f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/simple-xml-element-child.php @@ -0,0 +1,20 @@ +escapeInput($value), $namespace); + } + + private function escapeInput(?string $value): ?string + { + if ($value === null) { + return null; + } + return htmlspecialchars((string) normalizer_normalize($value), ENT_XML1, 'UTF-8'); + } +} From fd6a0f275f2a4d6dd21c450bb4d0a55d0ee1e43c Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 11 Nov 2024 12:48:50 +0100 Subject: [PATCH 0788/3097] Fix `for` endless loop detection --- src/Analyser/NodeScopeResolver.php | 5 ++- .../PHPStan/Analyser/StatementResultTest.php | 8 ++++ .../DeadCode/UnreachableStatementRuleTest.php | 6 +++ .../PHPStan/Rules/DeadCode/data/bug-11992.php | 37 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-11992.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1a6a069d28f..5c4d86526ee 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1369,7 +1369,10 @@ private function processStmtNode( } $bodyScope = $bodyScope->mergeWith($initScope); + + $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { + $alwaysIterates = $alwaysIterates->and($bodyScope->getType($lastCondExpr)->toBoolean()->isTrue()); $bodyScope = $this->processExprNode($stmt, $lastCondExpr, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); } @@ -1385,9 +1388,7 @@ private function processStmtNode( } $finalScope = $finalScope->generalizeWith($loopScope); - $alwaysIterates = TrinaryLogic::createFromBoolean($context->isTopLevel()); if ($lastCondExpr !== null) { - $alwaysIterates = $alwaysIterates->and($finalScope->getType($lastCondExpr)->toBoolean()->isTrue()); $finalScope = $finalScope->filterByFalseyValue($lastCondExpr); } diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index b86de8617ed..04ca04cb26b 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -297,6 +297,14 @@ public function dataIsAlwaysTerminating(): array 'for (; "a", "";) { }', false, ], + [ + 'for ($c = (0x80 | 0x40); $c & 0x80; $c = $c << 1) { }', + false, + ], + [ + 'for ($i = 0; $i < 10; $i++) { $i = 5; }', + true, + ], [ 'do { } while (doFoo());', false, diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 191ba391342..da076db1c7d 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -224,4 +224,10 @@ public function testBug11179(): void $this->analyse([__DIR__ . '/data/bug-11179.php'], []); } + public function testBug11992(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-11992.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-11992.php b/tests/PHPStan/Rules/DeadCode/data/bug-11992.php new file mode 100644 index 00000000000..e6c35c967dc --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-11992.php @@ -0,0 +1,37 @@ +valid(); + $it->next() + ) { + printf("name: %s\n", $it->getFilename()); + } + printf("done\n"); +} + +exampleA(); +exampleB(); +exampleC(); From 2a200beec3f2edd913b2af3bf69fbb428018dd74 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Nov 2024 16:22:41 +0100 Subject: [PATCH 0789/3097] Fix test --- tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 6b2a5a0c37e..abbe2927ab3 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -814,6 +814,10 @@ public function testBug9524(): void public function testSimpleXmlElementChildClass(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/simple-xml-element-child.php'], []); } From b071fc7a626639ae6caecea0327dfbeecf55e0c5 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:03:28 +0000 Subject: [PATCH 0790/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 23c5c46c7a9..3400105a87c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#1f0dca06d54cf187adb3481a9c3e7d74af01743b", + "jetbrains/phpstorm-stubs": "dev-master#893a3bc59b8ff1b995220abcbbf76796926b7e9a", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 4a377463ad2..d4f8f73719b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8793a11aa08550fce8d98648609fda3b", + "content-hash": "66f5013e5ca645152c225e1855311a21", "packages": [ { "name": "clue/ndjson-react", @@ -1442,18 +1442,18 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "1f0dca06d54cf187adb3481a9c3e7d74af01743b" + "reference": "893a3bc59b8ff1b995220abcbbf76796926b7e9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/1f0dca06d54cf187adb3481a9c3e7d74af01743b", - "reference": "1f0dca06d54cf187adb3481a9c3e7d74af01743b", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/893a3bc59b8ff1b995220abcbbf76796926b7e9a", + "reference": "893a3bc59b8ff1b995220abcbbf76796926b7e9a", "shasum": "" }, "require-dev": { "friendsofphp/php-cs-fixer": "v3.64.0", "nikic/php-parser": "v5.3.1", - "phpdocumentor/reflection-docblock": "5.4.1", + "phpdocumentor/reflection-docblock": "5.5.1", "phpunit/phpunit": "11.4.3" }, "default-branch": true, @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-04T21:28:48+00:00" + "time": "2024-11-10T16:24:24+00:00" }, { "name": "nette/bootstrap", From 7eef99af807040bc3f2d91fa1bdf28329b0c65b2 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:19:36 +0000 Subject: [PATCH 0791/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 3400105a87c..e739daa7f54 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.11", - "phpstan/php-8-stubs": "0.4.4", + "phpstan/php-8-stubs": "0.4.5", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index d4f8f73719b..2d188ccc6a3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "66f5013e5ca645152c225e1855311a21", + "content-hash": "92f8cc137cb93c0b9769fd3d8c10f71c", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.4", + "version": "0.4.5", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "c42f6e278d600b219b76d20f80f8455259bcd593" + "reference": "34c6f72940e784d1ccdda1b6a3249ed261f1637a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/c42f6e278d600b219b76d20f80f8455259bcd593", - "reference": "c42f6e278d600b219b76d20f80f8455259bcd593", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/34c6f72940e784d1ccdda1b6a3249ed261f1637a", + "reference": "34c6f72940e784d1ccdda1b6a3249ed261f1637a", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.4" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.5" }, - "time": "2024-11-01T00:22:02+00:00" + "time": "2024-11-12T00:19:02+00:00" }, { "name": "phpstan/phpdoc-parser", From 4edc329827908be5b617055c134740b240e21018 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Nov 2024 14:28:17 +0100 Subject: [PATCH 0792/3097] Run integration tests again --- .github/workflows/phar.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index dcc27d6be74..adea89e2323 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -103,14 +103,14 @@ jobs: - name: "Delete checksum PHAR" run: "rm tmp/phpstan.phar" -# -# integration-tests: -# if: github.event_name == 'pull_request' -# needs: compiler-tests -# uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x -# with: -# ref: 2.0.x -# phar-checksum: ${{needs.compiler-tests.outputs.checksum}} + + integration-tests: + if: github.event_name == 'pull_request' + needs: compiler-tests + uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x + with: + ref: 2.0.x + phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' From 3447391001f7a5c2bfb77f66c2d0e157242c1dae Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Nov 2024 17:24:37 +0100 Subject: [PATCH 0793/3097] Fix resolving class constant type using `self::` in a class attribute argument --- src/Rules/Classes/ClassAttributesRule.php | 9 ++++--- .../Rules/Classes/ClassAttributesRuleTest.php | 22 ++++++++++++++- .../PHPStan/Rules/Classes/data/bug-12011.php | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-12011.php diff --git a/src/Rules/Classes/ClassAttributesRule.php b/src/Rules/Classes/ClassAttributesRule.php index 319379ab2a2..afe9786db05 100644 --- a/src/Rules/Classes/ClassAttributesRule.php +++ b/src/Rules/Classes/ClassAttributesRule.php @@ -5,11 +5,12 @@ use Attribute; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClassNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; /** - * @implements Rule + * @implements Rule */ final class ClassAttributesRule implements Rule { @@ -20,14 +21,16 @@ public function __construct(private AttributesCheck $attributesCheck) public function getNodeType(): string { - return Node\Stmt\ClassLike::class; + return InClassNode::class; } public function processNode(Node $node, Scope $scope): array { + $classLikeNode = $node->getOriginalNode(); + return $this->attributesCheck->check( $scope, - $node->attrGroups, + $classLikeNode->attrGroups, Attribute::TARGET_CLASS, 'class', ); diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 6fa6252277f..f0deeee1082 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -22,6 +22,10 @@ class ClassAttributesRuleTest extends RuleTestCase { + private bool $checkExplicitMixed = false; + + private bool $checkImplicitMixed = false; + protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); @@ -29,7 +33,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false), + new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), @@ -148,4 +152,20 @@ public function testAllowDynamicPropertiesAttribute(): void $this->analyse([__DIR__ . '/data/allow-dynamic-properties-attribute.php'], []); } + public function testBug12011(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12011.php'], [ + [ + 'Parameter #1 $name of attribute class Bug12011\Table constructor expects string|null, int given.', + 23, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-12011.php b/tests/PHPStan/Rules/Classes/data/bug-12011.php new file mode 100644 index 00000000000..feb1795d58d --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-12011.php @@ -0,0 +1,27 @@ + Date: Tue, 12 Nov 2024 17:35:18 +0100 Subject: [PATCH 0794/3097] Do not report nonexistent variable passed to by-ref parameter with checkImplicitMixed --- src/Rules/FunctionCallParametersCheck.php | 6 +++++- .../Methods/CallStaticMethodsRuleTest.php | 8 +++++++ .../PHPStan/Rules/Methods/data/bug-12015.php | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12015.php diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 40fb657bcce..623b593f5f0 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -313,7 +313,11 @@ public function check( if ( !$parameter->passedByReference()->createsNewVariable() - || (!$isBuiltin && $this->checkUnresolvableParameterTypes) // bleeding edge only + || ( + !$isBuiltin + && $this->checkUnresolvableParameterTypes // bleeding edge only + && !$argumentValueType instanceof ErrorType + ) ) { $accepts = $this->ruleLevelHelper->acceptsWithReason($parameterType, $argumentValueType, $scope->isDeclareStrictTypes()); diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 1246699ff80..fbb5e414397 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -849,4 +849,12 @@ public function testBug10872(): void $this->analyse([__DIR__ . '/data/bug-10872.php'], []); } + public function testBug12015(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12015.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12015.php b/tests/PHPStan/Rules/Methods/data/bug-12015.php new file mode 100644 index 00000000000..c2a5618cd8e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12015.php @@ -0,0 +1,21 @@ + Date: Fri, 11 Oct 2024 12:42:16 +0200 Subject: [PATCH 0795/3097] add null to array_map(null, $a, $b) --- .../ArrayMapFunctionReturnTypeExtension.php | 36 +++++++++++++++++-- .../Analyser/nsrt/array_map_multiple.php | 4 +-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e8e9e3a4575..50094cc5731 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -32,7 +32,8 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - if (count($functionCall->getArgs()) < 2) { + $numArgs = count($functionCall->getArgs()); + if ($numArgs < 2) { return null; } @@ -54,10 +55,41 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )->getReturnType(); } elseif ($callableIsNull) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $argIterableValueTypes = []; + $addNull = false; + $expectedSize = null; foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { + $argType = $scope->getType($arg->value); + $argIterableValueTypes[$index] = $argType->getIterableValueType(); + if ($addNull) { + continue; + } + + $arraySizes = $argType->getArraySize()->getConstantScalarValues(); + if ($arraySizes === []) { + $addNull = true; + continue; + } + + foreach ($arraySizes as $size) { + $expectedSize ??= $size; + if ($expectedSize === $size) { + continue; + } + + $addNull = true; + break; + } + } + + foreach ($argIterableValueTypes as $index => $offsetValueType) { + if ($addNull) { + $offsetValueType = TypeCombinator::addNull($offsetValueType); + } + $arrayBuilder->setOffsetValueType( new ConstantIntegerType($index), - $scope->getType($arg->value)->getIterableValueType(), + $offsetValueType, ); } $valueType = $arrayBuilder->getArray(); diff --git a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php index ce73048a46d..2e05c6160dc 100644 --- a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php +++ b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php @@ -29,8 +29,8 @@ public function arrayMapNull(array $array, array $other): void assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); - assertType('non-empty-array', array_map(null, $array, $other)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $other)); } } From d0e7aca3c4bd58b881e104a4d5b3b218c796e547 Mon Sep 17 00:00:00 2001 From: schlndh Date: Fri, 11 Oct 2024 13:01:26 +0200 Subject: [PATCH 0796/3097] don't add null for array_map(null, $a, $a) --- .../ArrayMapFunctionReturnTypeExtension.php | 36 ++++++++++++++----- .../Analyser/nsrt/array_map_multiple.php | 6 +++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 50094cc5731..ce29b37d2a0 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -19,6 +19,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use function array_map; +use function array_reduce; use function array_slice; use function count; @@ -55,19 +56,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )->getReturnType(); } elseif ($callableIsNull) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $argIterableValueTypes = []; - $addNull = false; + $argTypes = []; + $areAllSameSize = true; $expectedSize = null; foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { - $argType = $scope->getType($arg->value); - $argIterableValueTypes[$index] = $argType->getIterableValueType(); - if ($addNull) { + $argTypes[$index] = $argType = $scope->getType($arg->value); + if (!$areAllSameSize || $numArgs === 2) { continue; } $arraySizes = $argType->getArraySize()->getConstantScalarValues(); if ($arraySizes === []) { - $addNull = true; + $areAllSameSize = false; continue; } @@ -77,12 +77,30 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, continue; } - $addNull = true; - break; + $areAllSameSize = false; + continue 2; } } - foreach ($argIterableValueTypes as $index => $offsetValueType) { + if (!$areAllSameSize) { + $firstArr = $functionCall->getArgs()[1]->value; + $identities = []; + foreach (array_slice($functionCall->getArgs(), 2) as $arg) { + $identities[] = new Node\Expr\BinaryOp\Identical($firstArr, $arg->value); + } + + $and = array_reduce( + $identities, + static fn (Node\Expr $a, Node\Expr $b) => new Node\Expr\BinaryOp\BooleanAnd($a, $b), + new Node\Expr\ConstFetch(new Node\Name('true')), + ); + $areAllSameSize = $scope->getType($and)->isTrue()->yes(); + } + + $addNull = !$areAllSameSize; + + foreach ($argTypes as $index => $argType) { + $offsetValueType = $argType->getIterableValueType(); if ($addNull) { $offsetValueType = TypeCombinator::addNull($offsetValueType); } diff --git a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php index 2e05c6160dc..d986969c3e9 100644 --- a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php +++ b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php @@ -29,8 +29,12 @@ public function arrayMapNull(array $array, array $other): void assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $array, $array)); assertType('non-empty-array', array_map(null, $array, $other)); + + assertType('array{1}|array{true}', array_map(null, rand() ? [1] : [true])); + assertType('array{1}|array{true, false}', array_map(null, rand() ? [1] : [true, false])); } } From 5a110c48270bcfd90943401eda125433c8575537 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Nov 2024 09:00:52 +0100 Subject: [PATCH 0797/3097] Fix build --- tests/PHPStan/Rules/Classes/data/bug-12011.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Classes/data/bug-12011.php b/tests/PHPStan/Rules/Classes/data/bug-12011.php index feb1795d58d..94671eec7a1 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-12011.php +++ b/tests/PHPStan/Rules/Classes/data/bug-12011.php @@ -1,4 +1,4 @@ -= 8.3 namespace Bug12011; From 6787df2bc822c98527d8dce7152e79532edbc9e2 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 13 Nov 2024 00:20:00 +0000 Subject: [PATCH 0798/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e739daa7f54..2dd85496595 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.42.0.11", - "phpstan/php-8-stubs": "0.4.5", + "phpstan/php-8-stubs": "0.4.6", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 2d188ccc6a3..57ec0c62c4f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "92f8cc137cb93c0b9769fd3d8c10f71c", + "content-hash": "01763204b0f17de678b17b578dd8ffbc", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.5", + "version": "0.4.6", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "34c6f72940e784d1ccdda1b6a3249ed261f1637a" + "reference": "25ba0a11dc14a02c062392786486ada62d36b66d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/34c6f72940e784d1ccdda1b6a3249ed261f1637a", - "reference": "34c6f72940e784d1ccdda1b6a3249ed261f1637a", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/25ba0a11dc14a02c062392786486ada62d36b66d", + "reference": "25ba0a11dc14a02c062392786486ada62d36b66d", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.5" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.6" }, - "time": "2024-11-12T00:19:02+00:00" + "time": "2024-11-13T00:19:28+00:00" }, { "name": "phpstan/phpdoc-parser", From 5a132f15d6296c34ef305d19bae405c101698fab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Nov 2024 09:35:48 +0100 Subject: [PATCH 0799/3097] Refactor ComposerPhpVersionFactory, ConstantResolver --- conf/config.neon | 1 - src/Analyser/ConstantResolver.php | 89 ++++++++++++++----- src/Analyser/ConstantResolverFactory.php | 4 +- .../ValidateIgnoredErrorsExtension.php | 4 +- src/Php/ComposerPhpVersionFactory.php | 55 ++---------- src/Testing/PHPStanTestCase.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-4434.php | 20 ++--- .../Analyser/nsrt/predefined-constants.php | 4 +- 8 files changed, 97 insertions(+), 84 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index df5c15ddec7..77b59a5d3b7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -345,7 +345,6 @@ services: - class: PHPStan\Php\ComposerPhpVersionFactory arguments: - phpVersion: %phpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 8176fe8abcb..846188b9858 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -3,10 +3,12 @@ namespace PHPStan\Analyser; use PhpParser\Node\Name; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -21,6 +23,8 @@ use PHPStan\Type\UnionType; use function array_key_exists; use function in_array; +use function is_array; +use function is_int; use function max; use function sprintf; use const INF; @@ -35,12 +39,13 @@ final class ConstantResolver /** * @param string[] $dynamicConstantNames + * @param int|array{min: int, max: int}|null $phpVersion */ public function __construct( private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames, - private ?PhpVersion $composerMinPhpVersion, - private ?PhpVersion $composerMaxPhpVersion, + private int|array|null $phpVersion, + private ComposerPhpVersionFactory $composerPhpVersionFactory, ) { } @@ -83,15 +88,23 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type new AccessoryNonFalsyStringType(), ]); } + + $minPhpVersion = null; + $maxPhpVersion = null; + if (in_array($resolvedConstantName, ['PHP_VERSION_ID', 'PHP_MAJOR_VERSION', 'PHP_MINOR_VERSION', 'PHP_RELEASE_VERSION'], true)) { + $minPhpVersion = $this->getMinPhpVersion(); + $maxPhpVersion = $this->getMaxPhpVersion(); + } + if ($resolvedConstantName === 'PHP_MAJOR_VERSION') { $minMajor = 5; $maxMajor = null; - if ($this->composerMinPhpVersion !== null) { - $minMajor = max($minMajor, $this->composerMinPhpVersion->getMajorVersionId()); + if ($minPhpVersion !== null) { + $minMajor = max($minMajor, $minPhpVersion->getMajorVersionId()); } - if ($this->composerMaxPhpVersion !== null) { - $maxMajor = $this->composerMaxPhpVersion->getMajorVersionId(); + if ($maxPhpVersion !== null) { + $maxMajor = $maxPhpVersion->getMajorVersionId(); } return $this->createInteger($minMajor, $maxMajor); @@ -101,12 +114,12 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type $maxMinor = null; if ( - $this->composerMinPhpVersion !== null - && $this->composerMaxPhpVersion !== null - && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() + $minPhpVersion !== null + && $maxPhpVersion !== null + && $maxPhpVersion->getMajorVersionId() === $minPhpVersion->getMajorVersionId() ) { - $minMinor = $this->composerMinPhpVersion->getMinorVersionId(); - $maxMinor = $this->composerMaxPhpVersion->getMinorVersionId(); + $minMinor = $minPhpVersion->getMinorVersionId(); + $maxMinor = $maxPhpVersion->getMinorVersionId(); } return $this->createInteger($minMinor, $maxMinor); @@ -116,13 +129,13 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type $maxRelease = null; if ( - $this->composerMinPhpVersion !== null - && $this->composerMaxPhpVersion !== null - && $this->composerMaxPhpVersion->getMajorVersionId() === $this->composerMinPhpVersion->getMajorVersionId() - && $this->composerMaxPhpVersion->getMinorVersionId() === $this->composerMinPhpVersion->getMinorVersionId() + $minPhpVersion !== null + && $maxPhpVersion !== null + && $maxPhpVersion->getMajorVersionId() === $minPhpVersion->getMajorVersionId() + && $maxPhpVersion->getMinorVersionId() === $minPhpVersion->getMinorVersionId() ) { - $minRelease = $this->composerMinPhpVersion->getPatchVersionId(); - $maxRelease = $this->composerMaxPhpVersion->getPatchVersionId(); + $minRelease = $minPhpVersion->getPatchVersionId(); + $maxRelease = $maxPhpVersion->getPatchVersionId(); } return $this->createInteger($minRelease, $maxRelease); @@ -130,11 +143,11 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type if ($resolvedConstantName === 'PHP_VERSION_ID') { $minVersion = 50207; $maxVersion = null; - if ($this->composerMinPhpVersion !== null) { - $minVersion = max($minVersion, $this->composerMinPhpVersion->getVersionId()); + if ($minPhpVersion !== null) { + $minVersion = max($minVersion, $minPhpVersion->getVersionId()); } - if ($this->composerMaxPhpVersion !== null) { - $maxVersion = $this->composerMaxPhpVersion->getVersionId(); + if ($maxPhpVersion !== null) { + $maxVersion = $maxPhpVersion->getVersionId(); } return $this->createInteger($minVersion, $maxVersion); @@ -351,6 +364,40 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type return null; } + private function getMinPhpVersion(): ?PhpVersion + { + if (is_int($this->phpVersion)) { + return null; + } + + if (is_array($this->phpVersion)) { + if ($this->phpVersion['max'] < $this->phpVersion['min']) { + throw new ShouldNotHappenException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); + } + + return new PhpVersion($this->phpVersion['min']); + } + + return $this->composerPhpVersionFactory->getMinVersion(); + } + + private function getMaxPhpVersion(): ?PhpVersion + { + if (is_int($this->phpVersion)) { + return null; + } + + if (is_array($this->phpVersion)) { + if ($this->phpVersion['max'] < $this->phpVersion['min']) { + throw new ShouldNotHappenException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); + } + + return new PhpVersion($this->phpVersion['max']); + } + + return $this->composerPhpVersionFactory->getMaxVersion(); + } + public function resolveConstantType(string $constantName, Type $constantType): Type { if ($constantType->isConstantValue()->yes() && in_array($constantName, $this->dynamicConstantNames, true)) { diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index f111da14ec7..5ccc516e424 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -23,8 +23,8 @@ public function create(): ConstantResolver return new ConstantResolver( $this->reflectionProviderProvider, $this->container->getParameter('dynamicConstantNames'), - $composerFactory->getMinVersion(), - $composerFactory->getMaxVersion(), + $this->container->getParameter('phpVersion'), + $composerFactory, ); } diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 92f0712e460..4560fde44cd 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -12,6 +12,7 @@ use PHPStan\Command\IgnoredRegexValidator; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileExcluder; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\DirectTypeNodeResolverExtensionRegistryProvider; use PHPStan\PhpDoc\TypeNodeResolver; @@ -65,7 +66,8 @@ public function loadConfiguration(): void $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); ReflectionProviderStaticAccessor::registerInstance($reflectionProvider); PhpVersionStaticAccessor::registerInstance(new PhpVersion(PHP_VERSION_ID)); - $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, null); + $composerPhpVersionFactory = new ComposerPhpVersionFactory([]); + $constantResolver = new ConstantResolver($reflectionProviderProvider, [], null, $composerPhpVersionFactory); $phpDocParserConfig = new ParserConfig([]); $ignoredRegexValidator = new IgnoredRegexValidator( diff --git a/src/Php/ComposerPhpVersionFactory.php b/src/Php/ComposerPhpVersionFactory.php index ebcf7c130ed..88b81022ee7 100644 --- a/src/Php/ComposerPhpVersionFactory.php +++ b/src/Php/ComposerPhpVersionFactory.php @@ -3,17 +3,10 @@ namespace PHPStan\Php; use Composer\Semver\VersionParser; -use Nette\Utils\Json; -use Nette\Utils\JsonException; use Nette\Utils\Strings; -use PHPStan\File\CouldNotReadFileException; -use PHPStan\File\FileReader; -use PHPStan\ShouldNotHappenException; +use PHPStan\Internal\ComposerHelper; use function count; use function end; -use function is_array; -use function is_file; -use function is_int; use function is_string; use function min; use function sprintf; @@ -29,11 +22,9 @@ final class ComposerPhpVersionFactory /** * @param string[] $composerAutoloaderProjectPaths - * @param int|array{min: int, max: int}|null $phpVersion */ public function __construct( private array $composerAutoloaderProjectPaths, - private int|array|null $phpVersion, ) { } @@ -42,23 +33,6 @@ private function initializeVersions(): void { $this->initialized = true; - $phpVersion = $this->phpVersion; - - if (is_int($phpVersion)) { - throw new ShouldNotHappenException(); - } - - if (is_array($phpVersion)) { - if ($phpVersion['max'] < $phpVersion['min']) { - throw new ShouldNotHappenException('Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.'); - } - - $this->minVersion = new PhpVersion($phpVersion['min']); - $this->maxVersion = new PhpVersion($phpVersion['max']); - - return; - } - // don't limit minVersion... PHPStan can analyze even PHP5 $this->maxVersion = new PhpVersion(PhpVersionFactory::MAX_PHP_VERSION); @@ -87,10 +61,6 @@ private function initializeVersions(): void public function getMinVersion(): ?PhpVersion { - if (is_int($this->phpVersion)) { - return null; - } - if ($this->initialized === false) { $this->initializeVersions(); } @@ -100,10 +70,6 @@ public function getMinVersion(): ?PhpVersion public function getMaxVersion(): ?PhpVersion { - if (is_int($this->phpVersion)) { - return null; - } - if ($this->initialized === false) { $this->initializeVersions(); } @@ -114,21 +80,18 @@ public function getMaxVersion(): ?PhpVersion private function getComposerRequireVersion(): ?string { $composerPhpVersion = null; + if (count($this->composerAutoloaderProjectPaths) > 0) { - $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; - if (is_file($composerJsonPath)) { - try { - $composerJsonContents = FileReader::read($composerJsonPath); - $composer = Json::decode($composerJsonContents, Json::FORCE_ARRAY); - $requiredVersion = $composer['require']['php'] ?? null; - if (is_string($requiredVersion)) { - $composerPhpVersion = $requiredVersion; - } - } catch (CouldNotReadFileException | JsonException) { - // pass + $composer = ComposerHelper::getComposerConfig(end($this->composerAutoloaderProjectPaths)); + if ($composer !== null) { + $requiredVersion = $composer['require']['php'] ?? null; + + if (is_string($requiredVersion)) { + $composerPhpVersion = $requiredVersion; } } } + return $composerPhpVersion; } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index aee824bfbc7..31cdfadb78d 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -21,6 +21,7 @@ use PHPStan\Internal\DirectoryCreatorException; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Parser\Parser; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; @@ -137,7 +138,8 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider } $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); - $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, null); + $composerPhpVersionFactory = $container->getByType(ComposerPhpVersionFactory::class); + $constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, null, $composerPhpVersionFactory); $initializerExprTypeResolver = new InitializerExprTypeResolver( $constantResolver, diff --git a/tests/PHPStan/Analyser/nsrt/bug-4434.php b/tests/PHPStan/Analyser/nsrt/bug-4434.php index a1f3bea0481..7b48df20fd4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4434.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4434.php @@ -10,14 +10,14 @@ class HelloWorld public function testSendEmailToLog(): void { foreach ([1] as $emailFile) { - assertType('int<5, max>', PHP_MAJOR_VERSION); - assertType('int<5, max>', \PHP_MAJOR_VERSION); + assertType('int<5, 8>', PHP_MAJOR_VERSION); + assertType('int<5, 8>', \PHP_MAJOR_VERSION); if (PHP_MAJOR_VERSION === 7) { assertType('7', PHP_MAJOR_VERSION); assertType('7', \PHP_MAJOR_VERSION); } else { - assertType('int<5, 6>|int<8, max>', PHP_MAJOR_VERSION); - assertType('int<5, 6>|int<8, max>', \PHP_MAJOR_VERSION); + assertType('8|int<5, 6>', PHP_MAJOR_VERSION); + assertType('8|int<5, 6>', \PHP_MAJOR_VERSION); } } } @@ -28,14 +28,14 @@ class HelloWorld2 public function testSendEmailToLog(): void { foreach ([1] as $emailFile) { - assertType('int<5, max>', PHP_MAJOR_VERSION); - assertType('int<5, max>', \PHP_MAJOR_VERSION); + assertType('int<5, 8>', PHP_MAJOR_VERSION); + assertType('int<5, 8>', \PHP_MAJOR_VERSION); if (PHP_MAJOR_VERSION === 100) { - assertType('100', PHP_MAJOR_VERSION); - assertType('100', \PHP_MAJOR_VERSION); + assertType('*NEVER*', PHP_MAJOR_VERSION); + assertType('*NEVER*', \PHP_MAJOR_VERSION); } else { - assertType('int<5, 99>|int<101, max>', PHP_MAJOR_VERSION); - assertType('int<5, 99>|int<101, max>', \PHP_MAJOR_VERSION); + assertType('int<5, 8>', PHP_MAJOR_VERSION); + assertType('int<5, 8>', \PHP_MAJOR_VERSION); } } } diff --git a/tests/PHPStan/Analyser/nsrt/predefined-constants.php b/tests/PHPStan/Analyser/nsrt/predefined-constants.php index 70dceda6537..9d9d1b1fc5e 100644 --- a/tests/PHPStan/Analyser/nsrt/predefined-constants.php +++ b/tests/PHPStan/Analyser/nsrt/predefined-constants.php @@ -4,10 +4,10 @@ // core, https://www.php.net/manual/en/reserved.constants.php assertType('non-falsy-string', PHP_VERSION); -assertType('int<5, max>', PHP_MAJOR_VERSION); +assertType('int<5, 8>', PHP_MAJOR_VERSION); assertType('int<0, max>', PHP_MINOR_VERSION); assertType('int<0, max>', PHP_RELEASE_VERSION); -assertType('int<50207, max>', PHP_VERSION_ID); +assertType('int<50207, 80499>', PHP_VERSION_ID); assertType('string', PHP_EXTRA_VERSION); assertType('0|1', PHP_ZTS); assertType('0|1', PHP_DEBUG); From 9717564bb5e229512113841c57950712e998353b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Nov 2024 12:56:50 +0100 Subject: [PATCH 0800/3097] Utilize phpVersion.min+max in VersionCompareFunctionDynamicReturnTypeExtension --- .github/workflows/e2e-tests.yml | 4 +++ conf/config.neon | 2 ++ .../.gitignore | 2 ++ .../composer.json | 5 ++++ .../phpstan.neon | 4 +++ .../test.php | 11 +++++++ ...pareFunctionDynamicReturnTypeExtension.php | 30 ++++++++++++++----- 7 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 e2e/composer-and-phpstan-version-config/.gitignore create mode 100644 e2e/composer-and-phpstan-version-config/composer.json create mode 100644 e2e/composer-and-phpstan-version-config/phpstan.neon create mode 100644 e2e/composer-and-phpstan-version-config/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 6d7233d38a3..96505cbf9ca 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -296,6 +296,10 @@ jobs: - script: | cd e2e/bug-11819 ../../bin/phpstan + - script: | + cd e2e/composer-and-phpstan-version-config + composer install --ignore-platform-reqs + ../../bin/phpstan analyze test.php --level=0 - script: | cd e2e/composer-max-version composer install diff --git a/conf/config.neon b/conf/config.neon index 77b59a5d3b7..739e8247dc7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1683,6 +1683,8 @@ services: - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension + arguments: + configPhpVersion: %phpVersion% tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/e2e/composer-and-phpstan-version-config/.gitignore b/e2e/composer-and-phpstan-version-config/.gitignore new file mode 100644 index 00000000000..3a9875b460f --- /dev/null +++ b/e2e/composer-and-phpstan-version-config/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/composer-and-phpstan-version-config/composer.json b/e2e/composer-and-phpstan-version-config/composer.json new file mode 100644 index 00000000000..8fe95708142 --- /dev/null +++ b/e2e/composer-and-phpstan-version-config/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^7.0" + } +} diff --git a/e2e/composer-and-phpstan-version-config/phpstan.neon b/e2e/composer-and-phpstan-version-config/phpstan.neon new file mode 100644 index 00000000000..003e5e1484e --- /dev/null +++ b/e2e/composer-and-phpstan-version-config/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + phpVersion: + min: 80103 + max: 80304 diff --git a/e2e/composer-and-phpstan-version-config/test.php b/e2e/composer-and-phpstan-version-config/test.php new file mode 100644 index 00000000000..a9afaa4b658 --- /dev/null +++ b/e2e/composer-and-phpstan-version-config/test.php @@ -0,0 +1,11 @@ +', PHP_VERSION_ID); +\PHPStan\Testing\assertType('8', PHP_MAJOR_VERSION); +\PHPStan\Testing\assertType('int<1, 3>', PHP_MINOR_VERSION); +\PHPStan\Testing\assertType('int<0, max>', PHP_RELEASE_VERSION); + +\PHPStan\Testing\assertType('1', version_compare(PHP_VERSION, '7.0.0')); +\PHPStan\Testing\assertType('false', version_compare(PHP_VERSION, '7.0.0', '<')); +\PHPStan\Testing\assertType('true', version_compare(PHP_VERSION, '7.0.0', '>')); diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index d79ebf07064..7ca5d8bac9d 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Php\ComposerPhpVersionFactory; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -16,12 +17,19 @@ use PHPStan\Type\TypeCombinator; use function array_filter; use function count; +use function is_array; use function version_compare; final class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - public function __construct(private ComposerPhpVersionFactory $composerPhpVersionFactory) + /** + * @param int|array{min: int, max: int}|null $configPhpVersion + */ + public function __construct( + private int|array|null $configPhpVersion, + private ComposerPhpVersionFactory $composerPhpVersionFactory, + ) { } @@ -93,13 +101,21 @@ private function getVersionStrings(Expr $expr, Scope $scope): array if ( $expr instanceof Expr\ConstFetch && $expr->name->toString() === 'PHP_VERSION' - && $this->composerPhpVersionFactory->getMinVersion() !== null - && $this->composerPhpVersionFactory->getMaxVersion() !== null ) { - return [ - new ConstantStringType($this->composerPhpVersionFactory->getMinVersion()->getVersionString()), - new ConstantStringType($this->composerPhpVersionFactory->getMaxVersion()->getVersionString()), - ]; + if (is_array($this->configPhpVersion)) { + $minVersion = new PhpVersion($this->configPhpVersion['min']); + $maxVersion = new PhpVersion($this->configPhpVersion['max']); + } else { + $minVersion = $this->composerPhpVersionFactory->getMinVersion(); + $maxVersion = $this->composerPhpVersionFactory->getMaxVersion(); + } + + if ($minVersion !== null && $maxVersion !== null) { + return [ + new ConstantStringType($minVersion->getVersionString()), + new ConstantStringType($maxVersion->getVersionString()), + ]; + } } return $scope->getType($expr)->getConstantStrings(); From f243636374fee78ec566f27d422bf114e1357ec3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Nov 2024 13:41:48 +0100 Subject: [PATCH 0801/3097] More details php version information in diagnose --- conf/config.neon | 1 + src/Diagnose/PHPStanDiagnoseExtension.php | 49 ++++++++++++++++++++--- src/Php/PhpVersion.php | 8 ++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 739e8247dc7..3f4414b6066 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2108,6 +2108,7 @@ services: arguments: composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% allConfigFiles: %allConfigFiles% + configPhpVersion: %phpVersion% autowired: false # Error formatters diff --git a/src/Diagnose/PHPStanDiagnoseExtension.php b/src/Diagnose/PHPStanDiagnoseExtension.php index 7c3057190c4..35cb6a862e4 100644 --- a/src/Diagnose/PHPStanDiagnoseExtension.php +++ b/src/Diagnose/PHPStanDiagnoseExtension.php @@ -7,6 +7,7 @@ use PHPStan\ExtensionInstaller\GeneratedConfig; use PHPStan\File\FileHelper; use PHPStan\Internal\ComposerHelper; +use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Php\PhpVersion; use ReflectionClass; use function array_key_exists; @@ -17,6 +18,7 @@ use function explode; use function implode; use function in_array; +use function is_array; use function is_file; use function is_readable; use function sprintf; @@ -29,14 +31,17 @@ final class PHPStanDiagnoseExtension implements DiagnoseExtension { /** + * @param int|array{min: int, max: int}|null $configPhpVersion * @param string[] $composerAutoloaderProjectPaths * @param string [] $allConfigFiles */ public function __construct( private PhpVersion $phpVersion, + private int|array|null $configPhpVersion, private FileHelper $fileHelper, private array $composerAutoloaderProjectPaths, private array $allConfigFiles, + private ComposerPhpVersionFactory $composerPhpVersionFactory, ) { } @@ -48,11 +53,45 @@ public function print(Output $output): void 'PHP runtime version: %s', $phpRuntimeVersion->getVersionString(), )); - $output->writeLineFormatted(sprintf( - 'PHP version for analysis: %s (from %s)', - $this->phpVersion->getVersionString(), - $this->phpVersion->getSourceLabel(), - )); + + if ( + $this->phpVersion->getSource() === PhpVersion::SOURCE_CONFIG + && is_array($this->configPhpVersion) + ) { + $minVersion = new PhpVersion($this->configPhpVersion['min']); + $maxVersion = new PhpVersion($this->configPhpVersion['max']); + + $output->writeLineFormatted(sprintf( + 'PHP version for analysis: %s-%s (from %s)', + $minVersion->getVersionString(), + $maxVersion->getVersionString(), + $this->phpVersion->getSourceLabel(), + )); + + } else { + $minComposerPhpVersion = $this->composerPhpVersionFactory->getMinVersion(); + $maxComposerPhpVersion = $this->composerPhpVersionFactory->getMaxVersion(); + if ($minComposerPhpVersion !== null && $maxComposerPhpVersion !== null) { + if ($minComposerPhpVersion->getVersionId() !== $maxComposerPhpVersion->getVersionId()) { + $output->writeLineFormatted(sprintf( + 'PHP composer.json required version: %s-%s', + $minComposerPhpVersion->getVersionString(), + $maxComposerPhpVersion->getVersionString(), + )); + } else { + $output->writeLineFormatted(sprintf( + 'PHP composer.json required version: %s', + $minComposerPhpVersion->getVersionString(), + )); + } + } + + $output->writeLineFormatted(sprintf( + 'PHP version for analysis: %s (from %s)', + $this->phpVersion->getVersionString(), + $this->phpVersion->getSourceLabel(), + )); + } $output->writeLineFormatted(''); $output->writeLineFormatted(sprintf( diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 83ca1245b5f..8520f6488d9 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -22,6 +22,14 @@ public function __construct(private int $versionId, private int $source = self:: { } + /** + * @return self::SOURCE_* + */ + public function getSource(): int + { + return $this->source; + } + public function getSourceLabel(): string { switch ($this->source) { From ceac3098a70f94cc99bc6574a7ce9cb96e9981aa Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 13 Nov 2024 14:11:48 +0100 Subject: [PATCH 0802/3097] Fix extract signature --- resources/functionMap.php | 4 ++-- resources/functionMap_bleedingEdge.php | 2 +- resources/functionMap_php74delta.php | 20 +++++++++---------- tests/PHPStan/Analyser/nsrt/extract.php | 18 +++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 5 +++++ .../Rules/Functions/data/bug-11759.php | 5 +++++ 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11759.php diff --git a/resources/functionMap.php b/resources/functionMap.php index b678f48e5bf..46492450d99 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -286,7 +286,7 @@ 'array_map' => ['array', 'callback'=>'?callable', 'array'=>'array', '...args='=>'array'], 'array_merge' => ['array', 'arr1'=>'array', '...args='=>'array'], 'array_merge_recursive' => ['array', 'arr1'=>'array', '...args='=>'array'], -'array_multisort' => ['bool', '&rw_array1'=>'array', 'array1_sort_order='=>'array|int', 'array1_sort_flags='=>'array|int', '...args='=>'array|int'], +'array_multisort' => ['bool', 'array1'=>'array', 'array1_sort_order='=>'array|int', 'array1_sort_flags='=>'array|int', '...args='=>'array|int'], 'array_pad' => ['array', 'input'=>'array', 'pad_size'=>'int', 'pad_value'=>'mixed'], 'array_pop' => ['mixed', '&rw_stack'=>'array'], 'array_product' => ['int|float', 'input'=>'array'], @@ -2637,7 +2637,7 @@ 'explode' => ['list|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], 'expm1' => ['float', 'number'=>'float'], 'extension_loaded' => ['bool', 'extension_name'=>'string'], -'extract' => ['0|positive-int', '&rw_var_array'=>'array', 'extract_type='=>'int', 'prefix='=>'string|null'], +'extract' => ['0|positive-int', 'array'=>'array', 'flags='=>'int', 'prefix='=>'string|null'], 'ezmlm_hash' => ['int', 'addr'=>'string'], 'fam_cancel_monitor' => ['bool', 'fam'=>'resource', 'fam_monitor'=>'resource'], 'fam_close' => ['void', 'fam'=>'resource'], diff --git a/resources/functionMap_bleedingEdge.php b/resources/functionMap_bleedingEdge.php index 11dd4fa7730..9d26a518b6a 100644 --- a/resources/functionMap_bleedingEdge.php +++ b/resources/functionMap_bleedingEdge.php @@ -146,7 +146,7 @@ 'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING| SCANDIR_SORT_NONE', 'context='=>'resource'], 'stream_socket_client' => ['resource|false', 'remoteaddress'=>'string', '&w_errcode='=>'int', '&w_errstring='=>'string', 'timeout='=>'float', 'flags='=>'int-mask', 'context='=>'resource'], 'stream_socket_enable_crypto' => ['0|bool', 'stream'=>'resource', 'enable'=>'bool', 'crypto_method='=>'STREAM_CRYPTO_METHOD_SSLv2_CLIENT|STREAM_CRYPTO_METHOD_SSLv3_CLIENT|STREAM_CRYPTO_METHOD_SSLv23_CLIENT|STREAM_CRYPTO_METHOD_ANY_CLIENT|STREAM_CRYPTO_METHOD_TLS_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT|STREAM_CRYPTO_METHOD_SSLv2_SERVER|STREAM_CRYPTO_METHOD_SSLv3_SERVER|STREAM_CRYPTO_METHOD_SSLv23_SERVER|STREAM_CRYPTO_METHOD_ANY_SERVER|STREAM_CRYPTO_METHOD_TLS_SERVER|STREAM_CRYPTO_METHOD_TLSv1_0_SERVER|STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER|STREAM_CRYPTO_METHOD_TLSv1_3_SERVER', 'session_stream='=>'resource'], - 'extract' => ['0|positive-int', '&rw_var_array'=>'array', 'extract_type='=>'EXTR_OVERWRITE|EXTR_SKIP|EXTR_PREFIX_SAME|EXTR_PREFIX_ALL|EXTR_PREFIX_INVALID|EXTR_IF_EXISTS|EXTR_PREFIX_IF_EXISTS|EXTR_REFS', 'prefix='=>'string|null'], + 'extract' => ['0|positive-int', 'array'=>'array', 'flags='=>'EXTR_OVERWRITE|EXTR_SKIP|EXTR_PREFIX_SAME|EXTR_PREFIX_ALL|EXTR_PREFIX_INVALID|EXTR_IF_EXISTS|EXTR_PREFIX_IF_EXISTS|EXTR_REFS', 'prefix='=>'string|null'], 'RecursiveIteratorIterator::__construct' => ['void', 'iterator'=>'RecursiveIterator|IteratorAggregate', 'mode='=>'RecursiveIteratorIterator::LEAVES_ONLY|RecursiveIteratorIterator::SELF_FIRST|RecursiveIteratorIterator::CHILD_FIRST', 'flags='=>'0|RecursiveIteratorIterator::CATCH_GET_CHILD'], 'Locale::composeLocale' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], 'locale_compose' => ['string|false', 'subtags'=>'array{language:string, script?:string, region?:string, variant?:array, private?:array, extlang?:array, variant0?:string, variant1?:string, variant2?:string, variant3?:string, variant4?:string, variant5?:string, variant6?:string, variant7?:string, variant8?:string, variant9?:string, variant10?:string, variant11?:string, variant12?:string, variant13?:string, variant14?:string, private0?:string, private1?:string, private2?:string, private3?:string, private4?:string, private5?:string, private6?:string, private7?:string, private8?:string, private9?:string, private10?:string, private11?:string, private12?:string, private13?:string, private14?:string, extlang0?:string, extlang1?:string, extlang2?:string}'], diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index b37fe5a7a5a..bb05fe2b6a2 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -22,21 +22,21 @@ */ return [ 'new' => [ - 'FFI::addr' => ['FFI\CData', '&ptr'=>'FFI\CData'], - 'FFI::alignof' => ['int', '&ptr'=>'mixed'], + 'FFI::addr' => ['FFI\CData', 'ptr'=>'FFI\CData'], + 'FFI::alignof' => ['int', 'ptr'=>'mixed'], 'FFI::arrayType' => ['FFI\CType', 'type'=>'string|FFI\CType', 'dims'=>'array'], - 'FFI::cast' => ['FFI\CData', 'type'=>'string|FFI\CType', '&ptr'=>''], + 'FFI::cast' => ['FFI\CData', 'type'=>'string|FFI\CType', 'ptr'=>''], 'FFI::cdef' => ['FFI', 'code='=>'string', 'lib='=>'?string'], - 'FFI::free' => ['void', '&ptr'=>'FFI\CData'], + 'FFI::free' => ['void', 'ptr'=>'FFI\CData'], 'FFI::load' => ['FFI', 'filename'=>'string'], - 'FFI::memcmp' => ['int', '&ptr1'=>'FFI\CData|string', '&ptr2'=>'FFI\CData|string', 'size'=>'int'], - 'FFI::memcpy' => ['void', '&dst'=>'FFI\CData', '&src'=>'string|FFI\CData', 'size'=>'int'], - 'FFI::memset' => ['void', '&ptr'=>'FFI\CData', 'ch'=>'int', 'size'=>'int'], + 'FFI::memcmp' => ['int', 'ptr1'=>'FFI\CData|string', 'ptr2'=>'FFI\CData|string', 'size'=>'int'], + 'FFI::memcpy' => ['void', 'dst'=>'FFI\CData', 'src'=>'string|FFI\CData', 'size'=>'int'], + 'FFI::memset' => ['void', 'ptr'=>'FFI\CData', 'ch'=>'int', 'size'=>'int'], 'FFI::new' => ['FFI\CData', 'type'=>'string|FFI\CType', 'owned='=>'bool', 'persistent='=>'bool'], 'FFI::scope' => ['FFI', 'scope_name'=>'string'], - 'FFI::sizeof' => ['int', '&ptr'=>'FFI\CData|FFI\CType'], - 'FFI::string' => ['string', '&ptr'=>'FFI\CData', 'size='=>'int'], - 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], + 'FFI::sizeof' => ['int', 'ptr'=>'FFI\CData|FFI\CType'], + 'FFI::string' => ['string', 'ptr'=>'FFI\CData', 'size='=>'int'], + 'FFI::typeof' => ['FFI\CType', 'ptr'=>'FFI\CData'], 'FFI::type' => ['FFI\CType', 'type'=>'string'], 'fread' => ['string|false', 'fp'=>'resource', 'length'=>'positive-int'], 'get_mangled_object_vars' => ['array', 'obj'=>'object'], diff --git a/tests/PHPStan/Analyser/nsrt/extract.php b/tests/PHPStan/Analyser/nsrt/extract.php index c57f2ef46d8..dff42bc4823 100644 --- a/tests/PHPStan/Analyser/nsrt/extract.php +++ b/tests/PHPStan/Analyser/nsrt/extract.php @@ -66,3 +66,21 @@ function doTyped3(array $vars): void assertVariableCertainty(TrinaryLogic::createNo(), $none); } + +function doTyped4(): void +{ + extract(['foo' => 42]); + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertType('42', $foo); +} + + +function doTyped5(): void +{ + $foo = ['foo' => 42]; + extract($foo); + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + assertType('42', $foo); +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 123fa4259f5..c105b2fe64f 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1898,4 +1898,9 @@ public function testBug9224(): void $this->analyse([__DIR__ . '/data/bug-9224.php'], []); } + public function testBug11759(): void + { + $this->analyse([__DIR__ . '/data/bug-11759.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11759.php b/tests/PHPStan/Rules/Functions/data/bug-11759.php new file mode 100644 index 00000000000..1a6cb12c7dd --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11759.php @@ -0,0 +1,5 @@ + 42 ]); From b7440412395a85e2265fefe46d303be587de24df Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 13 Nov 2024 17:24:13 +0100 Subject: [PATCH 0803/3097] Update fidry/cpu-core-counter --- composer.json | 2 +- composer.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 2614dcf5840..9aac57806fd 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "composer/ca-bundle": "^1.2", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", - "fidry/cpu-core-counter": "^0.5.0", + "fidry/cpu-core-counter": "^1.2", "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", diff --git a/composer.lock b/composer.lock index c0f104e2b7a..7a215f2f76f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "674f9ec5e66603e465b9ef2aaa90189a", + "content-hash": "07b46dce51ea41c025511b9415b3274c", "packages": [ { "name": "clue/ndjson-react", @@ -421,16 +421,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "0.5.1", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -438,13 +438,13 @@ }, "require-dev": { "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", "phpstan/phpstan": "^1.9.2", "phpstan/phpstan-deprecation-rules": "^1.0.0", "phpstan/phpstan-phpunit": "^1.2.2", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -470,7 +470,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -478,7 +478,7 @@ "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "fig/http-message-util", From 79385bd09325dd8a3f7b28c770cd781bd8fc61e7 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 14 Nov 2024 23:16:22 +0100 Subject: [PATCH 0804/3097] feat: add TypeCombinator::removeTruthy method --- src/Type/TypeCombinator.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index ccbb29f97b9..03ddffd988b 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -1341,4 +1341,9 @@ public static function removeFalsey(Type $type): Type return self::remove($type, StaticTypeFactory::falsey()); } + public static function removeTruthy(Type $type): Type + { + return self::remove($type, StaticTypeFactory::truthy()); + } + } From f83b559d56a1950e7db6f5827de29a91b568e5c3 Mon Sep 17 00:00:00 2001 From: Giovanni Giacobbi Date: Fri, 15 Nov 2024 12:09:53 +0100 Subject: [PATCH 0805/3097] Improve signature for get_defined_constants() --- stubs/core.stub | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stubs/core.stub b/stubs/core.stub index 853222642c7..abd719b4f52 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -328,3 +328,8 @@ function ltrim(string $string, string $characters = " \n\r\t\v\x00"): string {} * @return ($string is lowercase-string ? lowercase-string : string) */ function rtrim(string $string, string $characters = " \n\r\t\v\x00"): string {} + +/** + * @return ($categorize is true ? array> : array) + */ +function get_defined_constants(bool $categorize = false): array {} From 5e3a3643a1de9000c585aa11fca528492656d8e7 Mon Sep 17 00:00:00 2001 From: Giovanni Giacobbi Date: Sat, 16 Nov 2024 11:59:01 +0100 Subject: [PATCH 0806/3097] Add basic type narrowing for `$a != ''` --- src/Analyser/TypeSpecifier.php | 93 ++++++++- tests/PHPStan/Analyser/TypeSpecifierTest.php | 4 +- tests/PHPStan/Analyser/nsrt/equal-narrow.php | 180 ++++++++++++++++++ .../Analyser/nsrt/integer-range-types.php | 2 +- tests/PHPStan/Analyser/nsrt/narrow-cast.php | 2 +- .../Analyser/nsrt/non-empty-string.php | 4 +- .../ElseIfConstantConditionRuleTest.php | 4 + .../Rules/Comparison/data/bug-11674.php | 8 +- 8 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/equal-narrow.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 68864c18b6c..088236bac5c 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -43,6 +43,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; @@ -1610,7 +1611,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy } /** - * @return array{Expr, ConstantScalarType}|null + * @return array{Expr, ConstantScalarType, Type}|null */ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array { @@ -1632,13 +1633,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ && !$rightExpr instanceof ConstFetch && !$rightExpr instanceof ClassConstFetch ) { - return [$binaryOperation->right, $leftType]; + return [$binaryOperation->right, $leftType, $rightType]; } elseif ( $rightType instanceof ConstantScalarType && !$leftExpr instanceof ConstFetch && !$leftExpr instanceof ClassConstFetch ) { - return [$binaryOperation->left, $rightType]; + return [$binaryOperation->left, $rightType, $leftType]; } return null; @@ -1949,7 +1950,21 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif if ($expressions !== null) { $exprNode = $expressions[0]; $constantType = $expressions[1]; - if (!$context->null() && ($constantType->getValue() === false || $constantType->getValue() === null)) { + $otherType = $expressions[2]; + + if (!$context->null() && $constantType->getValue() === null) { + $trueTypes = [ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new ConstantStringType(''), + new ConstantArrayType([], []), + ]; + return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + } + + if (!$context->null() && $constantType->getValue() === false) { return $this->specifyTypesInCondition( $scope, $exprNode, @@ -1967,6 +1982,52 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif ); } + if (!$context->null() && $constantType->getValue() === 0 && !$otherType->isInteger()->yes() && !$otherType->isBoolean()->yes()) { + /* There is a difference between php 7.x and 8.x on the equality + * behavior between zero and the empty string, so to be conservative + * we leave it untouched regardless of the language version */ + if ($context->true()) { + $trueTypes = [ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new StringType(), + ]; + } else { + $trueTypes = [ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new ConstantStringType('0'), + ]; + } + return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + } + + if (!$context->null() && $constantType->getValue() === '') { + /* There is a difference between php 7.x and 8.x on the equality + * behavior between zero and the empty string, so to be conservative + * we leave it untouched regardless of the language version */ + if ($context->true()) { + $trueTypes = [ + new NullType(), + new ConstantBooleanType(false), + new ConstantIntegerType(0), + new ConstantFloatType(0.0), + new ConstantStringType(''), + ]; + } else { + $trueTypes = [ + new NullType(), + new ConstantBooleanType(false), + new ConstantStringType(''), + ]; + } + return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + } + if ( $exprNode instanceof FuncCall && $exprNode->name instanceof Name @@ -2060,11 +2121,13 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context, ?Expr $rootExpr): SpecifiedTypes { + // Normalize to: fn() === expr $leftExpr = $expr->left; $rightExpr = $expr->right; if ($rightExpr instanceof FuncCall && !$leftExpr instanceof FuncCall) { [$leftExpr, $rightExpr] = [$rightExpr, $leftExpr]; } + $unwrappedLeftExpr = $leftExpr; if ($leftExpr instanceof AlwaysRememberedExpr) { $unwrappedLeftExpr = $leftExpr->getExpr(); @@ -2073,8 +2136,10 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($rightExpr instanceof AlwaysRememberedExpr) { $unwrappedRightExpr = $rightExpr->getExpr(); } + $rightType = $scope->getType($rightExpr); + // (count($a) === $b) if ( !$context->null() && $unwrappedLeftExpr instanceof FuncCall @@ -2139,6 +2204,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + // strlen($a) === $b if ( !$context->null() && $unwrappedLeftExpr instanceof FuncCall @@ -2175,6 +2241,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + // preg_match($a) === $b if ( $context->true() && $unwrappedLeftExpr instanceof FuncCall @@ -2190,6 +2257,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty ); } + // get_class($a) === 'Foo' if ( $context->true() && $unwrappedLeftExpr instanceof FuncCall @@ -2209,6 +2277,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + // get_class($a) === 'Foo' if ( $context->truthy() && $unwrappedLeftExpr instanceof FuncCall @@ -2289,6 +2358,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } + // $a::class === 'Foo' if ( $context->true() && $unwrappedLeftExpr instanceof ClassConstFetch && @@ -2311,6 +2381,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } $leftType = $scope->getType($leftExpr); + + // 'Foo' === $a::class if ( $context->true() && $unwrappedRightExpr instanceof ClassConstFetch && @@ -2356,7 +2428,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $types = null; if ( count($leftType->getFiniteTypes()) === 1 - || ($context->true() && $leftType->isConstantValue()->yes() && !$rightType->equals($leftType) && $rightType->isSuperTypeOf($leftType)->yes()) + || ( + $context->true() + && $leftType->isConstantValue()->yes() + && !$rightType->equals($leftType) + && $rightType->isSuperTypeOf($leftType)->yes()) ) { $types = $this->create( $rightExpr, @@ -2379,7 +2455,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } if ( count($rightType->getFiniteTypes()) === 1 - || ($context->true() && $rightType->isConstantValue()->yes() && !$leftType->equals($rightType) && $leftType->isSuperTypeOf($rightType)->yes()) + || ( + $context->true() + && $rightType->isConstantValue()->yes() + && !$leftType->equals($rightType) + && $leftType->isSuperTypeOf($rightType)->yes() + ) ) { $leftTypes = $this->create( $leftExpr, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index ced1959085a..999c7169a39 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -477,8 +477,8 @@ public function dataCondition(): iterable new Variable('foo'), new Expr\ConstFetch(new Name('null')), ), - ['$foo' => self::SURE_NOT_TRUTHY], - ['$foo' => self::SURE_NOT_FALSEY], + ['$foo' => '0|0.0|\'\'|array{}|false|null'], + ['$foo' => '~0|0.0|\'\'|array{}|false|null'], ], [ new Expr\BinaryOp\Identical( diff --git a/tests/PHPStan/Analyser/nsrt/equal-narrow.php b/tests/PHPStan/Analyser/nsrt/equal-narrow.php new file mode 100644 index 00000000000..774377f400f --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/equal-narrow.php @@ -0,0 +1,180 @@ +|int<1, max>|non-empty-string", $y); + } + + if ($z == null) { + assertType("0|0.0|''|array{}|false|null", $z); + } else { + assertType("mixed~(0|0.0|''|array{}|false|null)", $z); + } +} + +/** + * @param 0|0.0|1|''|'0'|'x'|array{}|bool|object|null $x + * @param int|string|null $y + * @param mixed $z + */ +function doFalse($x, $y, $z): void +{ + if ($x == false) { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } else { + assertType("1|'x'|object|true", $x); + } + if (false != $x) { + assertType("1|'x'|object|true", $x); + } else { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } + + if (!$x) { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } else { + assertType("1|'x'|object|true", $x); + } + + if ($y == false) { + assertType("0|''|'0'|null", $y); + } else { + assertType("int|int<1, max>|non-falsy-string", $y); + } + + if ($z == false) { + assertType("0|0.0|''|'0'|array{}|false|null", $z); + } else { + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $z); + } +} + +/** + * @param 0|0.0|1|''|'0'|'x'|array{}|bool|object|null $x + * @param int|string|null $y + * @param mixed $z + */ +function doTrue($x, $y, $z): void +{ + if ($x == true) { + assertType("1|'x'|object|true", $x); + } else { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } + if (true != $x) { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } else { + assertType("1|'x'|object|true", $x); + } + + if ($x) { + assertType("1|'x'|object|true", $x); + } else { + assertType("0|0.0|''|'0'|array{}|false|null", $x); + } + + if ($y == true) { + assertType("int|int<1, max>|non-falsy-string", $y); + } else { + assertType("0|''|'0'|null", $y); + } + + if ($z == true) { + assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $z); + } else { + assertType("0|0.0|''|'0'|array{}|false|null", $z); + } +} + +/** + * @param 0|0.0|1|''|'0'|'x'|array{}|bool|object|null $x + * @param int|string|null $y + * @param mixed $z + */ +function doZero($x, $y, $z): void +{ + // PHP 7.x/8.x compatibility: Keep zero in both cases + if ($x == 0) { + assertType("0|0.0|''|'0'|'x'|false|null", $x); + } else { + assertType("1|''|'x'|array{}|object|true", $x); + } + if (0 != $x) { + assertType("1|''|'x'|array{}|object|true", $x); + } else { + assertType("0|0.0|''|'0'|'x'|false|null", $x); + } + + if ($y == 0) { + assertType("0|string|null", $y); + } else { + assertType("int|int<1, max>|string", $y); + } + + if ($z == 0) { + assertType("0|0.0|string|false|null", $z); + } else { + assertType("mixed~(0|0.0|'0'|false|null)", $z); + } +} + +/** + * @param 0|0.0|1|''|'0'|'x'|array{}|bool|object|null $x + * @param int|string|null $y + * @param mixed $z + */ +function doEmptyString($x, $y, $z): void +{ + // PHP 7.x/8.x compatibility: Keep zero in both cases + if ($x == '') { + assertType("0|0.0|''|false|null", $x); + } else { + assertType("0|0.0|1|'0'|'x'|array{}|object|true", $x); + } + if ('' != $x) { + assertType("0|0.0|1|'0'|'x'|array{}|object|true", $x); + } else { + assertType("0|0.0|''|false|null", $x); + } + + if ($y == '') { + assertType("0|''|null", $y); + } else { + assertType("int|non-empty-string", $y); + } + + if ($z == '') { + assertType("0|0.0|''|false|null", $z); + } else { + assertType("mixed~(''|false|null)", $z); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/integer-range-types.php b/tests/PHPStan/Analyser/nsrt/integer-range-types.php index cd35c779530..876a7913524 100644 --- a/tests/PHPStan/Analyser/nsrt/integer-range-types.php +++ b/tests/PHPStan/Analyser/nsrt/integer-range-types.php @@ -449,7 +449,7 @@ public function zeroIssues($positive, $negative) function subtract($m) { if ($m != 0) { - assertType("mixed", $m); // could be "mixed~0|0.0|''|'0'|array{}|false|null" + assertType("mixed~(0|0.0|'0'|false|null)", $m); // could be "mixed~(0|0.0|''|'0'|false|null)" assertType('int', (int) $m); } if ($m !== 0) { diff --git a/tests/PHPStan/Analyser/nsrt/narrow-cast.php b/tests/PHPStan/Analyser/nsrt/narrow-cast.php index 82e09e0bd3f..1da88584733 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-cast.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-cast.php @@ -33,7 +33,7 @@ function castString($x, string $s, bool $b) { if ((string) $x) { assertType('int<-5, 5>', $x); } else { - assertType('int<-5, 5>', $x); + assertType('0', $x); } if ((string) $b) { diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index da8426c9057..5400b1d31a3 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -422,8 +422,8 @@ function subtract($m) { assertType('non-falsy-string', (string) $m); } if ($m != '') { - assertType("mixed", $m); - assertType('string', (string) $m); + assertType("mixed~(''|false|null)", $m); + assertType('non-empty-string', (string) $m); } if ($m !== '') { assertType("mixed~''", $m); diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index bf671466842..6c510333096 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -135,6 +135,10 @@ public function testBug11674(): void 'Elseif condition is always false.', 28, ], + [ + 'Elseif condition is always false.', + 36, + ], ]); } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11674.php b/tests/PHPStan/Rules/Comparison/data/bug-11674.php index 7af6660da4d..6156b8e1cb8 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-11674.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-11674.php @@ -10,7 +10,7 @@ function show() : void { if ((int) $this->param) { echo 1; } elseif ($this->param) { - echo 2; + echo 2; // might be "0" } } @@ -18,7 +18,7 @@ function show2() : void { if ((float) $this->param) { echo 1; } elseif ($this->param) { - echo 2; + echo 2; // might be "0" } } @@ -26,7 +26,7 @@ function show3() : void { if ((bool) $this->param) { echo 1; } elseif ($this->param) { - echo 2; + echo 2; // not possible } } @@ -34,7 +34,7 @@ function show4() : void { if ((string) $this->param) { echo 1; } elseif ($this->param) { - echo 2; + echo 2; // not possible } } } From 65ddbcb6fe3d248076c49af0d1532d0dc4f75460 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 16 Nov 2024 12:02:35 +0100 Subject: [PATCH 0807/3097] Fix after merge --- src/Analyser/TypeSpecifier.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6628c7bdb19..17032ec8146 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1903,7 +1903,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif new ConstantStringType(''), new ConstantArrayType([], []), ]; - return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + return $this->create($exprNode, new UnionType($trueTypes), $context, $scope)->setRootExpr($expr); } if (!$context->null() && $constantType->getValue() === false) { @@ -1943,7 +1943,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif new ConstantStringType('0'), ]; } - return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + return $this->create($exprNode, new UnionType($trueTypes), $context, $scope)->setRootExpr($expr); } if (!$context->null() && $constantType->getValue() === '') { @@ -1965,7 +1965,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif new ConstantStringType(''), ]; } - return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr); + return $this->create($exprNode, new UnionType($trueTypes), $context, $scope)->setRootExpr($expr); } if ( From 25a06dd34e23c454e5764bfa17d2ce9d9dbd1250 Mon Sep 17 00:00:00 2001 From: Giovanni Giacobbi Date: Sat, 16 Nov 2024 16:54:39 +0100 Subject: [PATCH 0808/3097] Use the correct type for final constants --- src/Reflection/ClassConstantReflection.php | 3 ++- src/Reflection/ClassReflection.php | 2 ++ src/Reflection/InitializerExprTypeResolver.php | 1 + tests/PHPStan/Analyser/nsrt/class-constant-types.php | 7 +++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index f54fc37ba42..00874b90810 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -28,6 +28,7 @@ public function __construct( private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, + private bool $isFinal, ) { } @@ -124,7 +125,7 @@ public function isPublic(): bool public function isFinal(): bool { - return $this->reflection->isFinal(); + return $this->isFinal || $this->reflection->isFinal(); } public function isDeprecated(): TrinaryLogic diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index ff45172fd8f..02705f32cd0 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1080,6 +1080,7 @@ public function getConstant(string $name): ClassConstantReflection $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; $isDeprecated = $resolvedPhpDoc->isDeprecated(); $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); $varTags = $resolvedPhpDoc->getVarTags(); if (isset($varTags[0]) && count($varTags) === 1) { $phpDocType = $varTags[0]->getType(); @@ -1101,6 +1102,7 @@ public function getConstant(string $name): ClassConstantReflection $deprecatedDescription, $isDeprecated, $isInternal, + $isFinal, ); } return $this->constants[$name]; diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 178c16b4a81..8cedcd12723 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1987,6 +1987,7 @@ function (Type $type, callable $traverse): Type { $constantReflection = $constantClassReflection->getConstant($constantName); if ( !$constantClassReflection->isFinal() + && !$constantReflection->isFinal() && !$constantReflection->hasPhpDocType() && !$constantReflection->hasNativeType() ) { diff --git a/tests/PHPStan/Analyser/nsrt/class-constant-types.php b/tests/PHPStan/Analyser/nsrt/class-constant-types.php index 9d60af25c55..8f19f7659ef 100644 --- a/tests/PHPStan/Analyser/nsrt/class-constant-types.php +++ b/tests/PHPStan/Analyser/nsrt/class-constant-types.php @@ -15,6 +15,9 @@ class Foo /** @var string */ private const PRIVATE_TYPE = 'foo'; + /** @final */ + const FINAL_TYPE = 'zoo'; + public function doFoo() { assertType('1', self::NO_TYPE); @@ -28,6 +31,10 @@ public function doFoo() assertType('\'foo\'', self::PRIVATE_TYPE); assertType('string', static::PRIVATE_TYPE); assertType('string', $this::PRIVATE_TYPE); + + assertType('\'zoo\'', self::FINAL_TYPE); + assertType('\'zoo\'', static::FINAL_TYPE); + assertType('\'zoo\'', $this::FINAL_TYPE); } } From d9d93b7e11a2f919f79824baab499a1a76952ab5 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 16 Nov 2024 12:49:01 +0100 Subject: [PATCH 0809/3097] Simplified ArrayType with Mixed --- src/Type/ArrayType.php | 5 +++++ .../CallToFunctionParametersRuleTest.php | 11 ++++++++++- tests/PHPStan/Rules/Functions/data/bug-12051.php | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-12051.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 01ca997eeea..c3fc5050b19 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -21,6 +21,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\TemplateMixedType; +use PHPStan\Type\Generic\TemplateStrictMixedType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -50,6 +51,10 @@ public function __construct(Type $keyType, private Type $itemType) if ($keyType->describe(VerbosityLevel::value()) === '(int|string)') { $keyType = new MixedType(); } + if ($keyType instanceof StrictMixedType && !$keyType instanceof TemplateStrictMixedType) { + $keyType = new UnionType([new StringType(), new IntegerType()]); + } + $this->keyType = $keyType; } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c105b2fe64f..768d75551fb 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -21,12 +21,14 @@ class CallToFunctionParametersRuleTest extends RuleTestCase private bool $checkExplicitMixed = false; + private bool $checkImplicitMixed = false; + protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true), ); } @@ -1903,4 +1905,11 @@ public function testBug11759(): void $this->analyse([__DIR__ . '/data/bug-11759.php'], []); } + public function testBug12051(): void + { + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12051.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12051.php b/tests/PHPStan/Rules/Functions/data/bug-12051.php new file mode 100644 index 00000000000..d271c7bf525 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12051.php @@ -0,0 +1,15 @@ + $a */ +function foo($a): void { + print "ok\n"; +} + +/** + * @param array $a + */ +function bar($a): void { + foo($a); +} From 09f7c00bc60fc80eab808e876c422d08d87aeff2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 14 May 2024 14:29:13 +0200 Subject: [PATCH 0810/3097] `fgetcsv` accepts `null` for `$length` --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 46492450d99..eda76bc7977 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2932,7 +2932,7 @@ 'ffmpeg_movie::hasAudio' => ['bool'], 'ffmpeg_movie::hasVideo' => ['bool'], 'fgetc' => ['string|false', 'fp'=>'resource'], -'fgetcsv' => ['list|array{0: null}|false|null', 'fp'=>'resource', 'length='=>'0|positive-int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'fgetcsv' => ['list|array{0: null}|false|null', 'fp'=>'resource', 'length='=>'0|positive-int|null', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int'], 'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int', 'allowable_tags='=>'string'], 'file' => ['list|false', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'], From 30d20e6ee2dd93206aed2e26187aba0fc679d5c9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 17 Nov 2024 16:35:34 +0100 Subject: [PATCH 0811/3097] FunctionCallParametersCheck: Add native parameter type --- src/Rules/FunctionCallParametersCheck.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 9704d5e82e2..130dd0bef88 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -53,7 +53,6 @@ public function __construct( } /** - * @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall * @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType * @return list */ @@ -61,7 +60,7 @@ public function check( ParametersAcceptor $parametersAcceptor, Scope $scope, bool $isBuiltin, - $funcCall, + Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall, string $nodeType, TrinaryLogic $acceptsNamedArguments, string $singleInsufficientParameterMessage, From a2eddcc71a01d00888069dd85358a091070639f1 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:03:40 +0000 Subject: [PATCH 0812/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 55b58e1a56c..15e25f2a803 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#893a3bc59b8ff1b995220abcbbf76796926b7e9a", + "jetbrains/phpstorm-stubs": "dev-master#fda684a4826c1caf59efe1a1bf68d08c11aaddbf", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index cda1b9fb41b..8ee3e675ede 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b0700d67568e34950c8009d83dee8a3c", + "content-hash": "ca308caf852aa82ffe555d1235c75efa", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "893a3bc59b8ff1b995220abcbbf76796926b7e9a" + "reference": "fda684a4826c1caf59efe1a1bf68d08c11aaddbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/893a3bc59b8ff1b995220abcbbf76796926b7e9a", - "reference": "893a3bc59b8ff1b995220abcbbf76796926b7e9a", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/fda684a4826c1caf59efe1a1bf68d08c11aaddbf", + "reference": "fda684a4826c1caf59efe1a1bf68d08c11aaddbf", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-10T16:24:24+00:00" + "time": "2024-11-15T09:42:33+00:00" }, { "name": "nette/bootstrap", From b049d8d7d7e25df00c80b1ec585beb2dd2896123 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 19 Nov 2024 10:45:21 +0100 Subject: [PATCH 0813/3097] `Closure::bind` and `bindTo` return benevolent union with null --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index eda76bc7977..620138e0bc2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -994,8 +994,8 @@ 'closelog' => ['bool'], 'Closure::__construct' => ['void'], 'Closure::__invoke' => ['', '...args='=>''], -'Closure::bind' => ['Closure', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|string|null'], -'Closure::bindTo' => ['Closure', 'new'=>'?object', 'newscope='=>'object|string|null'], +'Closure::bind' => ['__benevolent', 'old'=>'Closure', 'to'=>'?object', 'scope='=>'object|string|null'], +'Closure::bindTo' => ['__benevolent', 'new'=>'?object', 'newscope='=>'object|string|null'], 'Closure::call' => ['', 'to'=>'object', '...parameters='=>''], 'Closure::fromCallable' => ['Closure', 'callable'=>'callable'], 'clusterObj::convertToString' => ['string'], From 4a6565e140ddbc8965fca52159e8ef314cd482d5 Mon Sep 17 00:00:00 2001 From: Sven Reichel Date: Mon, 18 Nov 2024 11:57:27 +0100 Subject: [PATCH 0814/3097] 3rd parameter of htmlentities|htmlspecialchars allows null --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 05268af81ea..baaef68a8cb 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3957,8 +3957,8 @@ 'HRTime\StopWatch::start' => ['void'], 'HRTime\StopWatch::stop' => ['void'], 'html_entity_decode' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string'], -'htmlentities' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'], -'htmlspecialchars' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string', 'double_encode='=>'bool'], +'htmlentities' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string|null', 'double_encode='=>'bool'], +'htmlspecialchars' => ['string', 'string'=>'string', 'quote_style='=>'int', 'encoding='=>'string|null', 'double_encode='=>'bool'], 'htmlspecialchars_decode' => ['string', 'string'=>'string', 'quote_style='=>'int'], 'http\Env\Request::__construct' => ['void'], 'http\Env\Request::getCookie' => ['mixed', 'name='=>'string', 'type='=>'mixed', 'defval='=>'mixed', 'delete='=>'bool|false'], From 5b77fa68d623dd89408171615f50352fd7d19ff0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 19 Nov 2024 10:56:22 +0100 Subject: [PATCH 0815/3097] Fix test --- .../Reflection/SignatureMap/Php8SignatureMapProviderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index 41d62d1c36f..9302b20f69a 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -206,7 +206,7 @@ public function dataMethods(): array 'variadic' => false, ], ], - new UnionType([ + new BenevolentUnionType([ new ObjectType('Closure'), new NullType(), ]), From cb9be914e7675176babaeab8647c149bec3347ed Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 19 Nov 2024 10:59:55 +0100 Subject: [PATCH 0816/3097] Fix sprintf dynamic return type --- ...intfFunctionDynamicReturnTypeExtension.php | 23 +++++----- tests/PHPStan/Analyser/nsrt/bug-12065.php | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12065.php diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index dc30f2afdd7..e379c4cc3cc 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -29,6 +29,7 @@ use function count; use function in_array; use function intval; +use function is_array; use function is_string; use function preg_match; use function sprintf; @@ -69,7 +70,7 @@ public function getTypeFromFunctionCall( static fn (Type $type): bool => $type->toString()->isLowercaseString()->yes() ); - $singlePlaceholderEarlyReturn = null; + $singlePlaceholderEarlyReturn = []; $allPatternsNonEmpty = count($formatStrings) !== 0; $allPatternsNonFalsy = count($formatStrings) !== 0; foreach ($formatStrings as $constantString) { @@ -93,8 +94,11 @@ public function getTypeFromFunctionCall( $allPatternsNonFalsy = false; } - // The printf format is %[argnum$][flags][width][.precision]specifier. - if (preg_match('/^%(?P[0-9]*\$)?(?P[0-9]*)\.?[0-9]*(?P[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1) { + if ( + is_array($singlePlaceholderEarlyReturn) + // The printf format is %[argnum$][flags][width][.precision]specifier. + && preg_match('/^%(?P[0-9]*\$)?(?P[0-9]*)\.?[0-9]*(?P[sbdeEfFgGhHouxX])$/', $constantString->getValue(), $matches) === 1 + ) { if ($matches['argnum'] !== '') { // invalid positional argument if ($matches['argnum'] === '0$') { @@ -122,24 +126,22 @@ public function getTypeFromFunctionCall( $constArgTypes = $checkArgType->getConstantScalarTypes(); } if ($constArgTypes !== []) { - $result = []; $printfArgs = array_fill(0, count($args) - 1, ''); foreach ($constArgTypes as $constArgType) { $printfArgs[$checkArg - 1] = $constArgType->getValue(); try { - $result[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs)); + $singlePlaceholderEarlyReturn[] = new ConstantStringType(@sprintf($constantString->getValue(), ...$printfArgs)); } catch (Throwable) { continue 2; } } - $singlePlaceholderEarlyReturn = TypeCombinator::union(...$result); continue; } - $singlePlaceholderEarlyReturn = $checkArgType->toString(); + $singlePlaceholderEarlyReturn[] = $checkArgType->toString(); } elseif ($matches['specifier'] !== 's') { - $singlePlaceholderEarlyReturn = $this->getStringReturnType( + $singlePlaceholderEarlyReturn[] = $this->getStringReturnType( new AccessoryNumericStringType(), $isLowercase, ); @@ -149,11 +151,10 @@ public function getTypeFromFunctionCall( } $singlePlaceholderEarlyReturn = null; - break; } - if ($singlePlaceholderEarlyReturn !== null) { - return $singlePlaceholderEarlyReturn; + if (is_array($singlePlaceholderEarlyReturn) && count($singlePlaceholderEarlyReturn) > 0) { + return TypeCombinator::union(...$singlePlaceholderEarlyReturn); } if ($allPatternsNonFalsy) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12065.php b/tests/PHPStan/Analyser/nsrt/bug-12065.php new file mode 100644 index 00000000000..e0f9353eec5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12065.php @@ -0,0 +1,43 @@ + $key + * @param bool $preserveKeys + * + * @return void + */ + public function bar2( + string $key, + bool $preserveKeys, + ): void { + $format = $preserveKeys ? '%s' : '%d'; + + $_key = sprintf($format, $key); + assertType("string", $_key); + } +} From a34c2f4c688c7394d034058f59e1c6d70da3e2ed Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 19 Nov 2024 16:38:41 +0200 Subject: [PATCH 0817/3097] Update curl_setopt string values and allow nullable --- src/Reflection/ParametersAcceptorSelector.php | 69 +++++++++++++++---- .../CallToFunctionParametersRuleTest.php | 26 +++++-- .../Rules/Functions/data/curl_setopt.php | 6 ++ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index fd2f2b8ae1e..82af7d4267d 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -918,49 +918,91 @@ private static function getCurlOptValueType(int $curlOpt): ?Type } } + $nullableStringConstants = [ + 'CURLOPT_ACCEPT_ENCODING', + 'CURLOPT_CUSTOMREQUEST', + 'CURLOPT_DNS_INTERFACE', + 'CURLOPT_DNS_LOCAL_IP4', + 'CURLOPT_DNS_LOCAL_IP6', + 'CURLOPT_DOH_URL', + 'CURLOPT_FTP_ACCOUNT', + 'CURLOPT_FTPPORT', + 'CURLOPT_HSTS', + 'CURLOPT_KRBLEVEL', + 'CURLOPT_RANGE', + 'CURLOPT_RTSP_SESSION_ID', + 'CURLOPT_UNIX_SOCKET_PATH', + 'CURLOPT_XOAUTH2_BEARER', + ]; + foreach ($nullableStringConstants as $constName) { + if (defined($constName) && constant($constName) === $curlOpt) { + return new UnionType([ + new NullType(), + TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ), + ]); + } + } + $nonEmptyStringConstants = [ 'CURLOPT_ABSTRACT_UNIX_SOCKET', + 'CURLOPT_ALTSVC', + 'CURLOPT_AWS_SIGV4', 'CURLOPT_CAINFO', 'CURLOPT_CAPATH', 'CURLOPT_COOKIE', 'CURLOPT_COOKIEJAR', 'CURLOPT_COOKIELIST', - 'CURLOPT_CUSTOMREQUEST', 'CURLOPT_DEFAULT_PROTOCOL', - 'CURLOPT_DNS_INTERFACE', - 'CURLOPT_DNS_LOCAL_IP4', - 'CURLOPT_DNS_LOCAL_IP6', + 'CURLOPT_DNS_SERVERS', 'CURLOPT_EGDSOCKET', - 'CURLOPT_FTPPORT', + 'CURLOPT_FTP_ALTERNATIVE_TO_USER', 'CURLOPT_INTERFACE', 'CURLOPT_KEYPASSWD', 'CURLOPT_KRB4LEVEL', 'CURLOPT_LOGIN_OPTIONS', + 'CURLOPT_MAIL_AUTH', + 'CURLOPT_MAIL_FROM', + 'CURLOPT_NOPROXY', + 'CURLOPT_PASSWORD', 'CURLOPT_PINNEDPUBLICKEY', - 'CURLOPT_PROXY_SERVICE_NAME', + 'CURLOPT_PROTOCOLS_STR', 'CURLOPT_PROXY_CAINFO', 'CURLOPT_PROXY_CAPATH', 'CURLOPT_PROXY_CRLFILE', + 'CURLOPT_PROXY_ISSUERCERT', 'CURLOPT_PROXY_KEYPASSWD', 'CURLOPT_PROXY_PINNEDPUBLICKEY', + 'CURLOPT_PROXY_SERVICE_NAME', + 'CURLOPT_PROXY_SSL_CIPHER_LIST', 'CURLOPT_PROXY_SSLCERT', 'CURLOPT_PROXY_SSLCERTTYPE', - 'CURLOPT_PROXY_SSL_CIPHER_LIST', - 'CURLOPT_PROXY_TLS13_CIPHERS', 'CURLOPT_PROXY_SSLKEY', 'CURLOPT_PROXY_SSLKEYTYPE', + 'CURLOPT_PROXY_TLS13_CIPHERS', 'CURLOPT_PROXY_TLSAUTH_PASSWORD', 'CURLOPT_PROXY_TLSAUTH_TYPE', 'CURLOPT_PROXY_TLSAUTH_USERNAME', + 'CURLOPT_PROXYPASSWORD', + 'CURLOPT_PROXYUSERNAME', 'CURLOPT_PROXYUSERPWD', 'CURLOPT_RANDOM_FILE', - 'CURLOPT_RANGE', + 'CURLOPT_REDIR_PROTOCOLS_STR', 'CURLOPT_REFERER', + 'CURLOPT_REQUEST_TARGET', + 'CURLOPT_RTSP_STREAM_URI', + 'CURLOPT_RTSP_TRANSPORT', + 'CURLOPT_SASL_AUTHZID', 'CURLOPT_SERVICE_NAME', + 'CURLOPT_SOCKS5_GSSAPI_SERVICE', 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5', - 'CURLOPT_SSH_PUBLIC_KEYFILE', + 'CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256', 'CURLOPT_SSH_PRIVATE_KEYFILE', + 'CURLOPT_SSH_PUBLIC_KEYFILE', 'CURLOPT_SSL_CIPHER_LIST', + 'CURLOPT_SSL_EC_CURVES', 'CURLOPT_SSLCERT', 'CURLOPT_SSLCERTPASSWD', 'CURLOPT_SSLCERTTYPE', @@ -970,13 +1012,14 @@ private static function getCurlOptValueType(int $curlOpt): ?Type 'CURLOPT_SSLKEYPASSWD', 'CURLOPT_SSLKEYTYPE', 'CURLOPT_TLS13_CIPHERS', - 'CURLOPT_UNIX_SOCKET_PATH', + 'CURLOPT_TLSAUTH_PASSWORD', + 'CURLOPT_TLSAUTH_TYPE', + 'CURLOPT_TLSAUTH_USERNAME', + 'CURLOPT_TRANSFER_ENCODING', 'CURLOPT_URL', 'CURLOPT_USERAGENT', 'CURLOPT_USERNAME', - 'CURLOPT_PASSWORD', 'CURLOPT_USERPWD', - 'CURLOPT_XOAUTH2_BEARER', ]; foreach ($nonEmptyStringConstants as $constName) { if (defined($constName) && constant($constName) === $curlOpt) { diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 7d28abb0fc8..15213c15ea8 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1316,33 +1316,45 @@ public function testCurlSetOpt(): void 'Parameter #3 $value of function curl_setopt expects array, int given.', 17, ], + [ + 'Parameter #3 $value of function curl_setopt expects non-empty-string, null given.', + 18, + ], [ 'Parameter #3 $value of function curl_setopt expects bool, int given.', - 19, + 20, ], [ 'Parameter #3 $value of function curl_setopt expects bool, string given.', - 20, + 21, ], [ 'Parameter #3 $value of function curl_setopt expects int, string given.', - 22, + 23, ], [ 'Parameter #3 $value of function curl_setopt expects array, string given.', - 24, + 25, ], [ 'Parameter #3 $value of function curl_setopt expects resource, string given.', - 26, + 27, ], [ 'Parameter #3 $value of function curl_setopt expects array|string, int given.', - 28, + 29, + ], + [ + 'Parameter #3 $value of function curl_setopt expects non-empty-string, \'\' given.', + 31, + ], + [ + 'Parameter #3 $value of function curl_setopt expects non-empty-string|null, \'\' given.', + 32, ], [ 'Parameter #3 $value of function curl_setopt expects array, array given.', - 67, + 73, ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index 76d3136a2d2..24987ddbc95 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -15,6 +15,7 @@ public function errors(int $i, string $s) { // expecting string curl_setopt($curl, CURLOPT_URL, $i); curl_setopt($curl, CURLOPT_HTTPHEADER, $i); + curl_setopt($curl, CURLOPT_ABSTRACT_UNIX_SOCKET, null); // expecting bool curl_setopt($curl, CURLOPT_AUTOREFERER, $i); curl_setopt($curl, CURLOPT_RETURNTRANSFER, $s); @@ -26,6 +27,9 @@ public function errors(int $i, string $s) { curl_setopt($curl, CURLOPT_FILE, $s); // expecting string or array curl_setopt($curl, CURLOPT_POSTFIELDS, $i); + // expecting non empty string + curl_setopt($curl, CURLOPT_URL, ''); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, ''); } /** @@ -41,6 +45,8 @@ public function allGood(string $url, array $header) { curl_setopt($curl, CURLOPT_AUTOREFERER, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_TIMEOUT, 10); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, null); $fp = fopen("example_homepage.txt", "w"); if ($fp === false) { From 3b47bc000091256f645c22e99d8823332a6cb43d Mon Sep 17 00:00:00 2001 From: Claude Pache Date: Tue, 19 Nov 2024 18:45:08 +0100 Subject: [PATCH 0818/3097] bccomp: more precise return type --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index baaef68a8cb..2b1df1a7902 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -418,7 +418,7 @@ 'bbcode_set_arg_parser' => ['bool', 'bbcode_container'=>'resource', 'bbcode_arg_parser'=>'resource'], 'bbcode_set_flags' => ['bool', 'bbcode_container'=>'resource', 'flags'=>'int', 'mode='=>'int'], 'bcadd' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], -'bccomp' => ['int', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], +'bccomp' => ['0|1|-1', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'bcdiv' => ['numeric-string|null', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'bcmod' => ['numeric-string|null', 'left_operand'=>'string', 'right_operand'=>'numeric-string', 'scale='=>'int'], 'bcmul' => ['numeric-string', 'left_operand'=>'numeric-string', 'right_operand'=>'numeric-string', 'scale='=>'int'], From 68d01a5f68c1e0897fd54e80c2b896ed98ae03a1 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Tue, 19 Nov 2024 19:33:41 +0000 Subject: [PATCH 0819/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 18 +++++++++--------- ...ctionEnumCaseDynamicReturnTypeExtension.php | 8 ++------ ...eflectionEnumDynamicReturnTypeExtension.php | 5 +---- .../adapter-reflection-enum-return-types.php | 9 +++++---- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 15e25f2a803..73f1cc620f1 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.42.0.11", + "ondrejmirtes/better-reflection": "6.43.0.2", "phpstan/php-8-stubs": "0.4.6", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 8ee3e675ede..d2adb5606cc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ca308caf852aa82ffe555d1235c75efa", + "content-hash": "a4991601b7590d9dc65443dfb5d34d16", "packages": [ { "name": "clue/ndjson-react", @@ -2187,22 +2187,22 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.42.0.11", + "version": "6.43.0.2", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "1ff390ad28758b6baa20c9a45b17926bdd850e44" + "reference": "c34ee726f9abc5a7057b0dacdf1c0991c9090584" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/1ff390ad28758b6baa20c9a45b17926bdd850e44", - "reference": "1ff390ad28758b6baa20c9a45b17926bdd850e44", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c34ee726f9abc5a7057b0dacdf1c0991c9090584", + "reference": "c34ee726f9abc5a7057b0dacdf1c0991c9090584", "shasum": "" }, "require": { "ext-json": "*", "jetbrains/phpstorm-stubs": "dev-master#217ed9356d07ef89109d3cd7d8c5df10aab4b0d4", - "nikic/php-parser": "^5.1.0", + "nikic/php-parser": "^5.3.1", "php": "^7.4 || ^8.0" }, "conflict": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.3.3", + "phpunit/phpunit": "^11.4.3", "rector/rector": "0.14.3" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.42.0.11" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.43.0.2" }, - "time": "2024-10-12T09:30:53+00:00" + "time": "2024-11-19T19:32:34+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php index d242403fb6c..4e1cfbcea63 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumCaseDynamicReturnTypeExtension.php @@ -4,9 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -56,9 +54,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method if ($methodReflection->getName() === 'getType') { return new UnionType([ - new ObjectType(ReflectionIntersectionType::class), - new ObjectType(ReflectionNamedType::class), - new ObjectType(ReflectionUnionType::class), + new ObjectType(ReflectionType::class), new NullType(), ]); } diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php index f0e25e92967..dfa3218442f 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php @@ -72,10 +72,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } if (in_array($methodReflection->getName(), ['getStartLine', 'getEndLine'], true)) { - return new UnionType([ - new IntegerType(), - new ConstantBooleanType(false), - ]); + return new IntegerType(); } if ($methodReflection->getName() === 'getReflectionConstant') { diff --git a/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php b/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php index 4002e1ce169..d21d17e7398 100644 --- a/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php +++ b/tests/PHPStan/Analyser/nsrt/adapter-reflection-enum-return-types.php @@ -5,12 +5,13 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType; use function PHPStan\Testing\assertType; function (ReflectionEnum $r, string $s): void { assertType('non-empty-string|false', $r->getFileName()); - assertType('int|false', $r->getStartLine()); - assertType('int|false', $r->getEndLine()); + assertType('int', $r->getStartLine()); + assertType('int', $r->getEndLine()); assertType('string|false', $r->getDocComment()); assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant|false', $r->getReflectionConstant($s)); assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass|false', $r->getParentClass()); @@ -20,10 +21,10 @@ function (ReflectionEnum $r, string $s): void { function (ReflectionEnumBackedCase $r): void { assertType('string|false', $r->getDocComment()); - assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|null', $r->getType()); + assertType(ReflectionType::class . '|null', $r->getType()); }; function (ReflectionEnumUnitCase $r): void { assertType('string|false', $r->getDocComment()); - assertType('PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|null', $r->getType()); + assertType(ReflectionType::class . '|null', $r->getType()); }; From ca2c937dff543ee80599ac11a1bd4235164e2b6a Mon Sep 17 00:00:00 2001 From: "Paul M. Jones" <25754+pmjones@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:22:48 -0600 Subject: [PATCH 0820/3097] Introduce uppercase-string --- conf/config.neon | 10 + phpstan-baseline.neon | 5 + resources/functionMap.php | 4 +- src/PhpDoc/TypeNodeResolver.php | 11 + .../InitializerExprTypeResolver.php | 6 + src/Rules/Api/ApiInstanceofTypeRule.php | 2 + .../StrictComparisonOfDifferentTypesRule.php | 11 +- src/Type/Accessory/AccessoryArrayListType.php | 5 + .../Accessory/AccessoryLiteralStringType.php | 5 + .../AccessoryLowercaseStringType.php | 5 + .../Accessory/AccessoryNonEmptyStringType.php | 5 + .../Accessory/AccessoryNonFalsyStringType.php | 5 + .../Accessory/AccessoryNumericStringType.php | 5 + .../AccessoryUppercaseStringType.php | 390 ++++++++++++++++++ src/Type/Accessory/HasOffsetType.php | 5 + src/Type/Accessory/HasOffsetValueType.php | 5 + src/Type/Accessory/NonEmptyArrayType.php | 5 + src/Type/Accessory/OversizedArrayType.php | 5 + src/Type/ArrayType.php | 5 + src/Type/CallableType.php | 5 + src/Type/ClassStringType.php | 5 + src/Type/ClosureType.php | 5 + src/Type/Constant/ConstantStringType.php | 11 + src/Type/FloatType.php | 5 + src/Type/IntersectionType.php | 14 +- src/Type/IterableType.php | 5 + src/Type/JustNullableTypeTrait.php | 5 + src/Type/MixedType.php | 17 + src/Type/NeverType.php | 5 + src/Type/NullType.php | 5 + src/Type/ObjectType.php | 5 + ...lodeFunctionDynamicReturnTypeExtension.php | 11 +- .../ImplodeFunctionReturnTypeExtension.php | 4 + .../Php/ParseStrParameterOutTypeExtension.php | 60 +++ ...aceFunctionsDynamicReturnTypeExtension.php | 5 + .../StrCaseFunctionsReturnTypeExtension.php | 21 + .../Php/StrPadFunctionReturnTypeExtension.php | 4 + .../StrRepeatFunctionReturnTypeExtension.php | 5 + .../Php/SubstrDynamicReturnTypeExtension.php | 4 + ...TrimFunctionDynamicReturnTypeExtension.php | 52 +++ src/Type/StaticType.php | 5 + src/Type/StrictMixedType.php | 5 + src/Type/StringType.php | 5 + src/Type/Traits/LateResolvableTypeTrait.php | 5 + src/Type/Traits/ObjectTypeTrait.php | 5 + src/Type/Type.php | 2 + src/Type/UnionType.php | 5 + src/Type/VerbosityLevel.php | 6 +- src/Type/VoidType.php | 5 + stubs/core.stub | 19 +- .../Analyser/LegacyNodeScopeResolverTest.php | 6 +- tests/PHPStan/Analyser/data/explode-php74.php | 3 +- tests/PHPStan/Analyser/data/explode-php80.php | 3 +- tests/PHPStan/Analyser/data/param-out.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-4711.php | 4 +- .../nsrt/isset-coalesce-empty-type.php | 2 +- .../PHPStan/Analyser/nsrt/literal-string.php | 14 +- ...-subtr.php => lowercase-string-substr.php} | 0 tests/PHPStan/Analyser/nsrt/more-types.php | 6 + .../Analyser/nsrt/non-empty-string.php | 8 +- .../Analyser/nsrt/non-falsy-string.php | 4 +- tests/PHPStan/Analyser/nsrt/str-casing.php | 24 +- .../nsrt/uppercase-string-implode.php | 22 + .../Analyser/nsrt/uppercase-string-pad.php | 23 ++ .../Analyser/nsrt/uppercase-string-parse.php | 36 ++ .../Analyser/nsrt/uppercase-string-repeat.php | 19 + .../nsrt/uppercase-string-replace.php | 42 ++ .../Analyser/nsrt/uppercase-string-substr.php | 30 ++ .../Analyser/nsrt/uppercase-string-trim.php | 29 ++ ...rictComparisonOfDifferentTypesRuleTest.php | 48 +++ .../Comparison/data/uppercase-string.php | 31 ++ .../CallToFunctionParametersRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 23 ++ .../Rules/Methods/data/uppercase-string.php | 33 ++ .../Type/Constant/ConstantStringTypeTest.php | 12 +- .../Constant/OversizedArrayBuilderTest.php | 9 + tests/PHPStan/Type/IntersectionTypeTest.php | 16 + tests/PHPStan/Type/TypeCombinatorTest.php | 114 +++++ tests/PHPStan/Type/TypeToPhpDocNodeTest.php | 11 + 79 files changed, 1321 insertions(+), 66 deletions(-) create mode 100644 src/Type/Accessory/AccessoryUppercaseStringType.php create mode 100644 src/Type/Php/ParseStrParameterOutTypeExtension.php create mode 100644 src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php rename tests/PHPStan/Analyser/nsrt/{lowercase-string-subtr.php => lowercase-string-substr.php} (100%) create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-implode.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-pad.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-parse.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-repeat.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-replace.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-substr.php create mode 100644 tests/PHPStan/Analyser/nsrt/uppercase-string-trim.php create mode 100644 tests/PHPStan/Rules/Comparison/data/uppercase-string.php create mode 100644 tests/PHPStan/Rules/Methods/data/uppercase-string.php diff --git a/conf/config.neon b/conf/config.neon index 11e845136d5..f8ad1af8b31 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1564,6 +1564,11 @@ services: tags: - phpstan.dynamicFunctionThrowTypeExtension + - + class: PHPStan\Type\Php\ParseStrParameterOutTypeExtension + tags: + - phpstan.functionParameterOutTypeExtension + - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension tags: @@ -1774,6 +1779,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension tags: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 498207dbe7b..1662d1fa9af 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -715,6 +715,11 @@ parameters: count: 1 path: src/Type/Accessory/AccessoryNumericStringType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + count: 1 + path: src/Type/Accessory/AccessoryUppercaseStringType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 1 diff --git a/resources/functionMap.php b/resources/functionMap.php index 620138e0bc2..9891593eb3f 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6362,7 +6362,7 @@ 'mb_strrpos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], 'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'], 'mb_strtolower' => ['lowercase-string', 'str'=>'string', 'encoding='=>'string'], -'mb_strtoupper' => ['string', 'str'=>'string', 'encoding='=>'string'], +'mb_strtoupper' => ['uppercase-string', 'str'=>'string', 'encoding='=>'string'], 'mb_strwidth' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'], 'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'], 'mb_substr' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'], @@ -12087,7 +12087,7 @@ 'strtok\'1' => ['non-empty-string|false', 'token'=>'string'], 'strtolower' => ['lowercase-string', 'str'=>'string'], 'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'], -'strtoupper' => ['string', 'str'=>'string'], +'strtoupper' => ['uppercase-string', 'str'=>'string'], 'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'], 'strtr\'1' => ['string', 'str'=>'string', 'replace_pairs'=>'array'], 'strval' => ['string', 'var'=>'__stringAndStringable|int|float|bool|resource|null'], diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 43f93cb8f52..744265adf67 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -50,6 +50,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -223,6 +224,9 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco case 'lowercase-string': return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); + case 'uppercase-string': + return new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]); + case 'literal-string': return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]); @@ -303,6 +307,13 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco new AccessoryLowercaseStringType(), ]); + case 'non-empty-uppercase-string': + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + new AccessoryUppercaseStringType(), + ]); + case 'truthy-string': case 'non-falsy-string': return new IntersectionType([ diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8cedcd12723..a55fd7831d4 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -31,6 +31,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -480,10 +481,15 @@ public function resolveConcatType(Type $left, Type $right): Type if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) { $accessoryTypes[] = new AccessoryLiteralStringType(); } + if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($leftStringType->isUppercaseString()->and($rightStringType->isUppercaseString())->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } + $leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType('')); if ($leftNumericStringNonEmpty->isNumericString()->yes()) { $allRightConstantsZeroOrMore = false; diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index 225e65b0002..ccda7006968 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -16,6 +16,7 @@ use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; @@ -86,6 +87,7 @@ final class ApiInstanceofTypeRule implements Rule AccessoryNumericStringType::class => 'Type::isNumericString()', AccessoryLiteralStringType::class => 'Type::isLiteralString()', AccessoryLowercaseStringType::class => 'Type::isLowercaseString()', + AccessoryUppercaseStringType::class => 'Type::isUppercaseString()', AccessoryNonEmptyStringType::class => 'Type::isNonEmptyString()', AccessoryNonFalsyStringType::class => 'Type::isNonFalsyString()', HasMethodType::class => 'Type::hasMethod()', diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index 7d9c7641325..d155be352ab 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -75,19 +75,26 @@ public function processNode(Node $node, Scope $scope): array }; $verbosity = VerbosityLevel::value(); + if ( ( $leftType->isConstantScalarValue()->yes() && !$leftType->isString()->no() && !$rightType->isConstantScalarValue()->yes() && !$rightType->isString()->no() - && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + && ( + TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + || TrinaryLogic::extremeIdentity($leftType->isUppercaseString(), $rightType->isUppercaseString())->maybe() + ) ) || ( $rightType->isConstantScalarValue()->yes() && !$rightType->isString()->no() && !$leftType->isConstantScalarValue()->yes() && !$leftType->isString()->no() - && TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + && ( + TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe() + || TrinaryLogic::extremeIdentity($leftType->isUppercaseString(), $rightType->isUppercaseString())->maybe() + ) ) ) { $verbosity = VerbosityLevel::precise(); diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 491af5b813d..c2eea343492 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -414,6 +414,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 700063570e1..d0970ba3468 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -313,6 +313,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index fe57034af12..672998fdb8f 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -309,6 +309,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 6587e85d35e..117779e376e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -310,6 +310,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 2a562644754..4bfb46f436a 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -310,6 +310,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 1ab8d31eff3..79c83c7b0f3 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -312,6 +312,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php new file mode 100644 index 00000000000..3ce287d6148 --- /dev/null +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -0,0 +1,390 @@ +acceptsWithReason($type, $strictTypes)->result; + } + + public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult + { + if ($type instanceof CompoundType) { + return $type->isAcceptedWithReasonBy($this, $strictTypes); + } + + return new AcceptsResult($type->isUppercaseString(), []); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + return $this->isSuperTypeOfWithReason($type)->result; + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOfWithReason($this); + } + + if ($this->equals($type)) { + return IsSuperTypeOfResult::createYes(); + } + + return new IsSuperTypeOfResult($type->isUppercaseString(), []); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + return $this->isSubTypeOfWithReason($otherType)->result; + } + + public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOfWithReason($this); + } + + return (new IsSuperTypeOfResult($otherType->isUppercaseString(), [])) + ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; + } + + public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult + { + return $this->isSubTypeOfWithReason($acceptingType)->toAcceptsResult(); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(VerbosityLevel $level): string + { + return 'uppercase-string'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isOffsetAccessLegal(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return $offsetType->isInteger()->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + return new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + $stringOffset = (new StringType())->setOffsetValueType($offsetType, $valueType, $unionValues); + + if ($stringOffset instanceof ErrorType) { + return $stringOffset; + } + + if ($valueType->isUppercaseString()->yes()) { + return $this; + } + + return new StringType(); + } + + public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type + { + return $this; + } + + public function unsetOffset(Type $offsetType): Type + { + return new ErrorType(); + } + + public function toNumber(): Type + { + return new ErrorType(); + } + + public function toAbsoluteNumber(): Type + { + return new ErrorType(); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toBoolean(): BooleanType + { + return new BooleanType(); + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + [1], + [], + TrinaryLogic::createYes(), + ); + } + + public function toArrayKey(): Type + { + return $this; + } + + public function isNull(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isConstantValue(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isConstantScalarValue(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getConstantScalarTypes(): array + { + return []; + } + + public function getConstantScalarValues(): array + { + return []; + } + + public function isTrue(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFalse(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isBoolean(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFloat(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isInteger(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonFalsyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isLowercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function isClassStringType(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getClassStringObjectType(): Type + { + return new ObjectWithoutClassType(); + } + + public function getObjectTypeOrClassStringObjectType(): Type + { + return new ObjectWithoutClassType(); + } + + public function hasMethod(string $methodName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isVoid(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isScalar(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType + { + return new BooleanType(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public function traverseSimultaneously(Type $right, callable $cb): Type + { + return $this; + } + + public function generalize(GeneralizePrecision $precision): Type + { + return new StringType(); + } + + public static function __set_state(array $properties): Type + { + return new self(); + } + + public function exponentiate(Type $exponent): Type + { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + ]); + } + + public function getFiniteTypes(): array + { + return []; + } + + public function toPhpDocNode(): TypeNode + { + return new IdentifierTypeNode('uppercase-string'); + } + +} diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index f1ccf3c2a20..23650ed8d3d 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -329,6 +329,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 600fa6ca636..b673a3e7eb7 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -385,6 +385,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index c822edcc7ae..7b9312e6c5f 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -390,6 +390,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 4d39431f7b2..3de317ba0e2 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -379,6 +379,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index c3fc5050b19..74b47dea0b0 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -373,6 +373,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index d9bbea57e6e..f182372fc08 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -606,6 +606,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 2f58e27d15e..30e761b660b 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -79,6 +79,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index bbe6d5a73ef..d12dda936e1 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -723,6 +723,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 6350d17cefd..f0e78cf903c 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -24,6 +24,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; @@ -51,6 +52,7 @@ use function key; use function strlen; use function strtolower; +use function strtoupper; use function substr; use function substr_count; @@ -356,6 +358,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createFromBoolean(strtolower($this->value) === $this->value); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean(strtoupper($this->value) === $this->value); + } + public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType->isInteger()->yes()) { @@ -467,6 +474,10 @@ public function generalize(GeneralizePrecision $precision): Type $accessories[] = new AccessoryLowercaseStringType(); } + if (strtoupper($this->getValue()) === $this->getValue()) { + $accessories[] = new AccessoryUppercaseStringType(); + } + return new IntersectionType($accessories); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index bd5b2a9082f..40ea01b5db6 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -239,6 +239,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 6a8d2ab329d..72fecc7de60 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -26,6 +26,7 @@ use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -341,11 +342,14 @@ private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes) || $type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonFalsyStringType || $type instanceof AccessoryLowercaseStringType + || $type instanceof AccessoryUppercaseStringType ) { - if ($type instanceof AccessoryLowercaseStringType && !$level->isPrecise()) { + if ( + ($type instanceof AccessoryLowercaseStringType || $type instanceof AccessoryUppercaseStringType) + && !$level->isPrecise() + ) { continue; } - if ($type instanceof AccessoryNonFalsyStringType) { $nonFalsyStr = true; } @@ -659,6 +663,11 @@ public function isLowercaseString(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); } + public function isUppercaseString(): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isUppercaseString()); + } + public function isClassStringType(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); @@ -1166,6 +1175,7 @@ public function toPhpDocNode(): TypeNode || $type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonFalsyStringType || $type instanceof AccessoryLowercaseStringType + || $type instanceof AccessoryUppercaseStringType ) { if ($type instanceof AccessoryNonFalsyStringType) { $nonFalsyStr = true; diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 662d2e1b0cc..2d0c6322982 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -387,6 +387,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index df6bee28b81..9545fff1dae 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -157,6 +157,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 0b9d87394ab..3ae9434a95f 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -25,6 +25,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\OversizedArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -963,6 +964,22 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + if ($this->subtractedType !== null) { + $uppercaseString = TypeCombinator::intersect( + new StringType(), + new AccessoryUppercaseStringType(), + ); + + if ($this->subtractedType->isSuperTypeOf($uppercaseString)->yes()) { + return TrinaryLogic::createNo(); + } + } + + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 45f9cb05f3b..a3d8daf1feb 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -496,6 +496,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 7b60d5d3cb9..2bd76023f8c 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -305,6 +305,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 7427aa167c7..9c633d62dae 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1046,6 +1046,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index ccad08cefb8..d675eee45cc 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -57,8 +58,16 @@ public function getTypeFromFunctionCall( } $stringType = $scope->getType($args[1]->value); + $accessory = []; if ($stringType->isLowercaseString()->yes()) { - $returnValueType = new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]); + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($stringType->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + if (count($accessory) > 0) { + $accessory[] = new StringType(); + $returnValueType = new IntersectionType($accessory); } else { $returnValueType = new StringType(); } diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index d29e20a7ccb..5c680b5b62f 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; @@ -94,6 +95,9 @@ private function implode(Type $arrayType, Type $separatorType): Type if ($arrayType->getIterableValueType()->isLowercaseString()->yes() && $separatorType->isLowercaseString()->yes()) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($arrayType->getIterableValueType()->isUppercaseString()->yes() && $separatorType->isUppercaseString()->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); diff --git a/src/Type/Php/ParseStrParameterOutTypeExtension.php b/src/Type/Php/ParseStrParameterOutTypeExtension.php new file mode 100644 index 00000000000..e0c7aa47e5a --- /dev/null +++ b/src/Type/Php/ParseStrParameterOutTypeExtension.php @@ -0,0 +1,60 @@ +getName()), ['parse_str', 'mb_parse_str'], true) + && $parameter->getName() === 'result'; + } + + public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $funcCall->getArgs(); + if (count($args) < 1) { + return null; + } + + $stringType = $scope->getType($args[0]->value); + $accessory = []; + if ($stringType->isLowercaseString()->yes()) { + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($stringType->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + if (count($accessory) > 0) { + $accessory[] = new StringType(); + $valueType = new IntersectionType($accessory); + } else { + $valueType = new StringType(); + } + + return new ArrayType( + new UnionType([new StringType(), new IntegerType()]), + new UnionType([new ArrayType(new MixedType(), new MixedType()), $valueType]), + ); + } + +} diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index c80c10d9c0e..db046bbe100 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; @@ -100,6 +101,10 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( $accessories[] = new AccessoryLowercaseStringType(); } + if ($subjectArgumentType->isUppercaseString()->yes() && $replaceArgumentType->isUppercaseString()->yes()) { + $accessories[] = new AccessoryUppercaseStringType(); + } + if (count($accessories) > 0) { $accessories[] = new StringType(); return new IntersectionType($accessories); diff --git a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php index 149caff0818..db36561ab67 100644 --- a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php +++ b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; @@ -23,6 +24,7 @@ use function is_callable; use function mb_check_encoding; use const MB_CASE_LOWER; +use const MB_CASE_UPPER; final class StrCaseFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -70,6 +72,8 @@ public function getTypeFromFunctionCall( $modes = []; $keepLowercase = false; $forceLowercase = false; + $keepUppercase = false; + $forceUppercase = false; if ($fnName === 'mb_convert_case') { $modeType = $scope->getType($args[1]->value); @@ -85,6 +89,16 @@ public function getTypeFromFunctionCall( 3, // MB_CASE_FOLD, 7, // MB_CASE_FOLD_SIMPLE ])) === 0; + $forceUppercase = count(array_diff($modes, [ + MB_CASE_UPPER, + 4, // MB_CASE_UPPER_SIMPLE + ])) === 0; + $keepUppercase = count(array_diff($modes, [ + MB_CASE_UPPER, + 4, // MB_CASE_UPPER_SIMPLE + 3, // MB_CASE_FOLD, + 7, // MB_CASE_FOLD_SIMPLE + ])) === 0; } } elseif (in_array($fnName, ['ucwords', 'mb_convert_kana'], true)) { if (count($args) >= 2) { @@ -97,6 +111,10 @@ public function getTypeFromFunctionCall( $forceLowercase = true; } elseif (in_array($fnName, ['lcfirst', 'mb_lcfirst'], true)) { $keepLowercase = true; + } elseif (in_array($fnName, ['strtoupper', 'mb_strtoupper'], true)) { + $forceUppercase = true; + } elseif (in_array($fnName, ['ucfirst', 'mb_ucfirst'], true)) { + $keepUppercase = true; } $constantStrings = array_map(static fn ($type) => $type->getValue(), $argType->getConstantStrings()); @@ -127,6 +145,9 @@ public function getTypeFromFunctionCall( if ($forceLowercase || ($keepLowercase && $argType->isLowercaseString()->yes())) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($forceUppercase || ($keepUppercase && $argType->isUppercaseString()->yes())) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } if ($argType->isNumericString()->yes()) { $accessoryTypes[] = new AccessoryNumericStringType(); diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index 92b0ec286dc..d556fff1be8 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; @@ -57,6 +58,9 @@ public function getTypeFromFunctionCall( if ($inputType->isLowercaseString()->yes() && ($padStringType === null || $padStringType->isLowercaseString()->yes())) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($inputType->isUppercaseString()->yes() && ($padStringType === null || $padStringType->isUppercaseString()->yes())) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 632fea85f05..98afacb7d71 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -11,6 +11,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -98,6 +99,10 @@ public function getTypeFromFunctionCall( $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($inputType->isUppercaseString()->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } + if (count($accessoryTypes) > 0) { $accessoryTypes[] = new StringType(); return new IntersectionType($accessoryTypes); diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index 4364f2374f4..df1d0cf0606 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -99,6 +100,9 @@ public function getTypeFromFunctionCall( if ($string->isLowercaseString()->yes()) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } + if ($string->isUppercaseString()->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { $isNotEmpty = true; if ($string->isNonFalsyString()->yes() && !$maybeOneLength) { diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..df06d98b437 --- /dev/null +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -0,0 +1,52 @@ +getName(), ['trim', 'rtrim', 'ltrim'], true); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + if (count($args) < 1) { + return null; + } + + $stringType = $scope->getType($args[0]->value); + $accessory = []; + if ($stringType->isLowercaseString()->yes()) { + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($stringType->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + if (count($accessory) > 0) { + $accessory[] = new StringType(); + return new IntersectionType($accessory); + } + + return new StringType(); + } + +} diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 5fd6f7e358f..7056fc5901c 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -580,6 +580,11 @@ public function isLowercaseString(): TrinaryLogic return $this->getStaticObjectType()->isLowercaseString(); } + public function isUppercaseString(): TrinaryLogic + { + return $this->getStaticObjectType()->isUppercaseString(); + } + public function isClassStringType(): TrinaryLogic { return $this->getStaticObjectType()->isClassStringType(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 13b0fee7ded..c839779a55d 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -290,6 +290,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 9fcd1e1895a..c49cf7823c1 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -245,6 +245,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 76855da1f48..e6355875ca1 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -468,6 +468,11 @@ public function isLowercaseString(): TrinaryLogic return $this->resolve()->isLowercaseString(); } + public function isUppercaseString(): TrinaryLogic + { + return $this->resolve()->isUppercaseString(); + } + public function isClassStringType(): TrinaryLogic { return $this->resolve()->isClassStringType(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 8ca9c69c293..7cf3659a505 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -203,6 +203,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 6c5064f4eae..6562ba20057 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -274,6 +274,8 @@ public function isLiteralString(): TrinaryLogic; public function isLowercaseString(): TrinaryLogic; + public function isUppercaseString(): TrinaryLogic; + public function isClassStringType(): TrinaryLogic; public function isVoid(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f0bef45903b..6f4cbf462b9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -650,6 +650,11 @@ public function isLowercaseString(): TrinaryLogic return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isLowercaseString()); } + public function isUppercaseString(): TrinaryLogic + { + return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isUppercaseString()); + } + public function isClassStringType(): TrinaryLogic { return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isClassStringType()); diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 47b0d2477c5..988b2a24053 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -8,6 +8,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; @@ -108,7 +109,10 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerbose = true; return $type; } - if ($type instanceof AccessoryLowercaseStringType) { + if ( + $type instanceof AccessoryLowercaseStringType + || $type instanceof AccessoryUppercaseStringType + ) { $moreVerbose = true; $veryVerbose = true; return $type; diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 4353c6efdd3..18b3d719c1a 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -217,6 +217,11 @@ public function isLowercaseString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isUppercaseString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isClassStringType(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/stubs/core.stub b/stubs/core.stub index abd719b4f52..d6c1463c81b 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -69,13 +69,13 @@ function str_shuffle(string $string): string {} /** * @param array $result - * @param-out ($string is lowercase-string ? array|lowercase-string> : array|string>) $result + * @param-out array|string> $result */ function parse_str(string $string, array &$result): void {} /** * @param array $result - * @param-out array|string> $result + * @param-out array|string> $result */ function mb_parse_str(string $string, array &$result): bool {} @@ -314,21 +314,6 @@ function is_callable(mixed $value, bool $syntax_only = false, ?string &$callable */ function abs($num) {} -/** - * @return ($string is lowercase-string ? lowercase-string : string) - */ -function trim(string $string, string $characters = " \n\r\t\v\x00"): string {} - -/** - * @return ($string is lowercase-string ? lowercase-string : string) - */ -function ltrim(string $string, string $characters = " \n\r\t\v\x00"): string {} - -/** - * @return ($string is lowercase-string ? lowercase-string : string) - */ -function rtrim(string $string, string $characters = " \n\r\t\v\x00"): string {} - /** * @return ($categorize is true ? array> : array) */ diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index aea1961e324..5434782186c 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1129,11 +1129,11 @@ public function dataArrayDestructuring(): array '$fourthStringArrayForeachList', ], [ - 'lowercase-string', + 'lowercase-string&uppercase-string', '$dateArray[\'Y\']', ], [ - 'lowercase-string', + 'lowercase-string&uppercase-string', '$dateArray[\'m\']', ], [ @@ -1141,7 +1141,7 @@ public function dataArrayDestructuring(): array '$dateArray[\'d\']', ], [ - 'lowercase-string', + 'lowercase-string&uppercase-string', '$intArrayForRewritingFirstElement[0]', ], [ diff --git a/tests/PHPStan/Analyser/data/explode-php74.php b/tests/PHPStan/Analyser/data/explode-php74.php index efdd4044249..b205b1d0be2 100644 --- a/tests/PHPStan/Analyser/data/explode-php74.php +++ b/tests/PHPStan/Analyser/data/explode-php74.php @@ -9,6 +9,7 @@ class ExplodingStrings public function doFoo(string $s): void { assertType('non-empty-list|false', explode($s, 'foo')); - assertType('non-empty-list|false', explode($s, 'FOO')); + assertType('non-empty-list|false', explode($s, 'FOO')); + assertType('non-empty-list|false', explode($s, 'Foo')); } } diff --git a/tests/PHPStan/Analyser/data/explode-php80.php b/tests/PHPStan/Analyser/data/explode-php80.php index 4fa45393564..1c012395877 100644 --- a/tests/PHPStan/Analyser/data/explode-php80.php +++ b/tests/PHPStan/Analyser/data/explode-php80.php @@ -9,6 +9,7 @@ class ExplodingStrings public function doFoo(string $s): void { assertType('non-empty-list', explode($s, 'foo')); - assertType('non-empty-list', explode($s, 'FOO')); + assertType('non-empty-list', explode($s, 'FOO')); + assertType('non-empty-list', explode($s, 'Foo')); } } diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index 05684938b8e..341a4069d4f 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -392,10 +392,10 @@ function fooHeadersSent() { function fooMbParseStr() { mb_parse_str("foo=bar", $output); - assertType('array', $output); + assertType('array', $output); mb_parse_str('email=mail@example.org&city=town&x=1&y[g]=3&f=1.23', $output); - assertType('array', $output); + assertType('array', $output); } function fooPreg() diff --git a/tests/PHPStan/Analyser/nsrt/bug-4711.php b/tests/PHPStan/Analyser/nsrt/bug-4711.php index 8fbed765a95..9050c74c15f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4711.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4711.php @@ -12,8 +12,8 @@ function x(string $string): void { return; } - assertType('non-empty-list', explode($string, '')); - assertType('non-empty-list', explode($string[0], '')); + assertType('non-empty-list', explode($string, '')); + assertType('non-empty-list', explode($string[0], '')); } } diff --git a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php index a38116b682a..8ffb1ddb895 100644 --- a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php +++ b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php @@ -472,7 +472,7 @@ function coalesce() assertType('int<0, max>', rand() ?? false); - assertType('0|lowercase-string', preg_replace('', '', '') ?? 0); + assertType('0|(lowercase-string&uppercase-string)', preg_replace('', '', '') ?? 0); $foo = new FooCoalesce(); diff --git a/tests/PHPStan/Analyser/nsrt/literal-string.php b/tests/PHPStan/Analyser/nsrt/literal-string.php index 93bf8949d93..c30fbdac808 100644 --- a/tests/PHPStan/Analyser/nsrt/literal-string.php +++ b/tests/PHPStan/Analyser/nsrt/literal-string.php @@ -37,8 +37,9 @@ public function doFoo($literalString, string $string, $numericString) str_repeat('a', 99) ); assertType('literal-string&lowercase-string&non-falsy-string', str_repeat('a', 100)); - assertType('literal-string&lowercase-string&non-empty-string&numeric-string', str_repeat('0', 100)); // could be non-falsy-string - assertType('literal-string&lowercase-string&non-falsy-string&numeric-string', str_repeat('1', 100)); + assertType('literal-string&non-falsy-string&uppercase-string', str_repeat('A', 100)); + assertType('literal-string&lowercase-string&non-empty-string&numeric-string&uppercase-string', str_repeat('0', 100)); // could be non-falsy-string + assertType('literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string', str_repeat('1', 100)); // Repeating a numeric type multiple times can lead to a non-numeric type: 3v4l.org/aRBdZ assertType('non-empty-string', str_repeat($numericString, 100)); @@ -51,13 +52,14 @@ public function doFoo($literalString, string $string, $numericString) assertType("non-empty-string", str_repeat($numericString, 2)); assertType("literal-string", str_repeat($literalString, 1)); $x = rand(1,2); - assertType("literal-string&lowercase-string&non-falsy-string", str_repeat(' 1 ', $x)); - assertType("literal-string&lowercase-string&non-falsy-string", str_repeat('+1', $x)); + assertType("literal-string&lowercase-string&non-falsy-string&uppercase-string", str_repeat(' 1 ', $x)); + assertType("literal-string&lowercase-string&non-falsy-string&uppercase-string", str_repeat('+1', $x)); assertType("literal-string&lowercase-string&non-falsy-string", str_repeat('1e9', $x)); - assertType("literal-string&lowercase-string&non-falsy-string&numeric-string", str_repeat('19', $x)); + assertType("literal-string&non-falsy-string&uppercase-string", str_repeat('1E9', $x)); + assertType("literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string", str_repeat('19', $x)); $x = rand(0,2); - assertType("literal-string&lowercase-string", str_repeat('19', $x)); + assertType("literal-string&lowercase-string&uppercase-string", str_repeat('19', $x)); $x = rand(-10,-1); assertType("*NEVER*", str_repeat('19', $x)); diff --git a/tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php b/tests/PHPStan/Analyser/nsrt/lowercase-string-substr.php similarity index 100% rename from tests/PHPStan/Analyser/nsrt/lowercase-string-subtr.php rename to tests/PHPStan/Analyser/nsrt/lowercase-string-substr.php diff --git a/tests/PHPStan/Analyser/nsrt/more-types.php b/tests/PHPStan/Analyser/nsrt/more-types.php index 9c3296c3d89..e98bc06a762 100644 --- a/tests/PHPStan/Analyser/nsrt/more-types.php +++ b/tests/PHPStan/Analyser/nsrt/more-types.php @@ -19,6 +19,8 @@ class Foo * @param non-empty-mixed $nonEmptyMixed * @param lowercase-string $lowercaseString * @param non-empty-lowercase-string $nonEmptyLowercaseString + * @param uppercase-string $uppercaseString + * @param non-empty-uppercase-string $nonEmptyUppercaseString */ public function doFoo( $pureCallable, @@ -32,6 +34,8 @@ public function doFoo( $nonEmptyMixed, $lowercaseString, $nonEmptyLowercaseString, + $uppercaseString, + $nonEmptyUppercaseString, ): void { assertType('pure-callable(): mixed', $pureCallable); @@ -45,6 +49,8 @@ public function doFoo( assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $nonEmptyMixed); assertType('lowercase-string', $lowercaseString); assertType('lowercase-string&non-empty-string', $nonEmptyLowercaseString); + assertType('uppercase-string', $uppercaseString); + assertType('non-empty-string&uppercase-string', $nonEmptyUppercaseString); } } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index 5400b1d31a3..cd831db4d82 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -318,12 +318,12 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo assertType('string', escapeshellcmd($s)); assertType('non-empty-string', escapeshellcmd($nonEmpty)); - assertType('string', strtoupper($s)); - assertType('non-empty-string', strtoupper($nonEmpty)); + assertType('uppercase-string', strtoupper($s)); + assertType('non-empty-string&uppercase-string', strtoupper($nonEmpty)); assertType('lowercase-string', strtolower($s)); assertType('lowercase-string&non-empty-string', strtolower($nonEmpty)); - assertType('string', mb_strtoupper($s)); - assertType('non-empty-string', mb_strtoupper($nonEmpty)); + assertType('uppercase-string', mb_strtoupper($s)); + assertType('non-empty-string&uppercase-string', mb_strtoupper($nonEmpty)); assertType('lowercase-string', mb_strtolower($s)); assertType('lowercase-string&non-empty-string', mb_strtolower($nonEmpty)); assertType('string', lcfirst($s)); diff --git a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php index 4127dd58705..c5fd9fc1d81 100644 --- a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php @@ -87,9 +87,9 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra assertType('non-falsy-string', escapeshellarg($nonFalsey)); assertType('non-falsy-string', escapeshellcmd($nonFalsey)); - assertType('non-falsy-string', strtoupper($nonFalsey)); + assertType('non-falsy-string&uppercase-string', strtoupper($nonFalsey)); assertType('lowercase-string&non-falsy-string', strtolower($nonFalsey)); - assertType('non-falsy-string', mb_strtoupper($nonFalsey)); + assertType('non-falsy-string&uppercase-string', mb_strtoupper($nonFalsey)); assertType('lowercase-string&non-falsy-string', mb_strtolower($nonFalsey)); assertType('non-falsy-string', lcfirst($nonFalsey)); assertType('non-falsy-string', ucfirst($nonFalsey)); diff --git a/tests/PHPStan/Analyser/nsrt/str-casing.php b/tests/PHPStan/Analyser/nsrt/str-casing.php index 3f0c76f2506..ebdbd8054d3 100644 --- a/tests/PHPStan/Analyser/nsrt/str-casing.php +++ b/tests/PHPStan/Analyser/nsrt/str-casing.php @@ -39,52 +39,52 @@ public function bar($numericS, $nonE, $lowercaseS, $literal, $edgeUnion, $caseMo assertType("non-falsy-string", mb_convert_kana('Abc123アガば漢', $mixed)); assertType("lowercase-string&numeric-string", strtolower($numericS)); - assertType("numeric-string", strtoupper($numericS)); + assertType("numeric-string&uppercase-string", strtoupper($numericS)); assertType("lowercase-string&numeric-string", mb_strtolower($numericS)); - assertType("numeric-string", mb_strtoupper($numericS)); + assertType("numeric-string&uppercase-string", mb_strtoupper($numericS)); assertType("numeric-string", lcfirst($numericS)); assertType("numeric-string", ucfirst($numericS)); assertType("numeric-string", ucwords($numericS)); - assertType("numeric-string", mb_convert_case($numericS, MB_CASE_UPPER)); + assertType("numeric-string&uppercase-string", mb_convert_case($numericS, MB_CASE_UPPER)); assertType("lowercase-string&numeric-string", mb_convert_case($numericS, MB_CASE_LOWER)); assertType("numeric-string", mb_convert_case($numericS, $mixed)); assertType("numeric-string", mb_convert_kana($numericS)); assertType("numeric-string", mb_convert_kana($numericS, $mixed)); assertType("lowercase-string&non-empty-string", strtolower($nonE)); - assertType("non-empty-string", strtoupper($nonE)); + assertType("non-empty-string&uppercase-string", strtoupper($nonE)); assertType("lowercase-string&non-empty-string", mb_strtolower($nonE)); - assertType("non-empty-string", mb_strtoupper($nonE)); + assertType("non-empty-string&uppercase-string", mb_strtoupper($nonE)); assertType("non-empty-string", lcfirst($nonE)); assertType("non-empty-string", ucfirst($nonE)); assertType("non-empty-string", ucwords($nonE)); - assertType("non-empty-string", mb_convert_case($nonE, MB_CASE_UPPER)); + assertType("non-empty-string&uppercase-string", mb_convert_case($nonE, MB_CASE_UPPER)); assertType("lowercase-string&non-empty-string", mb_convert_case($nonE, MB_CASE_LOWER)); assertType("non-empty-string", mb_convert_case($nonE, $mixed)); assertType("non-empty-string", mb_convert_kana($nonE)); assertType("non-empty-string", mb_convert_kana($nonE, $mixed)); assertType("lowercase-string", strtolower($literal)); - assertType("string", strtoupper($literal)); + assertType("uppercase-string", strtoupper($literal)); assertType("lowercase-string", mb_strtolower($literal)); - assertType("string", mb_strtoupper($literal)); + assertType("uppercase-string", mb_strtoupper($literal)); assertType("string", lcfirst($literal)); assertType("string", ucfirst($literal)); assertType("string", ucwords($literal)); - assertType("string", mb_convert_case($literal, MB_CASE_UPPER)); + assertType("uppercase-string", mb_convert_case($literal, MB_CASE_UPPER)); assertType("lowercase-string", mb_convert_case($literal, MB_CASE_LOWER)); assertType("string", mb_convert_case($literal, $mixed)); assertType("string", mb_convert_kana($literal)); assertType("string", mb_convert_kana($literal, $mixed)); assertType("lowercase-string", strtolower($lowercaseS)); - assertType("string", strtoupper($lowercaseS)); + assertType("uppercase-string", strtoupper($lowercaseS)); assertType("lowercase-string", mb_strtolower($lowercaseS)); - assertType("string", mb_strtoupper($lowercaseS)); + assertType("uppercase-string", mb_strtoupper($lowercaseS)); assertType("lowercase-string", lcfirst($lowercaseS)); assertType("string", ucfirst($lowercaseS)); assertType("string", ucwords($lowercaseS)); - assertType("string", mb_convert_case($lowercaseS, MB_CASE_UPPER)); + assertType("uppercase-string", mb_convert_case($lowercaseS, MB_CASE_UPPER)); assertType("lowercase-string", mb_convert_case($lowercaseS, MB_CASE_LOWER)); assertType("string", mb_convert_case($lowercaseS, $mixed)); assertType("lowercase-string", mb_convert_case($lowercaseS, rand(0, 1) ? MB_CASE_LOWER : MB_CASE_LOWER_SIMPLE)); diff --git a/tests/PHPStan/Analyser/nsrt/uppercase-string-implode.php b/tests/PHPStan/Analyser/nsrt/uppercase-string-implode.php new file mode 100644 index 00000000000..2ddf808da2a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/uppercase-string-implode.php @@ -0,0 +1,22 @@ + $commonStrings + * @param array $uppercaseStrings + */ + public function doFoo(string $s, string $ls, array $commonStrings, array $uppercaseStrings): void + { + assertType('string', implode($s, $commonStrings)); + assertType('string', implode($s, $uppercaseStrings)); + assertType('string', implode($ls, $commonStrings)); + assertType('uppercase-string', implode($ls, $uppercaseStrings)); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/uppercase-string-pad.php b/tests/PHPStan/Analyser/nsrt/uppercase-string-pad.php new file mode 100644 index 00000000000..7045582dc40 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/uppercase-string-pad.php @@ -0,0 +1,23 @@ += 8.0 + +namespace UppercaseStringSubstr; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @param uppercase-string $uppercase + */ + public function doSubstr(string $uppercase): void + { + assertType('uppercase-string', substr($uppercase, 5)); + assertType('uppercase-string', substr($uppercase, -5)); + assertType('uppercase-string', substr($uppercase, 0, 5)); + } + + /** + * @param uppercase-string $uppercase + */ + public function doMbSubstr(string $uppercase): void + { + assertType('uppercase-string', mb_substr($uppercase, 5)); + assertType('uppercase-string', mb_substr($uppercase, -5)); + assertType('uppercase-string', mb_substr($uppercase, 0, 5)); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/uppercase-string-trim.php b/tests/PHPStan/Analyser/nsrt/uppercase-string-trim.php new file mode 100644 index 00000000000..0c24268fafd --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/uppercase-string-trim.php @@ -0,0 +1,29 @@ +analyse([__DIR__ . '/data/lowercase-string.php'], $errors); } + public function testUppercaseString(): void + { + $errors = [ + [ + "Strict comparison using === between uppercase-string and 'ab' will always evaluate to false.", + 10, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using === between 'ab' and uppercase-string will always evaluate to false.", + 11, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using !== between 'ab' and uppercase-string will always evaluate to true.", + 12, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using === between uppercase-string and 'aBc' will always evaluate to false.", + 15, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Strict comparison using !== between uppercase-string and 'aBc' will always evaluate to true.", + 16, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]; + + if (PHP_VERSION_ID < 80000) { + $errors[] = [ + "Strict comparison using === between uppercase-string|false and 'ab' will always evaluate to false.", + 28, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ]; + } else { + $errors[] = [ + "Strict comparison using === between uppercase-string and 'ab' will always evaluate to false.", + 28, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ]; + } + + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/uppercase-string.php'], $errors); + } + public function testBug10493(): void { $this->checkAlwaysTrueStrictComparison = true; diff --git a/tests/PHPStan/Rules/Comparison/data/uppercase-string.php b/tests/PHPStan/Rules/Comparison/data/uppercase-string.php new file mode 100644 index 00000000000..fbd7a3eaf73 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/uppercase-string.php @@ -0,0 +1,31 @@ +checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/uppercase-string.php'], [ + [ + 'Parameter #1 $s of method UppercaseString\Bar::acceptUppercaseString() expects uppercase-string, \'NotUpperCase\' given.', + 26, + ], + [ + 'Parameter #1 $s of method UppercaseString\Bar::acceptUppercaseString() expects uppercase-string, string given.', + 28, + ], + [ + 'Parameter #1 $s of method UppercaseString\Bar::acceptUppercaseString() expects uppercase-string, numeric-string given.', + 30, + ], + ]); + } + public function testBug10159(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/uppercase-string.php b/tests/PHPStan/Rules/Methods/data/uppercase-string.php new file mode 100644 index 00000000000..de7500ee8c3 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/uppercase-string.php @@ -0,0 +1,33 @@ +acceptUppercaseString('NotUpperCase'); + $this->acceptUppercaseString('UPPERCASE'); + $this->acceptUppercaseString($string); + $this->acceptUppercaseString($uppercaseString); + $this->acceptUppercaseString($numericString); + $this->acceptUppercaseString($nonEmptyLowercaseString); + } +} diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 036d2b0f1fb..2098a1f02b3 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -154,12 +154,12 @@ public function testGeneralize(): void $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&non-falsy-string', (new ConstantStringType('A'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&lowercase-string&non-empty-string&numeric-string', (new ConstantStringType('0'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('1.123'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType(' 1 1 '))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('+1+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&non-falsy-string&uppercase-string', (new ConstantStringType('A'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-empty-string&numeric-string&uppercase-string', (new ConstantStringType('0'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string', (new ConstantStringType('1.123'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&uppercase-string', (new ConstantStringType(' 1 1 '))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string', (new ConstantStringType('+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&lowercase-string&non-falsy-string&uppercase-string', (new ConstantStringType('+1+1'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string&numeric-string', (new ConstantStringType('1e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('literal-string&lowercase-string&non-falsy-string', (new ConstantStringType('1e91e9'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); diff --git a/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php b/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php index 2e2bcf976c5..cd278837d6b 100644 --- a/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php +++ b/tests/PHPStan/Type/Constant/OversizedArrayBuilderTest.php @@ -31,6 +31,11 @@ public function dataBuild(): iterable 'non-empty-array&oversized-array', ]; + yield [ + '[1, 2, 3, ...[1, \'FOO\' => 2, 3]]', + 'non-empty-array&oversized-array', + ]; + yield [ '[1, 2, 2 => 3]', 'non-empty-list&oversized-array', @@ -51,6 +56,10 @@ public function dataBuild(): iterable '[1, \'foo\' => 2, 3]', 'non-empty-array&oversized-array', ]; + yield [ + '[1, \'FOO\' => 2, 3]', + 'non-empty-array&oversized-array', + ]; } /** diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 7c9bcc0722c..0b5c5c08180 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -8,6 +8,7 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -465,6 +466,21 @@ public function dataDescribe(): iterable VerbosityLevel::precise(), 'lowercase-string', ]; + yield [ + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + VerbosityLevel::typeOnly(), + 'string', + ]; + yield [ + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + VerbosityLevel::value(), + 'string', + ]; + yield [ + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + VerbosityLevel::precise(), + 'uppercase-string', + ]; } /** diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 55dc30542bd..0e125a4a81e 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -23,6 +23,7 @@ use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasOffsetValueType; @@ -2009,6 +2010,54 @@ public function dataUnion(): iterable UnionType::class, 'literal-string|lowercase-string', ], + [ + [ + new StringType(), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + StringType::class, + 'string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNumericStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + UnionType::class, + 'numeric-string|uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + UnionType::class, + 'non-falsy-string|uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + UnionType::class, + 'non-empty-string|uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLiteralStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + UnionType::class, + 'literal-string|uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + UnionType::class, + 'lowercase-string|uppercase-string', + ], [ [ TemplateTypeFactory::create( @@ -3934,6 +3983,54 @@ public function dataIntersect(): iterable IntersectionType::class, 'literal-string&lowercase-string', ], + [ + [ + new StringType(), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNumericStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'numeric-string&uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'non-falsy-string&uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'non-empty-string&uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLiteralStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'literal-string&uppercase-string', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + IntersectionType::class, + 'lowercase-string&uppercase-string', + ], ]; if (PHP_VERSION_ID < 80100) { @@ -4423,6 +4520,23 @@ public function dataIntersect(): iterable ConstantStringType::class, '\'foo\'', ]; + + yield [ + [ + new ConstantStringType('foo'), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + yield [ + [ + new ConstantStringType('FOO'), + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + ], + ConstantStringType::class, + '\'FOO\'', + ]; } /** diff --git a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php index eebdb080142..ee4e6b55c74 100644 --- a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php +++ b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -223,6 +224,11 @@ public function dataToPhpDocNode(): iterable 'lowercase-string', ]; + yield [ + new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]), + 'uppercase-string', + ]; + yield [ new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), 'non-empty-string', @@ -333,6 +339,11 @@ public function dataToPhpDocNodeWithoutCheckingEquals(): iterable '(literal-string & lowercase-string & non-falsy-string)', ]; + yield [ + new ConstantStringType("FOO\nBAR\nBAZ"), + '(literal-string & non-falsy-string & uppercase-string)', + ]; + yield [ new ConstantIntegerType(PHP_INT_MIN), (string) PHP_INT_MIN, From 95fbd572ad224f72eb5b4886049ab35873d32e6e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Nov 2024 09:12:30 +0100 Subject: [PATCH 0821/3097] ParseStrParameterOutTypeExtension - use explicit mixed array item type --- src/Type/Php/ParseStrParameterOutTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/ParseStrParameterOutTypeExtension.php b/src/Type/Php/ParseStrParameterOutTypeExtension.php index e0c7aa47e5a..caabc319bf9 100644 --- a/src/Type/Php/ParseStrParameterOutTypeExtension.php +++ b/src/Type/Php/ParseStrParameterOutTypeExtension.php @@ -53,7 +53,7 @@ public function getParameterOutTypeFromFunctionCall(FunctionReflection $function return new ArrayType( new UnionType([new StringType(), new IntegerType()]), - new UnionType([new ArrayType(new MixedType(), new MixedType()), $valueType]), + new UnionType([new ArrayType(new MixedType(), new MixedType(true)), $valueType]), ); } From 08df07a3e247336b406cc71d5cedb3ffb8121a6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Nov 2024 09:22:26 +0100 Subject: [PATCH 0822/3097] Regenerate baseline --- phpstan-baseline.neon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 141752f21ba..3b47d2e5fc5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -784,7 +784,8 @@ parameters: path: src/Type/Accessory/AccessoryUppercaseStringType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + message: '#^Doing instanceof PHPStan\\Type\\IntersectionType is error\-prone and deprecated\.$#' + identifier: phpstanApi.instanceofType count: 1 path: src/Type/Accessory/HasMethodType.php From f61d3247db160c875a2e1893aaee519963830f99 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Nov 2024 09:27:30 +0100 Subject: [PATCH 0823/3097] Patch wrong namespace prefixing in PHAR in WindowsRegistryLogicalFinder --- compiler/build/scoper.inc.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 0ea6df31ec5..ba198a0c910 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -218,6 +218,12 @@ function (string $filePath, string $prefix, string $content): string { return str_replace(sprintf('use %s\\PhpParser;', $prefix), 'use PhpParser;', $content); }, + function (string $filePath, string $prefix, string $content): string { + if ($filePath !== 'vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php') { + return $content; + } + return str_replace(sprintf('%s\\\\reg query', $prefix), 'reg query', $content); + }, ], 'exclude-namespaces' => [ 'PHPStan', From 162f774858b461b9cc89f0c604c798381053dc32 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 20 Nov 2024 09:33:46 +0100 Subject: [PATCH 0824/3097] Fix `static` return type in php-8-stubs --- .../SignatureMap/Php8SignatureMapProvider.php | 13 +++++++++++-- tests/PHPStan/Analyser/nsrt/bug-12077.php | 11 +++++++++++ .../SignatureMap/Php8SignatureMapProviderTest.php | 2 ++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12077.php diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 7bfdf37f9b2..59cbedb60c1 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -16,6 +16,7 @@ use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\PassedByReference; +use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\MixedType; @@ -50,6 +51,7 @@ public function __construct( private FileTypeMapper $fileTypeMapper, private PhpVersion $phpVersion, private InitializerExprTypeResolver $initializerExprTypeResolver, + private ReflectionProviderProvider $reflectionProviderProvider, ) { $this->map = new Php8StubsMap($phpVersion->getVersionId()); @@ -392,6 +394,13 @@ private function getSignature( $phpDocReturnType = $phpDoc->getReturnTag()->getType(); } } + + $classReflection = null; + if ($className !== null) { + $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); + $classReflection = $reflectionProvider->getClass($className); + } + $parameters = []; $variadic = false; foreach ($function->getParams() as $param) { @@ -399,7 +408,7 @@ private function getSignature( if (!$name instanceof Variable || !is_string($name->name)) { throw new ShouldNotHappenException(); } - $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, null); + $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection); $parameters[] = new ParameterSignature( $name->name, $param->default !== null || $param->variadic, @@ -417,7 +426,7 @@ private function getSignature( $variadic = $variadic || $param->variadic; } - $returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), null); + $returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), $classReflection); return new FunctionSignature( $parameters, diff --git a/tests/PHPStan/Analyser/nsrt/bug-12077.php b/tests/PHPStan/Analyser/nsrt/bug-12077.php new file mode 100644 index 00000000000..07163ecaa10 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12077.php @@ -0,0 +1,11 @@ += 8.3 + +namespace Bug12077; + +use ReflectionMethod; +use function PHPStan\Testing\assertType; + +function (): void { + $methodInfo = ReflectionMethod::createFromMethodName("Exception::getMessage"); + assertType(ReflectionMethod::class, $methodInfo); +}; diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index 57cb091e199..f912e0d78ef 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; +use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -162,6 +163,7 @@ private function createProvider(): Php8SignatureMapProvider self::getContainer()->getByType(FileTypeMapper::class), $phpVersion, self::getContainer()->getByType(InitializerExprTypeResolver::class), + self::getContainer()->getByType(ReflectionProviderProvider::class), ); } From 01bf65c59918a6e686b420007a4088e61d49b151 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 20 Nov 2024 13:42:31 +0100 Subject: [PATCH 0825/3097] Integer::toString() and Float::toString() are uppercase-string --- src/Type/FloatType.php | 2 + src/Type/IntegerRangeType.php | 3 ++ src/Type/IntegerType.php | 2 + .../PHPStan/Analyser/nsrt/array-fill-keys.php | 2 +- .../Analyser/nsrt/array-key-exists.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-10863.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-11129.php | 54 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-11716.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-4587.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-7387.php | 8 +-- tests/PHPStan/Analyser/nsrt/bug-8635.php | 2 +- .../Analyser/nsrt/cast-to-numeric-string.php | 42 +++++++-------- tests/PHPStan/Analyser/nsrt/filter-var.php | 4 +- tests/PHPStan/Analyser/nsrt/generics.php | 6 +-- tests/PHPStan/Analyser/nsrt/key-exists.php | 8 +-- .../non-empty-string-replace-functions.php | 4 +- .../PHPStan/Analyser/nsrt/range-to-string.php | 2 +- .../nsrt/set-type-type-specifying.php | 4 +- tests/PHPStan/Analyser/nsrt/strval.php | 6 +-- .../PHPStan/Rules/Generics/data/bug-6301.php | 2 +- 20 files changed, 88 insertions(+), 81 deletions(-) diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 40ea01b5db6..d0860248680 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -7,6 +7,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonArrayTypeTrait; @@ -134,6 +135,7 @@ public function toString(): Type { return new IntersectionType([ new StringType(), + new AccessoryUppercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 248d994266e..9d8e735f35e 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -13,6 +13,7 @@ use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use function array_filter; @@ -487,6 +488,7 @@ public function toString(): Type return new IntersectionType([ new StringType(), new AccessoryLowercaseStringType(), + new AccessoryUppercaseStringType(), new AccessoryNumericStringType(), new AccessoryNonFalsyStringType(), ]); @@ -495,6 +497,7 @@ public function toString(): Type return new IntersectionType([ new StringType(), new AccessoryLowercaseStringType(), + new AccessoryUppercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 354067f0a97..535928c045a 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -8,6 +8,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonArrayTypeTrait; @@ -83,6 +84,7 @@ public function toString(): Type return new IntersectionType([ new StringType(), new AccessoryLowercaseStringType(), + new AccessoryUppercaseStringType(), new AccessoryNumericStringType(), ]); } diff --git a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php index 50541863204..00aa87fc69c 100644 --- a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php +++ b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php @@ -91,7 +91,7 @@ function withNotConstantArray(array $foo, array $bar, array $baz, array $floats, assertType("array", array_fill_keys($foo, null)); assertType("array", array_fill_keys($bar, null)); assertType("array<'foo', null>", array_fill_keys($baz, null)); - assertType("array", array_fill_keys($floats, null)); + assertType("array", array_fill_keys($floats, null)); assertType("array", array_fill_keys($mixed, null)); assertType('array', array_fill_keys($list, null)); assertType('*ERROR*', array_fill_keys($objectsWithoutToString, null)); diff --git a/tests/PHPStan/Analyser/nsrt/array-key-exists.php b/tests/PHPStan/Analyser/nsrt/array-key-exists.php index 3ae615b2d9a..a7246dd0433 100644 --- a/tests/PHPStan/Analyser/nsrt/array-key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/array-key-exists.php @@ -50,16 +50,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (array_key_exists($key2, $a)) { - assertType('lowercase-string&numeric-string', $key2); + assertType('lowercase-string&numeric-string&uppercase-string', $key2); } if (array_key_exists($key3, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key3); + assertType('int|(lowercase-string&numeric-string&uppercase-string)', $key3); } if (array_key_exists($key4, $a)) { - assertType('(int|(lowercase-string&numeric-string))', $key4); + assertType('(int|(lowercase-string&numeric-string&uppercase-string))', $key4); } if (array_key_exists($key5, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key5); + assertType('int|(lowercase-string&numeric-string&uppercase-string)', $key5); } if (array_key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10863.php b/tests/PHPStan/Analyser/nsrt/bug-10863.php index 275bc3774e8..c88d3543824 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10863.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10863.php @@ -12,7 +12,7 @@ class Foo */ public function doFoo($b): void { - assertType('lowercase-string&non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string&uppercase-string', '@' . $b); } /** @@ -20,7 +20,7 @@ public function doFoo($b): void */ public function doFoo2($b): void { - assertType('lowercase-string&non-falsy-string', '@' . $b); + assertType('lowercase-string&non-falsy-string&uppercase-string', '@' . $b); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11129.php b/tests/PHPStan/Analyser/nsrt/bug-11129.php index 1f60b4089fb..a845bf1d2a5 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11129.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11129.php @@ -21,14 +21,14 @@ public function foo( $maybeNegativeConstStrings, $maybeNonNumericConstStrings, $maybeFloatConstStrings, bool $bool, float $float ): void { - assertType('lowercase-string&non-falsy-string', '0'.$i); - assertType('lowercase-string&non-falsy-string&numeric-string', $i.'0'); + assertType('lowercase-string&non-falsy-string&uppercase-string', '0'.$i); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $i.'0'); - assertType('lowercase-string&non-falsy-string&numeric-string', '0'.$positiveInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.'0'); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', '0'.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $positiveInt.'0'); - assertType('lowercase-string&non-falsy-string', '0'.$negativeInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.'0'); + assertType('lowercase-string&non-falsy-string&uppercase-string', '0'.$negativeInt); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $negativeInt.'0'); assertType("'00'|'01'|'02'", '0'.$positiveConstStrings); assertType( "'00'|'10'|'20'", $positiveConstStrings.'0'); @@ -39,36 +39,36 @@ public function foo( assertType("'00'|'01'|'0a'", '0'.$maybeNonNumericConstStrings); assertType("'00'|'10'|'a0'", $maybeNonNumericConstStrings.'0'); - assertType('lowercase-string&non-falsy-string&numeric-string', $i.$positiveConstStrings); - assertType('lowercase-string&non-falsy-string', $positiveConstStrings.$i); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $i.$positiveConstStrings); + assertType('lowercase-string&non-falsy-string&uppercase-string', $positiveConstStrings.$i); - assertType('lowercase-string&non-falsy-string', $i.$maybeNegativeConstStrings); - assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$i); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string&uppercase-string', $maybeNegativeConstStrings.$i); assertType('lowercase-string&non-falsy-string', $i.$maybeNonNumericConstStrings); assertType('lowercase-string&non-falsy-string', $maybeNonNumericConstStrings.$i); - assertType('lowercase-string&non-falsy-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' - assertType('lowercase-string&non-falsy-string', $maybeFloatConstStrings.$i); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.$maybeFloatConstStrings); // could be 'lowercase-string&non-falsy-string&numeric-string' + assertType('lowercase-string&non-falsy-string&uppercase-string', $maybeFloatConstStrings.$i); - assertType('lowercase-string&non-empty-string&numeric-string', $i.$bool); - assertType('lowercase-string&non-empty-string', $bool.$i); - assertType('lowercase-string&non-falsy-string&numeric-string', $positiveInt.$bool); - assertType('lowercase-string&non-falsy-string&numeric-string', $bool.$positiveInt); - assertType('lowercase-string&non-falsy-string&numeric-string', $negativeInt.$bool); - assertType('lowercase-string&non-falsy-string', $bool.$negativeInt); + assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $i.$bool); + assertType('lowercase-string&non-empty-string&uppercase-string', $bool.$i); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $positiveInt.$bool); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $bool.$positiveInt); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $negativeInt.$bool); + assertType('lowercase-string&non-falsy-string&uppercase-string', $bool.$negativeInt); - assertType('lowercase-string&non-falsy-string', $i.$i); - assertType('lowercase-string&non-falsy-string', $negativeInt.$negativeInt); - assertType('lowercase-string&non-falsy-string', $maybeNegativeConstStrings.$negativeInt); - assertType('lowercase-string&non-falsy-string', $negativeInt.$maybeNegativeConstStrings); + assertType('lowercase-string&non-falsy-string&uppercase-string', $i.$i); + assertType('lowercase-string&non-falsy-string&uppercase-string', $negativeInt.$negativeInt); + assertType('lowercase-string&non-falsy-string&uppercase-string', $maybeNegativeConstStrings.$negativeInt); + assertType('lowercase-string&non-falsy-string&uppercase-string', $negativeInt.$maybeNegativeConstStrings); // https://3v4l.org/BCS2K - assertType('non-falsy-string', $float.$float); - assertType('non-falsy-string&numeric-string', $float.$positiveInt); - assertType('non-falsy-string', $float.$negativeInt); - assertType('non-falsy-string', $float.$i); - assertType('non-falsy-string', $i.$float); // could be 'non-falsy-string&numeric-string' + assertType('non-falsy-string&uppercase-string', $float.$float); + assertType('non-falsy-string&numeric-string&uppercase-string', $float.$positiveInt); + assertType('non-falsy-string&uppercase-string', $float.$negativeInt); + assertType('non-falsy-string&uppercase-string', $float.$i); + assertType('non-falsy-string&uppercase-string', $i.$float); // could be 'non-falsy-string&numeric-string&uppercase-string' assertType('non-falsy-string', $numericString.$float); assertType('non-falsy-string', $numericString.$maybeFloatConstStrings); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11716.php b/tests/PHPStan/Analyser/nsrt/bug-11716.php index 83c10f32258..a2e86ffbec0 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11716.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11716.php @@ -75,7 +75,7 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyed assertType('int', $i); if (isset($intKeyedArr[$s])) { - assertType("lowercase-string&numeric-string", $s); + assertType("lowercase-string&numeric-string&uppercase-string", $s); } else { assertType('string', $s); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4587.php b/tests/PHPStan/Analyser/nsrt/bug-4587.php index 623fea93af1..061c953a286 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4587.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4587.php @@ -27,11 +27,11 @@ public function b(): void $type = array_map(static function (array $result): array { assertType('array{a: int}', $result); $result['a'] = (string) $result['a']; - assertType('array{a: lowercase-string&numeric-string}', $result); + assertType('array{a: lowercase-string&numeric-string&uppercase-string}', $result); return $result; }, $results); - assertType('list', $type); + assertType('list', $type); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7387.php b/tests/PHPStan/Analyser/nsrt/bug-7387.php index 4f28696fb99..1b283a79901 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7387.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7387.php @@ -29,7 +29,7 @@ public function inputTypes(int $i, float $f, string $s, int $intRange) { public function specifiers(int $i) { // https://3v4l.org/fmVIg - assertType('lowercase-string&numeric-string', sprintf('%14s', $i)); + assertType('lowercase-string&numeric-string&uppercase-string', sprintf('%14s', $i)); assertType('lowercase-string&numeric-string', sprintf('%d', $i)); @@ -59,9 +59,9 @@ public function specifiers(int $i) { */ public function positionalArgs($mixed, int $i, float $f, string $s, int $posInt, int $negInt, int $nonZeroIntRange, int $intRange) { // https://3v4l.org/vVL0c - assertType('lowercase-string&numeric-string', sprintf('%2$6s', $mixed, $i)); - assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $posInt)); - assertType('lowercase-string&non-falsy-string&numeric-string', sprintf('%2$6s', $mixed, $negInt)); + assertType('lowercase-string&numeric-string&uppercase-string', sprintf('%2$6s', $mixed, $i)); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', sprintf('%2$6s', $mixed, $posInt)); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', sprintf('%2$6s', $mixed, $negInt)); assertType("' 1'|' 2'|' 3'|' 4'|' 5'", sprintf('%2$6s', $mixed, $nonZeroIntRange)); // https://3v4l.org/1ECIq diff --git a/tests/PHPStan/Analyser/nsrt/bug-8635.php b/tests/PHPStan/Analyser/nsrt/bug-8635.php index fe49aa8a2db..46d5b728b86 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8635.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8635.php @@ -8,6 +8,6 @@ class HelloWorld { public function EchoInt(int $value): void { - assertType('lowercase-string&numeric-string', "$value"); + assertType('lowercase-string&numeric-string&uppercase-string', "$value"); } } diff --git a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php index 7c13500af25..dfe47aa39b3 100644 --- a/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/nsrt/cast-to-numeric-string.php @@ -13,13 +13,13 @@ * @param 1 $constantInt */ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('lowercase-string&numeric-string', (string)$a); - assertType('numeric-string', (string)$b); + assertType('lowercase-string&numeric-string&uppercase-string', (string)$a); + assertType('numeric-string&uppercase-string', (string)$b); assertType('numeric-string', (string)$numeric); assertType('numeric-string', (string)$numeric2); - assertType('numeric-string', (string)$number); - assertType('lowercase-string&non-falsy-string&numeric-string', (string)$positive); - assertType('lowercase-string&non-falsy-string&numeric-string', (string)$negative); + assertType('numeric-string&uppercase-string', (string)$number); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', (string)$positive); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', (string)$negative); assertType("'1'", (string)$constantInt); } @@ -32,32 +32,32 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param 1 $constantInt */ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('lowercase-string&numeric-string', '' . $a); - assertType('numeric-string', '' . $b); + assertType('lowercase-string&numeric-string&uppercase-string', '' . $a); + assertType('numeric-string&uppercase-string', '' . $b); assertType('numeric-string', '' . $numeric); assertType('numeric-string', '' . $numeric2); - assertType('numeric-string', '' . $number); - assertType('lowercase-string&non-falsy-string&numeric-string', '' . $positive); - assertType('lowercase-string&non-falsy-string&numeric-string', '' . $negative); + assertType('numeric-string&uppercase-string', '' . $number); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', '' . $positive); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', '' . $negative); assertType("'1'", '' . $constantInt); - assertType('lowercase-string&numeric-string', $a . ''); - assertType('numeric-string', $b . ''); + assertType('lowercase-string&numeric-string&uppercase-string', $a . ''); + assertType('numeric-string&uppercase-string', $b . ''); assertType('numeric-string', $numeric . ''); assertType('numeric-string', $numeric2 . ''); - assertType('numeric-string', $number . ''); - assertType('lowercase-string&non-falsy-string&numeric-string', $positive . ''); - assertType('lowercase-string&non-falsy-string&numeric-string', $negative . ''); + assertType('numeric-string&uppercase-string', $number . ''); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $positive . ''); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $negative . ''); assertType("'1'", $constantInt . ''); } function concatAssignEmptyString(int $i, float $f) { $i .= ''; - assertType('lowercase-string&numeric-string', $i); + assertType('lowercase-string&numeric-string&uppercase-string', $i); $s = ''; $s .= $f; - assertType('numeric-string', $s); + assertType('numeric-string&uppercase-string', $s); } /** @@ -66,13 +66,13 @@ function concatAssignEmptyString(int $i, float $f) { */ function integerRangeToString($positive, $negative) { - assertType('lowercase-string&numeric-string', (string) $positive); - assertType('lowercase-string&numeric-string', (string) $negative); + assertType('lowercase-string&numeric-string&uppercase-string', (string) $positive); + assertType('lowercase-string&numeric-string&uppercase-string', (string) $negative); if ($positive !== 0) { - assertType('lowercase-string&non-falsy-string&numeric-string', (string) $positive); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', (string) $positive); } if ($negative !== 0) { - assertType('lowercase-string&non-falsy-string&numeric-string', (string) $negative); + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', (string) $negative); } } diff --git a/tests/PHPStan/Analyser/nsrt/filter-var.php b/tests/PHPStan/Analyser/nsrt/filter-var.php index 28580c448c4..abe5331fc6f 100644 --- a/tests/PHPStan/Analyser/nsrt/filter-var.php +++ b/tests/PHPStan/Analyser/nsrt/filter-var.php @@ -154,11 +154,11 @@ public function scalars(bool $bool, float $float, int $int, string $string, int assertType("''|'1'", filter_var($bool)); assertType("'1'", filter_var(true)); assertType("''", filter_var(false)); - assertType('numeric-string', filter_var($float)); + assertType('numeric-string&uppercase-string', filter_var($float)); assertType("'17'", filter_var(17.0)); assertType("'17.1'", filter_var(17.1)); assertType("'1.0E-50'", filter_var(1e-50)); - assertType('lowercase-string&numeric-string', filter_var($int)); + assertType('lowercase-string&numeric-string&uppercase-string', filter_var($int)); assertType("'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", filter_var($intRange)); assertType("'17'", filter_var(17)); assertType('string', filter_var($string)); diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index 873ad66b6d5..80f10030e8f 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -147,10 +147,10 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('Closure(int): (lowercase-string&numeric-string)', function (int $a): string { + assertType('Closure(int): (lowercase-string&numeric-string&uppercase-string)', function (int $a): string { return (string)$a; }); - assertType('array', f($arrayOfInt, function (int $a): string { + assertType('array', f($arrayOfInt, function (int $a): string { return (string)$a; })); assertType('Closure(mixed): string', function ($a): string { @@ -224,7 +224,7 @@ function testArrayMap(array $listOfIntegers) return (string) $int; }, $listOfIntegers); - assertType('array', $strings); + assertType('array', $strings); } /** diff --git a/tests/PHPStan/Analyser/nsrt/key-exists.php b/tests/PHPStan/Analyser/nsrt/key-exists.php index 3c85802e739..23a2d0930d7 100644 --- a/tests/PHPStan/Analyser/nsrt/key-exists.php +++ b/tests/PHPStan/Analyser/nsrt/key-exists.php @@ -49,16 +49,16 @@ public function doBar(array $a, array $b, array $c, int $key1, string $key2, int assertType('int', $key1); } if (key_exists($key2, $a)) { - assertType('lowercase-string&numeric-string', $key2); + assertType('lowercase-string&numeric-string&uppercase-string', $key2); } if (key_exists($key3, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key3); + assertType('int|(lowercase-string&numeric-string&uppercase-string)', $key3); } if (key_exists($key4, $a)) { - assertType('(int|(lowercase-string&numeric-string))', $key4); + assertType('(int|(lowercase-string&numeric-string&uppercase-string))', $key4); } if (key_exists($key5, $a)) { - assertType('int|(lowercase-string&numeric-string)', $key5); + assertType('int|(lowercase-string&numeric-string&uppercase-string)', $key5); } if (key_exists($key1, $b)) { diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-replace-functions.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-replace-functions.php index 3627b8a409b..a774b4eba44 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-replace-functions.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-replace-functions.php @@ -26,9 +26,9 @@ public function replace(string $search, string $replacement, string $subject){ function foo(float $f) { $s = (string) $f; - assertType('numeric-string', $s); + assertType('numeric-string&uppercase-string', $s); $price = str_replace(',', '.', $s); - assertType('non-empty-string', $price); + assertType('non-empty-string&uppercase-string', $price); } } diff --git a/tests/PHPStan/Analyser/nsrt/range-to-string.php b/tests/PHPStan/Analyser/nsrt/range-to-string.php index 2d70eb4babc..49bb179309d 100644 --- a/tests/PHPStan/Analyser/nsrt/range-to-string.php +++ b/tests/PHPStan/Analyser/nsrt/range-to-string.php @@ -17,6 +17,6 @@ public function sayHello($i, $ii, $maxlong, $toolong): void assertType("'10'|'5'|'6'|'7'|'8'|'9'", (string) $i); assertType("'-1'|'-10'|'-2'|'-3'|'-4'|'-5'|'-6'|'-7'|'-8'|'-9'|'0'|'1'|'10'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'", (string) $ii); assertType("'0'|'1'|'10'|'100'|'101'|'102'|'103'|'104'|'105'|'106'|'107'|'108'|'109'|'11'|'110'|'111'|'112'|'113'|'114'|'115'|'116'|'117'|'118'|'119'|'12'|'120'|'121'|'122'|'123'|'124'|'125'|'126'|'127'|'128'|'13'|'14'|'15'|'16'|'17'|'18'|'19'|'2'|'20'|'21'|'22'|'23'|'24'|'25'|'26'|'27'|'28'|'29'|'3'|'30'|'31'|'32'|'33'|'34'|'35'|'36'|'37'|'38'|'39'|'4'|'40'|'41'|'42'|'43'|'44'|'45'|'46'|'47'|'48'|'49'|'5'|'50'|'51'|'52'|'53'|'54'|'55'|'56'|'57'|'58'|'59'|'6'|'60'|'61'|'62'|'63'|'64'|'65'|'66'|'67'|'68'|'69'|'7'|'70'|'71'|'72'|'73'|'74'|'75'|'76'|'77'|'78'|'79'|'8'|'80'|'81'|'82'|'83'|'84'|'85'|'86'|'87'|'88'|'89'|'9'|'90'|'91'|'92'|'93'|'94'|'95'|'96'|'97'|'98'|'99'", (string) $maxlong); - assertType("lowercase-string&numeric-string", (string) $toolong); + assertType("lowercase-string&numeric-string&uppercase-string", (string) $toolong); } } diff --git a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php index 1ab4badbe56..f7513c60450 100644 --- a/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php +++ b/tests/PHPStan/Analyser/nsrt/set-type-type-specifying.php @@ -11,10 +11,10 @@ function doString(string $s, int $i, float $f, array $a, object $o) assertType('string', $s); settype($i, 'string'); - assertType('lowercase-string&numeric-string', $i); + assertType('lowercase-string&numeric-string&uppercase-string', $i); settype($f, 'string'); - assertType('numeric-string', $f); + assertType('numeric-string&uppercase-string', $f); settype($a, 'string'); assertType('*ERROR*', $a); diff --git a/tests/PHPStan/Analyser/nsrt/strval.php b/tests/PHPStan/Analyser/nsrt/strval.php index a6c43978936..b28f31549b8 100644 --- a/tests/PHPStan/Analyser/nsrt/strval.php +++ b/tests/PHPStan/Analyser/nsrt/strval.php @@ -17,9 +17,9 @@ function strvalTest(string $string, string $class): void assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); assertType('\'42\'', strval(42)); - assertType('lowercase-string&numeric-string', strval(rand())); - assertType('numeric-string', strval(rand() * 0.1)); - assertType('lowercase-string&numeric-string', strval(strval(rand()))); + assertType('lowercase-string&numeric-string&uppercase-string', strval(rand())); + assertType('numeric-string&uppercase-string', strval(rand() * 0.1)); + assertType('lowercase-string&numeric-string&uppercase-string', strval(strval(rand()))); assertType('class-string', strval($class)); assertType('string', strval(new \Exception())); assertType('*ERROR*', strval(new \stdClass())); diff --git a/tests/PHPStan/Rules/Generics/data/bug-6301.php b/tests/PHPStan/Rules/Generics/data/bug-6301.php index 73dff935f20..8d325982942 100644 --- a/tests/PHPStan/Rules/Generics/data/bug-6301.php +++ b/tests/PHPStan/Rules/Generics/data/bug-6301.php @@ -22,7 +22,7 @@ public function str($s) * @param literal-string $literalString */ public function foo(int $i, $nonEmpty, $numericString, $literalString):void { - assertType('lowercase-string&numeric-string', $this->str((string) $i)); + assertType('lowercase-string&numeric-string&uppercase-string', $this->str((string) $i)); assertType('non-empty-string', $this->str($nonEmpty)); assertType('numeric-string', $this->str($numericString)); assertType('literal-string', $this->str($literalString)); From 78ac6f229b8b0e94f00f06e185a22a438435aec7 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 20 Nov 2024 14:20:15 +0100 Subject: [PATCH 0826/3097] Fix subtracting enums inside `in_array` Co-authored-by: Ondrej Mirtes --- ...InArrayFunctionTypeSpecifyingExtension.php | 4 +- tests/PHPStan/Analyser/nsrt/enum-in-array.php | 187 ++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/enum-in-array.php diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 62c022c8e1b..997cfea7528 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -110,7 +110,9 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $context->true() || ( $context->false() - && count($arrayValueType->getFiniteTypes()) === 1 + && count($arrayValueType->getFiniteTypes()) > 0 + && count($needleType->getFiniteTypes()) > 0 + && $arrayType->isIterableAtLeastOnce()->yes() ) ) { $specifiedTypes = $this->typeSpecifier->create( diff --git a/tests/PHPStan/Analyser/nsrt/enum-in-array.php b/tests/PHPStan/Analyser/nsrt/enum-in-array.php new file mode 100644 index 00000000000..ca34261bc8d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/enum-in-array.php @@ -0,0 +1,187 @@ += 8.1 + +use function PHPStan\Testing\assertType; + +enum MyEnum: string +{ + + case A = 'a'; + case B = 'b'; + case C = 'c'; + + const SET_AB = [self::A, self::B]; + const SET_C = [self::C]; + const SET_ABC = [self::A, self::B, self::C]; + + public function test1(): void + { + foreach (self::cases() as $enum) { + if (in_array($enum, MyEnum::SET_AB, true)) { + assertType('MyEnum::A|MyEnum::B', $enum); + } elseif (in_array($enum, MyEnum::SET_C, true)) { + assertType('MyEnum::C', $enum); + } else { + assertType('*NEVER*', $enum); + } + } + } + + public function test2(): void + { + foreach (self::cases() as $enum) { + if (in_array($enum, MyEnum::SET_ABC, true)) { + assertType('MyEnum::A|MyEnum::B|MyEnum::C', $enum); + } else { + assertType('*NEVER*', $enum); + } + } + } + + public function test3(): void + { + foreach (self::cases() as $enum) { + if (in_array($enum, MyEnum::SET_C, true)) { + assertType('MyEnum::C', $enum); + } else { + assertType('MyEnum::A|MyEnum::B', $enum); + } + } + } + + public function test4(): void + { + foreach ([MyEnum::C] as $enum) { + if (in_array($enum, MyEnum::SET_C, true)) { + assertType('MyEnum::C', $enum); + } else { + assertType('*NEVER*', $enum); + } + } + } + + public function testNegative1(): void + { + foreach (self::cases() as $enum) { + if (!in_array($enum, MyEnum::SET_AB, true)) { + assertType('MyEnum::C', $enum); + } else { + assertType('MyEnum::A|MyEnum::B', $enum); + } + } + } + + public function testNegative2(): void + { + foreach (self::cases() as $enum) { + if (!in_array($enum, MyEnum::SET_AB, true)) { + assertType('MyEnum::C', $enum); + } elseif (!in_array($enum, MyEnum::SET_AB, true)) { + assertType('*NEVER*', $enum); + } + } + } + + public function testNegative3(): void + { + foreach ([MyEnum::C] as $enum) { + if (!in_array($enum, MyEnum::SET_C, true)) { + assertType('*NEVER*', $enum); + } + } + } + + /** + * @param array $array + */ + public function testNegative4(MyEnum $enum, array $array): void + { + if (!in_array($enum, $array, true)) { + assertType('MyEnum', $enum); + assertType('array', $array); + } else { + assertType('MyEnum', $enum); + assertType('non-empty-array', $array); + } + } + +} + +class InArrayEnum +{ + + /** @param list $list */ + public function testPositive(MyEnum $enum, array $list): void + { + if (in_array($enum, $list, true)) { + return; + } + + assertType(MyEnum::class, $enum); + assertType('list', $list); + } + + /** @param list $list */ + public function testNegative(MyEnum $enum, array $list): void + { + if (!in_array($enum, $list, true)) { + return; + } + + assertType(MyEnum::class, $enum); + assertType('non-empty-list', $list); + } + +} + + +class InArrayOtherFiniteType { + + const SET_AB = ['a', 'b']; + const SET_C = ['c']; + const SET_ABC = ['a', 'b', 'c']; + + public function test1(): void + { + foreach (['a', 'b', 'c'] as $item) { + if (in_array($item, self::SET_AB, true)) { + assertType("'a'|'b'", $item); + } elseif (in_array($item, self::SET_C, true)) { + assertType("'c'", $item); + } else { + assertType('*NEVER*', $item); + } + } + } + + public function test2(): void + { + foreach (['a', 'b', 'c'] as $item) { + if (in_array($item, self::SET_ABC, true)) { + assertType("'a'|'b'|'c'", $item); + } else { + assertType('*NEVER*', $item); + } + } + } + + public function test3(): void + { + foreach (['a', 'b', 'c'] as $item) { + if (in_array($item, self::SET_C, true)) { + assertType("'c'", $item); + } else { + assertType("'a'|'b'", $item); + } + } + } + public function test4(): void + { + foreach (['c'] as $item) { + if (in_array($item, self::SET_C, true)) { + assertType("'c'", $item); + } else { + assertType('*NEVER*', $item); + } + } + } +} From c0bfae6a2efe3daa7df1ab08802960f0fa305ce2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 22 Nov 2024 19:09:12 +0100 Subject: [PATCH 0827/3097] Refactor TryRemove/Accepts for DateTime|DateTimeImmutable and Exception|Error --- phpstan-baseline.neon | 2 +- src/Type/ObjectType.php | 34 +++++++++++++--------------------- src/Type/UnionType.php | 28 ++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3b47d2e5fc5..d3e9a54a9d1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1422,7 +1422,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 6 + count: 3 path: src/Type/ObjectType.php - diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index a51539df05d..3290fb97c7e 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -5,11 +5,6 @@ use ArrayAccess; use Closure; use Countable; -use DateTime; -use DateTimeImmutable; -use DateTimeInterface; -use Error; -use Exception; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -46,7 +41,6 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; -use Throwable; use Traversable; use function array_key_exists; use function array_map; @@ -1560,23 +1554,21 @@ private function getInterfaces(): array public function tryRemove(Type $typeToRemove): ?Type { - if ($this->getClassName() === DateTimeInterface::class) { - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTimeImmutable::class) { - return new ObjectType(DateTime::class); - } - - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === DateTime::class) { - return new ObjectType(DateTimeImmutable::class); - } - } + if ($typeToRemove instanceof ObjectType) { + foreach (UnionType::EQUAL_UNION_CLASSES as $baseClass => $classes) { + if ($this->getClassName() !== $baseClass) { + continue; + } - if ($this->getClassName() === Throwable::class) { - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Error::class) { - return new ObjectType(Exception::class); // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException - } + foreach ($classes as $index => $class) { + if ($typeToRemove->getClassName() === $class) { + unset($classes[$index]); - if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === Exception::class) { // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException - return new ObjectType(Error::class); + return TypeCombinator::union( + ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes), + ); + } + } } } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 1505fa1bb24..5d7627b3060 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -5,6 +5,8 @@ use DateTime; use DateTimeImmutable; use DateTimeInterface; +use Error; +use Exception; use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; @@ -26,6 +28,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; +use Throwable; use function array_diff_assoc; use function array_fill_keys; use function array_map; @@ -45,6 +48,11 @@ class UnionType implements CompoundType use NonGeneralizableTypeTrait; + public const EQUAL_UNION_CLASSES = [ + DateTimeInterface::class => [DateTimeImmutable::class, DateTime::class], + Throwable::class => [Error::class, Exception::class], // phpcs:ignore SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException + ]; + private bool $sortedTypes = false; /** @var array */ @@ -183,14 +191,18 @@ public function getConstantStrings(): array public function accepts(Type $type, bool $strictTypes): AcceptsResult { - if ( - $type->equals(new ObjectType(DateTimeInterface::class)) - && $this->accepts( - new UnionType([new ObjectType(DateTime::class), new ObjectType(DateTimeImmutable::class)]), - $strictTypes, - )->yes() - ) { - return AcceptsResult::createYes(); + foreach (self::EQUAL_UNION_CLASSES as $baseClass => $classes) { + if (!$type->equals(new ObjectType($baseClass))) { + continue; + } + + $union = TypeCombinator::union( + ...array_map(static fn (string $objectClass): Type => new ObjectType($objectClass), $classes), + ); + if ($this->accepts($union, $strictTypes)->yes()) { + return AcceptsResult::createYes(); + } + break; } $result = AcceptsResult::createNo(); From d9082ecf3c80d8ba169f0c2b15fed20613212bf0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 23 Nov 2024 11:23:12 +0100 Subject: [PATCH 0828/3097] Implement Scope->getPhpVersion() --- src/Analyser/MutatingScope.php | 11 +++ src/Analyser/Scope.php | 3 + src/Php/PhpVersions.php | 26 +++++++ src/Rules/Methods/FinalPrivateMethodRule.php | 9 +-- tests/PHPStan/Php/PhpVersionsTest.php | 68 +++++++++++++++++++ .../Methods/FinalPrivateMethodRuleTest.php | 36 ++++++++-- .../data/final-private-method-phpversions.php | 43 ++++++++++++ 7 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 src/Php/PhpVersions.php create mode 100644 tests/PHPStan/Php/PhpVersionsTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/final-private-method-phpversions.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b8dcee4fd85..61f16a51195 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -47,6 +47,7 @@ use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; +use PHPStan\Php\PhpVersions; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\Callables\CallableParametersAcceptor; @@ -5721,4 +5722,14 @@ public function getIterableValueType(Type $iteratee): Type return $iteratee->getIterableValueType(); } + public function getPhpVersion(): PhpVersions + { + $versionExpr = new ConstFetch(new Name('PHP_VERSION_ID')); + if (!$this->hasExpressionType($versionExpr)->yes()) { + return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); + } + + return new PhpVersions($this->getType($versionExpr)); + } + } diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index a14c9d108d0..bf89cbdf481 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Param; +use PHPStan\Php\PhpVersions; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; @@ -136,4 +137,6 @@ public function filterByFalseyValue(Expr $expr): self; public function isInFirstLevelStatement(): bool; + public function getPhpVersion(): PhpVersions; + } diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php new file mode 100644 index 00000000000..a85c6c4a42e --- /dev/null +++ b/src/Php/PhpVersions.php @@ -0,0 +1,26 @@ +isSuperTypeOf($this->phpVersions)->result; + } + +} diff --git a/src/Rules/Methods/FinalPrivateMethodRule.php b/src/Rules/Methods/FinalPrivateMethodRule.php index b205234dc23..7331e21bc19 100644 --- a/src/Rules/Methods/FinalPrivateMethodRule.php +++ b/src/Rules/Methods/FinalPrivateMethodRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; @@ -14,12 +13,6 @@ final class FinalPrivateMethodRule implements Rule { - public function __construct( - private PhpVersion $phpVersion, - ) - { - } - public function getNodeType(): string { return InClassMethodNode::class; @@ -28,7 +21,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $method = $node->getMethodReflection(); - if (!$this->phpVersion->producesWarningForFinalPrivateMethods()) { + if ($scope->getPhpVersion()->producesWarningForFinalPrivateMethods()->no()) { return []; } diff --git a/tests/PHPStan/Php/PhpVersionsTest.php b/tests/PHPStan/Php/PhpVersionsTest.php new file mode 100644 index 00000000000..b1563e2eb12 --- /dev/null +++ b/tests/PHPStan/Php/PhpVersionsTest.php @@ -0,0 +1,68 @@ +assertSame( + $expected->describe(), + $phpVersions->producesWarningForFinalPrivateMethods()->describe(), + ); + } + + public function dataProducesWarningForFinalPrivateMethods(): iterable + { + yield [ + TrinaryLogic::createNo(), + new ConstantIntegerType(70400), + ]; + + yield [ + TrinaryLogic::createYes(), + new ConstantIntegerType(80000), + ]; + + yield [ + TrinaryLogic::createYes(), + new ConstantIntegerType(80100), + ]; + + yield [ + TrinaryLogic::createYes(), + IntegerRangeType::fromInterval(80000, null), + ]; + + yield [ + TrinaryLogic::createMaybe(), + IntegerRangeType::fromInterval(null, 80000), + ]; + + yield [ + TrinaryLogic::createNo(), + IntegerRangeType::fromInterval(70200, 70400), + ]; + + yield [ + TrinaryLogic::createMaybe(), + new UnionType([ + IntegerRangeType::fromInterval(70200, 70400), + IntegerRangeType::fromInterval(80200, 80400), + ]), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleTest.php b/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleTest.php index 64a4b89c0f9..05be45a3806 100644 --- a/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleTest.php @@ -5,18 +5,15 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** @extends RuleTestCase */ class FinalPrivateMethodRuleTest extends RuleTestCase { - private int $phpVersionId; - protected function getRule(): Rule { - return new FinalPrivateMethodRule( - new PhpVersion($this->phpVersionId), - ); + return new FinalPrivateMethodRule(); } public function dataRule(): array @@ -44,8 +41,35 @@ public function dataRule(): array */ public function testRule(int $phpVersion, array $errors): void { - $this->phpVersionId = $phpVersion; + $testVersion = new PhpVersion($phpVersion); + $runtimeVersion = new PhpVersion(PHP_VERSION_ID); + + if ( + $testVersion->getMajorVersionId() !== $runtimeVersion->getMajorVersionId() + || $testVersion->getMinorVersionId() !== $runtimeVersion->getMinorVersionId() + ) { + $this->markTestSkipped('Test requires PHP version ' . $testVersion->getMajorVersionId() . '.' . $testVersion->getMinorVersionId() . '.*'); + } + $this->analyse([__DIR__ . '/data/final-private-method.php'], $errors); } + public function testRulePhpVersions(): void + { + $this->analyse([__DIR__ . '/data/final-private-method-phpversions.php'], [ + [ + 'Private method FinalPrivateMethodPhpVersions\FooBarPhp8orHigher::foo() cannot be final as it is never overridden by other classes.', + 9, + ], + [ + 'Private method FinalPrivateMethodPhpVersions\FooBarPhp74OrHigher::foo() cannot be final as it is never overridden by other classes.', + 29, + ], + [ + 'Private method FinalPrivateMethodPhpVersions\FooBarBaz::foo() cannot be final as it is never overridden by other classes.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/final-private-method-phpversions.php b/tests/PHPStan/Rules/Methods/data/final-private-method-phpversions.php new file mode 100644 index 00000000000..c9424720d10 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/final-private-method-phpversions.php @@ -0,0 +1,43 @@ += 80000) { + class FooBarPhp8orHigher + { + + final private function foo(): void + { + } + } +} + +if (PHP_VERSION_ID < 80000) { + class FooBarPhp7 + { + + final private function foo(): void + { + } + } +} + +if (PHP_VERSION_ID > 70400) { + class FooBarPhp74OrHigher + { + + final private function foo(): void + { + } + } +} + +if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 80100) { + class FooBarBaz + { + + final private function foo(): void + { + } + } +} From adcabb3078603f46c811c7dc460174c9df551310 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 20 Nov 2024 15:48:39 +0100 Subject: [PATCH 0829/3097] TooWidePropertyTypeRule: dont skip even always-read properties --- src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 8d77cb01b4d..68a513f2955 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -61,9 +61,6 @@ public function processNode(Node $node, Scope $scope): array continue; } foreach ($this->extensionProvider->getExtensions() as $extension) { - if ($extension->isAlwaysRead($propertyReflection, $propertyName)) { - continue 2; - } if ($extension->isAlwaysWritten($propertyReflection, $propertyName)) { continue 2; } From 993db818fa59ae4f715a94a9e938aca69e7060a6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 24 Nov 2024 09:17:40 +0100 Subject: [PATCH 0830/3097] Revert "TooWidePropertyTypeRule: dont skip even always-read properties" This reverts commit adcabb3078603f46c811c7dc460174c9df551310. --- src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php index 68a513f2955..8d77cb01b4d 100644 --- a/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php +++ b/src/Rules/TooWideTypehints/TooWidePropertyTypeRule.php @@ -61,6 +61,9 @@ public function processNode(Node $node, Scope $scope): array continue; } foreach ($this->extensionProvider->getExtensions() as $extension) { + if ($extension->isAlwaysRead($propertyReflection, $propertyName)) { + continue 2; + } if ($extension->isAlwaysWritten($propertyReflection, $propertyName)) { continue 2; } From e3867c05576f0b08656ce83f6ac2f6503ff22f94 Mon Sep 17 00:00:00 2001 From: schlndh Date: Sun, 24 Nov 2024 09:24:08 +0100 Subject: [PATCH 0831/3097] Bleeding edge - check that values passed to array_sum/product are castable to number --- conf/bleedingEdge.neon | 1 + conf/config.level5.neon | 6 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + .../ParameterCastableToNumberRule.php | 84 +++++++++ .../ParameterCastableToNumberRuleTest.php | 162 ++++++++++++++++++ .../Rules/Functions/data/bug-11883.php | 14 ++ ...aram-castable-to-number-functions-enum.php | 14 ++ ...astable-to-number-functions-named-args.php | 15 ++ .../param-castable-to-number-functions.php | 45 +++++ 10 files changed, 343 insertions(+) create mode 100644 src/Rules/Functions/ParameterCastableToNumberRule.php create mode 100644 tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11883.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-enum.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-named-args.php create mode 100644 tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8e06b22fda5..7227e369b38 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -1,5 +1,6 @@ parameters: featureToggles: bleedingEdge: true + checkParameterCastableToNumberFunctions: true skipCheckGenericClasses!: [] stricterFunctionMap: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index b89a6dbddf5..fd3835fbf1e 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -5,6 +5,10 @@ parameters: checkFunctionArgumentTypes: true checkArgumentsPassedByReference: true +conditionalTags: + PHPStan\Rules\Functions\ParameterCastableToNumberRule: + phpstan.rules.rule: %featureToggles.checkParameterCastableToNumberFunctions% + rules: - PHPStan\Rules\DateTimeInstantiationRule - PHPStan\Rules\Functions\CallUserFuncRule @@ -36,3 +40,5 @@ services: treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Functions\ParameterCastableToNumberRule diff --git a/conf/config.neon b/conf/config.neon index 4679d5f31c0..ba4bbb2f622 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,6 +22,7 @@ parameters: tooWideThrowType: true featureToggles: bleedingEdge: false + checkParameterCastableToNumberFunctions: false skipCheckGenericClasses: [] stricterFunctionMap: false fileExtensions: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 6c7f9c40e61..f73011dcad4 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -28,6 +28,7 @@ parametersSchema: ]) featureToggles: structure([ bleedingEdge: bool(), + checkParameterCastableToNumberFunctions: bool(), skipCheckGenericClasses: listOf(string()), stricterFunctionMap: bool() ]) diff --git a/src/Rules/Functions/ParameterCastableToNumberRule.php b/src/Rules/Functions/ParameterCastableToNumberRule.php new file mode 100644 index 00000000000..640c73a4402 --- /dev/null +++ b/src/Rules/Functions/ParameterCastableToNumberRule.php @@ -0,0 +1,84 @@ + + */ +final class ParameterCastableToNumberRule implements Rule +{ + + public function __construct( + private ReflectionProvider $reflectionProvider, + private ParameterCastableToStringCheck $parameterCastableToStringCheck, + ) + { + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Node\Name)) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + $functionName = $functionReflection->getName(); + + if (!in_array($functionName, ['array_sum', 'array_product'], true)) { + return []; + } + + $origArgs = $node->getArgs(); + + if (count($origArgs) !== 1) { + return []; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $origArgs, + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $errorMessage = 'Parameter %s of function %s expects an array of values castable to number, %s given.'; + $functionParameters = $parametersAcceptor->getParameters(); + $error = $this->parameterCastableToStringCheck->checkParameter( + $origArgs[0], + $scope, + $errorMessage, + static fn (Type $t) => $t->toNumber(), + $functionName, + $this->parameterCastableToStringCheck->getParameterName( + $origArgs[0], + 0, + $functionParameters[0] ?? null, + ), + ); + + return $error !== null + ? [$error] + : []; + } + +} diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php new file mode 100644 index 00000000000..77b64c3a304 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php @@ -0,0 +1,162 @@ + + */ +class ParameterCastableToNumberRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $broker = $this->createReflectionProvider(); + return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/param-castable-to-number-functions.php'], $this->hackPhp74ErrorMessages([ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array> given.', + 20, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 21, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 22, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 23, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 24, + ], + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 25, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array> given.', + 27, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 28, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 29, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 30, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 31, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 32, + ], + ])); + } + + public function testNamedArguments(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/param-castable-to-number-functions-named-args.php'], [ + [ + 'Parameter $array of function array_sum expects an array of values castable to number, array> given.', + 7, + ], + [ + 'Parameter $array of function array_product expects an array of values castable to number, array> given.', + 8, + ], + ]); + } + + public function testEnum(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/param-castable-to-number-functions-enum.php'], [ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 12, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 13, + ], + ]); + } + + public function testBug11883(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-11883.php'], [ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 13, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 14, + ], + ]); + } + + /** + * @param list $errors + * @return list + */ + private function hackPhp74ErrorMessages(array $errors): array + { + if (PHP_VERSION_ID >= 80000) { + return $errors; + } + + return array_map(static function (array $error): array { + $error[0] = str_replace( + [ + '$array of function array_sum', + '$array of function array_product', + 'array', + ], + [ + '$input of function array_sum', + '$input of function array_product', + 'array', + ], + $error[0], + ); + + return $error; + }, $errors); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/bug-11883.php b/tests/PHPStan/Rules/Functions/data/bug-11883.php new file mode 100644 index 00000000000..a14174777b4 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11883.php @@ -0,0 +1,14 @@ += 8.1 + +namespace Bug11883; + +enum SomeEnum: int +{ + case A = 1; + case B = 2; +} + +$enums1 = [SomeEnum::A, SomeEnum::B]; + +var_dump(array_sum($enums1)); +var_dump(array_product($enums1)); diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-enum.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-enum.php new file mode 100644 index 00000000000..91e9f1f686f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-enum.php @@ -0,0 +1,14 @@ += 8.1 + +namespace ParamCastableToNumberFunctionsEnum; + +enum FooEnum +{ + case A; +} + +function invalidUsages() +{ + array_sum([FooEnum::A]); + array_product([FooEnum::A]); +} diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-named-args.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-named-args.php new file mode 100644 index 00000000000..4fdc5460628 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions-named-args.php @@ -0,0 +1,15 @@ += 8.0 + +namespace ParamCastableToNumberFunctionsNamedArgs; + +function invalidUsages() +{ + var_dump(array_sum(array: [[0]])); + var_dump(array_product(array: [[0]])); +} + +function validUsages() +{ + var_dump(array_sum(array: [1])); + var_dump(array_product(array: [1])); +} diff --git a/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions.php b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions.php new file mode 100644 index 00000000000..9e7c5da4d25 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/param-castable-to-number-functions.php @@ -0,0 +1,45 @@ +7.7'), 5, 5.5, null])); + var_dump(array_product(['5.5', false, true, new \SimpleXMLElement('7.7'), 5, 5.5, null])); +} From 2d637dab640f194f19b2c50a565b13e331c0227c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 24 Nov 2024 09:25:51 +0100 Subject: [PATCH 0832/3097] `Scope::getPhpVersion()` allows array via phpVersion min+max config --- src/Analyser/DirectInternalScopeFactory.php | 5 +++ src/Analyser/LazyInternalScopeFactory.php | 1 + src/Analyser/MutatingScope.php | 6 ++++ src/Testing/PHPStanTestCase.php | 1 + .../FinalPrivateMethodRuleConfigPhpTest.php | 34 +++++++++++++++++++ ...final-private-method-config-phpversion.php | 11 ++++++ .../data/final-private-php-version.neon | 4 +++ 7 files changed, 62 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleConfigPhpTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/final-private-method-config-phpversion.php create mode 100644 tests/PHPStan/Rules/Methods/data/final-private-php-version.neon diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 99f4287dfe9..74b43d80c9c 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -19,6 +19,9 @@ final class DirectInternalScopeFactory implements InternalScopeFactory { + /** + * @param int|array{min: int, max: int}|null $configPhpVersion + */ public function __construct( private ReflectionProvider $reflectionProvider, private InitializerExprTypeResolver $initializerExprTypeResolver, @@ -31,6 +34,7 @@ public function __construct( private NodeScopeResolver $nodeScopeResolver, private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private PhpVersion $phpVersion, + private int|array|null $configPhpVersion, private ConstantResolver $constantResolver, ) { @@ -78,6 +82,7 @@ public function create( $this->constantResolver, $context, $this->phpVersion, + $this->configPhpVersion, $declareStrictTypes, $function, $namespace, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 79d34fb54f7..ac5b757991b 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -67,6 +67,7 @@ public function create( $this->container->getByType(ConstantResolver::class), $context, $this->container->getByType(PhpVersion::class), + $this->container->getParameter('phpVersion'), $declareStrictTypes, $function, $namespace, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 61f16a51195..e7afd055e47 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -144,6 +144,7 @@ use function get_class; use function implode; use function in_array; +use function is_array; use function is_bool; use function is_numeric; use function is_string; @@ -184,6 +185,7 @@ final class MutatingScope implements Scope private static int $resolveClosureTypeDepth = 0; /** + * @param int|array{min: int, max: int}|null $configPhpVersion * @param array $expressionTypes * @param array $conditionalExpressions * @param list $inClosureBindScopeClasses @@ -207,6 +209,7 @@ public function __construct( private ConstantResolver $constantResolver, private ScopeContext $context, private PhpVersion $phpVersion, + private int|array|null $configPhpVersion, private bool $declareStrictTypes = false, private PhpFunctionFromParserNodeReflection|null $function = null, ?string $namespace = null, @@ -5726,6 +5729,9 @@ public function getPhpVersion(): PhpVersions { $versionExpr = new ConstFetch(new Name('PHP_VERSION_ID')); if (!$this->hasExpressionType($versionExpr)->yes()) { + if (is_array($this->configPhpVersion)) { + return new PhpVersions(IntegerRangeType::fromInterval($this->configPhpVersion['min'], $this->configPhpVersion['max'])); + } return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 31cdfadb78d..849fbfac11b 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -163,6 +163,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $container->getByType(NodeScopeResolver::class), new RicherScopeGetTypeHelper($initializerExprTypeResolver), $container->getByType(PhpVersion::class), + $container->getParameter('phpVersion'), $constantResolver, ), ); diff --git a/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleConfigPhpTest.php b/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleConfigPhpTest.php new file mode 100644 index 00000000000..dddd414b723 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleConfigPhpTest.php @@ -0,0 +1,34 @@ + */ +class FinalPrivateMethodRuleConfigPhpTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new FinalPrivateMethodRule(); + } + + public function testRulePhpVersions(): void + { + $this->analyse([__DIR__ . '/data/final-private-method-config-phpversion.php'], [ + [ + 'Private method FinalPrivateMethodConfigPhpVersions\PhpVersionViaNEONConfg::foo() cannot be final as it is never overridden by other classes.', + 8, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/data/final-private-php-version.neon', + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/final-private-method-config-phpversion.php b/tests/PHPStan/Rules/Methods/data/final-private-method-config-phpversion.php new file mode 100644 index 00000000000..6880568c934 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/final-private-method-config-phpversion.php @@ -0,0 +1,11 @@ + Date: Tue, 26 Nov 2024 01:06:55 +0900 Subject: [PATCH 0833/3097] Last value was not recognized when passing an associative array as an argument --- src/Rules/FunctionCallParametersCheck.php | 17 +++++--- .../Rules/Classes/InstantiationRuleTest.php | 5 +++ .../PHPStan/Rules/Classes/data/bug-11815.php | 43 +++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11815.php diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 130dd0bef88..d320c24d605 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -30,6 +30,7 @@ use function array_key_exists; use function count; use function implode; +use function in_array; use function is_int; use function is_string; use function max; @@ -128,30 +129,32 @@ public function check( if ($arg->unpack) { $arrays = $type->getConstantArrays(); if (count($arrays) > 0) { - $minKeys = null; + $maxKeys = null; foreach ($arrays as $array) { $countType = $array->getArraySize(); if ($countType instanceof ConstantIntegerType) { $keysCount = $countType->getValue(); } elseif ($countType instanceof IntegerRangeType) { - $keysCount = $countType->getMin(); + $keysCount = $countType->getMax(); if ($keysCount === null) { throw new ShouldNotHappenException(); } } else { throw new ShouldNotHappenException(); } - if ($minKeys !== null && $keysCount >= $minKeys) { + if ($maxKeys !== null && $keysCount >= $maxKeys) { continue; } - $minKeys = $keysCount; + $maxKeys = $keysCount; } - for ($j = 0; $j < $minKeys; $j++) { + for ($j = 0; $j < $maxKeys; $j++) { $types = []; $commonKey = null; + $isOptionalKey = false; foreach ($arrays as $constantArray) { + $isOptionalKey = in_array($j, $constantArray->getOptionalKeys(), true); $types[] = $constantArray->getValueTypes()[$j]; $keyType = $constantArray->getKeyTypes()[$j]; if ($commonKey === null) { @@ -165,6 +168,10 @@ public function check( $keyArgumentName = $commonKey; $hasNamedArguments = true; } + if ($isOptionalKey) { + continue; + } + $arguments[] = [ $arg->value, TypeCombinator::union(...$types), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 657c47bd754..43b9091daf0 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -499,4 +499,9 @@ public function testBug10248(): void $this->analyse([__DIR__ . '/data/bug-10248.php'], []); } + public function testBug11815(): void + { + $this->analyse([__DIR__ . '/data/bug-11815.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-11815.php b/tests/PHPStan/Rules/Classes/data/bug-11815.php new file mode 100644 index 00000000000..c08dccb9ea9 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11815.php @@ -0,0 +1,43 @@ += 8.2 + +declare(strict_types = 1); + +class Dimensions +{ + public function __construct( + public int $width, + public int $height, + ) { + } +} + +class StoreProcessorResult +{ + public function __construct( + public string $path, + public string $mimetype, + public Dimensions $dimensions, + public int $filesize, + public true|null $identical = null, + ) { + } +} + +/** + * @return array{path: string, identical?: true} + */ +function getPath(): array +{ + $data = ['path' => 'some/path']; + if ((bool)rand(0, 1)) { + $data['identical'] = true; + } + return $data; +} + +$data = getPath(); +$data['dimensions'] = new Dimensions(100, 100); +$data['mimetype'] = 'image/png'; +$data['filesize'] = 123456; + +$dto = new StoreProcessorResult(...$data); From 970117e52512790507cd232d4146055fab1daccf Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 24 Nov 2024 21:51:21 +0100 Subject: [PATCH 0834/3097] Remove sha256 definition --- resources/functionMap.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 9891593eb3f..611ac517ec8 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10448,8 +10448,6 @@ 'settype' => ['bool', '&rw_var'=>'mixed', 'type'=>'string'], 'sha1' => ['non-falsy-string&lowercase-string', 'str'=>'string', 'raw_output='=>'bool'], 'sha1_file' => ['(non-falsy-string&lowercase-string)|false', 'filename'=>'string', 'raw_output='=>'bool'], -'sha256' => ['string', 'str'=>'string', 'raw_output='=>'bool'], -'sha256_file' => ['string', 'filename'=>'string', 'raw_output='=>'bool'], 'shapefileObj::__construct' => ['void', 'filename'=>'string', 'type'=>'int'], 'shapefileObj::addPoint' => ['int', 'point'=>'pointObj'], 'shapefileObj::addShape' => ['int', 'shape'=>'shapeObj'], From a0e007aee46a0a3ac56d74789f1107a87d0f5740 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 24 Nov 2024 11:26:38 +0100 Subject: [PATCH 0835/3097] non-capturing catch support-detection is scope php-version dependent --- src/Php/PhpVersions.php | 5 +++++ src/Rules/Exceptions/NoncapturingCatchRule.php | 7 +------ .../Rules/Exceptions/NoncapturingCatchRuleTest.php | 14 ++++++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index a85c6c4a42e..7bdc70e3bff 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -18,6 +18,11 @@ public function __construct( { } + public function supportsNoncapturingCatches(): TrinaryLogic + { + return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; + } + public function producesWarningForFinalPrivateMethods(): TrinaryLogic { return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; diff --git a/src/Rules/Exceptions/NoncapturingCatchRule.php b/src/Rules/Exceptions/NoncapturingCatchRule.php index 499b62e154f..a4d91a9ba20 100644 --- a/src/Rules/Exceptions/NoncapturingCatchRule.php +++ b/src/Rules/Exceptions/NoncapturingCatchRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -14,10 +13,6 @@ final class NoncapturingCatchRule implements Rule { - public function __construct(private PhpVersion $phpVersion) - { - } - public function getNodeType(): string { return Node\Stmt\Catch_::class; @@ -28,7 +23,7 @@ public function getNodeType(): string */ public function processNode(Node $node, Scope $scope): array { - if ($this->phpVersion->supportsNoncapturingCatches()) { + if ($scope->getPhpVersion()->supportsNoncapturingCatches()->yes()) { return []; } diff --git a/tests/PHPStan/Rules/Exceptions/NoncapturingCatchRuleTest.php b/tests/PHPStan/Rules/Exceptions/NoncapturingCatchRuleTest.php index 9c8181f4a39..8139960e668 100644 --- a/tests/PHPStan/Rules/Exceptions/NoncapturingCatchRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/NoncapturingCatchRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -12,11 +13,9 @@ class NoncapturingCatchRuleTest extends RuleTestCase { - private PhpVersion $phpVersion; - protected function getRule(): Rule { - return new NoncapturingCatchRule($this->phpVersion); + return new NoncapturingCatchRule(); } public function dataRule(): array @@ -49,7 +48,14 @@ public function dataRule(): array */ public function testRule(int $phpVersion, array $expectedErrors): void { - $this->phpVersion = new PhpVersion($phpVersion); + $testVersion = new PhpVersion($phpVersion); + $runtimeVersion = new PhpVersion(PHP_VERSION_ID); + if ( + $testVersion->getMajorVersionId() !== $runtimeVersion->getMajorVersionId() + || $testVersion->getMinorVersionId() !== $runtimeVersion->getMinorVersionId() + ) { + $this->markTestSkipped('Test requires PHP version ' . $testVersion->getMajorVersionId() . '.' . $testVersion->getMinorVersionId() . '.*'); + } $this->analyse([ __DIR__ . '/data/noncapturing-catch.php', From d35a2f464d7756022b12acc83c994bcb39d07ac9 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 26 Nov 2024 00:03:38 +0000 Subject: [PATCH 0836/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 73f1cc620f1..71c48bfbc7c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#fda684a4826c1caf59efe1a1bf68d08c11aaddbf", + "jetbrains/phpstorm-stubs": "dev-master#9efcc4aa48b9c752c68de25f6240fcced0c95151", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index d2adb5606cc..6a7f1ff9051 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4991601b7590d9dc65443dfb5d34d16", + "content-hash": "274e72a3422f81d92070bbbedbb9f8f5", "packages": [ { "name": "clue/ndjson-react", @@ -1442,18 +1442,18 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "fda684a4826c1caf59efe1a1bf68d08c11aaddbf" + "reference": "9efcc4aa48b9c752c68de25f6240fcced0c95151" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/fda684a4826c1caf59efe1a1bf68d08c11aaddbf", - "reference": "fda684a4826c1caf59efe1a1bf68d08c11aaddbf", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/9efcc4aa48b9c752c68de25f6240fcced0c95151", + "reference": "9efcc4aa48b9c752c68de25f6240fcced0c95151", "shasum": "" }, "require-dev": { "friendsofphp/php-cs-fixer": "v3.64.0", "nikic/php-parser": "v5.3.1", - "phpdocumentor/reflection-docblock": "5.5.1", + "phpdocumentor/reflection-docblock": "5.6.0", "phpunit/phpunit": "11.4.3" }, "default-branch": true, @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-15T09:42:33+00:00" + "time": "2024-11-25T11:21:35+00:00" }, { "name": "nette/bootstrap", From 96f721d40a0492e2f33a2316efaa50da0d385b0f Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Tue, 26 Nov 2024 20:00:44 +0000 Subject: [PATCH 0837/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 71c48bfbc7c..8963aa75c32 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.43.0.2", + "ondrejmirtes/better-reflection": "6.43.0.4", "phpstan/php-8-stubs": "0.4.6", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 6a7f1ff9051..a1f8f8e83a7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "274e72a3422f81d92070bbbedbb9f8f5", + "content-hash": "1866d60acbec835ca58159c3cb6c07a8", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.43.0.2", + "version": "6.43.0.4", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "c34ee726f9abc5a7057b0dacdf1c0991c9090584" + "reference": "6b869bb58972b7877509e8a24757b4108effcd79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c34ee726f9abc5a7057b0dacdf1c0991c9090584", - "reference": "c34ee726f9abc5a7057b0dacdf1c0991c9090584", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/6b869bb58972b7877509e8a24757b4108effcd79", + "reference": "6b869bb58972b7877509e8a24757b4108effcd79", "shasum": "" }, "require": { @@ -2213,7 +2213,7 @@ "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", "phpunit/phpunit": "^11.4.3", - "rector/rector": "0.14.3" + "rector/rector": "1.2.10" }, "suggest": { "composer/composer": "Required to use the ComposerSourceLocator" @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.43.0.2" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.43.0.4" }, - "time": "2024-11-19T19:32:34+00:00" + "time": "2024-11-26T19:58:24+00:00" }, { "name": "phpstan/php-8-stubs", From 17da15181a75ec1bd184959185005e6e857e1220 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Tue, 26 Nov 2024 20:52:02 +0000 Subject: [PATCH 0838/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8963aa75c32..988292f2d6a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.43.0.4", + "ondrejmirtes/better-reflection": "6.44.0.2", "phpstan/php-8-stubs": "0.4.6", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index a1f8f8e83a7..d705cd728ff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1866d60acbec835ca58159c3cb6c07a8", + "content-hash": "6c77a249a859b7eb74df36c091206c59", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.43.0.4", + "version": "6.44.0.2", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "6b869bb58972b7877509e8a24757b4108effcd79" + "reference": "50fe615bd6665bbc541b6ddb419bff11fc0718a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/6b869bb58972b7877509e8a24757b4108effcd79", - "reference": "6b869bb58972b7877509e8a24757b4108effcd79", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/50fe615bd6665bbc541b6ddb419bff11fc0718a1", + "reference": "50fe615bd6665bbc541b6ddb419bff11fc0718a1", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.43.0.4" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.2" }, - "time": "2024-11-26T19:58:24+00:00" + "time": "2024-11-26T20:50:48+00:00" }, { "name": "phpstan/php-8-stubs", From 1c4c1009a523a709d69cbe417580dd8aa44b4245 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:21:18 +0000 Subject: [PATCH 0839/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 988292f2d6a..da38b8d286e 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.44.0.2", - "phpstan/php-8-stubs": "0.4.6", + "phpstan/php-8-stubs": "0.4.7", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index d705cd728ff..569d8996347 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6c77a249a859b7eb74df36c091206c59", + "content-hash": "d934490a94126dd32923c058834ef486", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.6", + "version": "0.4.7", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "25ba0a11dc14a02c062392786486ada62d36b66d" + "reference": "794d410e0de8779afb4706d8667d2b3a11a3865a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/25ba0a11dc14a02c062392786486ada62d36b66d", - "reference": "25ba0a11dc14a02c062392786486ada62d36b66d", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/794d410e0de8779afb4706d8667d2b3a11a3865a", + "reference": "794d410e0de8779afb4706d8667d2b3a11a3865a", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.6" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.7" }, - "time": "2024-11-13T00:19:28+00:00" + "time": "2024-11-27T00:20:37+00:00" }, { "name": "phpstan/phpdoc-parser", From 5ec47623fa31f63912d904ba90693eac64b0de0c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 10:00:39 +0100 Subject: [PATCH 0840/3097] PropertyHookType should not be prefixed in BetterReflection --- compiler/build/scoper.inc.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 957cfe721d4..0d0008d3411 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -226,6 +226,13 @@ function (string $filePath, string $prefix, string $content): string { sprintf('\\\\%s', $prefix), ], '', $content); }, + function (string $filePath, string $prefix, string $content): string { + if (!str_starts_with($filePath, 'vendor/ondrejmirtes/better-reflection')) { + return $content; + } + + return str_replace(sprintf('%s\\PropertyHookType', $prefix), 'PropertyHookType', $content); + }, function (string $filePath, string $prefix, string $content): string { if ( $filePath !== 'vendor/nette/utils/src/Utils/Strings.php' From 265a39bb014534095b2f1651de27f4c10e770c09 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Wed, 27 Nov 2024 10:50:13 +0000 Subject: [PATCH 0841/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index da38b8d286e..593163649ed 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.44.0.2", + "ondrejmirtes/better-reflection": "6.44.0.3", "phpstan/php-8-stubs": "0.4.7", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 569d8996347..a31b6c1e337 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d934490a94126dd32923c058834ef486", + "content-hash": "402089d9a3d76a9b791773a44ed74775", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.44.0.2", + "version": "6.44.0.3", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "50fe615bd6665bbc541b6ddb419bff11fc0718a1" + "reference": "c30607531b1ae173ea69a72c21ae598f15fef4af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/50fe615bd6665bbc541b6ddb419bff11fc0718a1", - "reference": "50fe615bd6665bbc541b6ddb419bff11fc0718a1", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c30607531b1ae173ea69a72c21ae598f15fef4af", + "reference": "c30607531b1ae173ea69a72c21ae598f15fef4af", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.2" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.3" }, - "time": "2024-11-26T20:50:48+00:00" + "time": "2024-11-27T10:47:28+00:00" }, { "name": "phpstan/php-8-stubs", From 1abeeb77771fc3fece5c7196f75dfc1478840515 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 14:01:24 +0100 Subject: [PATCH 0842/3097] Un-finalize Printer See https://github.com/rectorphp/rector-src/pull/6517#issuecomment-2503502706 --- build/PHPStan/Build/FinalClassRule.php | 2 ++ src/Node/Printer/Printer.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index a4758648c6d..10f30a8d480 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\File\FileHelper; use PHPStan\Node\InClassNode; +use PHPStan\Node\Printer\Printer; use PHPStan\Reflection\FunctionVariant; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\Php\DummyParameter; @@ -54,6 +55,7 @@ public function processNode(Node $node, Scope $scope): array ExtendedFunctionVariant::class, DummyParameter::class, PhpFunctionFromParserNodeReflection::class, + Printer::class, ], true)) { return []; } diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 131376d66d3..744c857f8fb 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -22,7 +22,7 @@ /** * @api */ -final class Printer extends Standard +class Printer extends Standard { public function __construct() From 07d7c44d50677874086cb8796a97482d406d3442 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 14:26:42 +0100 Subject: [PATCH 0843/3097] Revert "Un-finalize Printer" This reverts commit 1abeeb77771fc3fece5c7196f75dfc1478840515. --- build/PHPStan/Build/FinalClassRule.php | 2 -- src/Node/Printer/Printer.php | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index 10f30a8d480..a4758648c6d 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\File\FileHelper; use PHPStan\Node\InClassNode; -use PHPStan\Node\Printer\Printer; use PHPStan\Reflection\FunctionVariant; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\Php\DummyParameter; @@ -55,7 +54,6 @@ public function processNode(Node $node, Scope $scope): array ExtendedFunctionVariant::class, DummyParameter::class, PhpFunctionFromParserNodeReflection::class, - Printer::class, ], true)) { return []; } diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 744c857f8fb..131376d66d3 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -22,7 +22,7 @@ /** * @api */ -class Printer extends Standard +final class Printer extends Standard { public function __construct() From fa9212fc7e68f9609dbfc5b1042ce83c5b34450d Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 27 Nov 2024 19:26:13 +0700 Subject: [PATCH 0844/3097] Remove shortArraySyntax definiton on Printer::construct() It seems alreayd short array syntax by default --- src/Node/Printer/Printer.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 131376d66d3..6f00bc8031d 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -24,12 +24,6 @@ */ final class Printer extends Standard { - - public function __construct() - { - parent::__construct(['shortArraySyntax' => true]); - } - protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignore { return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise())); From 68803e997f4ab10c98a014e4a6d057b99e08b6b7 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Wed, 27 Nov 2024 13:47:17 +0000 Subject: [PATCH 0845/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 593163649ed..684e819342f 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.44.0.3", + "ondrejmirtes/better-reflection": "6.44.0.4", "phpstan/php-8-stubs": "0.4.7", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index a31b6c1e337..1f3a161391e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "402089d9a3d76a9b791773a44ed74775", + "content-hash": "ac31d4286789897fa7f8d303b33a72b0", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.44.0.3", + "version": "6.44.0.4", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "c30607531b1ae173ea69a72c21ae598f15fef4af" + "reference": "3865f1f8779d4e3562886ee261ccfdbf2da15650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c30607531b1ae173ea69a72c21ae598f15fef4af", - "reference": "c30607531b1ae173ea69a72c21ae598f15fef4af", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/3865f1f8779d4e3562886ee261ccfdbf2da15650", + "reference": "3865f1f8779d4e3562886ee261ccfdbf2da15650", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.3" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.4" }, - "time": "2024-11-27T10:47:28+00:00" + "time": "2024-11-27T13:45:40+00:00" }, { "name": "phpstan/php-8-stubs", From e1b1cad3c7da1ef175b039c4b1af37212d52a22f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 15:04:32 +0100 Subject: [PATCH 0846/3097] Fix CS --- src/Node/Printer/Printer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index 6f00bc8031d..6d9dab1062f 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -24,6 +24,7 @@ */ final class Printer extends Standard { + protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignore { return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise())); From f9e84e05bf9b4e15a4b019d4e372b71ba98e42e8 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:03:11 +0000 Subject: [PATCH 0847/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 684e819342f..1505dfeb903 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#9efcc4aa48b9c752c68de25f6240fcced0c95151", + "jetbrains/phpstorm-stubs": "dev-master#7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 1f3a161391e..b40729b153e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ac31d4286789897fa7f8d303b33a72b0", + "content-hash": "c1cf63bf474629a0d7b5fea00c2ec54c", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "9efcc4aa48b9c752c68de25f6240fcced0c95151" + "reference": "7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/9efcc4aa48b9c752c68de25f6240fcced0c95151", - "reference": "9efcc4aa48b9c752c68de25f6240fcced0c95151", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", + "reference": "7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-25T11:21:35+00:00" + "time": "2024-11-27T14:37:09+00:00" }, { "name": "nette/bootstrap", From 0b925a9f5a3b664d1a02c9d6b9d1721263a34a06 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 27 Nov 2024 22:25:38 +0100 Subject: [PATCH 0848/3097] Retain list type when assigning to offset 1 of `non-empty-list` --- src/Type/IntersectionType.php | 9 +++++- tests/PHPStan/Analyser/nsrt/list-type.php | 25 +++++++++++++++ .../TypesAssignedToPropertiesRuleTest.php | 12 +++++++ .../Rules/Properties/data/bug-12131.php | 31 +++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100755 tests/PHPStan/Rules/Properties/data/bug-12131.php diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 72fecc7de60..d41b79e951f 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -756,7 +756,14 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni ); }); } - return $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues)); + + $result = $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues)); + + if ($offsetType !== null && $this->isList()->yes() && $this->isIterableAtLeastOnce()->yes() && (new ConstantIntegerType(1))->isSuperTypeOf($offsetType)->yes()) { + $result = AccessoryArrayListType::intersectWith($result); + } + + return $result; } public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type diff --git a/tests/PHPStan/Analyser/nsrt/list-type.php b/tests/PHPStan/Analyser/nsrt/list-type.php index a80e8b066dc..26640a51417 100644 --- a/tests/PHPStan/Analyser/nsrt/list-type.php +++ b/tests/PHPStan/Analyser/nsrt/list-type.php @@ -106,4 +106,29 @@ public function testUnset(array $list): void assertType('array|int<3, max>, int>', $list); } + /** @param list $list */ + public function testSetOffsetExplicitlyWithoutGap(array $list): void + { + assertType('list', $list); + $list[0] = 17; + assertType('non-empty-list&hasOffsetValue(0, 17)', $list); + $list[1] = 19; + assertType('non-empty-list&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)', $list); + $list[0] = 21; + assertType('non-empty-list&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)', $list); + + $list[2] = 23; + assertType('non-empty-array, int>&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)&hasOffsetValue(2, 23)', $list); + } + + /** @param list $list */ + public function testSetOffsetExplicitlyWithGap(array $list): void + { + assertType('list', $list); + $list[0] = 17; + assertType('non-empty-list&hasOffsetValue(0, 17)', $list); + $list[2] = 21; + assertType('non-empty-array, int>&hasOffsetValue(0, 17)&hasOffsetValue(2, 21)', $list); + } + } diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index ff9f5be5aeb..4c87845d242 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -692,4 +692,16 @@ public function testBug11617(): void ]); } + public function testBug12131(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12131.php'], [ + [ + 'Property Bug12131\Test::$array (non-empty-list) does not accept non-empty-array, int>.', + 29, + 'non-empty-array, int> might not be a list.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12131.php b/tests/PHPStan/Rules/Properties/data/bug-12131.php new file mode 100755 index 00000000000..6f7f8d83d8f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12131.php @@ -0,0 +1,31 @@ += 7.4 + +namespace Bug12131; + +class Test +{ + /** + * @var non-empty-list + */ + public array $array; + + public function __construct() + { + $this->array = array_fill(0, 10, 1); + } + + public function setAtZero(): void + { + $this->array[0] = 1; + } + + public function setAtOne(): void + { + $this->array[1] = 1; + } + + public function setAtTwo(): void + { + $this->array[2] = 1; + } +} From 62c6a0a8b654224e4c6f0b5e111740b4a2d260e4 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 27 Nov 2024 22:37:57 +0100 Subject: [PATCH 0849/3097] Remove unnecessary test code --- tests/PHPStan/Analyser/nsrt/list-type.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/list-type.php b/tests/PHPStan/Analyser/nsrt/list-type.php index 26640a51417..40d94efff3d 100644 --- a/tests/PHPStan/Analyser/nsrt/list-type.php +++ b/tests/PHPStan/Analyser/nsrt/list-type.php @@ -116,9 +116,6 @@ public function testSetOffsetExplicitlyWithoutGap(array $list): void assertType('non-empty-list&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)', $list); $list[0] = 21; assertType('non-empty-list&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)', $list); - - $list[2] = 23; - assertType('non-empty-array, int>&hasOffsetValue(0, 21)&hasOffsetValue(1, 19)&hasOffsetValue(2, 23)', $list); } /** @param list $list */ From ae2b6a709ddcd9a4260587565809bd7f1a50d2bd Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:21:27 +0000 Subject: [PATCH 0850/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1505dfeb903..a9260b897cb 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.44.0.4", - "phpstan/php-8-stubs": "0.4.7", + "phpstan/php-8-stubs": "0.4.8", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index b40729b153e..1135a6a5747 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c1cf63bf474629a0d7b5fea00c2ec54c", + "content-hash": "d1310e0c489abe492a66dced167d3f1e", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.7", + "version": "0.4.8", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "794d410e0de8779afb4706d8667d2b3a11a3865a" + "reference": "8a6278b2b9c9781cb969c4128da361b78eead604" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/794d410e0de8779afb4706d8667d2b3a11a3865a", - "reference": "794d410e0de8779afb4706d8667d2b3a11a3865a", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/8a6278b2b9c9781cb969c4128da361b78eead604", + "reference": "8a6278b2b9c9781cb969c4128da361b78eead604", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.7" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.8" }, - "time": "2024-11-27T00:20:37+00:00" + "time": "2024-11-28T00:20:48+00:00" }, { "name": "phpstan/phpdoc-parser", From c95367d52b04d524f1fb154e944ddb1ab8cab9a8 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Thu, 28 Nov 2024 20:40:22 +0000 Subject: [PATCH 0851/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index a9260b897cb..912996ab0da 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.44.0.4", + "ondrejmirtes/better-reflection": "6.44.0.5", "phpstan/php-8-stubs": "0.4.8", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 1135a6a5747..7441a9d501d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d1310e0c489abe492a66dced167d3f1e", + "content-hash": "53bd280e3693edf29e8871fa22ad20f7", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.44.0.4", + "version": "6.44.0.5", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "3865f1f8779d4e3562886ee261ccfdbf2da15650" + "reference": "b6d0a9adbe61544f6e8992611590a4e61487a638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/3865f1f8779d4e3562886ee261ccfdbf2da15650", - "reference": "3865f1f8779d4e3562886ee261ccfdbf2da15650", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/b6d0a9adbe61544f6e8992611590a4e61487a638", + "reference": "b6d0a9adbe61544f6e8992611590a4e61487a638", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.4" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.5" }, - "time": "2024-11-27T13:45:40+00:00" + "time": "2024-11-28T20:34:45+00:00" }, { "name": "phpstan/php-8-stubs", From 86197c9987529b5545c0a09ac6ad92581087526f Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Thu, 28 Nov 2024 21:07:43 +0000 Subject: [PATCH 0852/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 912996ab0da..0f28344835c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.44.0.5", + "ondrejmirtes/better-reflection": "6.44.0.6", "phpstan/php-8-stubs": "0.4.8", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 7441a9d501d..7cd32ee1ad8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "53bd280e3693edf29e8871fa22ad20f7", + "content-hash": "aa42dc6e1a7a8d5fc9844d231515e30f", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.44.0.5", + "version": "6.44.0.6", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "b6d0a9adbe61544f6e8992611590a4e61487a638" + "reference": "d942fd0af0214bb1250a55c2560f061b7b0c4bd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/b6d0a9adbe61544f6e8992611590a4e61487a638", - "reference": "b6d0a9adbe61544f6e8992611590a4e61487a638", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/d942fd0af0214bb1250a55c2560f061b7b0c4bd4", + "reference": "d942fd0af0214bb1250a55c2560f061b7b0c4bd4", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.5" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.6" }, - "time": "2024-11-28T20:34:45+00:00" + "time": "2024-11-28T21:05:45+00:00" }, { "name": "phpstan/php-8-stubs", From e6dc705b29b90dcc5f9773377c05aecbfe9fba3a Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz <80641364+jakubtobiasz@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:00:31 +0100 Subject: [PATCH 0853/3097] Sanity checks around hooked properties in interfaces and classes --- .github/workflows/lint.yml | 2 +- Makefile | 10 ++ build/collision-detector.json | 2 +- conf/config.level0.neon | 1 + src/Node/ClassPropertyNode.php | 18 +++ src/Php/PhpVersion.php | 5 + .../Properties/PropertiesInInterfaceRule.php | 59 ++++++- src/Rules/Properties/PropertyInClassRule.php | 113 +++++++++++++ .../PropertiesInInterfaceRuleTest.php | 96 ++++++++++- .../Properties/PropertyInClassRuleTest.php | 150 ++++++++++++++++++ .../abstract-hooked-properties-in-class.php | 10 ++ ...abstract-hooked-properties-with-bodies.php | 26 +++ ...on-hooked-properties-in-abstract-class.php | 10 ++ .../data/hooked-properties-in-class.php | 11 ++ ...ked-properties-without-bodies-in-class.php | 10 ++ ...ct-hooked-properties-in-abstract-class.php | 35 ++++ ...on-abstract-hooked-properties-in-class.php | 10 ++ .../data/properties-in-interface.php | 2 + .../property-hooks-bodies-in-interface.php | 20 +++ .../data/property-hooks-in-interface.php | 10 ++ ...property-hooks-visibility-in-interface.php | 12 ++ 21 files changed, 600 insertions(+), 12 deletions(-) create mode 100644 src/Rules/Properties/PropertyInClassRule.php create mode 100644 tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-in-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-with-bodies.php create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-non-hooked-properties-in-abstract-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/hooked-properties-in-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-abstract-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-class.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-hooks-bodies-in-interface.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-hooks-in-interface.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-hooks-visibility-in-interface.php diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4680ddb82dd..86ee004f07c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -116,7 +116,7 @@ jobs: uses: "shivammathur/setup-php@v2" with: coverage: "none" - php-version: "8.3" + php-version: "8.4" - name: "Install dependencies" run: "composer install --no-interaction --no-progress" diff --git a/Makefile b/Makefile index 3282ee2c4e7..be44969c3c2 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,16 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/extends-readonly-class.php \ --exclude tests/PHPStan/Rules/Classes/data/instantiation-promoted-properties.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-11592.php \ + --exclude tests/PHPStan/Rules/Properties/data/property-hooks-bodies-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/property-hooks-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/property-hooks-visibility-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-in-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-with-bodies.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-non-hooked-properties-in-abstract-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-abstract-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-in-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ src tests cs: diff --git a/build/collision-detector.json b/build/collision-detector.json index 21228704f50..12de9af1d39 100644 --- a/build/collision-detector.json +++ b/build/collision-detector.json @@ -12,5 +12,5 @@ "../tests/notAutoloaded", "../tests/PHPStan/Rules/Functions/data/define-bug-3349.php", "../tests/PHPStan/Levels/data/stubs/function.php" - ] + ] } diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d4927a56c47..8c2a23a9d4e 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -96,6 +96,7 @@ rules: - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\PropertiesInInterfaceRule - PHPStan\Rules\Properties\PropertyAttributesRule + - PHPStan\Rules\Properties\PropertyInClassRule - PHPStan\Rules\Properties\ReadOnlyPropertyRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule - PHPStan\Rules\Regexp\RegularExpressionPatternRule diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index 3f500b62c1d..c8602aef794 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -111,6 +111,11 @@ public function isAllowedPrivateMutation(): bool return $this->isAllowedPrivateMutation; } + public function isAbstract(): bool + { + return (bool) ($this->flags & Modifiers::ABSTRACT); + } + public function getNativeType(): ?Type { return $this->type; @@ -142,4 +147,17 @@ public function getSubNodeNames(): array return []; } + public function hasHooks(): bool + { + return $this->getHooks() !== []; + } + + /** + * @return Node\PropertyHook[] + */ + public function getHooks(): array + { + return $this->originalNode->hooks; + } + } diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 8520f6488d9..e636945cc2f 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -347,6 +347,11 @@ public function supportsPregCaptureOnlyNamedGroups(): bool return $this->versionId >= 80200; } + public function supportsPropertyHooks(): bool + { + return $this->versionId >= 80400; + } + public function hasDateTimeExceptions(): bool { return $this->versionId >= 80300; diff --git a/src/Rules/Properties/PropertiesInInterfaceRule.php b/src/Rules/Properties/PropertiesInInterfaceRule.php index d9f62fa6187..df5354adb37 100644 --- a/src/Rules/Properties/PropertiesInInterfaceRule.php +++ b/src/Rules/Properties/PropertiesInInterfaceRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\ClassPropertyNode; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -14,6 +15,10 @@ final class PropertiesInInterfaceRule implements Rule { + public function __construct(private PhpVersion $phpVersion) + { + } + public function getNodeType(): string { return ClassPropertyNode::class; @@ -25,12 +30,54 @@ public function processNode(Node $node, Scope $scope): array return []; } - return [ - RuleErrorBuilder::message('Interfaces may not include properties.') - ->nonIgnorable() - ->identifier('property.inInterface') - ->build(), - ]; + if (!$this->phpVersion->supportsPropertyHooks()) { + return [ + RuleErrorBuilder::message('Interfaces cannot include properties.') + ->nonIgnorable() + ->identifier('property.inInterface') + ->build(), + ]; + } + + if (!$node->hasHooks()) { + return [ + RuleErrorBuilder::message('Interfaces can only include hooked properties.') + ->nonIgnorable() + ->identifier('property.nonHookedInInterface') + ->build(), + ]; + } + + if (!$node->isPublic()) { + return [ + RuleErrorBuilder::message('Interfaces cannot include non-public properties.') + ->nonIgnorable() + ->identifier('property.nonPublicInInterface') + ->build(), + ]; + } + + if ($this->hasAnyHookBody($node)) { + return [ + RuleErrorBuilder::message('Interfaces cannot include property hooks with bodies.') + ->nonIgnorable() + ->identifier('property.hookBodyInInterface') + ->build(), + ]; + } + + return []; + } + + private function hasAnyHookBody(ClassPropertyNode $node): bool + { + foreach ($node->getHooks() as $hook) { + if ($hook->body !== null) { + return true; + } + } + + return false; } } diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php new file mode 100644 index 00000000000..3500959541e --- /dev/null +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -0,0 +1,113 @@ + + */ +final class PropertyInClassRule implements Rule +{ + + public function __construct(private PhpVersion $phpVersion) + { + } + + public function getNodeType(): string + { + return ClassPropertyNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + + if (!$classReflection->isClass()) { + return []; + } + + if (!$this->phpVersion->supportsPropertyHooks()) { + if ($node->hasHooks()) { + return [ + RuleErrorBuilder::message('Property hooks are supported only on PHP 8.4 and later.') + ->nonIgnorable() + ->identifier('property.hooksNotSupported') + ->build(), + ]; + } + + return []; + } + + if ($node->isAbstract()) { + if (!$node->hasHooks()) { + return [ + RuleErrorBuilder::message('Only hooked properties can be declared abstract.') + ->nonIgnorable() + ->identifier('property.abstractNonHooked') + ->build(), + ]; + } + + if (!$this->isAtLeastOneHookBodyEmpty($node)) { + return [ + RuleErrorBuilder::message('Abstract properties must specify at least one abstract hook.') + ->nonIgnorable() + ->identifier('property.abstractWithoutAbstractHook') + ->build(), + ]; + } + + if (!$classReflection->isAbstract()) { + return [ + RuleErrorBuilder::message('Non-abstract classes cannot include abstract properties.') + ->nonIgnorable() + ->identifier('property.abstract') + ->build(), + ]; + } + + return []; + } + + if (!$this->doAllHooksHaveBody($node)) { + return [ + RuleErrorBuilder::message('Non-abstract properties cannot include hooks without bodies.') + ->nonIgnorable() + ->identifier('property.hookWithoutBody') + ->build(), + ]; + } + + return []; + } + + private function doAllHooksHaveBody(ClassPropertyNode $node): bool + { + foreach ($node->getHooks() as $hook) { + if ($hook->body === null) { + return false; + } + } + + return true; + } + + private function isAtLeastOneHookBodyEmpty(ClassPropertyNode $node): bool + { + foreach ($node->getHooks() as $hook) { + if ($hook->body === null) { + return true; + } + } + + return false; + } + +} diff --git a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php index ecf4597d974..de425931dab 100644 --- a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -13,21 +15,107 @@ class PropertiesInInterfaceRuleTest extends RuleTestCase protected function getRule(): Rule { - return new PropertiesInInterfaceRule(); + return new PropertiesInInterfaceRule(new PhpVersion(PHP_VERSION_ID)); } - public function testRule(): void + public function testPhp83AndPropertiesInInterface(): void { + if (PHP_VERSION_ID >= 80400) { + $this->markTestSkipped('Test requires PHP 8.3 or earlier.'); + } + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); + } + + $this->analyse([__DIR__ . '/data/properties-in-interface.php'], [ + [ + 'Interfaces cannot include properties.', + 7, + ], + [ + 'Interfaces cannot include properties.', + 9, + ], + [ + 'Interfaces cannot include properties.', + 11, + ], + ]); + } + + public function testPhp83AndPropertyHooksInInterface(): void + { + if (PHP_VERSION_ID >= 80400) { + $this->markTestSkipped('Test requires PHP 8.3 or earlier.'); + } + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); + } + + $this->analyse([__DIR__ . '/data/property-hooks-in-interface.php'], [ + [ + 'Interfaces cannot include properties.', + 7, + ], + [ + 'Interfaces cannot include properties.', + 9, + ], + ]); + } + + public function testPhp84AndPropertiesInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + $this->analyse([__DIR__ . '/data/properties-in-interface.php'], [ [ - 'Interfaces may not include properties.', + 'Interfaces can only include hooked properties.', + 9, + ], + [ + 'Interfaces can only include hooked properties.', + 11, + ], + ]); + } + + public function testPhp84AndNonPublicPropertyHooksInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/property-hooks-visibility-in-interface.php'], [ + [ + 'Interfaces cannot include non-public properties.', 7, ], [ - 'Interfaces may not include properties.', + 'Interfaces cannot include non-public properties.', 9, ], ]); } + public function testPhp84AndPropertyHooksWithBodiesInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/property-hooks-bodies-in-interface.php'], [ + [ + 'Interfaces cannot include property hooks with bodies.', + 7, + ], + [ + 'Interfaces cannot include property hooks with bodies.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php new file mode 100644 index 00000000000..0b2ca5ba096 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -0,0 +1,150 @@ + + */ +class PropertyInClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new PropertyInClassRule(new PhpVersion(PHP_VERSION_ID)); + } + + public function testPhpLessThan84AndHookedPropertiesInClass(): void + { + if (PHP_VERSION_ID >= 80400) { + $this->markTestSkipped('Test requires PHP 8.3 or earlier.'); + } + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Property hooks cause syntax error on PHP 7.4'); + } + + $this->analyse([__DIR__ . '/data/hooked-properties-in-class.php'], [ + [ + 'Property hooks are supported only on PHP 8.4 and later.', + 7, + ], + ]); + } + + public function testPhp84AndHookedPropertiesWithoutBodiesInClass(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/hooked-properties-without-bodies-in-class.php'], [ + [ + 'Non-abstract properties cannot include hooks without bodies.', + 7, + ], + [ + 'Non-abstract properties cannot include hooks without bodies.', + 9, + ], + ]); + } + + public function testPhp84AndNonAbstractHookedPropertiesInClass(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/non-abstract-hooked-properties-in-class.php'], [ + [ + 'Non-abstract properties cannot include hooks without bodies.', + 7, + ], + [ + 'Non-abstract properties cannot include hooks without bodies.', + 9, + ], + ]); + } + + public function testPhp84AndAbstractHookedPropertiesInClass(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-hooked-properties-in-class.php'], [ + [ + 'Non-abstract classes cannot include abstract properties.', + 7, + ], + [ + 'Non-abstract classes cannot include abstract properties.', + 9, + ], + ]); + } + + public function testPhp84AndNonAbstractHookedPropertiesInAbstractClass(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/non-abstract-hooked-properties-in-abstract-class.php'], [ + [ + 'Non-abstract properties cannot include hooks without bodies.', + 7, + ], + [ + 'Non-abstract properties cannot include hooks without bodies.', + 9, + ], + [ + 'Non-abstract properties cannot include hooks without bodies.', + 25, + ], + ]); + } + + public function testPhp84AndAbstractNonHookedPropertiesInAbstractClass(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-non-hooked-properties-in-abstract-class.php'], [ + [ + 'Only hooked properties can be declared abstract.', + 7, + ], + [ + 'Only hooked properties can be declared abstract.', + 9, + ], + ]); + } + + public function testPhp84AndAbstractHookedPropertiesWithBodies(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-hooked-properties-with-bodies.php'], [ + [ + 'Abstract properties must specify at least one abstract hook.', + 7, + ], + [ + 'Abstract properties must specify at least one abstract hook.', + 12, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-in-class.php b/tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-in-class.php new file mode 100644 index 00000000000..d035d36810f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-hooked-properties-in-class.php @@ -0,0 +1,10 @@ + $this->name; + set => $this->name = $value; + } + + public abstract string $lastName { + get => $this->lastName; + set => $this->lastName = $value; + } + + public abstract string $middleName { + get => $this->name; + set; + } + + public abstract string $familyName { + get; + set; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/abstract-non-hooked-properties-in-abstract-class.php b/tests/PHPStan/Rules/Properties/data/abstract-non-hooked-properties-in-abstract-class.php new file mode 100644 index 00000000000..b34e66a8860 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-non-hooked-properties-in-abstract-class.php @@ -0,0 +1,10 @@ + $this->name; + set => $this->name = $value; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php new file mode 100644 index 00000000000..dc839f0d2cf --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php @@ -0,0 +1,10 @@ + Date: Mon, 11 Nov 2024 17:15:04 +0100 Subject: [PATCH 0854/3097] get_defined_vars() return type contains known variables Co-Authored-By: Ruud Kamphuis --- conf/config.neon | 5 ++ ...DefinedVarsFunctionReturnTypeExtension.php | 46 +++++++++++++++++++ .../Analyser/nsrt/get-defined-vars.php | 46 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/get-defined-vars.php diff --git a/conf/config.neon b/conf/config.neon index f8ad1af8b31..19b6388858d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1525,6 +1525,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\GetDefinedVarsFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php b/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php new file mode 100644 index 00000000000..35999b424bf --- /dev/null +++ b/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php @@ -0,0 +1,46 @@ +getName() === 'get_defined_vars'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if ($scope->canAnyVariableExist()) { + return new ArrayType( + new StringType(), + new MixedType(), + ); + } + + $typeBuilder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($scope->getDefinedVariables() as $variable) { + $typeBuilder->setOffsetValueType(new ConstantStringType($variable), $scope->getVariableType($variable), false); + } + + foreach ($scope->getMaybeDefinedVariables() as $variable) { + $typeBuilder->setOffsetValueType(new ConstantStringType($variable), $scope->getVariableType($variable), true); + } + + return $typeBuilder->getArray(); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/get-defined-vars.php b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php new file mode 100644 index 00000000000..345d54dbd3e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php @@ -0,0 +1,46 @@ +', get_defined_vars()); // any variable can exist + +function doFoo(int $param) { + $local = "foo"; + assertType('array{param: int, local: \'foo\'}', get_defined_vars()); + assertType('array{\'param\', \'local\'}', array_keys(get_defined_vars())); +} + +function doBar(int $param) { + global $global; + $local = "foo"; + assertType('array{param: int, global: mixed, local: \'foo\'}', get_defined_vars()); + assertType('array{\'param\', \'global\', \'local\'}', array_keys(get_defined_vars())); +} + +function doConditional(int $param) { + $local = "foo"; + if(true) { + $conditional = "bar"; + assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); + } else { + $other = "baz"; + assertType('array{param: int, local: \'foo\', other: \'baz\'}', get_defined_vars()); + } + assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); +} + +function doRandom(int $param) { + $local = "foo"; + if(rand(0, 1)) { + $random1 = "bar"; + assertType('array{param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars()); + } else { + $random2 = "baz"; + assertType('array{param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars()); + } + assertType('array{param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars()); +} From 5efffc694288b20e591709748a34aae7239e173e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Nov 2024 07:55:27 +0100 Subject: [PATCH 0855/3097] Lazier return in `UnionType->isSuperTypeOfWithReason()` --- src/Type/UnionType.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 6f4cbf462b9..14eb8122674 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -245,10 +245,15 @@ public function isSuperTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return $otherType->isSubTypeOfWithReason($this); } - $result = IsSuperTypeOfResult::createNo()->or(...array_map(static fn (Type $innerType) => $innerType->isSuperTypeOfWithReason($otherType), $this->types)); - if ($result->yes()) { - return $result; + $results = []; + foreach ($this->types as $innerType) { + $result = $innerType->isSuperTypeOfWithReason($otherType); + if ($result->yes()) { + return $result; + } + $results[] = $result; } + $result = IsSuperTypeOfResult::createNo()->or(...$results); if ($otherType instanceof TemplateUnionType) { return $result->or($otherType->isSubTypeOfWithReason($this)); From 25d712e9dd8ed236ff2b94a35e5add932b9a81a8 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Wed, 20 Nov 2024 20:48:04 +0800 Subject: [PATCH 0856/3097] Fix `iterator_to_array` return type with generators --- ...atorToArrayFunctionReturnTypeExtension.php | 9 +++++- .../Analyser/nsrt/iterator_to_array.php | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php index 3eba7891757..623e3c0bcde 100644 --- a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php +++ b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php @@ -8,7 +8,9 @@ use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerType; +use PHPStan\Type\NeverType; use PHPStan\Type\Type; use function strtolower; @@ -29,7 +31,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $traversableType = $scope->getType($arguments[0]->value); - $arrayKeyType = $traversableType->getIterableKeyType(); + $arrayKeyType = $traversableType->getIterableKeyType()->toArrayKey(); + + if ($arrayKeyType instanceof ErrorType) { + return new NeverType(true); + } + $isList = false; if (isset($arguments[1])) { diff --git a/tests/PHPStan/Analyser/nsrt/iterator_to_array.php b/tests/PHPStan/Analyser/nsrt/iterator_to_array.php index 64ecbdeb055..4c7ddbc2b05 100644 --- a/tests/PHPStan/Analyser/nsrt/iterator_to_array.php +++ b/tests/PHPStan/Analyser/nsrt/iterator_to_array.php @@ -2,6 +2,7 @@ namespace IteratorToArray; +use stdClass; use Traversable; use function iterator_to_array; use function PHPStan\Testing\assertType; @@ -31,4 +32,33 @@ public function testNotPreservingKeys(Traversable $foo) { assertType('list', iterator_to_array($foo, false)); } + + public function testBehaviorOnGenerators(): void + { + $generator1 = static function (): iterable { + yield 0 => 1; + yield true => 2; + yield 2 => 3; + yield null => 4; + }; + $generator2 = static function (): iterable { + yield 0 => 1; + yield 'a' => 2; + yield null => 3; + yield true => 4; + }; + + assertType('array<0|1|2|\'\', 1|2|3|4>', iterator_to_array($generator1())); + assertType('array<0|1|\'\'|\'a\', 1|2|3|4>', iterator_to_array($generator2())); + } + + public function testOnGeneratorsWithIllegalKeysForArray(): void + { + $illegalGenerator = static function (): iterable { + yield 'a' => 'b'; + yield new stdClass => 'c'; + }; + + assertType('*NEVER*', iterator_to_array($illegalGenerator())); + } } From bd4452864826dd690faac1ddc7f060c44fcea3da Mon Sep 17 00:00:00 2001 From: schlndh Date: Sat, 30 Nov 2024 09:01:04 +0100 Subject: [PATCH 0857/3097] skip param castable to X on non-arrays Fixes bug 12146 --- src/Rules/ParameterCastableToStringCheck.php | 8 +-- ...plodeParameterCastableToStringRuleTest.php | 12 ++++- .../ParameterCastableToNumberRuleTest.php | 16 +++++- .../ParameterCastableToStringRuleTest.php | 16 +++++- .../SortParameterCastableToStringRuleTest.php | 12 ++++- .../Rules/Functions/data/bug-12146.php | 49 +++++++++++++++++++ 6 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-12146.php diff --git a/src/Rules/ParameterCastableToStringCheck.php b/src/Rules/ParameterCastableToStringCheck.php index 2d4dea0dbd4..97538635577 100644 --- a/src/Rules/ParameterCastableToStringCheck.php +++ b/src/Rules/ParameterCastableToStringCheck.php @@ -36,11 +36,13 @@ public function checkParameter( $scope, $parameter->value, '', - static fn (Type $type): bool => !$castFn($type->getIterableValueType()) instanceof ErrorType, + static fn (Type $type): bool => $type->isArray()->yes() && !$castFn($type->getIterableValueType()) instanceof ErrorType, ); - if ($typeResult->getType() instanceof ErrorType - || !$castFn($typeResult->getType()->getIterableValueType()) instanceof ErrorType) { + if ( + ! $typeResult->getType()->isArray()->yes() + || !$castFn($typeResult->getType()->getIterableValueType()) instanceof ErrorType + ) { return null; } diff --git a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php index 8111e9a9657..65e4714487d 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php @@ -17,7 +17,7 @@ class ImplodeParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); + return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); } public function testNamedArguments(): void @@ -100,4 +100,14 @@ public function testBug8467a(): void $this->analyse([__DIR__ . '/../Arrays/data/bug-8467a.php'], []); } + public function testBug12146(): void + { + $this->analyse([__DIR__ . '/data/bug-12146.php'], [ + [ + 'Parameter #2 $array of function implode expects array, array given.', + 28, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php index 77b64c3a304..e4708c5f4c5 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php @@ -19,7 +19,7 @@ class ParameterCastableToNumberRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); + return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); } public function testRule(): void @@ -130,6 +130,20 @@ public function testBug11883(): void ]); } + public function testBug12146(): void + { + $this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackPhp74ErrorMessages([ + [ + 'Parameter #1 $array of function array_sum expects an array of values castable to number, array given.', + 16, + ], + [ + 'Parameter #1 $array of function array_product expects an array of values castable to number, array given.', + 22, + ], + ])); + } + /** * @param list $errors * @return list diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php index 83b0f405678..0ac63042314 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class ParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); + return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); } public function testRule(): void @@ -196,6 +196,20 @@ public function testBug11141(): void ]); } + public function testBug12146(): void + { + $this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackParameterNames([ + [ + 'Parameter #1 $array of function array_intersect expects an array of values castable to string, array given.', + 34, + ], + [ + 'Parameter #1 $keys of function array_fill_keys expects an array of values castable to string, array given.', + 40, + ], + ])); + } + /** * @param list $errors * @return list diff --git a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php index 8c0105a424e..2fc82648218 100644 --- a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class SortParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, false, false, false))); + return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); } public function testRule(): void @@ -145,6 +145,16 @@ public function testBug11167(): void $this->analyse([__DIR__ . '/data/bug-11167.php'], []); } + public function testBug12146(): void + { + $this->analyse([__DIR__ . '/data/bug-12146.php'], $this->hackParameterNames([ + [ + 'Parameter #1 $array of function array_unique expects an array of values castable to string, array given.', + 46, + ], + ])); + } + /** * @param list $errors * @return list diff --git a/tests/PHPStan/Rules/Functions/data/bug-12146.php b/tests/PHPStan/Rules/Functions/data/bug-12146.php new file mode 100644 index 00000000000..bd0bf858c3a --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12146.php @@ -0,0 +1,49 @@ +|array $validArrayUnion valid + * @param array|array<\stdClass> $invalidArrayUnion invalid, report + * @param ?array<\stdClass> $nullableInvalidArray invalid, but don't report because it's reported by CallToFunctionParametersRule + * @param array<\stdClass>|\SplFixedArray $arrayOrSplArray invalid, but don't report because it's reported by CallToFunctionParametersRule + * @return void + */ +function foo($mixed, $validArrayUnion, $invalidArrayUnion, $nullableInvalidArray, $arrayOrSplArray) { + var_dump(array_sum($mixed)); + var_dump(array_sum($validArrayUnion)); + var_dump(array_sum($invalidArrayUnion)); + var_dump(array_sum($nullableInvalidArray)); + var_dump(array_sum($arrayOrSplArray)); + + var_dump(array_product($mixed)); + var_dump(array_product($validArrayUnion)); + var_dump(array_product($invalidArrayUnion)); + var_dump(array_product($nullableInvalidArray)); + var_dump(array_product($arrayOrSplArray)); + + var_dump(implode(',', $mixed)); + var_dump(implode(',', $validArrayUnion)); + var_dump(implode(',', $invalidArrayUnion)); + var_dump(implode(',', $nullableInvalidArray)); + var_dump(implode(',', $arrayOrSplArray)); + + var_dump(array_intersect($mixed, [5])); + var_dump(array_intersect($validArrayUnion, [5])); + var_dump(array_intersect($invalidArrayUnion, [5])); + var_dump(array_intersect($nullableInvalidArray, [5])); + var_dump(array_intersect($arrayOrSplArray, [5])); + + var_dump(array_fill_keys($mixed, 1)); + var_dump(array_fill_keys($validArrayUnion, 1)); + var_dump(array_fill_keys($invalidArrayUnion, 1)); + var_dump(array_fill_keys($nullableInvalidArray, 1)); + var_dump(array_fill_keys($arrayOrSplArray, 1)); + + var_dump(array_unique($mixed)); + var_dump(array_unique($validArrayUnion)); + var_dump(array_unique($invalidArrayUnion)); + var_dump(array_unique($nullableInvalidArray)); + var_dump(array_unique($arrayOrSplArray)); +} From 48f899084c0786d84ae86463bfc25711e4aacd36 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 30 Nov 2024 14:49:14 +0100 Subject: [PATCH 0858/3097] 5x Faster `IntersectionType->getEnumCases()` --- src/Type/IntersectionType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index d41b79e951f..519649a384e 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -842,7 +842,7 @@ public function getEnumCases(): array foreach ($this->types as $type) { $oneType = []; foreach ($type->getEnumCases() as $enumCase) { - $oneType[md5($enumCase->describe(VerbosityLevel::typeOnly()))] = $enumCase; + $oneType[$enumCase->getClassName() . '::' . $enumCase->getEnumCaseName()] = $enumCase; } $compare[] = $oneType; } From c5860144ae7c00bc860ae8eb41d7343137829467 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Nov 2024 16:09:56 +0100 Subject: [PATCH 0859/3097] `MixedType::toArrayKey()` returns BenevolentUnionType --- src/Type/MixedType.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 4 ++-- tests/PHPStan/Analyser/nsrt/array-column-php82.php | 12 ++++++------ tests/PHPStan/Analyser/nsrt/array-column.php | 14 +++++++------- tests/PHPStan/Analyser/nsrt/array-flip-php7.php | 2 +- tests/PHPStan/Analyser/nsrt/array-flip-php8.php | 2 +- tests/PHPStan/Analyser/nsrt/array-flip.php | 2 +- tests/PHPStan/Analyser/nsrt/non-empty-array.php | 2 +- .../data/slevomat-foreach-array-key-exists-bug.php | 10 +++++----- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 3ae9434a95f..6ca4e4bad81 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -600,7 +600,7 @@ public function toArray(): Type public function toArrayKey(): Type { - return new UnionType([new IntegerType(), new StringType()]); + return new BenevolentUnionType([new IntegerType(), new StringType()]); } public function isIterable(): TrinaryLogic diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 5434782186c..42b0d24959a 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -3328,7 +3328,7 @@ public function dataLiteralArraysKeys(): array "'BooleansArray'", ], [ - 'int|string', + '(int|string)', "'UnknownConstantArray'", ], ]; @@ -9147,7 +9147,7 @@ public function dataGeneralizeScope(): array { return [ [ - 'array, removeCount: int<0, max>, loadCount: int<0, max>, hitCount: int<0, max>}>>', + 'array, removeCount: int<0, max>, loadCount: int<0, max>, hitCount: int<0, max>}>>', '$statistics', ], ]; diff --git a/tests/PHPStan/Analyser/nsrt/array-column-php82.php b/tests/PHPStan/Analyser/nsrt/array-column-php82.php index 62350f59927..d53ab61f00d 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column-php82.php +++ b/tests/PHPStan/Analyser/nsrt/array-column-php82.php @@ -177,8 +177,8 @@ public function testImprecise5(array $array): void assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('array', array_column($array, 'nodeName', 'foo')); - assertType('array', array_column($array, null, 'foo')); + assertType('array', array_column($array, 'nodeName', 'foo')); + assertType('array', array_column($array, null, 'foo')); } /** @param non-empty-array $array */ @@ -189,8 +189,8 @@ public function testObjects1(array $array): void assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); - assertType('non-empty-array', array_column($array, null, 'foo')); + assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); + assertType('non-empty-array', array_column($array, null, 'foo')); } /** @param array{DOMElement} $array */ @@ -201,8 +201,8 @@ public function testObjects2(array $array): void assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); - assertType('non-empty-array', array_column($array, null, 'foo')); + assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); + assertType('non-empty-array', array_column($array, null, 'foo')); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-column.php b/tests/PHPStan/Analyser/nsrt/array-column.php index 2455d6ace9d..3e3cedbbffa 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column.php +++ b/tests/PHPStan/Analyser/nsrt/array-column.php @@ -191,8 +191,8 @@ public function testImprecise5(array $array): void assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('array', array_column($array, 'nodeName', 'foo')); - assertType('array', array_column($array, null, 'foo')); + assertType('array', array_column($array, 'nodeName', 'foo')); + assertType('array', array_column($array, null, 'foo')); } /** @param non-empty-array $array */ @@ -203,8 +203,8 @@ public function testObjects1(array $array): void assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); - assertType('non-empty-array', array_column($array, null, 'foo')); + assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); + assertType('non-empty-array', array_column($array, null, 'foo')); } /** @param array{DOMElement} $array */ @@ -215,8 +215,8 @@ public function testObjects2(array $array): void assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); assertType('array', array_column($array, 'foo', 'tagName')); - assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); - assertType('non-empty-array', array_column($array, null, 'foo')); + assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); + assertType('non-empty-array', array_column($array, null, 'foo')); } } @@ -228,7 +228,7 @@ final class Foo public function doFoo(array $a): void { assertType('list', array_column($a, 'nodeName')); - assertType('array', array_column($a, 'nodeName', 'tagName')); + assertType('array', array_column($a, 'nodeName', 'tagName')); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-flip-php7.php b/tests/PHPStan/Analyser/nsrt/array-flip-php7.php index 0b7058de010..0f14074f5c1 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip-php7.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip-php7.php @@ -7,7 +7,7 @@ function mixedAndSubtractedArray($mixed) { if (is_array($mixed)) { - assertType('array', array_flip($mixed)); + assertType('array<(int|string)>', array_flip($mixed)); } else { assertType('mixed~array', $mixed); assertType('null', array_flip($mixed)); diff --git a/tests/PHPStan/Analyser/nsrt/array-flip-php8.php b/tests/PHPStan/Analyser/nsrt/array-flip-php8.php index b8f0e6793d1..be439d0427d 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip-php8.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip-php8.php @@ -7,7 +7,7 @@ function mixedAndSubtractedArray($mixed) { if (is_array($mixed)) { - assertType('array', array_flip($mixed)); + assertType('array<(int|string)>', array_flip($mixed)); } else { assertType('mixed~array', $mixed); assertType('*NEVER*', array_flip($mixed)); diff --git a/tests/PHPStan/Analyser/nsrt/array-flip.php b/tests/PHPStan/Analyser/nsrt/array-flip.php index 2f02f1e733f..b6a6eb6c665 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip.php @@ -20,7 +20,7 @@ function foo3($list) { $flip = array_flip($list); - assertType('array', $flip); + assertType('array<(int|string)>', $flip); } /** diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-array.php b/tests/PHPStan/Analyser/nsrt/non-empty-array.php index a7cdc6540ac..9f95a09c0a0 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-array.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-array.php @@ -54,7 +54,7 @@ public function arrayFunctions($array, $list, $stringArray): void assertType('non-empty-array', array_replace($array, [])); assertType('non-empty-array', array_replace($array, $array)); - assertType('non-empty-array', array_flip($array)); + assertType('non-empty-array<(int|string)>', array_flip($array)); assertType('non-empty-array', array_flip($stringArray)); } } diff --git a/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php b/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php index bddbb8f06d2..0588be365b7 100644 --- a/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php +++ b/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php @@ -15,26 +15,26 @@ public function doFoo(array $percentageIntervals, array $changes): void if ($percentageInterval->isInInterval((float) $changeInPercents)) { $key = $percentageInterval->getFormatted(); if (array_key_exists($key, $intervalResults)) { - assertType('array', $intervalResults); + assertType('array', $intervalResults); assertType('array{itemsCount: mixed, interval: mixed}', $intervalResults[$key]); $intervalResults[$key]['itemsCount'] += $itemsCount; - assertType('non-empty-array', $intervalResults); + assertType('non-empty-array', $intervalResults); assertType('array{itemsCount: (array|float|int), interval: mixed}', $intervalResults[$key]); } else { - assertType('array', $intervalResults); + assertType('array', $intervalResults); assertType('array{itemsCount: mixed, interval: mixed}', $intervalResults[$key]); $intervalResults[$key] = [ 'itemsCount' => $itemsCount, 'interval' => $percentageInterval, ]; - assertType('non-empty-array', $intervalResults); + assertType('non-empty-array', $intervalResults); assertType('array{itemsCount: mixed, interval: mixed}', $intervalResults[$key]); } } } } - assertType('array', $intervalResults); + assertType('array', $intervalResults); foreach ($intervalResults as $data) { echo $data['interval']; } From f8d27d5a803d7923a60e267cc8a78bfc2b8b9e10 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 2 Dec 2024 00:22:35 +0000 Subject: [PATCH 0860/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 0f28344835c..5e0a1f1e018 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.44.0.6", - "phpstan/php-8-stubs": "0.4.8", + "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 7cd32ee1ad8..22c9a79b6e7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aa42dc6e1a7a8d5fc9844d231515e30f", + "content-hash": "db7c74816a1b1cd707c98ae62551ce35", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.8", + "version": "0.4.9", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "8a6278b2b9c9781cb969c4128da361b78eead604" + "reference": "1857c330fea6e795af1f7435ed02a18652e7dd8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/8a6278b2b9c9781cb969c4128da361b78eead604", - "reference": "8a6278b2b9c9781cb969c4128da361b78eead604", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1857c330fea6e795af1f7435ed02a18652e7dd8c", + "reference": "1857c330fea6e795af1f7435ed02a18652e7dd8c", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.8" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.9" }, - "time": "2024-11-28T00:20:48+00:00" + "time": "2024-12-02T00:21:59+00:00" }, { "name": "phpstan/phpdoc-parser", From 90933b318e332c88f675c8aa946cdfb5788a51fe Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 2 Dec 2024 22:54:08 +0200 Subject: [PATCH 0861/3097] Remove incorrect CURLOPT_ACCEPT_ENCODING alias --- src/Reflection/ParametersAcceptorSelector.php | 3 +-- .../CallToFunctionParametersRuleTest.php | 26 ++++++++++++------- .../Rules/Functions/data/curl_setopt.php | 4 +++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 82af7d4267d..703d3458065 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -919,7 +919,6 @@ private static function getCurlOptValueType(int $curlOpt): ?Type } $nullableStringConstants = [ - 'CURLOPT_ACCEPT_ENCODING', 'CURLOPT_CUSTOMREQUEST', 'CURLOPT_DNS_INTERFACE', 'CURLOPT_DNS_LOCAL_IP4', @@ -1032,7 +1031,7 @@ private static function getCurlOptValueType(int $curlOpt): ?Type $stringConstants = [ 'CURLOPT_COOKIEFILE', - 'CURLOPT_ENCODING', + 'CURLOPT_ENCODING', // Alias: CURLOPT_ACCEPT_ENCODING 'CURLOPT_PRE_PROXY', 'CURLOPT_PRIVATE', 'CURLOPT_PROXY', diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index af3d4af2c32..89017fe2cf1 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1321,40 +1321,48 @@ public function testCurlSetOpt(): void 18, ], [ - 'Parameter #3 $value of function curl_setopt expects bool, int given.', + 'Parameter #3 $value of function curl_setopt expects string, int given.', + 19, + ], + [ + 'Parameter #3 $value of function curl_setopt expects string, int given.', 20, ], + [ + 'Parameter #3 $value of function curl_setopt expects bool, int given.', + 22, + ], [ 'Parameter #3 $value of function curl_setopt expects bool, string given.', - 21, + 23, ], [ 'Parameter #3 $value of function curl_setopt expects int, string given.', - 23, + 25, ], [ 'Parameter #3 $value of function curl_setopt expects array, string given.', - 25, + 27, ], [ 'Parameter #3 $value of function curl_setopt expects resource, string given.', - 27, + 29, ], [ 'Parameter #3 $value of function curl_setopt expects array|string, int given.', - 29, + 31, ], [ 'Parameter #3 $value of function curl_setopt expects non-empty-string, \'\' given.', - 31, + 33, ], [ 'Parameter #3 $value of function curl_setopt expects non-empty-string|null, \'\' given.', - 32, + 34, ], [ 'Parameter #3 $value of function curl_setopt expects array, array given.', - 73, + 77, ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index 24987ddbc95..bc5b8f6ceab 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -16,6 +16,8 @@ public function errors(int $i, string $s) { curl_setopt($curl, CURLOPT_URL, $i); curl_setopt($curl, CURLOPT_HTTPHEADER, $i); curl_setopt($curl, CURLOPT_ABSTRACT_UNIX_SOCKET, null); + curl_setopt($curl, CURLOPT_ENCODING, $i); + curl_setopt($curl, CURLOPT_ACCEPT_ENCODING, $i); // expecting bool curl_setopt($curl, CURLOPT_AUTOREFERER, $i); curl_setopt($curl, CURLOPT_RETURNTRANSFER, $s); @@ -62,6 +64,8 @@ public function allGood(string $url, array $header) { curl_setopt($curl, CURLOPT_PRE_PROXY, ''); curl_setopt($curl, CURLOPT_PROXY, ''); curl_setopt($curl, CURLOPT_PRIVATE, ''); + curl_setopt($curl, CURLOPT_ENCODING, ''); + curl_setopt($curl, CURLOPT_ACCEPT_ENCODING, ''); } public function bug9263() { From 28dfac80bb4a05891ad4da675677c2077098d080 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:03:57 +0000 Subject: [PATCH 0862/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 5e0a1f1e018..c678b5998fa 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", + "jetbrains/phpstorm-stubs": "dev-master#bb981ec60b3838e56473a078edf7d0739ca20403", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 22c9a79b6e7..8557d36dae6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db7c74816a1b1cd707c98ae62551ce35", + "content-hash": "ee384a5c11fbc1dd087ab8dc956b8d73", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7" + "reference": "bb981ec60b3838e56473a078edf7d0739ca20403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", - "reference": "7809499af1a5f3bdd20f1cab71717a2a9e1f8cf7", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/bb981ec60b3838e56473a078edf7d0739ca20403", + "reference": "bb981ec60b3838e56473a078edf7d0739ca20403", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-27T14:37:09+00:00" + "time": "2024-11-27T16:45:26+00:00" }, { "name": "nette/bootstrap", From fbcad414543b6f6fae4acc2801de21bfa1348887 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Thu, 5 Dec 2024 12:44:05 +0100 Subject: [PATCH 0863/3097] Fix `fgetcsv` return type; never returns null --- resources/functionMap_php80delta.php | 1 + tests/PHPStan/Analyser/nsrt/fgetcsv-php7.php | 12 ++++++++++++ tests/PHPStan/Analyser/nsrt/fgetcsv-php8.php | 12 ++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/fgetcsv-php7.php create mode 100644 tests/PHPStan/Analyser/nsrt/fgetcsv-php8.php diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index bb267a5e494..879dc310e4b 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -46,6 +46,7 @@ 'error_log' => ['bool', 'message'=>'string', 'message_type='=>'0|1|3|4', 'destination='=>'string', 'extra_headers='=>'string'], 'explode' => ['list', 'separator'=>'non-empty-string', 'str'=>'string', 'limit='=>'int'], 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], + 'fgetcsv' => ['list|array{0: null}|false', 'fp'=>'resource', 'length='=>'0|positive-int|null', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'filter_input' => ['mixed', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'variable_name'=>'string', 'filter='=>'int', 'options='=>'array|int'], 'filter_input_array' => ['array|false|null', 'type'=>'INPUT_GET|INPUT_POST|INPUT_COOKIE|INPUT_SERVER|INPUT_ENV', 'definition='=>'int|array', 'add_empty='=>'bool'], 'floor' => ['float', 'number'=>'float'], diff --git a/tests/PHPStan/Analyser/nsrt/fgetcsv-php7.php b/tests/PHPStan/Analyser/nsrt/fgetcsv-php7.php new file mode 100644 index 00000000000..8a384650406 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/fgetcsv-php7.php @@ -0,0 +1,12 @@ +|false|null', fgetcsv($resource)); // nullable when invalid argument is given (https://3v4l.org/4WmR5#v7.4.30) +} diff --git a/tests/PHPStan/Analyser/nsrt/fgetcsv-php8.php b/tests/PHPStan/Analyser/nsrt/fgetcsv-php8.php new file mode 100644 index 00000000000..fccf29931c4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/fgetcsv-php8.php @@ -0,0 +1,12 @@ += 8.0 + +declare(strict_types = 1); + +namespace TestFGetCsvPhp8; + +use function PHPStan\Testing\assertType; + +function test($resource): void +{ + assertType('list|false', fgetcsv($resource)); +} From 7b4c9afd090d89d595eb113831bc4b79b45d22e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Thu, 5 Dec 2024 12:15:02 +0100 Subject: [PATCH 0864/3097] Workaround bug in slevomat/coding-standard TypeNameMatchesFileName For each root namespace, the slevomat rule considers the left-most match of the given directory in the absolute path of the file. That is, for /home/user/src/phpstan-src/ the root namespace PHPStan is not assigned to /home/user/src/phpstan-src/src, but to /home/user/src, which is obviously wrong. The bug is known as slevomat/coding-standard#1249 for a long time, but yet to be fixed. To avoid issues for developers of PHPStan, we can set a basepath of "." in the PHP CodeSniffer config, which causes paths to be evaluated relative to the current directory, avoiding false-positives in the path leading up to the phpstan-src directory. --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index 7ec0a21da25..fa9198745fb 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,6 +1,7 @@ + From 4f142f59424b7d49856fdba9d70ac4d3ef184d36 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Fri, 6 Dec 2024 20:09:22 +0800 Subject: [PATCH 0865/3097] Fix `iterator_to_array` to early return when `$preserveKeys` is false --- ...atorToArrayFunctionReturnTypeExtension.php | 27 ++++++++----------- .../Analyser/nsrt/iterator_to_array.php | 3 +++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php index 623e3c0bcde..d1dc17f176a 100644 --- a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php +++ b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php @@ -31,33 +31,28 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $traversableType = $scope->getType($arguments[0]->value); - $arrayKeyType = $traversableType->getIterableKeyType()->toArrayKey(); - - if ($arrayKeyType instanceof ErrorType) { - return new NeverType(true); - } - - $isList = false; if (isset($arguments[1])) { $preserveKeysType = $scope->getType($arguments[1]->value); if ($preserveKeysType->isFalse()->yes()) { - $arrayKeyType = new IntegerType(); - $isList = true; + return AccessoryArrayListType::intersectWith(new ArrayType( + new IntegerType(), + $traversableType->getIterableValueType(), + )); } } - $arrayType = new ArrayType( - $arrayKeyType, - $traversableType->getIterableValueType(), - ); + $arrayKeyType = $traversableType->getIterableKeyType()->toArrayKey(); - if ($isList) { - $arrayType = AccessoryArrayListType::intersectWith($arrayType); + if ($arrayKeyType instanceof ErrorType) { + return new NeverType(true); } - return $arrayType; + return new ArrayType( + $arrayKeyType, + $traversableType->getIterableValueType(), + ); } } diff --git a/tests/PHPStan/Analyser/nsrt/iterator_to_array.php b/tests/PHPStan/Analyser/nsrt/iterator_to_array.php index 4c7ddbc2b05..038b36f7fd3 100644 --- a/tests/PHPStan/Analyser/nsrt/iterator_to_array.php +++ b/tests/PHPStan/Analyser/nsrt/iterator_to_array.php @@ -49,7 +49,9 @@ public function testBehaviorOnGenerators(): void }; assertType('array<0|1|2|\'\', 1|2|3|4>', iterator_to_array($generator1())); + assertType('list<1|2|3|4>', iterator_to_array($generator1(), false)); assertType('array<0|1|\'\'|\'a\', 1|2|3|4>', iterator_to_array($generator2())); + assertType('list<1|2|3|4>', iterator_to_array($generator2(), false)); } public function testOnGeneratorsWithIllegalKeysForArray(): void @@ -60,5 +62,6 @@ public function testOnGeneratorsWithIllegalKeysForArray(): void }; assertType('*NEVER*', iterator_to_array($illegalGenerator())); + assertType('list<\'b\'|\'c\'>', iterator_to_array($illegalGenerator(), false)); } } From 9a718ee52c07c6b62c0eceac3bf170ac45740b6a Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Wed, 4 Dec 2024 20:26:32 -0500 Subject: [PATCH 0866/3097] Fix-GH-12021 --- ...mptyStringFunctionsReturnTypeExtension.php | 30 +++++++++++++++++++ .../Analyser/nsrt/non-empty-string.php | 9 ++++++ .../Analyser/nsrt/non-falsy-string.php | 5 ++++ 3 files changed, 44 insertions(+) diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index 083914085a7..893627aae2a 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -2,17 +2,20 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use function count; use function in_array; +use const ENT_SUBSTITUTE; final class NonEmptyStringFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -45,6 +48,15 @@ public function getTypeFromFunctionCall( return null; } + if (in_array($functionReflection->getName(), [ + 'htmlspecialchars', + 'htmlentities', + ], true)) { + if (!$this->isSubstituteFlagSet($args, $scope)) { + return new StringType(); + } + } + $argType = $scope->getType($args[0]->value); if ($argType->isNonFalsyString()->yes()) { return new IntersectionType([ @@ -62,4 +74,22 @@ public function getTypeFromFunctionCall( return new StringType(); } + /** + * @param Arg[] $args + */ + private function isSubstituteFlagSet( + array $args, + Scope $scope, + ): bool + { + if (!isset($args[1])) { + return true; + } + $flagsType = $scope->getType($args[1]->value); + if (!$flagsType instanceof ConstantIntegerType) { + return false; + } + return (bool) ($flagsType->getValue() & ENT_SUBSTITUTE); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index cd831db4d82..59d1af39317 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -8,6 +8,7 @@ use function strtolower; use function strtoupper; use function ucfirst; +use const ENT_SUBSTITUTE; class Foo { @@ -333,9 +334,17 @@ public function doFoo(string $s, string $nonEmpty, string $nonFalsy, int $i, boo assertType('string', ucwords($s)); assertType('non-empty-string', ucwords($nonEmpty)); assertType('string', htmlspecialchars($s)); + assertType('string', htmlspecialchars($s, ENT_SUBSTITUTE)); + assertType('string', htmlspecialchars($s, 0)); assertType('non-empty-string', htmlspecialchars($nonEmpty)); + assertType('non-empty-string', htmlspecialchars($nonEmpty, ENT_SUBSTITUTE)); + assertType('string', htmlspecialchars($nonEmpty, 0)); assertType('string', htmlentities($s)); + assertType('string', htmlentities($s, ENT_SUBSTITUTE)); + assertType('string', htmlentities($s, 0)); assertType('non-empty-string', htmlentities($nonEmpty)); + assertType('non-empty-string', htmlentities($nonEmpty, ENT_SUBSTITUTE)); + assertType('string', htmlentities($nonEmpty, 0)); assertType('string', urlencode($s)); assertType('non-empty-string', urlencode($nonEmpty)); diff --git a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php index c5fd9fc1d81..744bdaf3b53 100644 --- a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php @@ -3,6 +3,7 @@ namespace NonFalseyString; use function PHPStan\Testing\assertType; +use const ENT_SUBSTITUTE; class Foo { /** @@ -95,7 +96,11 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra assertType('non-falsy-string', ucfirst($nonFalsey)); assertType('non-falsy-string', ucwords($nonFalsey)); assertType('non-falsy-string', htmlspecialchars($nonFalsey)); + assertType('non-falsy-string', htmlspecialchars($nonFalsey, ENT_SUBSTITUTE)); + assertType('string', htmlspecialchars($nonFalsey, 0)); assertType('non-falsy-string', htmlentities($nonFalsey)); + assertType('non-falsy-string', htmlentities($nonFalsey, ENT_SUBSTITUTE)); + assertType('string', htmlentities($nonFalsey, 0)); assertType('non-falsy-string', urlencode($nonFalsey)); assertType('non-falsy-string', urldecode($nonFalsey)); From acb109f0c4ad8871c11d6df962a78255b313f463 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Dec 2024 09:56:05 +0100 Subject: [PATCH 0867/3097] Try reproduce a bug locally --- .../Rules/Keywords/RequireFileExistsRuleTest.php | 14 ++++++++++++++ tests/PHPStan/Rules/Keywords/data/bug-12203.php | 6 ++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/PHPStan/Rules/Keywords/data/bug-12203.php diff --git a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php index 6bd3e1dfd72..6bc5dc45c24 100644 --- a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php @@ -121,4 +121,18 @@ public function testBug11738(): void $this->analyse([__DIR__ . '/data/bug-11738/bug-11738.php'], []); } + public function testBug12203(): void + { + $this->analyse([__DIR__ . '/data/bug-12203.php'], [ + [ + 'Path in require_once() "../bug-12203-sure-does-not-exist.php" is not a file or it does not exist.', + 5, + ], + [ + 'Path in require_once() "' . __DIR__ . '/data/../bug-12203-sure-does-not-exist.php" is not a file or it does not exist.', + 6, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Keywords/data/bug-12203.php b/tests/PHPStan/Rules/Keywords/data/bug-12203.php new file mode 100644 index 00000000000..d64dcc6ebeb --- /dev/null +++ b/tests/PHPStan/Rules/Keywords/data/bug-12203.php @@ -0,0 +1,6 @@ + Date: Mon, 9 Dec 2024 10:06:07 +0100 Subject: [PATCH 0868/3097] Fix --- tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php index 6bc5dc45c24..732819b506c 100644 --- a/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/RequireFileExistsRuleTest.php @@ -8,6 +8,7 @@ use function implode; use function realpath; use function set_include_path; +use const DIRECTORY_SEPARATOR; use const PATH_SEPARATOR; /** @@ -129,7 +130,7 @@ public function testBug12203(): void 5, ], [ - 'Path in require_once() "' . __DIR__ . '/data/../bug-12203-sure-does-not-exist.php" is not a file or it does not exist.', + 'Path in require_once() "' . __DIR__ . DIRECTORY_SEPARATOR . 'data/../bug-12203-sure-does-not-exist.php" is not a file or it does not exist.', 6, ], ]); From 621e16829817e412f948420f24640971ee84e667 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 9 Dec 2024 10:10:24 +0100 Subject: [PATCH 0869/3097] Optimization - do not enter anonymous classes during loop analysis --- src/Analyser/NodeScopeResolver.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 5c4d86526ee..5f518077a25 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -846,6 +846,9 @@ private function processStmtNode( } elseif ($stmt instanceof Node\Stmt\Trait_) { return new StatementResult($scope, false, false, [], [], []); } elseif ($stmt instanceof Node\Stmt\ClassLike) { + if (!$context->isTopLevel()) { + return new StatementResult($scope, false, false, [], [], []); + } $hasYield = false; $throwPoints = []; $impurePoints = []; From 09eab21c16ec92d238feb95520b5edf16a5a0d1d Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 9 Dec 2024 10:38:29 +0100 Subject: [PATCH 0870/3097] Add regression test for array self-append --- .../Analyser/AnalyserIntegrationTest.php | 6 ++ tests/PHPStan/Analyser/data/bug-6948.php | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-6948.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index ebdf3a03e41..a4ee70e41f5 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -981,6 +981,12 @@ public function testArrayUnion(): void $this->assertNoErrors($errors); } + public function testBug6948(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-6948.php'); + $this->assertNoErrors($errors); + } + public function testBug7963(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7963.php'); diff --git a/tests/PHPStan/Analyser/data/bug-6948.php b/tests/PHPStan/Analyser/data/bug-6948.php new file mode 100644 index 00000000000..592f0af7520 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6948.php @@ -0,0 +1,64 @@ + Date: Mon, 9 Dec 2024 21:56:00 +0100 Subject: [PATCH 0871/3097] Array map on multiple elements is a list --- .../Php/ArrayMapFunctionReturnTypeExtension.php | 10 ++++++---- .../PHPStan/Analyser/nsrt/array_map_multiple.php | 10 +++++----- .../PHPStan/Rules/Methods/ReturnTypeRuleTest.php | 5 +++++ tests/PHPStan/Rules/Methods/data/bug-12223.php | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12223.php diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index ce29b37d2a0..31c239af58a 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -168,10 +168,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); } } else { - $mappedArrayType = TypeCombinator::intersect(new ArrayType( - new IntegerType(), - $valueType, - ), ...TypeUtils::getAccessoryTypes($arrayType)); + $mappedArrayType = AccessoryArrayListType::intersectWith( + TypeCombinator::intersect(new ArrayType( + new IntegerType(), + $valueType, + ), ...TypeUtils::getAccessoryTypes($arrayType)), + ); } if ($arrayType->isIterableAtLeastOnce()->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php index d986969c3e9..2918ebeb898 100644 --- a/tests/PHPStan/Analyser/nsrt/array_map_multiple.php +++ b/tests/PHPStan/Analyser/nsrt/array_map_multiple.php @@ -15,7 +15,7 @@ public function doFoo(int $i, string $s): void return rand(0, 1) ? $a : $b; }, ['foo' => $i], ['bar' => $s]); - assertType('non-empty-array', $result); + assertType('non-empty-list', $result); } /** @@ -26,12 +26,12 @@ public function arrayMapNull(array $array, array $other): void { assertType('array{}', array_map(null, [])); assertType('array{foo: true}', array_map(null, ['foo' => true])); - assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); + assertType('non-empty-list', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); - assertType('non-empty-array', array_map(null, $array, $array, $array)); - assertType('non-empty-array', array_map(null, $array, $other)); + assertType('non-empty-list', array_map(null, $array, $array)); + assertType('non-empty-list', array_map(null, $array, $array, $array)); + assertType('non-empty-list', array_map(null, $array, $other)); assertType('array{1}|array{true}', array_map(null, rand() ? [1] : [true])); assertType('array{1}|array{true, false}', array_map(null, rand() ? [1] : [true, false])); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index eaa0465a428..13082d79b00 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1064,4 +1064,9 @@ public function testBug11857(): void $this->analyse([__DIR__ . '/data/bug-11857-builder.php'], []); } + public function testBug12223(): void + { + $this->analyse([__DIR__ . '/data/bug-12223.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12223.php b/tests/PHPStan/Rules/Methods/data/bug-12223.php new file mode 100644 index 00000000000..29334bf8359 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12223.php @@ -0,0 +1,15 @@ + + */ + public function sayHello(): array + { + $a = [1 => 'foo', 3 => 'bar', 5 => 'baz']; + return array_map(static fn(string $s, int $i): string => $s . $i, $a, array_keys($a)); + } +} From 71b25bc5c50b84433ec608284630ca8c25d97781 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 10 Dec 2024 11:47:07 +0100 Subject: [PATCH 0872/3097] Fix after merge --- src/Type/Php/ArrayMapFunctionReturnTypeExtension.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e79a889dd79..145756b971e 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -168,12 +168,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); } } else { - $mappedArrayType = AccessoryArrayListType::intersectWith( - TypeCombinator::intersect(new ArrayType( - new IntegerType(), - $valueType, - ), ...TypeUtils::getAccessoryTypes($arrayType)), - ); + $mappedArrayType = TypeCombinator::intersect(new ArrayType( + new IntegerType(), + $valueType, + ), new AccessoryArrayListType(), ...TypeUtils::getAccessoryTypes($arrayType)); } if ($arrayType->isIterableAtLeastOnce()->yes()) { From 9ca11225524a12176640d0c519cd835a3a6596af Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Sun, 1 Dec 2024 02:45:14 +0800 Subject: [PATCH 0873/3097] Introduce `ClassAsClassConstantRule` --- Makefile | 1 + conf/config.level0.neon | 1 + .../Constants/ClassAsClassConstantRule.php | 40 +++++++++++++++++++ .../ClassAsClassConstantRuleTest.php | 33 +++++++++++++++ .../data/class-as-class-constant.php | 17 ++++++++ 5 files changed, 92 insertions(+) create mode 100644 src/Rules/Constants/ClassAsClassConstantRule.php create mode 100644 tests/PHPStan/Rules/Constants/ClassAsClassConstantRuleTest.php create mode 100644 tests/PHPStan/Rules/Constants/data/class-as-class-constant.php diff --git a/Makefile b/Makefile index 3282ee2c4e7..11807088cac 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php \ --exclude tests/PHPStan/Rules/Classes/data/instantiation-callable.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-9402.php \ + --exclude tests/PHPStan/Rules/Constants/data/class-as-class-constant.php \ --exclude tests/PHPStan/Rules/Constants/data/value-assigned-to-class-constant-native-type.php \ --exclude tests/PHPStan/Rules/Constants/data/overriding-constant-native-types.php \ --exclude tests/PHPStan/Rules/Methods/data/bug-10043.php \ diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1382d99ee16..15f1048b866 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -68,6 +68,7 @@ rules: - PHPStan\Rules\Classes\NonClassAttributeClassRule - PHPStan\Rules\Classes\ReadOnlyClassRule - PHPStan\Rules\Classes\TraitAttributeClassRule + - PHPStan\Rules\Constants\ClassAsClassConstantRule - PHPStan\Rules\Constants\DynamicClassConstantFetchRule - PHPStan\Rules\Constants\FinalConstantRule - PHPStan\Rules\Constants\NativeTypedClassConstantRule diff --git a/src/Rules/Constants/ClassAsClassConstantRule.php b/src/Rules/Constants/ClassAsClassConstantRule.php new file mode 100644 index 00000000000..b10d2d00800 --- /dev/null +++ b/src/Rules/Constants/ClassAsClassConstantRule.php @@ -0,0 +1,40 @@ + + */ +final class ClassAsClassConstantRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Stmt\ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + + foreach ($node->consts as $const) { + if ($const->name->toLowerString() !== 'class') { + continue; + } + + $errors[] = RuleErrorBuilder::message('A class constant must not be called \'class\'; it is reserved for class name fetching.') + ->line($const->getStartLine()) + ->identifier('classConstant.class') + ->nonIgnorable() + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Constants/ClassAsClassConstantRuleTest.php b/tests/PHPStan/Rules/Constants/ClassAsClassConstantRuleTest.php new file mode 100644 index 00000000000..1a8fda4bf6a --- /dev/null +++ b/tests/PHPStan/Rules/Constants/ClassAsClassConstantRuleTest.php @@ -0,0 +1,33 @@ + + */ +class ClassAsClassConstantRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ClassAsClassConstantRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/class-as-class-constant.php'], [ + [ + 'A class constant must not be called \'class\'; it is reserved for class name fetching.', + 9, + ], + [ + 'A class constant must not be called \'class\'; it is reserved for class name fetching.', + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/class-as-class-constant.php b/tests/PHPStan/Rules/Constants/data/class-as-class-constant.php new file mode 100644 index 00000000000..fdf86f77f12 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/class-as-class-constant.php @@ -0,0 +1,17 @@ + Date: Sun, 1 Dec 2024 02:58:42 +0800 Subject: [PATCH 0874/3097] Use native PHPDocs for `Rule` and `RuleTestCase` --- src/Rules/Rule.php | 6 +++--- src/Testing/RuleTestCase.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rules/Rule.php b/src/Rules/Rule.php index e145e4b530f..f0ebf3be854 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/Rule.php @@ -20,18 +20,18 @@ * Learn more: https://phpstan.org/developing-extensions/rules * * @api - * @phpstan-template TNodeType of Node + * @template TNodeType of Node */ interface Rule { /** - * @phpstan-return class-string + * @return class-string */ public function getNodeType(): string; /** - * @phpstan-param TNodeType $node + * @param TNodeType $node * @return (string|RuleError)[] errors */ public function processNode(Node $node, Scope $scope): array; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b507b05ae60..b3ff058c112 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -45,7 +45,7 @@ abstract class RuleTestCase extends PHPStanTestCase private ?Analyser $analyser = null; /** - * @phpstan-return TRule + * @return TRule */ abstract protected function getRule(): Rule; From 4d3a9abb165484e18d0d9d787842d1328843b684 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Tue, 10 Dec 2024 21:56:01 +0800 Subject: [PATCH 0875/3097] Use native PHPDocs wherever possible --- src/Collectors/Collector.php | 8 ++++---- src/Collectors/Registry.php | 9 +++------ src/DependencyInjection/Container.php | 7 +++---- src/DependencyInjection/Nette/NetteContainer.php | 7 +++---- src/Rules/DirectRegistry.php | 9 +++------ src/Rules/LazyRegistry.php | 9 +++------ src/Rules/Registry.php | 6 ++---- stubs/bleedingEdge/Rule.stub | 6 +++--- 8 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/Collectors/Collector.php b/src/Collectors/Collector.php index e046f897503..d7c87c8eccb 100644 --- a/src/Collectors/Collector.php +++ b/src/Collectors/Collector.php @@ -20,19 +20,19 @@ * Learn more: https://phpstan.org/developing-extensions/collectors * * @api - * @phpstan-template-covariant TNodeType of Node - * @phpstan-template-covariant TValue + * @template-covariant TNodeType of Node + * @template-covariant TValue */ interface Collector { /** - * @phpstan-return class-string + * @return class-string */ public function getNodeType(): string; /** - * @phpstan-param TNodeType $node + * @param TNodeType $node * @return TValue|null Collected data */ public function processNode(Node $node, Scope $scope); diff --git a/src/Collectors/Registry.php b/src/Collectors/Registry.php index 11586e3097b..cc0ae09a97b 100644 --- a/src/Collectors/Registry.php +++ b/src/Collectors/Registry.php @@ -27,10 +27,8 @@ public function __construct(array $collectors) /** * @template TNodeType of Node - * @phpstan-param class-string $nodeType - * @param Node $nodeType - * @phpstan-return array> - * @return Collector[] + * @param class-string $nodeType + * @return array> */ public function getCollectors(string $nodeType): array { @@ -48,8 +46,7 @@ public function getCollectors(string $nodeType): array } /** - * @phpstan-var array> $selectedCollectors - * @var Collector[] $selectedCollectors + * @var array> $selectedCollectors */ $selectedCollectors = $this->cache[$nodeType]; diff --git a/src/DependencyInjection/Container.php b/src/DependencyInjection/Container.php index 07a7a574e1b..cd785677b1b 100644 --- a/src/DependencyInjection/Container.php +++ b/src/DependencyInjection/Container.php @@ -14,10 +14,9 @@ public function hasService(string $serviceName): bool; public function getService(string $serviceName); /** - * @phpstan-template T of object - * @phpstan-param class-string $className - * @phpstan-return T - * @return mixed + * @template T of object + * @param class-string $className + * @return T */ public function getByType(string $className); diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index 75469932976..914d0a43f1e 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -32,10 +32,9 @@ public function getService(string $serviceName) } /** - * @phpstan-template T of object - * @phpstan-param class-string $className - * @phpstan-return T - * @return mixed + * @template T of object + * @param class-string $className + * @return T */ public function getByType(string $className) { diff --git a/src/Rules/DirectRegistry.php b/src/Rules/DirectRegistry.php index 0dfb5d71ecc..13c8bfe3a29 100644 --- a/src/Rules/DirectRegistry.php +++ b/src/Rules/DirectRegistry.php @@ -30,10 +30,8 @@ public function __construct(array $rules) /** * @template TNodeType of Node - * @phpstan-param class-string $nodeType - * @param Node $nodeType - * @phpstan-return array> - * @return Rule[] + * @param class-string $nodeType + * @return array> */ public function getRules(string $nodeType): array { @@ -51,8 +49,7 @@ public function getRules(string $nodeType): array } /** - * @phpstan-var array> $selectedRules - * @var Rule[] $selectedRules + * @var array> $selectedRules */ $selectedRules = $this->cache[$nodeType]; diff --git a/src/Rules/LazyRegistry.php b/src/Rules/LazyRegistry.php index f1b91819231..ec5b1dc13b5 100644 --- a/src/Rules/LazyRegistry.php +++ b/src/Rules/LazyRegistry.php @@ -24,10 +24,8 @@ public function __construct(private Container $container) /** * @template TNodeType of Node - * @phpstan-param class-string $nodeType - * @param Node $nodeType - * @phpstan-return array> - * @return Rule[] + * @param class-string $nodeType + * @return array> */ public function getRules(string $nodeType): array { @@ -46,8 +44,7 @@ public function getRules(string $nodeType): array } /** - * @phpstan-var array> $selectedRules - * @var Rule[] $selectedRules + * @var array> $selectedRules */ $selectedRules = $this->cache[$nodeType]; diff --git a/src/Rules/Registry.php b/src/Rules/Registry.php index 36c609811be..792f4428f00 100644 --- a/src/Rules/Registry.php +++ b/src/Rules/Registry.php @@ -9,10 +9,8 @@ interface Registry /** * @template TNodeType of Node - * @phpstan-param class-string $nodeType - * @param Node $nodeType - * @phpstan-return array> - * @return Rule[] + * @param class-string $nodeType + * @return array> */ public function getRules(string $nodeType): array; diff --git a/stubs/bleedingEdge/Rule.stub b/stubs/bleedingEdge/Rule.stub index 0a86ea9d2c3..0e721b0497d 100644 --- a/stubs/bleedingEdge/Rule.stub +++ b/stubs/bleedingEdge/Rule.stub @@ -7,18 +7,18 @@ use PHPStan\Analyser\Scope; /** * @api - * @phpstan-template TNodeType of Node + * @template TNodeType of Node */ interface Rule { /** - * @phpstan-return class-string + * @return class-string */ public function getNodeType(): string; /** - * @phpstan-param TNodeType $node + * @param TNodeType $node * @return list */ public function processNode(Node $node, Scope $scope): array; From e9e419c12a1f0ec973ce05e1bfec08d4f7a2b957 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Dec 2024 09:45:38 +0100 Subject: [PATCH 0876/3097] RegexArrayShapeMatcher: fix regex wildcard omitted from type --- src/Type/Regex/RegexGroupParser.php | 16 +++++++++++----- tests/PHPStan/Analyser/nsrt/bug-12211.php | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12211.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index dba7fcfe4e6..946e492aa79 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -535,13 +535,19 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap if ( $appendLiterals && $onlyLiterals !== null - && (!in_array($value, ['.'], true) || $isEscaped || $inCharacterClass) ) { - if ($onlyLiterals === []) { - $onlyLiterals = [$value]; + if ( + in_array($value, ['.'], true) + && !($isEscaped || $inCharacterClass) + ) { + $onlyLiterals = null; } else { - foreach ($onlyLiterals as &$literal) { - $literal .= $value; + if ($onlyLiterals === []) { + $onlyLiterals = [$value]; + } else { + foreach ($onlyLiterals as &$literal) { + $literal .= $value; + } } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12211.php b/tests/PHPStan/Analyser/nsrt/bug-12211.php new file mode 100644 index 00000000000..72a268c5060 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12211.php @@ -0,0 +1,16 @@ += 7.4 + +declare(strict_types = 1); + +namespace Bug12211; + +use function PHPStan\Testing\assertType; + +const REGEX = '((m.x))'; + +function foo(string $text): void { + assert(preg_match(REGEX, $text, $match) === 1); + assertType('array{string, non-falsy-string}', $match); +} + + From f390b54abf7a2c423ddfe292542e822072d8039e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Dec 2024 10:47:06 +0100 Subject: [PATCH 0877/3097] RegexArrayShapeMatcher: fix regex alternatives in capture group are concatenated --- src/Type/Regex/RegexGroupParser.php | 25 ++++++++++++++++++++- tests/PHPStan/Analyser/nsrt/bug-12210.php | 27 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12210.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 946e492aa79..75db1668c74 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -446,7 +446,30 @@ private function walkGroupAst( } if ($ast->getId() === '#alternation') { - $inAlternation = true; + $newLiterals = []; + foreach ($children as $child) { + $walkResult = $this->walkGroupAst( + $child, + true, + $inClass, + $patternModifiers, + $walkResult->onlyLiterals([]), + ); + + if ($newLiterals === null) { + continue; + } + + if (count($walkResult->getOnlyLiterals() ?? []) > 0) { + foreach ($walkResult->getOnlyLiterals() as $alternationLiterals) { + $newLiterals[] = $alternationLiterals; + } + } else { + $newLiterals = null; + } + } + + return $walkResult->onlyLiterals($newLiterals); } // [^0-9] should not parse as numeric-string, and [^list-everything-but-numbers] is technically diff --git a/tests/PHPStan/Analyser/nsrt/bug-12210.php b/tests/PHPStan/Analyser/nsrt/bug-12210.php new file mode 100644 index 00000000000..165b61b63e6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12210.php @@ -0,0 +1,27 @@ += 7.4 + +declare(strict_types = 1); + +namespace Bug12210; + +use function PHPStan\Testing\assertType; + +function bug12210a(string $text): void { + assert(preg_match('(((sum|min|max)))', $text, $match) === 1); + assertType("array{string, 'max'|'min'|'sum', 'max'|'min'|'sum'}", $match); +} + +function bug12210b(string $text): void { + assert(preg_match('(((sum|min|ma.)))', $text, $match) === 1); + assertType("array{string, non-empty-string, non-falsy-string}", $match); +} + +function bug12210c(string $text): void { + assert(preg_match('(((su.|min|max)))', $text, $match) === 1); + assertType("array{string, non-empty-string, non-falsy-string}", $match); +} + +function bug12210d(string $text): void { + assert(preg_match('(((sum|mi.|max)))', $text, $match) === 1); + assertType("array{string, non-empty-string, non-falsy-string}", $match); +} From 9a6e12cf9d000420842922194384f5b7d6342370 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Dec 2024 11:01:03 +0100 Subject: [PATCH 0878/3097] Added regression test --- tests/PHPStan/Analyser/nsrt/bug-12173.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12173.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-12173.php b/tests/PHPStan/Analyser/nsrt/bug-12173.php new file mode 100644 index 00000000000..e92ce7da4e3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12173.php @@ -0,0 +1,19 @@ += 7.4 + +namespace Bug12173; + +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + public function parse(string $string): void + { + $regex = '#.*(?(apple|orange)).*#'; + + if (preg_match($regex, $string, $matches) !== 1) { + throw new \Exception('Invalid input'); + } + + assertType("'apple'|'orange'", $matches['fruit']);; + } +} From ecdd6c6d2734c34893e5875f9e77b99f6d72fe63 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 11 Dec 2024 11:32:03 +0100 Subject: [PATCH 0879/3097] RegexArrayShapeMatcher: Don't narrow 'J' modifier --- src/Type/Regex/RegexGroupParser.php | 12 +++++++- tests/PHPStan/Analyser/nsrt/bug-12126.php | 36 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12126.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 75db1668c74..c818426111e 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -34,6 +34,10 @@ final class RegexGroupParser { + private const NOT_SUPPORTED_MODIFIERS = [ + 'J', // rare modifier too complicated to support + ]; + private static ?Parser $parser = null; public function __construct( @@ -67,8 +71,14 @@ public function parseGroups(string $regex): ?array return null; } - $captureOnlyNamed = false; $modifiers = $this->regexExpressionHelper->getPatternModifiers($regex) ?? ''; + foreach (self::NOT_SUPPORTED_MODIFIERS as $notSupportedModifier) { + if (str_contains($modifiers, $notSupportedModifier)) { + return null; + } + } + + $captureOnlyNamed = false; if ($this->phpVersion->supportsPregCaptureOnlyNamedGroups()) { $captureOnlyNamed = str_contains($modifiers, 'n'); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12126.php b/tests/PHPStan/Analyser/nsrt/bug-12126.php new file mode 100644 index 00000000000..c494d8d60df --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12126.php @@ -0,0 +1,36 @@ += 7.4 + +namespace Bug12126; + +use function PHPStan\Testing\assertType; + + +class HelloWorld +{ + public function sayHello(): void + { + $options = ['footest', 'testfoo']; + $key = array_rand($options, 1); + + $regex = '/foo(?Ptest)|test(?Pfoo)/J'; + if (!preg_match_all($regex, $options[$key], $matches, PREG_SET_ORDER)) { + return; + } + + assertType('list>', $matches); + // could be assertType("list", $matches); + if (!preg_match_all($regex, $options[$key], $matches, PREG_PATTERN_ORDER)) { + return; + } + + assertType('array>', $matches); + // could be assertType("array{0: list, test: list<'foo'|'test'>, 1: list<'test'|''>, 2: list<''|'foo'>}", $matches); + + if (!preg_match($regex, $options[$key], $matches)) { + return; + } + + assertType('array', $matches); + // could be assertType("array{0: list, test: 'foo', 1: '', 2: 'foo'}|array{0: list, test: 'test', 1: 'test', 2: ''}", $matches); + } +} From e98335bd09659c4567b1259f524f6ff7b785c6e7 Mon Sep 17 00:00:00 2001 From: vindic Date: Thu, 12 Dec 2024 10:19:28 +0200 Subject: [PATCH 0880/3097] Fix apcu_cache_info and apcu_sma_info signatures --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index e2fcdaf7e62..9543cf7cd3f 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -209,7 +209,7 @@ 'APCIterator::valid' => ['bool'], 'apcu_add' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'], 'apcu_add\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], -'apcu_cache_info' => ['array', 'limited='=>'bool'], +'apcu_cache_info' => ['array|false', 'limited='=>'bool'], 'apcu_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'], 'apcu_clear_cache' => ['bool'], 'apcu_dec' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], @@ -220,7 +220,7 @@ 'apcu_exists\'1' => ['array', 'keys'=>'string[]'], 'apcu_fetch' => ['mixed', 'key'=>'string|string[]', '&w_success='=>'bool'], 'apcu_inc' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], -'apcu_sma_info' => ['array', 'limited='=>'bool'], +'apcu_sma_info' => ['array|false', 'limited='=>'bool'], 'apcu_store' => ['bool', 'key'=>'string', 'var='=>'', 'ttl='=>'int'], 'apcu_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], 'APCuIterator::__construct' => ['void', 'search='=>'string|string[]|null', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'], From e7e80934023abc94a4f4bb9066ba6d6db26f6cde Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 12 Dec 2024 09:53:14 +0100 Subject: [PATCH 0881/3097] Make these benevolent --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 9543cf7cd3f..b49b97178c2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -209,7 +209,7 @@ 'APCIterator::valid' => ['bool'], 'apcu_add' => ['bool', 'key'=>'string', 'var'=>'', 'ttl='=>'int'], 'apcu_add\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], -'apcu_cache_info' => ['array|false', 'limited='=>'bool'], +'apcu_cache_info' => ['__benevolent|false>', 'limited='=>'bool'], 'apcu_cas' => ['bool', 'key'=>'string', 'old'=>'int', 'new'=>'int'], 'apcu_clear_cache' => ['bool'], 'apcu_dec' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], @@ -220,7 +220,7 @@ 'apcu_exists\'1' => ['array', 'keys'=>'string[]'], 'apcu_fetch' => ['mixed', 'key'=>'string|string[]', '&w_success='=>'bool'], 'apcu_inc' => ['int', 'key'=>'string', 'step='=>'int', '&w_success='=>'bool', 'ttl='=>'int'], -'apcu_sma_info' => ['array|false', 'limited='=>'bool'], +'apcu_sma_info' => ['__benevolent', 'limited='=>'bool'], 'apcu_store' => ['bool', 'key'=>'string', 'var='=>'', 'ttl='=>'int'], 'apcu_store\'1' => ['array', 'values'=>'array', 'unused='=>'', 'ttl='=>'int'], 'APCuIterator::__construct' => ['void', 'search='=>'string|string[]|null', 'format='=>'int', 'chunk_size='=>'int', 'list='=>'int'], From 627ad2a75c564ce81793f1a25056d73421a11351 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Thu, 12 Dec 2024 12:44:39 +0000 Subject: [PATCH 0882/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index c678b5998fa..166ac443881 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.44.0.6", + "ondrejmirtes/better-reflection": "6.46.0.0", "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 8557d36dae6..f619dbd9c9b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ee384a5c11fbc1dd087ab8dc956b8d73", + "content-hash": "9dad77eea28263a8f7daa01a1b3dd47a", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.44.0.6", + "version": "6.46.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "d942fd0af0214bb1250a55c2560f061b7b0c4bd4" + "reference": "242b3e1cdb59a81585be5722b6c44ae44c74c671" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/d942fd0af0214bb1250a55c2560f061b7b0c4bd4", - "reference": "d942fd0af0214bb1250a55c2560f061b7b0c4bd4", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/242b3e1cdb59a81585be5722b6c44ae44c74c671", + "reference": "242b3e1cdb59a81585be5722b6c44ae44c74c671", "shasum": "" }, "require": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.4.3", + "phpunit/phpunit": "^11.5.1", "rector/rector": "1.2.10" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.44.0.6" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.46.0.0" }, - "time": "2024-11-28T21:05:45+00:00" + "time": "2024-12-12T12:40:29+00:00" }, { "name": "phpstan/php-8-stubs", From 0edf18d140c5b640a63317c3017088322a566180 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Dec 2024 20:06:47 +0100 Subject: [PATCH 0883/3097] Added `strictRulesInstalled` parameter --- conf/config.neon | 1 + conf/parametersSchema.neon | 1 + 2 files changed, 2 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index 9b382375c3e..40e57d400de 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -51,6 +51,7 @@ parameters: checkTooWideReturnTypesInProtectedAndPublicMethods: false checkUninitializedProperties: false checkDynamicProperties: false + strictRulesInstalled: false deprecationRulesInstalled: false inferPrivatePropertyTypeFromConstructor: false reportMaybes: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f73011dcad4..3dbe4e87ec9 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -57,6 +57,7 @@ parametersSchema: checkTooWideReturnTypesInProtectedAndPublicMethods: bool() checkUninitializedProperties: bool() checkDynamicProperties: bool() + strictRulesInstalled: bool() deprecationRulesInstalled: bool() inferPrivatePropertyTypeFromConstructor: bool() From edc13ad57c10c8ba6ad590fa1b8b7be621a7ba95 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 15 Dec 2024 13:54:08 +0100 Subject: [PATCH 0884/3097] More precise reflection-classes return types --- resources/functionMap.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 611ac517ec8..d21c346e0e1 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -9934,10 +9934,10 @@ 'ReflectionClass::getConstructor' => ['ReflectionMethod|null'], 'ReflectionClass::getDefaultProperties' => ['array'], 'ReflectionClass::getDocComment' => ['string|false'], -'ReflectionClass::getEndLine' => ['int|false'], +'ReflectionClass::getEndLine' => ['positive-int|false'], 'ReflectionClass::getExtension' => ['ReflectionExtension|null'], 'ReflectionClass::getExtensionName' => ['string|false'], -'ReflectionClass::getFileName' => ['string|false'], +'ReflectionClass::getFileName' => ['non-empty-string|false'], 'ReflectionClass::getInterfaceNames' => ['list'], 'ReflectionClass::getInterfaces' => ['array'], 'ReflectionClass::getMethod' => ['ReflectionMethod', 'name'=>'string'], @@ -9951,7 +9951,7 @@ 'ReflectionClass::getReflectionConstant' => ['ReflectionClassConstant|false', 'name'=>'string'], 'ReflectionClass::getReflectionConstants' => ['list'], 'ReflectionClass::getShortName' => ['string'], -'ReflectionClass::getStartLine' => ['int|false'], +'ReflectionClass::getStartLine' => ['positive-int|false'], 'ReflectionClass::getStaticProperties' => ['array'], 'ReflectionClass::getStaticPropertyValue' => ['mixed', 'name'=>'string', 'default='=>'mixed'], 'ReflectionClass::getTraitAliases' => ['array'], @@ -10012,10 +10012,10 @@ 'ReflectionFunction::getClosureScopeClass' => ['ReflectionClass'], 'ReflectionFunction::getClosureThis' => ['bool'], 'ReflectionFunction::getDocComment' => ['string|false'], -'ReflectionFunction::getEndLine' => ['int|false'], +'ReflectionFunction::getEndLine' => ['positive-int|false'], 'ReflectionFunction::getExtension' => ['ReflectionExtension|null'], 'ReflectionFunction::getExtensionName' => ['string|false'], -'ReflectionFunction::getFileName' => ['string|false'], +'ReflectionFunction::getFileName' => ['non-empty-string|false'], 'ReflectionFunction::getName' => ['non-empty-string'], 'ReflectionFunction::getNamespaceName' => ['string'], 'ReflectionFunction::getNumberOfParameters' => ['int'], @@ -10023,7 +10023,7 @@ 'ReflectionFunction::getParameters' => ['list'], 'ReflectionFunction::getReturnType' => ['?ReflectionType'], 'ReflectionFunction::getShortName' => ['string'], -'ReflectionFunction::getStartLine' => ['int|false'], +'ReflectionFunction::getStartLine' => ['positive-int|false'], 'ReflectionFunction::getStaticVariables' => ['array'], 'ReflectionFunction::inNamespace' => ['bool'], 'ReflectionFunction::invoke' => ['mixed', '...args='=>'mixed'], @@ -10041,10 +10041,10 @@ 'ReflectionFunctionAbstract::getClosureScopeClass' => ['ReflectionClass|null'], 'ReflectionFunctionAbstract::getClosureThis' => ['object|null'], 'ReflectionFunctionAbstract::getDocComment' => ['string|false'], -'ReflectionFunctionAbstract::getEndLine' => ['int|false'], +'ReflectionFunctionAbstract::getEndLine' => ['positive-int|false'], 'ReflectionFunctionAbstract::getExtension' => ['ReflectionExtension|null'], 'ReflectionFunctionAbstract::getExtensionName' => ['string|false'], -'ReflectionFunctionAbstract::getFileName' => ['string|false'], +'ReflectionFunctionAbstract::getFileName' => ['non-empty-string|false'], 'ReflectionFunctionAbstract::getName' => ['non-empty-string'], 'ReflectionFunctionAbstract::getNamespaceName' => ['string'], 'ReflectionFunctionAbstract::getNumberOfParameters' => ['int'], @@ -10052,7 +10052,7 @@ 'ReflectionFunctionAbstract::getParameters' => ['list'], 'ReflectionFunctionAbstract::getReturnType' => ['?ReflectionType'], 'ReflectionFunctionAbstract::getShortName' => ['string'], -'ReflectionFunctionAbstract::getStartLine' => ['int|false'], +'ReflectionFunctionAbstract::getStartLine' => ['positive-int|false'], 'ReflectionFunctionAbstract::getStaticVariables' => ['array'], 'ReflectionFunctionAbstract::hasReturnType' => ['bool'], 'ReflectionFunctionAbstract::inNamespace' => ['bool'], From 5d07949d88c8554bad583f8e5584fdb7534d737c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 15 Dec 2024 13:32:32 +0100 Subject: [PATCH 0885/3097] Remove incorrect doc leftover from 1.x --- src/Type/Type.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Type/Type.php b/src/Type/Type.php index 8021baae624..0badf2930cf 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -64,13 +64,6 @@ public function getConstantArrays(): array; /** @return list */ public function getConstantStrings(): array; - /** - * This is like accepts() but gives reasons - * why the type was not/might not be accepted in some non-intuitive scenarios. - * - * In PHPStan 2.0 this method will be removed and the return type of accepts() - * will change to AcceptsResult. - */ public function accepts(Type $type, bool $strictTypes): AcceptsResult; public function isSuperTypeOf(Type $type): IsSuperTypeOfResult; From de0553c5f03685f235f484433e8d439b3a2b2cb0 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Sun, 15 Dec 2024 12:56:23 +0000 Subject: [PATCH 0886/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 166ac443881..8670cde3eeb 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#bb981ec60b3838e56473a078edf7d0739ca20403", + "jetbrains/phpstorm-stubs": "dev-master#0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index f619dbd9c9b..265045a14fe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9dad77eea28263a8f7daa01a1b3dd47a", + "content-hash": "3f90069e33e3f9bd4610862a5a5aed2e", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "bb981ec60b3838e56473a078edf7d0739ca20403" + "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/bb981ec60b3838e56473a078edf7d0739ca20403", - "reference": "bb981ec60b3838e56473a078edf7d0739ca20403", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", + "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-11-27T16:45:26+00:00" + "time": "2024-12-14T08:03:12+00:00" }, { "name": "nette/bootstrap", From 2d6468685d134686d0987b7bd0cf2788bf4ad3b3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 16 Dec 2024 14:02:26 +0100 Subject: [PATCH 0887/3097] 10% faster FunctionCallParametersCheck --- src/Rules/FunctionCallParametersCheck.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 623b593f5f0..69c8467515e 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -88,8 +88,7 @@ public function check( $hasNamedArguments = false; $hasUnpackedArgument = false; $errors = []; - foreach ($args as $i => $arg) { - $type = $scope->getType($arg->value); + foreach ($args as $arg) { if ($hasNamedArguments && $arg->unpack) { $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by an unpacked (...) argument.') ->identifier('argument.unpackAfterNamed') @@ -113,6 +112,7 @@ public function check( $argumentName = $arg->name->toString(); } if ($arg->unpack) { + $type = $scope->getType($arg->value); $arrays = $type->getConstantArrays(); if (count($arrays) > 0) { $minKeys = null; @@ -191,7 +191,7 @@ public function check( if (!$hasNamedArguments) { $invokedParametersCount = count($arguments); - foreach ($arguments as $i => [$argumentValue, $argumentValueType, $unpack, $argumentName]) { + foreach ($arguments as [$argumentValue, $argumentValueType, $unpack, $argumentName]) { if ($unpack) { $invokedParametersCount = max($functionParametersMinCount, $functionParametersMaxCount); break; From b66058fe93b5187d0b6755b7c7111133c5722e10 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 17 Dec 2024 08:50:44 +0000 Subject: [PATCH 0888/3097] Add support for internal classes that overload offset access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- src/Type/ObjectType.php | 14 ++- ...nexistentOffsetInArrayDimFetchRuleTest.php | 26 ++++++ ...s-overload-offset-access-invalid-php84.php | 91 +++++++++++++++++++ ...classes-overload-offset-access-invalid.php | 52 +++++++++++ ...l-classes-overload-offset-access-php84.php | 74 +++++++++++++++ ...nternal-classes-overload-offset-access.php | 37 ++++++++ 6 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-invalid-php84.php create mode 100644 tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-invalid.php create mode 100644 tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-php84.php create mode 100644 tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 9c633d62dae..fab7c056b08 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -67,7 +67,19 @@ class ObjectType implements TypeWithClassName, SubtractableType use UndecidedComparisonTypeTrait; use NonGeneralizableTypeTrait; - private const EXTRA_OFFSET_CLASSES = ['SimpleXMLElement', 'DOMNodeList', 'Threaded']; + private const EXTRA_OFFSET_CLASSES = [ + 'DOMNamedNodeMap', // Only read and existence + 'Dom\NamedNodeMap', // Only read and existence + 'DOMNodeList', // Only read and existence + 'Dom\NodeList', // Only read and existence + 'Dom\HTMLCollection', // Only read and existence + 'Dom\DtdNamedNodeMap', // Only read and existence + 'PDORow', // Only read and existence + 'ResourceBundle', // Only read + 'FFI\CData', // Very funky and weird + 'SimpleXMLElement', + 'Threaded', + ]; private ?Type $subtractedType; diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 2838f2cbd9f..6699adb5f79 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -898,4 +898,30 @@ public function testBug2634(): void $this->analyse([__DIR__ . '/data/bug-2634.php'], []); } + public function testInternalClassesWithOverloadedOffsetAccess(): void + { + $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access.php'], []); + } + + public function testInternalClassesWithOverloadedOffsetAccess84(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-php84.php'], []); + } + + public function testInternalClassesWithOverloadedOffsetAccessInvalid(): void + { + $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid.php'], []); + } + + public function testInternalClassesWithOverloadedOffsetAccessInvalid84(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid-php84.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-invalid-php84.php b/tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-invalid-php84.php new file mode 100644 index 00000000000..f57bd141222 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/internal-classes-overload-offset-access-invalid-php84.php @@ -0,0 +1,91 @@ + Date: Tue, 17 Dec 2024 09:34:14 +0100 Subject: [PATCH 0889/3097] Faster `MutatingScope->getNodeKey()` --- src/Analyser/MutatingScope.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5659a107bcf..52ae6763977 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -710,15 +710,16 @@ private function getNodeKey(Expr $node): string { $key = $this->exprPrinter->printExpr($node); + $attributes = $node->getAttributes(); if ( $node instanceof Node\FunctionLike - && $node->hasAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME) - && $node->hasAttribute('startFilePos') + && (($attributes[ArrayMapArgVisitor::ATTRIBUTE_NAME] ?? null) !== null) + && (($attributes['startFilePos'] ?? null) !== null) ) { - $key .= '/*' . $node->getAttribute('startFilePos') . '*/'; + $key .= '/*' . $attributes['startFilePos'] . '*/'; } - if ($node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME) === true) { + if (($attributes[self::KEEP_VOID_ATTRIBUTE_NAME] ?? null) === true) { $key .= '/*' . self::KEEP_VOID_ATTRIBUTE_NAME . '*/'; } From 73d0f13cdfb39503044c8b1f404941935809146b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 Dec 2024 11:32:44 +0100 Subject: [PATCH 0890/3097] Fix `DOMDocument::create*()` return types --- resources/functionMap.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index d21c346e0e1..1b8e4c28a4d 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1898,15 +1898,15 @@ 'DOMCharacterData::substringData' => ['string', 'offset'=>'int', 'count'=>'int'], 'DOMComment::__construct' => ['void', 'value='=>'string'], 'DOMDocument::__construct' => ['void', 'version='=>'string', 'encoding='=>'string'], -'DOMDocument::createAttribute' => ['DOMAttr', 'name'=>'string'], -'DOMDocument::createAttributeNS' => ['DOMAttr', 'namespaceuri'=>'string', 'qualifiedname'=>'string'], -'DOMDocument::createCDATASection' => ['DOMCDATASection', 'data'=>'string'], +'DOMDocument::createAttribute' => ['__benevolent', 'name'=>'string'], +'DOMDocument::createAttributeNS' => ['__benevolent', 'namespaceuri'=>'string', 'qualifiedname'=>'string'], +'DOMDocument::createCDATASection' => ['__benevolent', 'data'=>'string'], 'DOMDocument::createComment' => ['DOMComment', 'data'=>'string'], 'DOMDocument::createDocumentFragment' => ['DOMDocumentFragment'], -'DOMDocument::createElement' => ['DOMElement', 'name'=>'string', 'value='=>'string'], -'DOMDocument::createElementNS' => ['DOMElement', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'], -'DOMDocument::createEntityReference' => ['DOMEntityReference', 'name'=>'string'], -'DOMDocument::createProcessingInstruction' => ['DOMProcessingInstruction', 'target'=>'string', 'data='=>'string'], +'DOMDocument::createElement' => ['__benevolent', 'name'=>'string', 'value='=>'string'], +'DOMDocument::createElementNS' => ['__benevolent', 'namespaceuri'=>'string', 'qualifiedname'=>'string', 'value='=>'string'], +'DOMDocument::createEntityReference' => ['__benevolent', 'name'=>'string'], +'DOMDocument::createProcessingInstruction' => ['__benevolent', 'target'=>'string', 'data='=>'string'], 'DOMDocument::createTextNode' => ['DOMText', 'content'=>'string'], 'DOMDocument::getElementById' => ['DOMElement|null', 'elementid'=>'string'], 'DOMDocument::getElementsByTagName' => ['DOMNodeList', 'name'=>'string'], From c053dbc01983f6dd78a78b8154a00afb64088b33 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 Dec 2024 11:35:44 +0100 Subject: [PATCH 0891/3097] Support `#` comments in regex with `x` modifier --- src/Type/Regex/RegexGroupParser.php | 20 +++++++++----- tests/PHPStan/Analyser/nsrt/bug-12242.php | 32 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12242.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index c818426111e..a98fb20b42e 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -23,6 +23,7 @@ use function count; use function in_array; use function is_int; +use function preg_replace; use function rtrim; use function sscanf; use function str_contains; @@ -64,13 +65,6 @@ public function parseGroups(string $regex): ?array return null; } - $rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex); - try { - $ast = self::$parser->parse($rawRegex); - } catch (Exception) { - return null; - } - $modifiers = $this->regexExpressionHelper->getPatternModifiers($regex) ?? ''; foreach (self::NOT_SUPPORTED_MODIFIERS as $notSupportedModifier) { if (str_contains($modifiers, $notSupportedModifier)) { @@ -78,6 +72,18 @@ public function parseGroups(string $regex): ?array } } + if (str_contains($modifiers, 'x')) { + // in freespacing mode the # character starts a comment and runs until the end of the line + $regex = preg_replace('/[^?]#.*/', '', $regex) ?? ''; + } + + $rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex); + try { + $ast = self::$parser->parse($rawRegex); + } catch (Exception) { + return null; + } + $captureOnlyNamed = false; if ($this->phpVersion->supportsPregCaptureOnlyNamedGroups()) { $captureOnlyNamed = str_contains($modifiers, 'n'); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12242.php b/tests/PHPStan/Analyser/nsrt/bug-12242.php new file mode 100644 index 00000000000..cb6d424567c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12242.php @@ -0,0 +1,32 @@ += 7.4 + +namespace Bug12242; + +use function PHPStan\Testing\assertType; + +function foo(string $str): void +{ + $regexp = '/ + # ( + ([\d,]*) + # ) + /x'; + if (preg_match($regexp, $str, $match)) { + assertType('array{string, string}', $match); + } +} + +function bar(string $str): void +{ + $regexp = '/^ + (\w+) # column type [1] + [\(] # ( + ?([\d,]*) # size or size, precision [2] + [\)] # ) + ?\s* # whitespace + (\w*) # extra description (UNSIGNED, CHARACTER SET, ...) [3] + $/x'; + if (preg_match($regexp, $str, $matches)) { + assertType('array{string, non-empty-string, string, string}', $matches); + } +} From 7dc4cc62e885d3895af515129b19dc50e75c1d01 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 17 Dec 2024 11:52:29 +0100 Subject: [PATCH 0892/3097] Fix regex comment support eating too much chars --- src/Type/Regex/RegexGroupParser.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-12242.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index a98fb20b42e..beea95bce01 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -74,7 +74,7 @@ public function parseGroups(string $regex): ?array if (str_contains($modifiers, 'x')) { // in freespacing mode the # character starts a comment and runs until the end of the line - $regex = preg_replace('/[^?]#.*/', '', $regex) ?? ''; + $regex = preg_replace('/(?regexExpressionHelper->removeDelimitersAndModifiers($regex); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12242.php b/tests/PHPStan/Analyser/nsrt/bug-12242.php index cb6d424567c..4d065367a2b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12242.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12242.php @@ -30,3 +30,14 @@ function bar(string $str): void assertType('array{string, non-empty-string, string, string}', $matches); } } + +function foobar(string $str): void +{ + $regexp = '/ + # ( + ([\d,]*)# a comment immediately behind with a closing parenthesis ) + /x'; + if (preg_match($regexp, $str, $match)) { + assertType('array{string, string}', $match); + } +} From 5dfc5830f3b37b8b23e73514c0d6a43c65c57152 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 16:52:07 +0100 Subject: [PATCH 0893/3097] Fix typo --- src/Rules/AttributesCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index e04381033e0..6976c7d0490 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -151,7 +151,7 @@ public function check( 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', 'Parameter %s of attribute class ' . $attributeClassName . ' constructor contains unresolvable type.', - 'Attribute class ' . $attributeClassName . ' constructorinvoked with %s, but it\'s not allowed because of @no-named-arguments.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %s, but it\'s not allowed because of @no-named-arguments.', ], 'attribute', $attributeConstructor->acceptsNamedArguments(), From 202dd81d113dd8262977fc32c895386e38bda166 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Dec 2024 10:00:02 +0100 Subject: [PATCH 0894/3097] Readonly classes cannot be combined with `#[AllowDynamicProperties]` + check trait attributes --- Makefile | 2 + conf/config.level0.neon | 1 + src/Rules/Classes/ClassAttributesRule.php | 32 ++++++- src/Rules/Traits/TraitAttributesRule.php | 51 ++++++++++ .../Rules/Classes/ClassAttributesRuleTest.php | 24 +++++ .../PHPStan/Rules/Classes/data/bug-12281.php | 16 ++++ .../Rules/Traits/TraitAttributesRuleTest.php | 96 +++++++++++++++++++ tests/PHPStan/Rules/Traits/data/bug-12011.php | 26 +++++ tests/PHPStan/Rules/Traits/data/bug-12281.php | 19 ++++ .../Rules/Traits/data/trait-attributes.php | 30 ++++++ 10 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Traits/TraitAttributesRule.php create mode 100644 tests/PHPStan/Rules/Classes/data/bug-12281.php create mode 100644 tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php create mode 100644 tests/PHPStan/Rules/Traits/data/bug-12011.php create mode 100644 tests/PHPStan/Rules/Traits/data/bug-12281.php create mode 100644 tests/PHPStan/Rules/Traits/data/trait-attributes.php diff --git a/Makefile b/Makefile index ad6075e3bf1..d8566bfdb00 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,8 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ + --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ + --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ src tests cs: diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 3964727b8da..c84cf8f5f79 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -103,6 +103,7 @@ rules: - PHPStan\Rules\Regexp\RegularExpressionPatternRule - PHPStan\Rules\Traits\ConflictingTraitConstantsRule - PHPStan\Rules\Traits\ConstantsInTraitsRule + - PHPStan\Rules\Traits\TraitAttributesRule - PHPStan\Rules\Types\InvalidTypesInUnionRule - PHPStan\Rules\Variables\UnsetRule - PHPStan\Rules\Whitespace\FileWhitespaceRule diff --git a/src/Rules/Classes/ClassAttributesRule.php b/src/Rules/Classes/ClassAttributesRule.php index afe9786db05..190be4331b9 100644 --- a/src/Rules/Classes/ClassAttributesRule.php +++ b/src/Rules/Classes/ClassAttributesRule.php @@ -8,6 +8,9 @@ use PHPStan\Node\InClassNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use function count; +use function sprintf; /** * @implements Rule @@ -28,12 +31,39 @@ public function processNode(Node $node, Scope $scope): array { $classLikeNode = $node->getOriginalNode(); - return $this->attributesCheck->check( + $errors = $this->attributesCheck->check( $scope, $classLikeNode->attrGroups, Attribute::TARGET_CLASS, 'class', ); + + $classReflection = $node->getClassReflection(); + if ( + $classReflection->isReadOnly() + || $classReflection->isEnum() + || $classReflection->isInterface() + ) { + $typeName = 'readonly class'; + $identifier = 'class.allowDynamicPropertiesReadonly'; + if ($classReflection->isEnum()) { + $typeName = 'enum'; + $identifier = 'enum.allowDynamicProperties'; + } + if ($classReflection->isInterface()) { + $typeName = 'interface'; + $identifier = 'interface.allowDynamicProperties'; + } + + if (count($classReflection->getNativeReflection()->getAttributes('AllowDynamicProperties')) > 0) { + $errors[] = RuleErrorBuilder::message(sprintf('Attribute class AllowDynamicProperties cannot be used with %s.', $typeName)) + ->identifier($identifier) + ->nonIgnorable() + ->build(); + } + } + + return $errors; } } diff --git a/src/Rules/Traits/TraitAttributesRule.php b/src/Rules/Traits/TraitAttributesRule.php new file mode 100644 index 00000000000..2b9fa768d03 --- /dev/null +++ b/src/Rules/Traits/TraitAttributesRule.php @@ -0,0 +1,51 @@ + + */ +final class TraitAttributesRule implements Rule +{ + + public function __construct( + private AttributesCheck $attributesCheck, + ) + { + } + + public function getNodeType(): string + { + return InTraitNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + $errors = $this->attributesCheck->check( + $scope, + $originalNode->attrGroups, + Attribute::TARGET_CLASS, + 'class', + ); + + if (count($node->getTraitReflection()->getNativeReflection()->getAttributes('AllowDynamicProperties')) > 0) { + $errors[] = RuleErrorBuilder::message('Attribute class AllowDynamicProperties cannot be used with trait.') + ->identifier('trait.allowDynamicProperties') + ->nonIgnorable() + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 1d72b629a8a..18e7c7a1fd6 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -167,4 +167,28 @@ public function testBug12011(): void ]); } + public function testBug12281(): void + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Test requires PHP 8.2.'); + } + + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12281.php'], [ + [ + 'Attribute class AllowDynamicProperties cannot be used with readonly class.', + 05, + ], + [ + 'Attribute class AllowDynamicProperties cannot be used with enum.', + 12, + ], + [ + 'Attribute class AllowDynamicProperties cannot be used with interface.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-12281.php b/tests/PHPStan/Rules/Classes/data/bug-12281.php new file mode 100644 index 00000000000..293d9e5e413 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-12281.php @@ -0,0 +1,16 @@ += 8.2 + +namespace Bug12281; + +#[\AllowDynamicProperties] +readonly class BlogData { /* … */ } + +/** @readonly */ +#[\AllowDynamicProperties] +class BlogDataPhpdoc { /* … */ } + +#[\AllowDynamicProperties] +enum BlogDataEnum { /* … */ } + +#[\AllowDynamicProperties] +interface BlogDataInterface { /* … */ } diff --git a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php new file mode 100644 index 00000000000..a3be5ead046 --- /dev/null +++ b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php @@ -0,0 +1,96 @@ + + */ +class TraitAttributesRuleTest extends RuleTestCase +{ + + private bool $checkExplicitMixed = false; + + private bool $checkImplicitMixed = false; + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new TraitAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new NullsafeCheck(), + new PhpVersion(80000), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + true, + true, + true, + true, + ), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, false), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, + ), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/trait-attributes.php'], [ + [ + 'Attribute class TraitAttributes\AbstractAttribute is abstract.', + 8, + ], + [ + 'Attribute class TraitAttributes\MyTargettedAttribute does not have the class target.', + 20, + ], + ]); + } + + public function testBug12011(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12011.php'], [ + [ + 'Parameter #1 $name of attribute class Bug12011Trait\Table constructor expects string|null, int given.', + 8, + ], + ]); + } + + public function testBug12281(): void + { + $this->analyse([__DIR__ . '/data/bug-12281.php'], [ + [ + 'Attribute class AllowDynamicProperties cannot be used with trait.', + 11, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Traits/data/bug-12011.php b/tests/PHPStan/Rules/Traits/data/bug-12011.php new file mode 100644 index 00000000000..32b09d38d36 --- /dev/null +++ b/tests/PHPStan/Rules/Traits/data/bug-12011.php @@ -0,0 +1,26 @@ += 8.3 + +namespace Bug12011Trait; + +use Attribute; + + +#[Table(self::TABLE_NAME)] +trait MyTrait +{ + private const int TABLE_NAME = 1; +} + +class X { + use MyTrait; +} + +#[Attribute(Attribute::TARGET_CLASS)] +final class Table +{ + public function __construct( + public readonly string|null $name = null, + public readonly string|null $schema = null, + ) { + } +} diff --git a/tests/PHPStan/Rules/Traits/data/bug-12281.php b/tests/PHPStan/Rules/Traits/data/bug-12281.php new file mode 100644 index 00000000000..da7d088f1a6 --- /dev/null +++ b/tests/PHPStan/Rules/Traits/data/bug-12281.php @@ -0,0 +1,19 @@ += 8.2 + +namespace Bug12281Traits; + +#[\AllowDynamicProperties] +enum BlogDataEnum { /* … */ } // reported by ClassAttributesRule + +#[\AllowDynamicProperties] +interface BlogDataInterface { /* … */ } // reported by ClassAttributesRule + +#[\AllowDynamicProperties] +trait BlogDataTrait { /* … */ } + +class Uses +{ + + use BlogDataTrait; + +} diff --git a/tests/PHPStan/Rules/Traits/data/trait-attributes.php b/tests/PHPStan/Rules/Traits/data/trait-attributes.php new file mode 100644 index 00000000000..67906a2dfe5 --- /dev/null +++ b/tests/PHPStan/Rules/Traits/data/trait-attributes.php @@ -0,0 +1,30 @@ + Date: Fri, 20 Dec 2024 10:04:20 +0100 Subject: [PATCH 0895/3097] Named argument detection is scope-PHP version dependent --- src/Analyser/MutatingScope.php | 6 ++-- src/Php/PhpVersions.php | 5 +++ src/Rules/FunctionCallParametersCheck.php | 4 +-- .../Analyser/Bug9307CallMethodsRuleTest.php | 4 +-- .../nsrt/bug-2600-php-version-scope.php | 26 ++++++++++++++ .../Rules/Classes/ClassAttributesRuleTest.php | 2 -- .../ClassConstantAttributesRuleTest.php | 2 -- .../ForbiddenNameCheckExtensionRuleTest.php | 3 +- .../Rules/Classes/InstantiationRuleTest.php | 11 ++++-- .../EnumCases/EnumCaseAttributesRuleTest.php | 2 -- .../ArrowFunctionAttributesRuleTest.php | 2 -- .../Rules/Functions/CallCallablesRuleTest.php | 6 ++-- .../CallToFunctionParametersRuleTest.php | 13 +++---- .../Rules/Functions/CallUserFuncRuleTest.php | 3 +- .../Functions/ClosureAttributesRuleTest.php | 2 -- .../Functions/FunctionAttributesRuleTest.php | 2 -- .../Functions/ParamAttributesRuleTest.php | 2 -- .../Rules/Methods/CallMethodsRuleTest.php | 30 ++++++++++++---- .../Methods/CallStaticMethodsRuleTest.php | 6 ++-- .../Methods/MethodAttributesRuleTest.php | 7 ---- ...llow-named-arguments-php-version-scope.php | 35 +++++++++++++++++++ .../Properties/PropertyAttributesRuleTest.php | 2 -- 22 files changed, 119 insertions(+), 56 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-2600-php-version-scope.php create mode 100644 tests/PHPStan/Rules/Methods/data/disallow-named-arguments-php-version-scope.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index aa143cc3af2..832c73b1c09 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3139,7 +3139,7 @@ private function enterFunctionLike( $paramExprString = '$' . $parameter->getName(); if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) { + if (!$this->getPhpVersion()->supportsNamedArguments()->no() && $functionReflection->acceptsNamedArguments()->yes()) { $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType); } else { $parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType()); @@ -3154,7 +3154,7 @@ private function enterFunctionLike( $nativeParameterType = $parameter->getNativeType(); if ($parameter->isVariadic()) { - if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()->yes()) { + if (!$this->getPhpVersion()->supportsNamedArguments()->no() && $functionReflection->acceptsNamedArguments()->yes()) { $nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType); } else { $nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType()); @@ -3629,7 +3629,7 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type ); } if ($isVariadic) { - if ($this->phpVersion->supportsNamedArguments()) { + if (!$this->getPhpVersion()->supportsNamedArguments()->no()) { return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType( $type, false, diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 7bdc70e3bff..74474b28b09 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -28,4 +28,9 @@ public function producesWarningForFinalPrivateMethods(): TrinaryLogic return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; } + public function supportsNamedArguments(): TrinaryLogic + { + return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; + } + } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index c39da64ce1f..50371ab23c1 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -6,7 +6,6 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; @@ -42,7 +41,6 @@ final class FunctionCallParametersCheck public function __construct( private RuleLevelHelper $ruleLevelHelper, private NullsafeCheck $nullsafeCheck, - private PhpVersion $phpVersion, private UnresolvableTypeHelper $unresolvableTypeHelper, private PropertyReflectionFinder $propertyReflectionFinder, private bool $checkArgumentTypes, @@ -201,7 +199,7 @@ public function check( ]; } - if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments() && !(bool) $funcCall->getAttribute('isAttribute', false)) { + if ($hasNamedArguments && !$scope->getPhpVersion()->supportsNamedArguments()->yes() && !(bool) $funcCall->getAttribute('isAttribute', false)) { $errors[] = RuleErrorBuilder::message('Named arguments are supported only on PHP 8.0 and later.') ->identifier('argument.namedNotSupported') ->line($funcCall->getStartLine()) diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index a49a6502863..ff1be831095 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Analyser; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\Methods\CallMethodsRule; use PHPStan\Rules\Methods\MethodCallCheck; @@ -12,7 +11,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; -use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -26,7 +24,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-2600-php-version-scope.php b/tests/PHPStan/Analyser/nsrt/bug-2600-php-version-scope.php new file mode 100644 index 00000000000..bf13358857b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-2600-php-version-scope.php @@ -0,0 +1,26 @@ + 7.4 + +namespace Bug2600PhpVersionScope; + +use function PHPStan\Testing\assertType; + +if (PHP_VERSION_ID >= 80000) { + class Foo8 { + /** + * @param mixed $x + */ + public function doBaz(...$x) { + assertType('array', $x); + } + } +} else { + class Foo9 { + /** + * @param mixed $x + */ + public function doBaz(...$x) { + assertType('list', $x); + } + } + +} diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 18e7c7a1fd6..d4a9dc96d52 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -35,7 +34,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index d5787f8344f..64b97838771 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 4907d1d7ec8..5286e079fc4 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; @@ -26,7 +25,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 43b9091daf0..e4155ce55b0 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Classes; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; @@ -26,7 +25,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), @@ -290,6 +289,10 @@ public function testBug4056(): void public function testNamedArguments(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/instantiation-named-arguments.php'], [ [ 'Missing parameter $j (int) in call to InstantiationNamedArguments\Foo constructor.', @@ -501,6 +504,10 @@ public function testBug10248(): void public function testBug11815(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/bug-11815.php'], []); } diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 52428ced2b7..d61265e3e79 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\EnumCases; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80100), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index af9266b7e55..1be1cfae69c 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 31a92a4f92c..a8ffcaf8003 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -27,7 +26,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( $ruleLevelHelper, new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, @@ -158,6 +156,10 @@ public function testRule(): void public function testNamedArguments(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/callables-named-arguments.php'], [ [ 'Missing parameter $j (int) in call to closure.', diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 89017fe2cf1..a6513f03cb1 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -28,7 +27,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } @@ -477,6 +476,10 @@ public function testGenericFunction(): void public function testNamedArguments(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $errors = [ [ 'Missing parameter $j (int) in call to function FunctionNamedArguments\foo.', @@ -491,12 +494,6 @@ public function testNamedArguments(): void 14, ], ]; - if (PHP_VERSION_ID < 80000) { - $errors[] = [ - 'Missing parameter $arr1 (array) in call to function array_merge.', - 14, - ]; - } require_once __DIR__ . '/data/named-arguments-define.php'; $this->analyse([__DIR__ . '/data/named-arguments.php'], $errors); diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index f4eb4c2c6eb..ccff7cb19d4 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -21,7 +20,7 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true)); + return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index dbab699a2be..41cc6084579 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 6d028b1b96a..1961e8e81cd 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 842d0513a1a..678738ac509 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Functions; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -30,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index bd20b469b8c..0da6911a43e 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Methods; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; @@ -28,15 +27,13 @@ class CallMethodsRuleTest extends RuleTestCase private bool $checkImplicitMixed = false; - private int $phpVersion = PHP_VERSION_ID; - protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, false); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } @@ -1822,12 +1819,29 @@ public function testDisallowNamedArguments(): void ]); } + public function testDisallowNamedArgumentsInPhpVersionScope(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + $this->analyse([__DIR__ . '/data/disallow-named-arguments-php-version-scope.php'], [ + [ + 'Named arguments are supported only on PHP 8.0 and later.', + 26, + ], + ]); + } + public function testNamedArguments(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; - $this->phpVersion = 80000; $this->analyse([__DIR__ . '/data/named-arguments.php'], [ [ @@ -2104,7 +2118,11 @@ public function testBug4800(): void $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; - $this->phpVersion = 80000; + + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/bug-4800.php'], [ [ 'Missing parameter $bar (string) in call to method Bug4800\HelloWorld2::a().', diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 80d61e299e6..51210125cf6 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Methods; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; use PHPStan\Rules\ClassNameCheck; @@ -47,7 +46,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( $ruleLevelHelper, new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, @@ -414,6 +412,10 @@ public function testNamedArguments(): void { $this->checkThisOnly = false; + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->analyse([__DIR__ . '/data/static-method-named-arguments.php'], [ [ 'Missing parameter $j (int) in call to static method StaticMethodNamedArguments\Foo::doFoo().', diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index ef5ca25aef6..c87aabde90b 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Methods; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -21,8 +20,6 @@ class MethodAttributesRuleTest extends RuleTestCase { - private int $phpVersion; - protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); @@ -32,7 +29,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, @@ -51,8 +47,6 @@ protected function getRule(): Rule public function testRule(): void { - $this->phpVersion = 80000; - $this->analyse([__DIR__ . '/data/method-attributes.php'], [ [ 'Attribute class MethodAttributes\Foo does not have the method target.', @@ -63,7 +57,6 @@ public function testRule(): void public function testBug5898(): void { - $this->phpVersion = 70400; $this->analyse([__DIR__ . '/data/bug-5898.php'], []); } diff --git a/tests/PHPStan/Rules/Methods/data/disallow-named-arguments-php-version-scope.php b/tests/PHPStan/Rules/Methods/data/disallow-named-arguments-php-version-scope.php new file mode 100644 index 00000000000..174fef244db --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/disallow-named-arguments-php-version-scope.php @@ -0,0 +1,35 @@ += 80000) { + class Foo + { + + public function doFoo(): void + { + $this->doBar(i: 1); + } + + public function doBar(int $i): void + { + + } + + } +} else { + class FooBar + { + + public function doFoo(): void + { + $this->doBar(i: 1); + } + + public function doBar(int $i): void + { + + } + + } +} diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index 02b75d1007b..6f4a6121ec2 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Properties; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -29,7 +28,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, From defd40604f38b0f62b25cfeee99c21d90dc960ff Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 19 Dec 2024 20:31:20 +0100 Subject: [PATCH 0896/3097] Fix preg_match() group containing start/end meta characters --- src/Type/Regex/RegexGroupParser.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-12297.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12297.php diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index beea95bce01..a8a9755e8a5 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -527,7 +527,7 @@ private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool if ($literal !== '' && $literal !== '0') { $isNonFalsy = true; } - return false; + return $literal === ''; } foreach ($node->getChildren() as $child) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12297.php b/tests/PHPStan/Analyser/nsrt/bug-12297.php new file mode 100644 index 00000000000..4a956e42a49 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12297.php @@ -0,0 +1,19 @@ + Date: Fri, 20 Dec 2024 10:12:43 +0100 Subject: [PATCH 0897/3097] Fix test --- tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php index a3be5ead046..fb35b438a0e 100644 --- a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Traits; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassForbiddenNameCheck; @@ -35,7 +34,6 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), new NullsafeCheck(), - new PhpVersion(80000), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, @@ -54,6 +52,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/trait-attributes.php'], [ [ 'Attribute class TraitAttributes\AbstractAttribute is abstract.', @@ -85,6 +87,10 @@ public function testBug12011(): void public function testBug12281(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/bug-12281.php'], [ [ 'Attribute class AllowDynamicProperties cannot be used with trait.', From 90e48fa876696f221874a2766c2bf3fc1bea0ec0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 10:16:44 +0100 Subject: [PATCH 0898/3097] Attributes rules use `In*Node` virtual nodes for more precise Scope --- src/Rules/Functions/ArrowFunctionAttributesRule.php | 7 ++++--- src/Rules/Functions/ClosureAttributesRule.php | 7 ++++--- src/Rules/Functions/FunctionAttributesRule.php | 7 ++++--- src/Rules/Methods/MethodAttributesRule.php | 7 ++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Rules/Functions/ArrowFunctionAttributesRule.php b/src/Rules/Functions/ArrowFunctionAttributesRule.php index b849f968aa7..67af9eb5e04 100644 --- a/src/Rules/Functions/ArrowFunctionAttributesRule.php +++ b/src/Rules/Functions/ArrowFunctionAttributesRule.php @@ -5,11 +5,12 @@ use Attribute; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InArrowFunctionNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; /** - * @implements Rule + * @implements Rule */ final class ArrowFunctionAttributesRule implements Rule { @@ -20,14 +21,14 @@ public function __construct(private AttributesCheck $attributesCheck) public function getNodeType(): string { - return Node\Expr\ArrowFunction::class; + return InArrowFunctionNode::class; } public function processNode(Node $node, Scope $scope): array { return $this->attributesCheck->check( $scope, - $node->attrGroups, + $node->getOriginalNode()->attrGroups, Attribute::TARGET_FUNCTION, 'function', ); diff --git a/src/Rules/Functions/ClosureAttributesRule.php b/src/Rules/Functions/ClosureAttributesRule.php index 841ae2f46cc..fc206e19d45 100644 --- a/src/Rules/Functions/ClosureAttributesRule.php +++ b/src/Rules/Functions/ClosureAttributesRule.php @@ -5,11 +5,12 @@ use Attribute; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClosureNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; /** - * @implements Rule + * @implements Rule */ final class ClosureAttributesRule implements Rule { @@ -20,14 +21,14 @@ public function __construct(private AttributesCheck $attributesCheck) public function getNodeType(): string { - return Node\Expr\Closure::class; + return InClosureNode::class; } public function processNode(Node $node, Scope $scope): array { return $this->attributesCheck->check( $scope, - $node->attrGroups, + $node->getOriginalNode()->attrGroups, Attribute::TARGET_FUNCTION, 'function', ); diff --git a/src/Rules/Functions/FunctionAttributesRule.php b/src/Rules/Functions/FunctionAttributesRule.php index 153c222091c..9c5ad24d73e 100644 --- a/src/Rules/Functions/FunctionAttributesRule.php +++ b/src/Rules/Functions/FunctionAttributesRule.php @@ -5,11 +5,12 @@ use Attribute; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InFunctionNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; /** - * @implements Rule + * @implements Rule */ final class FunctionAttributesRule implements Rule { @@ -20,14 +21,14 @@ public function __construct(private AttributesCheck $attributesCheck) public function getNodeType(): string { - return Node\Stmt\Function_::class; + return InFunctionNode::class; } public function processNode(Node $node, Scope $scope): array { return $this->attributesCheck->check( $scope, - $node->attrGroups, + $node->getOriginalNode()->attrGroups, Attribute::TARGET_FUNCTION, 'function', ); diff --git a/src/Rules/Methods/MethodAttributesRule.php b/src/Rules/Methods/MethodAttributesRule.php index fefc7bac89d..433931baf31 100644 --- a/src/Rules/Methods/MethodAttributesRule.php +++ b/src/Rules/Methods/MethodAttributesRule.php @@ -5,11 +5,12 @@ use Attribute; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClassMethodNode; use PHPStan\Rules\AttributesCheck; use PHPStan\Rules\Rule; /** - * @implements Rule + * @implements Rule */ final class MethodAttributesRule implements Rule { @@ -20,14 +21,14 @@ public function __construct(private AttributesCheck $attributesCheck) public function getNodeType(): string { - return Node\Stmt\ClassMethod::class; + return InClassMethodNode::class; } public function processNode(Node $node, Scope $scope): array { return $this->attributesCheck->check( $scope, - $node->attrGroups, + $node->getOriginalNode()->attrGroups, Attribute::TARGET_METHOD, 'method', ); From f6c556f625078b39caff3d67fafd6ae49957333e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Dec 2024 10:24:48 +0100 Subject: [PATCH 0899/3097] Scope: use `scope->getConstant` instead --- src/Analyser/MutatingScope.php | 40 +++++++++++------ src/Php/PhpVersions.php | 5 +++ .../PHPStan/Analyser/ScopePhpVersionTest.php | 43 +++++++++++++++++++ .../Analyser/data/scope-constants-global.php | 9 ++++ .../data/scope-constants-namespace.php | 9 ++++ 5 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/ScopePhpVersionTest.php create mode 100644 tests/PHPStan/Analyser/data/scope-constants-global.php create mode 100644 tests/PHPStan/Analyser/data/scope-constants-namespace.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 832c73b1c09..f883c955784 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -610,12 +610,7 @@ public function hasConstant(Name $name): bool return $this->fileHasCompilerHaltStatementCalls(); } - if (!$name->isFullyQualified() && $this->getNamespace() !== null) { - if ($this->hasExpressionType(new ConstFetch(new FullyQualified([$this->getNamespace(), $name->toString()])))->yes()) { - return true; - } - } - if ($this->hasExpressionType(new ConstFetch(new FullyQualified($name->toString())))->yes()) { + if ($this->getGlobalConstantType($name) !== null) { return true; } @@ -5686,6 +5681,25 @@ private function getConstantTypes(): array return $constantTypes; } + private function getGlobalConstantType(Name $name): ?Type + { + $fetches = []; + if (!$name->isFullyQualified() && $this->getNamespace() !== null) { + $fetches[] = new ConstFetch(new FullyQualified([$this->getNamespace(), $name->toString()])); + } + + $fetches[] = new ConstFetch(new FullyQualified($name->toString())); + $fetches[] = new ConstFetch($name); + + foreach ($fetches as $constFetch) { + if ($this->hasExpressionType($constFetch)->yes()) { + return $this->getType($constFetch); + } + } + + return null; + } + /** * @return array */ @@ -5728,15 +5742,15 @@ public function getIterableValueType(Type $iteratee): Type public function getPhpVersion(): PhpVersions { - $versionExpr = new ConstFetch(new Name('PHP_VERSION_ID')); - if (!$this->hasExpressionType($versionExpr)->yes()) { - if (is_array($this->configPhpVersion)) { - return new PhpVersions(IntegerRangeType::fromInterval($this->configPhpVersion['min'], $this->configPhpVersion['max'])); - } - return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); + $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); + if ($constType !== null) { + return new PhpVersions($constType); } - return new PhpVersions($this->getType($versionExpr)); + if (is_array($this->configPhpVersion)) { + return new PhpVersions(IntegerRangeType::fromInterval($this->configPhpVersion['min'], $this->configPhpVersion['max'])); + } + return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); } } diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 74474b28b09..229dccb72d9 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -18,6 +18,11 @@ public function __construct( { } + public function getType(): Type + { + return $this->phpVersions; + } + public function supportsNoncapturingCatches(): TrinaryLogic { return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; diff --git a/tests/PHPStan/Analyser/ScopePhpVersionTest.php b/tests/PHPStan/Analyser/ScopePhpVersionTest.php new file mode 100644 index 00000000000..fac3b8a0660 --- /dev/null +++ b/tests/PHPStan/Analyser/ScopePhpVersionTest.php @@ -0,0 +1,43 @@ +', + __DIR__ . '/data/scope-constants-global.php', + ], + [ + 'int<80000, 80499>', + __DIR__ . '/data/scope-constants-namespace.php', + ], + ]; + } + + /** + * @dataProvider dataTestPhpVersion + */ + public function testPhpVersion(string $expected, string $file): void + { + self::processFile($file, function (Node $node, Scope $scope) use ($expected): void { + if (!($node instanceof Exit_)) { + return; + } + $this->assertSame( + $expected, + $scope->getPhpVersion()->getType()->describe(VerbosityLevel::precise()), + ); + }); + } + +} diff --git a/tests/PHPStan/Analyser/data/scope-constants-global.php b/tests/PHPStan/Analyser/data/scope-constants-global.php new file mode 100644 index 00000000000..56eba41c9a0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/scope-constants-global.php @@ -0,0 +1,9 @@ + Date: Fri, 20 Dec 2024 14:52:28 +0100 Subject: [PATCH 0900/3097] Bleeding edge - UnusedFunctionParametersCheck: report precise line --- conf/bleedingEdge.neon | 1 + conf/config.neon | 3 ++ conf/parametersSchema.neon | 1 + .../UnusedConstructorParametersRule.php | 7 ++-- src/Rules/Functions/UnusedClosureUsesRule.php | 9 +---- src/Rules/UnusedFunctionParametersCheck.php | 35 +++++++++++++------ .../UnusedConstructorParametersRuleTest.php | 20 ++++++++++- .../Functions/UnusedClosureUsesRuleTest.php | 6 ++-- 8 files changed, 55 insertions(+), 27 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 7227e369b38..76dd0d89041 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,3 +4,4 @@ parameters: checkParameterCastableToNumberFunctions: true skipCheckGenericClasses!: [] stricterFunctionMap: true + reportPreciseLineForUnusedFunctionParameter: true diff --git a/conf/config.neon b/conf/config.neon index 40e57d400de..d77e8895316 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -25,6 +25,7 @@ parameters: checkParameterCastableToNumberFunctions: false skipCheckGenericClasses: [] stricterFunctionMap: false + reportPreciseLineForUnusedFunctionParameter: false fileExtensions: - php checkAdvancedIsset: false @@ -1043,6 +1044,8 @@ services: - class: PHPStan\Rules\UnusedFunctionParametersCheck + arguments: + reportExactLine: %featureToggles.reportPreciseLineForUnusedFunctionParameter% - class: PHPStan\Rules\TooWideTypehints\TooWideParameterOutTypeCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3dbe4e87ec9..f7328f7863b 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -31,6 +31,7 @@ parametersSchema: checkParameterCastableToNumberFunctions: bool(), skipCheckGenericClasses: listOf(string()), stricterFunctionMap: bool() + reportPreciseLineForUnusedFunctionParameter: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index e42a60d5f7d..8b38392470a 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -15,7 +15,6 @@ use function array_map; use function array_values; use function count; -use function is_string; use function sprintf; use function strtolower; @@ -56,11 +55,11 @@ public function processNode(Node $node, Scope $scope): array return $this->check->getUnusedParameters( $scope, - array_map(static function (Param $parameter): string { - if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + array_map(static function (Param $parameter): Variable { + if (!$parameter->var instanceof Variable) { throw new ShouldNotHappenException(); } - return $parameter->var->name; + return $parameter->var; }, array_values(array_filter($originalNode->params, static fn (Param $parameter): bool => $parameter->flags === 0))), $originalNode->stmts, $message, diff --git a/src/Rules/Functions/UnusedClosureUsesRule.php b/src/Rules/Functions/UnusedClosureUsesRule.php index c019d692404..ed9639e41f0 100644 --- a/src/Rules/Functions/UnusedClosureUsesRule.php +++ b/src/Rules/Functions/UnusedClosureUsesRule.php @@ -6,10 +6,8 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\UnusedFunctionParametersCheck; -use PHPStan\ShouldNotHappenException; use function array_map; use function count; -use function is_string; /** * @implements Rule @@ -34,12 +32,7 @@ public function processNode(Node $node, Scope $scope): array return $this->check->getUnusedParameters( $scope, - array_map(static function (Node\ClosureUse $use): string { - if (!is_string($use->var->name)) { - throw new ShouldNotHappenException(); - } - return $use->var->name; - }, $node->uses), + array_map(static fn (Node\ClosureUse $use): Node\Expr\Variable => $use->var, $node->uses), $node->stmts, 'Anonymous function has an unused use $%s.', 'closure.unusedUse', diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 4fbe76d20d4..628041a0320 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -3,11 +3,13 @@ namespace PHPStan\Rules; use PhpParser\Node; +use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; -use function array_fill_keys; -use function array_keys; +use function array_combine; +use function array_map; use function array_merge; use function is_array; use function is_string; @@ -16,25 +18,34 @@ final class UnusedFunctionParametersCheck { - public function __construct(private ReflectionProvider $reflectionProvider) + public function __construct( + private ReflectionProvider $reflectionProvider, + private bool $reportExactLine, + ) { } /** - * @param string[] $parameterNames + * @param Variable[] $parameterVars * @param Node[] $statements * @param 'constructor.unusedParameter'|'closure.unusedUse' $identifier * @return list */ public function getUnusedParameters( Scope $scope, - array $parameterNames, + array $parameterVars, array $statements, string $unusedParameterMessage, string $identifier, ): array { - $unusedParameters = array_fill_keys($parameterNames, true); + $parameterNames = array_map(static function (Variable $variable): string { + if (!is_string($variable->name)) { + throw new ShouldNotHappenException(); + } + return $variable->name; + }, $parameterVars); + $unusedParameters = array_combine($parameterNames, $parameterVars); foreach ($this->getUsedVariables($scope, $statements) as $variableName) { if (!isset($unusedParameters[$variableName])) { continue; @@ -43,10 +54,12 @@ public function getUnusedParameters( unset($unusedParameters[$variableName]); } $errors = []; - foreach (array_keys($unusedParameters) as $name) { - $errors[] = RuleErrorBuilder::message( - sprintf($unusedParameterMessage, $name), - )->identifier($identifier)->build(); + foreach ($unusedParameters as $name => $variable) { + $errorBuilder = RuleErrorBuilder::message(sprintf($unusedParameterMessage, $name))->identifier($identifier); + if ($this->reportExactLine) { + $errorBuilder->line($variable->getStartLine()); + } + $errors[] = $errorBuilder->build(); } return $errors; @@ -66,7 +79,7 @@ private function getUsedVariables(Scope $scope, $node): array return $scope->getDefinedVariables(); } } - if ($node instanceof Node\Expr\Variable && is_string($node->name) && $node->name !== 'this') { + if ($node instanceof Variable && is_string($node->name) && $node->name !== 'this') { return [$node->name]; } if ($node instanceof Node\ClosureUse && is_string($node->var->name)) { diff --git a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php index b6530920dff..beb402c2674 100644 --- a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php +++ b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php @@ -12,15 +12,19 @@ class UnusedConstructorParametersRuleTest extends RuleTestCase { + private bool $reportExactLine = true; + protected function getRule(): Rule { return new UnusedConstructorParametersRule(new UnusedFunctionParametersCheck( $this->createReflectionProvider(), + $this->reportExactLine, )); } - public function testUnusedConstructorParameters(): void + public function testUnusedConstructorParametersNoExactLine(): void { + $this->reportExactLine = false; $this->analyse([__DIR__ . '/data/unused-constructor-parameters.php'], [ [ 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $unusedParameter.', @@ -33,6 +37,20 @@ public function testUnusedConstructorParameters(): void ]); } + public function testUnusedConstructorParameters(): void + { + $this->analyse([__DIR__ . '/data/unused-constructor-parameters.php'], [ + [ + 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $unusedParameter.', + 19, + ], + [ + 'Constructor of class UnusedConstructorParameters\Foo has an unused parameter $anotherUnusedParameter.', + 20, + ], + ]); + } + public function testPromotedProperties(): void { $this->analyse([__DIR__ . '/data/unused-constructor-parameters-promoted-properties.php'], []); diff --git a/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php b/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php index 0d033268d81..38a555afda4 100644 --- a/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/UnusedClosureUsesRuleTest.php @@ -14,7 +14,7 @@ class UnusedClosureUsesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnusedClosureUsesRule(new UnusedFunctionParametersCheck($this->createReflectionProvider())); + return new UnusedClosureUsesRule(new UnusedFunctionParametersCheck($this->createReflectionProvider(), true)); } public function testUnusedClosureUses(): void @@ -22,11 +22,11 @@ public function testUnusedClosureUses(): void $this->analyse([__DIR__ . '/data/unused-closure-uses.php'], [ [ 'Anonymous function has an unused use $unused.', - 3, + 6, ], [ 'Anonymous function has an unused use $anotherUnused.', - 3, + 7, ], [ 'Anonymous function has an unused use $usedInClosureUse.', From 72b31c081c52682736318d62a09b60f740d9ce41 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Fri, 20 Dec 2024 19:31:57 +0000 Subject: [PATCH 0901/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 8670cde3eeb..30dfc158c37 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.46.0.0", + "ondrejmirtes/better-reflection": "6.49.0.0", "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 265045a14fe..731974732a0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f90069e33e3f9bd4610862a5a5aed2e", + "content-hash": "7887860dff8af8b2ff60352d573b1aba", "packages": [ { "name": "clue/ndjson-react", @@ -2187,21 +2187,21 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.46.0.0", + "version": "6.49.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "242b3e1cdb59a81585be5722b6c44ae44c74c671" + "reference": "11abb6b4c9c8b29ee2730a3307ebae77b17fa94d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/242b3e1cdb59a81585be5722b6c44ae44c74c671", - "reference": "242b3e1cdb59a81585be5722b6c44ae44c74c671", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/11abb6b4c9c8b29ee2730a3307ebae77b17fa94d", + "reference": "11abb6b4c9c8b29ee2730a3307ebae77b17fa94d", "shasum": "" }, "require": { "ext-json": "*", - "jetbrains/phpstorm-stubs": "dev-master#217ed9356d07ef89109d3cd7d8c5df10aab4b0d4", + "jetbrains/phpstorm-stubs": "dev-master#b61d4a5f40c3940be440d85355fef4e2416b8527", "nikic/php-parser": "^5.3.1", "php": "^7.4 || ^8.0" }, @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.46.0.0" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.49.0.0" }, - "time": "2024-12-12T12:40:29+00:00" + "time": "2024-12-20T19:27:15+00:00" }, { "name": "phpstan/php-8-stubs", From 46b98196c0643eeb3b0eb3c5e91e10099e00f5c1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 15:55:41 +0100 Subject: [PATCH 0902/3097] Prepare for 2.1.x-dev --- .github/workflows/apiref.yml | 4 ++-- .github/workflows/issue-bot.yml | 4 ++-- .github/workflows/pr-base-on-previous-branch.yml | 4 ++-- .github/workflows/update-phpstorm-stubs.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/apiref.yml b/.github/workflows/apiref.yml index 7700ceb9115..24ba44284be 100644 --- a/.github/workflows/apiref.yml +++ b/.github/workflows/apiref.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: push: branches: - - "2.0.x" + - "2.1.x" paths: - 'src/**' - 'composer.lock' @@ -14,7 +14,7 @@ on: - '.github/workflows/apiref.yml' env: - COMPOSER_ROOT_VERSION: "2.0.x-dev" + COMPOSER_ROOT_VERSION: "2.1.x-dev" concurrency: group: apigen-${{ github.ref }} # will be canceled on subsequent pushes in branch diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index c3d3488feab..3165350af2d 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -11,7 +11,7 @@ on: - 'changelog-generator/**' push: branches: - - "2.0.x" + - "2.1.x" paths-ignore: - 'compiler/**' - 'apigen/**' @@ -164,7 +164,7 @@ jobs: - name: "Evaluate results - push" working-directory: "issue-bot" - if: "github.repository_owner == 'phpstan' && github.ref == 'refs/heads/2.0.x'" + if: "github.repository_owner == 'phpstan' && github.ref == 'refs/heads/2.1.x'" env: GITHUB_PAT: ${{ secrets.PHPSTAN_BOT_TOKEN }} PHPSTAN_SRC_COMMIT_BEFORE: ${{ github.event.before }} diff --git a/.github/workflows/pr-base-on-previous-branch.yml b/.github/workflows/pr-base-on-previous-branch.yml index 7c2f57188d7..34ef71bb837 100644 --- a/.github/workflows/pr-base-on-previous-branch.yml +++ b/.github/workflows/pr-base-on-previous-branch.yml @@ -7,7 +7,7 @@ on: types: - opened branches: - - '2.1.x' + - '2.2.x' jobs: @@ -19,6 +19,6 @@ jobs: - name: Comment PR uses: peter-evans/create-or-update-comment@v4 with: - body: "You've opened the pull request against the latest branch 2.1.x. PHPStan 2.1 is not going to be released for months. If your code is relevant on 2.0.x and you want it to be released sooner, please rebase your pull request and change its target to 2.0.x." + body: "You've opened the pull request against the latest branch 2.2.x. PHPStan 2.2 is not going to be released for months. If your code is relevant on 2.1.x and you want it to be released sooner, please rebase your pull request and change its target to 2.1.x." token: ${{ secrets.PHPSTAN_BOT_TOKEN }} issue-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/update-phpstorm-stubs.yml b/.github/workflows/update-phpstorm-stubs.yml index 320b4eba1be..396be1c0bef 100644 --- a/.github/workflows/update-phpstorm-stubs.yml +++ b/.github/workflows/update-phpstorm-stubs.yml @@ -16,7 +16,7 @@ jobs: - name: "Checkout" uses: actions/checkout@v4 with: - ref: 2.0.x + ref: 2.1.x fetch-depth: '0' token: ${{ secrets.PHPSTAN_BOT_TOKEN }} - name: "Install PHP" From 1892dc9d37bb41d7485968bcdfd4777be37b15e0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 09:47:48 +0100 Subject: [PATCH 0903/3097] Open 2.1.x-dev --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/build-issue-bot.yml | 2 +- .github/workflows/changelog-generator.yml | 2 +- .github/workflows/checksum-phar.yml | 8 +++---- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/phar.yml | 24 ++++++++++---------- .github/workflows/reflection-golden-test.yml | 2 +- .github/workflows/spelling.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- composer.json | 2 +- composer.lock | 2 +- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 52d4cc8cc61..541d15addcc 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "2.0.x" + - "2.1.x" paths: - 'src/**' - '.github/workflows/backward-compatibility.yml' diff --git a/.github/workflows/build-issue-bot.yml b/.github/workflows/build-issue-bot.yml index ab5f7c28d8b..0c6904033bb 100644 --- a/.github/workflows/build-issue-bot.yml +++ b/.github/workflows/build-issue-bot.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/build-issue-bot.yml' push: branches: - - "2.0.x" + - "2.1.x" paths: - 'issue-bot/**' - '.github/workflows/build-issue-bot.yml' diff --git a/.github/workflows/changelog-generator.yml b/.github/workflows/changelog-generator.yml index 33a3db908bb..aaa121a6828 100644 --- a/.github/workflows/changelog-generator.yml +++ b/.github/workflows/changelog-generator.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/changelog-generator.yml' push: branches: - - "2.0.x" + - "2.1.x" paths: - 'changelog-generator/**' - '.github/workflows/changelog-generator.yml' diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml index e586a82aa2e..185fc779b42 100644 --- a/.github/workflows/checksum-phar.yml +++ b/.github/workflows/checksum-phar.yml @@ -12,7 +12,7 @@ on: - '.github/workflows/checksum-phar.yml' push: branches: - - "2.0.x" + - "2.1.x" paths: - 'compiler/**' - '.github/workflows/checksum-phar.yml' @@ -34,7 +34,7 @@ jobs: with: repository: phpstan/phpstan path: phpstan-dist - ref: 2.0.x + ref: 2.1.x - name: "Get info" id: info @@ -98,14 +98,14 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" env: - COMPOSER_ROOT_VERSION: "2.0.x-dev" + COMPOSER_ROOT_VERSION: "2.1.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "2.0.x-dev" + COMPOSER_ROOT_VERSION: "2.1.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 96505cbf9ca..872d5363144 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "2.0.x" + - "2.1.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 86ee004f07c..d93e59843f9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "2.0.x" + - "2.1.x" concurrency: group: lint-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index adea89e2323..0cf91034a36 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -6,9 +6,9 @@ on: pull_request: push: branches: - - "2.0.x" + - "2.1.x" tags: - - '2.0.*' + - '2.1.*' concurrency: group: phar-${{ github.ref }} # will be canceled on subsequent pushes in both branches and pull requests @@ -77,14 +77,14 @@ jobs: - name: "Composer dump" run: "composer install --no-interaction --no-progress" env: - COMPOSER_ROOT_VERSION: "2.0.x-dev" + COMPOSER_ROOT_VERSION: "2.1.x-dev" - name: "Compile PHAR for checksum" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" env: PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "2.0.x-dev" + COMPOSER_ROOT_VERSION: "2.1.x-dev" - name: "Re-sign PHAR" run: "php compiler/build/resign.php tmp/phpstan.phar" @@ -107,30 +107,30 @@ jobs: integration-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.0.x + uses: phpstan/phpstan/.github/workflows/integration-tests.yml@2.1.x with: - ref: 2.0.x + ref: 2.1.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} extension-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/extension-tests.yml@2.0.x + uses: phpstan/phpstan/.github/workflows/extension-tests.yml@2.1.x with: - ref: 2.0.x + ref: 2.1.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} other-tests: if: github.event_name == 'pull_request' needs: compiler-tests - uses: phpstan/phpstan/.github/workflows/other-tests.yml@2.0.x + uses: phpstan/phpstan/.github/workflows/other-tests.yml@2.1.x with: - ref: 2.0.x + ref: 2.1.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} commit: name: "Commit PHAR" - if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/2.0.x' || startsWith(github.ref, 'refs/tags/'))" + if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/2.1.x' || startsWith(github.ref, 'refs/tags/'))" needs: compiler-tests runs-on: "ubuntu-latest" timeout-minutes: 60 @@ -152,7 +152,7 @@ jobs: repository: phpstan/phpstan path: phpstan-dist token: ${{ secrets.PHPSTAN_BOT_TOKEN }} - ref: 2.0.x + ref: 2.1.x - name: "Get previous pushed dist commit" id: previous-commit diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index ca9f6bd2463..5aea839c83b 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "2.0.x" + - "2.1.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index b7f4249e8bc..d34bbec06a0 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "2.0.x" + - "2.1.x" jobs: typos: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 161309d17ca..6c71c8d1f06 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -9,7 +9,7 @@ on: - 'apigen/**' push: branches: - - "2.0.x" + - "2.1.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 97cf5b30da3..d7c4673b404 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ on: - 'issue-bot/**' push: branches: - - "2.0.x" + - "2.1.x" paths-ignore: - 'compiler/**' - 'apigen/**' diff --git a/composer.json b/composer.json index 30dfc158c37..df3cf4dd96e 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "symfony/string": "^5.4.3" }, "replace": { - "phpstan/phpstan": "2.0.x", + "phpstan/phpstan": "2.1.x", "symfony/polyfill-php73": "*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 731974732a0..b5b530e6138 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7887860dff8af8b2ff60352d573b1aba", + "content-hash": "ece80b265c4a1c26fb5395d183eba963", "packages": [ { "name": "clue/ndjson-react", From b5fc9ecbb8654c6f0c3567827e39668b549bec79 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 15:59:32 +0100 Subject: [PATCH 0904/3097] Branch 1.12.x should merge into 2.1.x --- .github/workflows/merge-maintained-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-maintained-branch.yml b/.github/workflows/merge-maintained-branch.yml index f00b6ac9223..0ac13c5f68a 100644 --- a/.github/workflows/merge-maintained-branch.yml +++ b/.github/workflows/merge-maintained-branch.yml @@ -20,5 +20,5 @@ jobs: with: github_token: "${{ secrets.PHPSTAN_BOT_TOKEN }}" source_ref: ${{ github.ref }} - target_branch: '2.0.x' + target_branch: '2.1.x' commit_message_template: 'Merge branch {source_ref} into {target_branch}' From aa4a1232d2fbd6460632cb55d337e18539757281 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 21 Dec 2024 23:01:47 +0100 Subject: [PATCH 0905/3097] Improve loose comparison on integer --- src/Php/PhpVersion.php | 5 ++ src/Type/IntegerType.php | 13 +++++ src/Type/Traits/ConstantScalarTypeTrait.php | 2 +- tests/PHPStan/Analyser/nsrt/equal.php | 3 +- .../Analyser/nsrt/loose-comparisons-php7.php | 15 ++++++ .../Analyser/nsrt/loose-comparisons-php8.php | 22 ++++++++ .../Analyser/nsrt/loose-comparisons.php | 52 +++++++++++++++++++ .../ConstantLooseComparisonRuleTest.php | 23 +++++++- 8 files changed, 131 insertions(+), 4 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index e636945cc2f..98f86eac4df 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -284,6 +284,11 @@ public function castsNumbersToStringsOnLooseComparison(): bool return $this->versionId >= 80000; } + public function nonNumericStringAndIntegerIsFalseOnLooseComparison(): bool + { + return $this->versionId >= 80000; + } + public function supportsCallableInstanceMethods(): bool { return $this->versionId < 80000; diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 31ef7517155..4eb3bd50fd7 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -10,6 +10,7 @@ use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; @@ -139,6 +140,18 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isArray()->yes()) { + return new ConstantBooleanType(false); + } + + if ( + $phpVersion->nonNumericStringAndIntegerIsFalseOnLooseComparison() + && $type->isString()->yes() + && $type->isNumericString()->no() + ) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index b4757aaac25..f4585126225 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -58,7 +58,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType } if ($type->isConstantArray()->yes() && $type->isIterableAtLeastOnce()->no()) { - // @phpstan-ignore equal.notAllowed, equal.invalid + // @phpstan-ignore equal.notAllowed, equal.invalid, equal.alwaysFalse return new ConstantBooleanType($this->getValue() == []); // phpcs:ignore } diff --git a/tests/PHPStan/Analyser/nsrt/equal.php b/tests/PHPStan/Analyser/nsrt/equal.php index aad0f3ef5b0..e91a274257b 100644 --- a/tests/PHPStan/Analyser/nsrt/equal.php +++ b/tests/PHPStan/Analyser/nsrt/equal.php @@ -135,7 +135,7 @@ public static function createStdClass(): \stdClass class Baz { - public function doFoo(string $a, int $b, float $c): void + public function doFoo(string $a, float $c): void { $nullableA = $a; if (rand(0, 1)) { @@ -152,7 +152,6 @@ public function doFoo(string $a, int $b, float $c): void assertType('false', 'a' != 'a'); assertType('true', 'a' != 'b'); - assertType('bool', $b == 'a'); assertType('bool', $a == 1); assertType('true', 1 == 1); assertType('false', 1 == 0); diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php index d95fecab118..9e00dd0f657 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php @@ -46,4 +46,19 @@ public function sayEmptyStr( { assertType('true', $emptyStr == $zero); } + + /** + * @param 'php' $phpStr + * @param '' $emptyStr + */ + public function sayInt( + $emptyStr, + $phpStr, + int $int + ): void + { + assertType('bool', $int == $emptyStr); + assertType('bool', $int == $phpStr); + assertType('bool', $int == 'a'); + } } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php index bba8a89f204..a3ca84cf64b 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php @@ -46,4 +46,26 @@ public function sayEmptyStr( { assertType('false', $emptyStr == $zero); // PHP8+ only } + + /** + * @param 'php' $phpStr + * @param '' $emptyStr + * @param int<10, 20> $intRange + */ + public function sayInt( + $emptyStr, + $phpStr, + int $int, + int $intRange + ): void + { + assertType('false', $int == $emptyStr); + assertType('false', $int == $phpStr); + assertType('false', $int == 'a'); + + assertType('false', $intRange == $emptyStr); + assertType('false', $intRange == $phpStr); + assertType('false', $intRange == 'a'); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index 92bec36518d..cc3eba83f38 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -601,4 +601,56 @@ public function sayEmptyStr( assertType('false', $emptyStr == $phpStr); assertType('true', $emptyStr == $emptyStr); } + + /** + * @param true $true + * @param false $false + * @param 1 $one + * @param 0 $zero + * @param -1 $minusOne + * @param '1' $oneStr + * @param '0' $zeroStr + * @param '-1' $minusOneStr + * @param '+1' $plusOneStr + * @param null $null + * @param array{} $emptyArr + * @param 'php' $phpStr + * @param '' $emptyStr + * @param int<10, 20> $intRange + */ + public function sayInt( + $true, + $false, + $one, + $zero, + $minusOne, + $oneStr, + $zeroStr, + $minusOneStr, + $plusOneStr, + $null, + $emptyArr, + array $array, + int $int, + int $intRange, + ): void + { + assertType('bool', $int == $true); + assertType('bool', $int == $false); + assertType('bool', $int == $one); + assertType('bool', $int == $zero); + assertType('bool', $int == $minusOne); + assertType('bool', $int == $oneStr); + assertType('bool', $int == $zeroStr); + assertType('bool', $int == $minusOneStr); + assertType('bool', $int == $plusOneStr); + assertType('bool', $int == $null); + assertType('false', $int == $emptyArr); + assertType('false', $int == $array); + + assertType('false', $intRange == $emptyArr); + assertType('false', $intRange == $array); + + } + } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index e0bbf466abd..c4a819d1fc6 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function array_merge; use const PHP_VERSION_ID; /** @@ -142,7 +143,7 @@ public function testTreatPhpDocTypesAsCertain(bool $treatPhpDocTypesAsCertain, a public function testBug11694(): void { - $this->analyse([__DIR__ . '/data/bug-11694.php'], [ + $expectedErrors = [ [ 'Loose comparison using == between 3 and int<10, 20> will always evaluate to false.', 17, @@ -173,6 +174,24 @@ public function testBug11694(): void 27, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], + ]; + + if (PHP_VERSION_ID >= 80000) { + $expectedErrors = array_merge($expectedErrors, [ + [ + "Loose comparison using == between '13foo' and int<10, 20> will always evaluate to false.", + 29, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Loose comparison using == between int<10, 20> and '13foo' will always evaluate to false.", + 30, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + + $expectedErrors = array_merge($expectedErrors, [ [ 'Loose comparison using == between \' 3\' and int<10, 20> will always evaluate to false.', 32, @@ -204,6 +223,8 @@ public function testBug11694(): void 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ]); + + $this->analyse([__DIR__ . '/data/bug-11694.php'], $expectedErrors); } } From beba172c0e6bed60383e732075845f747f4480f1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 23 Dec 2024 20:21:19 +0100 Subject: [PATCH 0906/3097] Improve loose comparison on union type --- .../AccessoryLowercaseStringType.php | 9 ++ .../Accessory/AccessoryNonEmptyStringType.php | 4 + .../AccessoryUppercaseStringType.php | 9 ++ src/Type/Accessory/NonEmptyArrayType.php | 5 + src/Type/ArrayType.php | 4 + src/Type/BooleanType.php | 12 ++ src/Type/IntersectionType.php | 4 +- src/Type/UnionType.php | 4 +- .../Analyser/nsrt/loose-comparisons.php | 104 ++++++++++++++++++ .../ConstantLooseComparisonRuleTest.php | 13 ++- .../Rules/Comparison/data/bug-8800.php | 11 ++ 11 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8800.php diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index c4b8b0b3643..97edeb39ba8 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -11,6 +11,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; @@ -326,6 +327,14 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ( + $type->isString()->yes() + && $type->isLowercaseString()->no() + && ($type->isNumericString()->no() || $this->isNumericString()->no()) + ) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 08f47900013..f31e3108b49 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -11,6 +11,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; @@ -322,6 +323,9 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isString()->yes() && $type->isNonEmptyString()->no()) { + return new ConstantBooleanType(false); + } return new BooleanType(); } diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index f9ae03d0ac8..a034eea3214 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -11,6 +11,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; @@ -326,6 +327,14 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ( + $type->isString()->yes() + && $type->isUppercaseString()->no() + && ($type->isNumericString()->no() || $this->isNumericString()->no()) + ) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index e99bb4cbaa5..a46eefc807f 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -9,6 +9,7 @@ use PHPStan\Type\AcceptsResult; use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -402,6 +403,10 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isArray()->yes() && $type->isIterableAtLeastOnce()->no()) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 402c985b0ec..9570359eaf3 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -249,6 +249,10 @@ public function isConstantValue(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isInteger()->yes()) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index df059481e6a..0b0eb798ec5 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -163,4 +163,16 @@ public function toPhpDocNode(): TypeNode return new IdentifierTypeNode('bool'); } + public function toTrinaryLogic(): TrinaryLogic + { + if ($this->isTrue()->yes()) { + return TrinaryLogic::createYes(); + } + if ($this->isFalse()->yes()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createMaybe(); + } + } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 255f56dd7af..b79f6ed7eaa 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -716,7 +716,9 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - return new BooleanType(); + return $this->intersectResults( + static fn (Type $innerType): TrinaryLogic => $innerType->looseCompare($type, $phpVersion)->toTrinaryLogic() + )->toBooleanType(); } public function isOffsetAccessible(): TrinaryLogic diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index c69888cf308..9156bc09b79 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -676,7 +676,9 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - return new BooleanType(); + return $this->unionResults( + static fn (Type $innerType): TrinaryLogic => $innerType->looseCompare($type, $phpVersion)->toTrinaryLogic() + )->toBooleanType(); } public function isOffsetAccessible(): TrinaryLogic diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index cc3eba83f38..16c414170b8 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -653,4 +653,108 @@ public function sayInt( } + /** + * @param true|1|"1" $looseOne + * @param false|0|"0" $looseZero + * @param false|1 $constMix + */ + public function sayConstUnion( + $looseOne, + $looseZero, + $constMix + ): void + { + assertType('true', $looseOne == 1); + assertType('false', $looseOne == 0); + assertType('true', $looseOne == true); + assertType('false', $looseOne == false); + assertType('true', $looseOne == "1"); + assertType('false', $looseOne == "0"); + assertType('false', $looseOne == []); + + assertType('false', $looseZero == 1); + assertType('true', $looseZero == 0); + assertType('false', $looseZero == true); + assertType('true', $looseZero == false); + assertType('false', $looseZero == "1"); + assertType('true', $looseZero == "0"); + assertType('bool', $looseZero == []); + + assertType('bool', $constMix == 0); + assertType('bool', $constMix == 1); + assertType('bool', $constMix == true); + assertType('bool', $constMix == false); + assertType('bool', $constMix == "1"); + assertType('bool', $constMix == "0"); + assertType('bool', $constMix == []); + + assertType('true', $looseOne == $looseOne); + assertType('true', $looseZero == $looseZero); + assertType('false', $looseOne == $looseZero); + assertType('false', $looseZero == $looseOne); + assertType('bool', $looseOne == $constMix); + assertType('bool', $constMix == $looseOne); + assertType('bool', $looseZero == $constMix); + assertType('bool', $constMix == $looseZero); + } + + /** + * @param uppercase-string $upper + * @param lowercase-string $lower + * @param array{} $emptyArr + * @param non-empty-array $nonEmptyArr + * @param int<10, 20> $intRange + */ + public function sayIntersection( + string $upper, + string $lower, + string $s, + array $emptyArr, + array $nonEmptyArr, + array $arr, + int $i, + int $intRange, + ): void + { + // https://3v4l.org/q8OP2 + assertType('true', '1e2' == '1E2'); + assertType('false', '1e2' === '1E2'); + + assertType('bool', '' == $upper); + assertType('bool', '0' == $upper); + assertType('false', 'a' == $upper); + assertType('false', 'abc' == $upper); + assertType('false', 'aBc' == $upper); + assertType('bool', '1e2' == $upper); + assertType('bool', strtoupper($s) == $upper); + assertType('bool', strtolower($s) == $upper); + assertType('bool', $upper == $lower); + + assertType('bool', '0' == $lower); + assertType('false', 'A' == $lower); + assertType('false', 'ABC' == $lower); + assertType('false', 'AbC' == $lower); + assertType('bool', '1E2' == $lower); + assertType('bool', strtoupper($s) == $lower); + assertType('bool', strtolower($s) == $lower); + assertType('bool', $lower == $upper); + + assertType('false', $arr == $i); + assertType('false', $nonEmptyArr == $i); + assertType('false', $arr == $intRange); + assertType('false', $nonEmptyArr == $intRange); + assertType('bool', $emptyArr == $nonEmptyArr); // should be false + assertType('false', $nonEmptyArr == $emptyArr); + assertType('bool', $arr == $nonEmptyArr); + assertType('bool', $nonEmptyArr == $arr); + + assertType('bool', '' == $lower); + if ($lower != '') { + assertType('false', '' == $lower); + } + if ($upper != '') { + assertType('false', '' == $upper); + } + } + } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index c4a819d1fc6..6e56f9dfcdc 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -181,12 +181,10 @@ public function testBug11694(): void [ "Loose comparison using == between '13foo' and int<10, 20> will always evaluate to false.", 29, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ "Loose comparison using == between int<10, 20> and '13foo' will always evaluate to false.", 30, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ]); } @@ -227,4 +225,15 @@ public function testBug11694(): void $this->analyse([__DIR__ . '/data/bug-11694.php'], $expectedErrors); } + public function testBug8800(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8800.php'], [ + [ + 'Loose comparison using == between 0|1|false and 2 will always evaluate to false.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8800.php b/tests/PHPStan/Rules/Comparison/data/bug-8800.php new file mode 100644 index 00000000000..c8656f5da25 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8800.php @@ -0,0 +1,11 @@ + Date: Tue, 24 Dec 2024 00:03:34 +0000 Subject: [PATCH 0907/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index df3cf4dd96e..6e2c1cb8c4d 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", + "jetbrains/phpstorm-stubs": "dev-master#db675e059f57071e8209c99075128b92d8a727e7", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index b5b530e6138..880e1134519 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ece80b265c4a1c26fb5395d183eba963", + "content-hash": "f3a19a9abe4cf8cfbe9a6a76cf161369", "packages": [ { "name": "clue/ndjson-react", @@ -1442,19 +1442,19 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c" + "reference": "db675e059f57071e8209c99075128b92d8a727e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", - "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/db675e059f57071e8209c99075128b92d8a727e7", + "reference": "db675e059f57071e8209c99075128b92d8a727e7", "shasum": "" }, "require-dev": { - "friendsofphp/php-cs-fixer": "v3.64.0", - "nikic/php-parser": "v5.3.1", - "phpdocumentor/reflection-docblock": "5.6.0", - "phpunit/phpunit": "11.4.3" + "friendsofphp/php-cs-fixer": "^v3.64.0", + "nikic/php-parser": "^v5.3.1", + "phpdocumentor/reflection-docblock": "^5.6.0", + "phpunit/phpunit": "^11.4.3" }, "default-branch": true, "type": "library", @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-12-14T08:03:12+00:00" + "time": "2024-12-23T11:36:45+00:00" }, { "name": "nette/bootstrap", From e174d795358718264d7841d94228daad403b4fad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Nov 2024 14:11:59 +0100 Subject: [PATCH 0908/3097] Support for `#[Deprecated]` attribute --- src/Analyser/NodeScopeResolver.php | 60 ++++++ src/Internal/DeprecatedAttributeHelper.php | 45 +++++ src/Reflection/ClassReflection.php | 5 +- src/Reflection/EnumCaseReflection.php | 23 ++- src/Reflection/Php/PhpFunctionReflection.php | 6 + src/Reflection/Php/PhpMethodReflection.php | 6 + .../RealClassClassConstantReflection.php | 8 +- .../NativeFunctionReflectionProvider.php | 2 +- .../Annotations/DeprecatedAnnotationsTest.php | 189 ++++++++++++++++++ ...hpFunctionFromParserReflectionRuleTest.php | 111 ++++++++++ .../data/deprecated-attribute-constants.php | 21 ++ .../data/deprecated-attribute-enum.php | 19 ++ .../data/deprecated-attribute-functions.php | 34 ++++ .../data/deprecated-attribute-methods.php | 33 +++ 14 files changed, 555 insertions(+), 7 deletions(-) create mode 100644 src/Internal/DeprecatedAttributeHelper.php create mode 100644 tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php create mode 100644 tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-constants.php create mode 100644 tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-enum.php create mode 100644 tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-functions.php create mode 100644 tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-methods.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a70c0ca5ec6..7aa9791928a 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -134,6 +134,7 @@ use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Native\NativeMethodReflection; @@ -525,6 +526,10 @@ private function processStmtNode( $nodeCallback($stmt->returnType, $scope); } + if (!$isDeprecated) { + [$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $stmt); + } + $functionScope = $scope->enterFunction( $stmt, $templateTypeMap, @@ -609,6 +614,10 @@ private function processStmtNode( $nodeCallback($stmt->returnType, $scope); } + if (!$isDeprecated) { + [$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $stmt); + } + $methodScope = $scope->enterClassMethod( $stmt, $templateTypeMap, @@ -1933,6 +1942,57 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { return new StatementResult($scope, $hasYield, false, [], $throwPoints, $impurePoints); } + /** + * @return array{bool, string|null} + */ + private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod $stmt): array + { + $initializerExprContext = InitializerExprContext::fromStubParameter( + null, + $scope->getFile(), + $stmt, + ); + $isDeprecated = false; + $deprecatedDescription = null; + $deprecatedDescriptionType = null; + foreach ($stmt->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toString() !== 'Deprecated') { + continue; + } + $isDeprecated = true; + $arguments = $attr->args; + foreach ($arguments as $i => $arg) { + $argName = $arg->name; + if ($argName === null) { + if ($i !== 0) { + continue; + } + + $deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext); + break; + } + + if ($argName->toString() !== 'message') { + continue; + } + + $deprecatedDescriptionType = $this->initializerExprTypeResolver->getType($arg->value, $initializerExprContext); + break; + } + } + } + + if ($deprecatedDescriptionType !== null) { + $constantStrings = $deprecatedDescriptionType->getConstantStrings(); + if (count($constantStrings) === 1) { + $deprecatedDescription = $constantStrings[0]->getValue(); + } + } + + return [$isDeprecated, $deprecatedDescription]; + } + /** * @return ThrowPoint[]|null */ diff --git a/src/Internal/DeprecatedAttributeHelper.php b/src/Internal/DeprecatedAttributeHelper.php new file mode 100644 index 00000000000..8217fa1ef4f --- /dev/null +++ b/src/Internal/DeprecatedAttributeHelper.php @@ -0,0 +1,45 @@ + $attributes + */ + public static function getDeprecatedDescription(array $attributes): ?string + { + $deprecated = ReflectionAttributeHelper::filterAttributesByName($attributes, 'Deprecated'); + foreach ($deprecated as $attr) { + $arguments = $attr->getArguments(); + foreach ($arguments as $i => $arg) { + if (!is_string($arg)) { + continue; + } + + if (is_int($i)) { + if ($i !== 0) { + continue; + } + + return $arg; + } + + if ($i !== 'message') { + continue; + } + + return $arg; + } + } + + return null; + } + +} diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 54555706ee0..cd7ca830b34 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -772,9 +772,8 @@ public function getEnumCases(): array if ($case instanceof ReflectionEnumBackedCase) { $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext); } - /** @var string $caseName */ $caseName = $case->getName(); - $cases[$caseName] = new EnumCaseReflection($this, $caseName, $valueType); + $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType); } return $this->enumCases = $cases; @@ -800,7 +799,7 @@ public function getEnumCase(string $name): EnumCaseReflection $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this)); } - return new EnumCaseReflection($this, $name, $valueType); + return new EnumCaseReflection($this, $case, $valueType); } public function isClass(): bool diff --git a/src/Reflection/EnumCaseReflection.php b/src/Reflection/EnumCaseReflection.php index 2ce5cc63cfa..7c250f0cf59 100644 --- a/src/Reflection/EnumCaseReflection.php +++ b/src/Reflection/EnumCaseReflection.php @@ -2,6 +2,10 @@ namespace PHPStan\Reflection; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase; +use PHPStan\Internal\DeprecatedAttributeHelper; +use PHPStan\TrinaryLogic; use PHPStan\Type\Type; /** @@ -10,7 +14,7 @@ final class EnumCaseReflection { - public function __construct(private ClassReflection $declaringEnum, private string $name, private ?Type $backingValueType) + public function __construct(private ClassReflection $declaringEnum, private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, private ?Type $backingValueType) { } @@ -21,7 +25,7 @@ public function getDeclaringEnum(): ClassReflection public function getName(): string { - return $this->name; + return $this->reflection->getName(); } public function getBackingValueType(): ?Type @@ -29,4 +33,19 @@ public function getBackingValueType(): ?Type return $this->backingValueType; } + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); + } + + public function getDeprecatedDescription(): ?string + { + if ($this->reflection->isDeprecated()) { + $attributes = $this->reflection->getBetterReflection()->getAttributes(); + return DeprecatedAttributeHelper::getDeprecatedDescription($attributes); + } + + return null; + } + } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index e8fbc2e8245..ea6764ec2ec 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\Internal\DeprecatedAttributeHelper; use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicFunctionsVisitor; use PHPStan\Reflection\Assertions; @@ -190,6 +191,11 @@ public function getDeprecatedDescription(): ?string return $this->deprecatedDescription; } + if ($this->reflection->isDeprecated()) { + $attributes = $this->reflection->getBetterReflection()->getAttributes(); + return DeprecatedAttributeHelper::getDeprecatedDescription($attributes); + } + return null; } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index d32c16e75eb..888530f2705 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\Internal\DeprecatedAttributeHelper; use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicMethodsVisitor; use PHPStan\Reflection\Assertions; @@ -352,6 +353,11 @@ public function getDeprecatedDescription(): ?string return $this->deprecatedDescription; } + if ($this->reflection->isDeprecated()) { + $attributes = $this->reflection->getBetterReflection()->getAttributes(); + return DeprecatedAttributeHelper::getDeprecatedDescription($attributes); + } + return null; } diff --git a/src/Reflection/RealClassClassConstantReflection.php b/src/Reflection/RealClassClassConstantReflection.php index 85d863ba819..f9194090d3c 100644 --- a/src/Reflection/RealClassClassConstantReflection.php +++ b/src/Reflection/RealClassClassConstantReflection.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; +use PHPStan\Internal\DeprecatedAttributeHelper; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; @@ -111,7 +112,7 @@ public function isFinal(): bool public function isDeprecated(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->isDeprecated); + return TrinaryLogic::createFromBoolean($this->isDeprecated || $this->reflection->isDeprecated()); } public function getDeprecatedDescription(): ?string @@ -120,6 +121,11 @@ public function getDeprecatedDescription(): ?string return $this->deprecatedDescription; } + if ($this->reflection->isDeprecated()) { + $attributes = $this->reflection->getBetterReflection()->getAttributes(); + return DeprecatedAttributeHelper::getDeprecatedDescription($attributes); + } + return null; } diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 2a94fc4da58..6332238c330 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -57,6 +57,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); $returnsByReference = TrinaryLogic::createFromBoolean($reflectionFunctionAdapter->returnsReference()); $realFunctionName = $reflectionFunction->getName(); + $isDeprecated = $reflectionFunction->isDeprecated(); if ($reflectionFunction->getFileName() !== null) { $fileName = $reflectionFunction->getFileName(); $docComment = $reflectionFunction->getDocComment(); @@ -66,7 +67,6 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef if ($throwsTag !== null) { $throwType = $throwsTag->getType(); } - $isDeprecated = $reflectionFunction->isDeprecated(); } } } catch (IdentifierNotFound | InvalidIdentifierName) { diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 75fbfa45276..ff57846f969 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -10,9 +10,13 @@ use DeprecatedAnnotations\Foo; use DeprecatedAnnotations\FooInterface; use DeprecatedAnnotations\SubBazInterface; +use DeprecatedAttributeConstants\FooWithConstants; +use DeprecatedAttributeMethods\FooWithMethods; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; +use const PHP_VERSION_ID; class DeprecatedAnnotationsTest extends PHPStanTestCase { @@ -153,4 +157,189 @@ public function testNotDeprecatedChildMethods(): void $this->assertTrue($reflectionProvider->getClass(Baz::class)->getNativeMethod('superDeprecated')->isDeprecated()->no()); } + public function dataDeprecatedAttributeAboveFunction(): iterable + { + yield [ + 'DeprecatedAttributeFunctions\\notDeprecated', + TrinaryLogic::createNo(), + null, + ]; + yield [ + 'DeprecatedAttributeFunctions\\foo', + TrinaryLogic::createYes(), + null, + ]; + yield [ + 'DeprecatedAttributeFunctions\\fooWithMessage', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + 'DeprecatedAttributeFunctions\\fooWithMessage2', + TrinaryLogic::createYes(), + 'msg2', + ]; + yield [ + 'DeprecatedAttributeFunctions\\fooWithConstantMessage', + TrinaryLogic::createYes(), + 'DeprecatedAttributeFunctions\\fooWithConstantMessage', + ]; + } + + /** + * @dataProvider dataDeprecatedAttributeAboveFunction + * + * @param non-empty-string $functionName + */ + public function testDeprecatedAttributeAboveFunction(string $functionName, TrinaryLogic $isDeprecated, ?string $deprecatedDescription): void + { + require_once __DIR__ . '/data/deprecated-attribute-functions.php'; + + $reflectionProvider = $this->createReflectionProvider(); + $function = $reflectionProvider->getFunction(new Name($functionName), null); + $this->assertSame($isDeprecated->describe(), $function->isDeprecated()->describe()); + $this->assertSame($deprecatedDescription, $function->getDeprecatedDescription()); + } + + public function dataDeprecatedAttributeAboveMethod(): iterable + { + yield [ + FooWithMethods::class, + 'notDeprecated', + TrinaryLogic::createNo(), + null, + ]; + yield [ + FooWithMethods::class, + 'foo', + TrinaryLogic::createYes(), + null, + ]; + yield [ + FooWithMethods::class, + 'fooWithMessage', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + FooWithMethods::class, + 'fooWithMessage2', + TrinaryLogic::createYes(), + 'msg2', + ]; + } + + /** + * @dataProvider dataDeprecatedAttributeAboveMethod + */ + public function testDeprecatedAttributeAboveMethod(string $className, string $methodName, TrinaryLogic $isDeprecated, ?string $deprecatedDescription): void + { + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); + $method = $class->getNativeMethod($methodName); + $this->assertSame($isDeprecated->describe(), $method->isDeprecated()->describe()); + $this->assertSame($deprecatedDescription, $method->getDeprecatedDescription()); + } + + public function dataDeprecatedAttributeAboveClassConstant(): iterable + { + yield [ + FooWithConstants::class, + 'notDeprecated', + TrinaryLogic::createNo(), + null, + ]; + yield [ + FooWithConstants::class, + 'foo', + TrinaryLogic::createYes(), + null, + ]; + yield [ + FooWithConstants::class, + 'fooWithMessage', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + FooWithConstants::class, + 'fooWithMessage2', + TrinaryLogic::createYes(), + 'msg2', + ]; + + if (PHP_VERSION_ID < 80100) { + return; + } + + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'foo', + TrinaryLogic::createYes(), + null, + ]; + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'fooWithMessage', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'fooWithMessage2', + TrinaryLogic::createYes(), + 'msg2', + ]; + } + + /** + * @dataProvider dataDeprecatedAttributeAboveClassConstant + */ + public function testDeprecatedAttributeAboveClassConstant(string $className, string $constantName, TrinaryLogic $isDeprecated, ?string $deprecatedDescription): void + { + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); + $constant = $class->getConstant($constantName); + $this->assertSame($isDeprecated->describe(), $constant->isDeprecated()->describe()); + $this->assertSame($deprecatedDescription, $constant->getDeprecatedDescription()); + } + + public function dataDeprecatedAttributeAboveEnumCase(): iterable + { + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'foo', + TrinaryLogic::createYes(), + null, + ]; + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'fooWithMessage', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + 'DeprecatedAttributeEnum\\EnumWithDeprecatedCases', + 'fooWithMessage2', + TrinaryLogic::createYes(), + 'msg2', + ]; + } + + /** + * @dataProvider dataDeprecatedAttributeAboveEnumCase + */ + public function testDeprecatedAttributeAboveEnumCase(string $className, string $caseName, TrinaryLogic $isDeprecated, ?string $deprecatedDescription): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); + $case = $class->getEnumCase($caseName); + $this->assertSame($isDeprecated->describe(), $case->isDeprecated()->describe()); + $this->assertSame($deprecatedDescription, $case->getDeprecatedDescription()); + } + } diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php new file mode 100644 index 00000000000..ce520ef7aa3 --- /dev/null +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php @@ -0,0 +1,111 @@ + + */ +class DeprecatedAttributePhpFunctionFromParserReflectionRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new /** @implements Rule */ class implements Rule { + + public function getNodeType(): string + { + return Node\Stmt::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof InFunctionNode) { + $reflection = $node->getFunctionReflection(); + } elseif ($node instanceof InClassMethodNode) { + $reflection = $node->getMethodReflection(); + } else { + return []; + } + + if (!$reflection->isDeprecated()->yes()) { + return [ + RuleErrorBuilder::message('Not deprecated')->identifier('tests.notDeprecated')->build(), + ]; + } + + $description = $reflection->getDeprecatedDescription(); + if ($description === null) { + return [ + RuleErrorBuilder::message('Deprecated')->identifier('tests.deprecated')->build(), + ]; + } + + return [ + RuleErrorBuilder::message(sprintf('Deprecated: %s', $description))->identifier('tests.deprecated')->build(), + ]; + } + + }; + } + + public function testFunctionRule(): void + { + $this->analyse([__DIR__ . '/data/deprecated-attribute-functions.php'], [ + [ + 'Not deprecated', + 7, + ], + [ + 'Deprecated', + 12, + ], + [ + 'Deprecated: msg', + 18, + ], + [ + 'Deprecated: msg2', + 24, + ], + [ + 'Deprecated: DeprecatedAttributeFunctions\\fooWithConstantMessage', + 30, + ], + ]); + } + + public function testMethodRule(): void + { + $this->analyse([__DIR__ . '/data/deprecated-attribute-methods.php'], [ + [ + 'Not deprecated', + 10, + ], + [ + 'Deprecated', + 15, + ], + [ + 'Deprecated: msg', + 21, + ], + [ + 'Deprecated: msg2', + 27, + ], + ]); + } + +} diff --git a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-constants.php b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-constants.php new file mode 100644 index 00000000000..6de5cc39d6d --- /dev/null +++ b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-constants.php @@ -0,0 +1,21 @@ += 8.1 + +namespace DeprecatedAttributeEnum; + +use Deprecated; + +enum EnumWithDeprecatedCases +{ + + #[Deprecated] + case foo; + + #[Deprecated('msg')] + case fooWithMessage; + + #[Deprecated(since: '1.0', message: 'msg2')] + case fooWithMessage2; + +} diff --git a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-functions.php b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-functions.php new file mode 100644 index 00000000000..a7325b8fc39 --- /dev/null +++ b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-functions.php @@ -0,0 +1,34 @@ + Date: Wed, 11 Dec 2024 09:02:00 +0100 Subject: [PATCH 0909/3097] Detect hooked properties outside of constructor --- Makefile | 1 + .../Classes/InvalidPromotedPropertiesRule.php | 7 ++++++- .../InvalidPromotedPropertiesRuleTest.php | 15 +++++++++++++ .../data/invalid-hooked-properties.php | 21 +++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php diff --git a/Makefile b/Makefile index d8566bfdb00..407333c955a 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ + --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ src tests cs: diff --git a/src/Rules/Classes/InvalidPromotedPropertiesRule.php b/src/Rules/Classes/InvalidPromotedPropertiesRule.php index 22434786a88..6472285f76a 100644 --- a/src/Rules/Classes/InvalidPromotedPropertiesRule.php +++ b/src/Rules/Classes/InvalidPromotedPropertiesRule.php @@ -31,7 +31,12 @@ public function processNode(Node $node, Scope $scope): array $hasPromotedProperties = false; foreach ($node->getParams() as $param) { - if ($param->flags === 0) { + if ($param->flags !== 0) { + $hasPromotedProperties = true; + break; + } + + if ($param->hooks === []) { continue; } diff --git a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php index de80ea8f955..2ce3e7e268c 100644 --- a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php @@ -104,4 +104,19 @@ public function testBug9577(): void $this->analyse([__DIR__ . '/data/bug-9577.php'], []); } + public function testHooks(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->phpVersion = 80100; + $this->analyse([__DIR__ . '/data/invalid-hooked-properties.php'], [ + [ + 'Promoted properties can be in constructor only.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php b/tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php new file mode 100644 index 00000000000..e0933face14 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php @@ -0,0 +1,21 @@ + Date: Wed, 11 Dec 2024 09:17:36 +0100 Subject: [PATCH 0910/3097] Invoke virtual ClassPropertyNode for hooked promoted properties without visibility modifier --- src/Analyser/NodeScopeResolver.php | 2 +- .../Rules/Properties/PropertyInClassRuleTest.php | 4 ++++ .../data/hooked-properties-without-bodies-in-class.php | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7aa9791928a..c9a73a68e44 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -645,7 +645,7 @@ private function processStmtNode( $isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct'; if ($isFromTrait || $stmt->name->toLowerString() === '__construct') { foreach ($stmt->params as $param) { - if ($param->flags === 0) { + if ($param->flags === 0 && $param->hooks === []) { continue; } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 0b2ca5ba096..f6fde1e51c9 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -50,6 +50,10 @@ public function testPhp84AndHookedPropertiesWithoutBodiesInClass(): void 'Non-abstract properties cannot include hooks without bodies.', 9, ], + [ + 'Non-abstract properties cannot include hooks without bodies.', + 15, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php index dc839f0d2cf..fb0edcc3ce4 100644 --- a/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php +++ b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php @@ -8,3 +8,13 @@ class AbstractPerson public string $lastName { get; set; } } + +class PromotedHookedPropertyWithoutVisibility +{ + + public function __construct(mixed $test { get; }) + { + + } + +} From a3596c161cd39ef1dd1fc31c6a2a759883fe8963 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 11 Dec 2024 10:01:00 +0100 Subject: [PATCH 0911/3097] Process property hooks * They get a new virtual node InPropertyHookNode * Existing virtual nodes like InFunctionNode or InClassMethodNode are NOT invoked for them * But rules for FunctionLike node are invoked for property hooks * `Scope::getFunction()` returns PhpMethodFromParserNodeReflection inside property hooks --- src/Analyser/MutatingScope.php | 85 ++++++++++ src/Analyser/NodeScopeResolver.php | 116 ++++++++++++-- src/Node/InPropertyHookNode.php | 53 +++++++ .../PhpFunctionFromParserNodeReflection.php | 9 +- .../Php/PhpMethodFromParserNodeReflection.php | 74 +++++++-- .../PHPStan/Analyser/nsrt/property-hooks.php | 150 ++++++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 30 ++++ .../Rules/Variables/data/property-hooks.php | 54 +++++++ 8 files changed, 547 insertions(+), 24 deletions(-) create mode 100644 src/Node/InPropertyHookNode.php create mode 100644 tests/PHPStan/Analyser/nsrt/property-hooks.php create mode 100644 tests/PHPStan/Rules/Variables/data/property-hooks.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f883c955784..bfa07d782d3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -7,6 +7,7 @@ use Generator; use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\ComplexType; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\BinaryOp; @@ -21,6 +22,7 @@ use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; use PhpParser\Node\InterpolatedStringPart; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; @@ -2961,6 +2963,7 @@ public function enterClassMethod( new PhpMethodFromParserNodeReflection( $this->getClassReflection(), $classMethod, + null, $this->getFile(), $templateTypeMap, $this->getRealParameterTypes($classMethod), @@ -2986,6 +2989,88 @@ public function enterClassMethod( ); } + /** + * @param Type[] $phpDocParameterTypes + */ + public function enterPropertyHook( + Node\PropertyHook $hook, + string $propertyName, + Identifier|Name|ComplexType|null $nativePropertyTypeNode, + ?Type $phpDocPropertyType, + array $phpDocParameterTypes, + ?Type $throwType, + ?string $phpDocComment, + ): self + { + if (!$this->isInClass()) { + throw new ShouldNotHappenException(); + } + + $phpDocParameterTypes = array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes); + + $hookName = $hook->name->toLowerString(); + if ($hookName === 'set') { + if ($hook->params === []) { + $hook = clone $hook; + $hook->params = [ + new Node\Param(new Variable('value'), null, $nativePropertyTypeNode), + ]; + } + + $firstParam = $hook->params[0] ?? null; + if ( + $firstParam !== null + && $phpDocPropertyType !== null + && $firstParam->var instanceof Variable + && is_string($firstParam->var->name) + ) { + $valueParamPhpDocType = $phpDocParameterTypes[$firstParam->var->name] ?? null; + if ($valueParamPhpDocType === null) { + $phpDocParameterTypes[$firstParam->var->name] = $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocPropertyType)); + } + } + + $realReturnType = new VoidType(); + $phpDocReturnType = null; + } elseif ($hookName === 'get') { + $realReturnType = $this->getFunctionType($nativePropertyTypeNode, false, false); + $phpDocReturnType = $phpDocPropertyType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocPropertyType)) : null; + } else { + throw new ShouldNotHappenException(); + } + + $realParameterTypes = $this->getRealParameterTypes($hook); + + return $this->enterFunctionLike( + new PhpMethodFromParserNodeReflection( + $this->getClassReflection(), + $hook, + $propertyName, + $this->getFile(), + TemplateTypeMap::createEmpty(), + $realParameterTypes, + $phpDocParameterTypes, + [], + $realReturnType, + $phpDocReturnType, + $throwType, + null, + false, + false, + false, + false, + true, + Assertions::createEmpty(), + null, + $phpDocComment, + [], + [], + [], + ), + true, + ); + } + private function transformStaticType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c9a73a68e44..4dd117aab83 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -10,6 +10,7 @@ use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\AttributeGroup; +use PhpParser\Node\ComplexType; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; @@ -35,6 +36,7 @@ use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Ternary; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Break_; use PhpParser\Node\Stmt\Class_; @@ -98,6 +100,7 @@ use PHPStan\Node\InClosureNode; use PHPStan\Node\InForeachNode; use PHPStan\Node\InFunctionNode; +use PHPStan\Node\InPropertyHookNode; use PHPStan\Node\InstantiationCallableNode; use PHPStan\Node\InTraitNode; use PHPStan\Node\InvalidateExprNode; @@ -642,6 +645,8 @@ private function processStmtNode( throw new ShouldNotHappenException(); } + $classReflection = $scope->getClassReflection(); + $isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct'; if ($isFromTrait || $stmt->name->toLowerString() === '__construct') { foreach ($stmt->params as $param) { @@ -659,7 +664,7 @@ private function processStmtNode( $nodeCallback(new ClassPropertyNode( $param->var->name, $param->flags, - $param->type !== null ? ParserNodeTypeToPHPStanType::resolve($param->type, $scope->getClassReflection()) : null, + $param->type !== null ? ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection) : null, null, $phpDoc, $phpDocParameterTypes[$param->var->name] ?? null, @@ -668,10 +673,19 @@ private function processStmtNode( $param, false, $scope->isInTrait(), - $scope->getClassReflection()->isReadOnly(), + $classReflection->isReadOnly(), false, - $scope->getClassReflection(), + $classReflection, ), $methodScope); + $this->processPropertyHooks( + $stmt, + $param->type, + $phpDocParameterTypes[$param->var->name] ?? null, + $param->var->name, + $param->hooks, + $scope, + $nodeCallback, + ); $methodScope = $methodScope->assignExpression(new PropertyInitializationExpr($param->var->name), new MixedType(), new MixedType()); } } @@ -681,7 +695,7 @@ private function processStmtNode( if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { throw new ShouldNotHappenException(); } - $nodeCallback(new InClassMethodNode($scope->getClassReflection(), $methodReflection, $stmt), $methodScope); + $nodeCallback(new InClassMethodNode($classReflection, $methodReflection, $stmt), $methodScope); } if ($stmt->stmts !== null) { @@ -730,8 +744,6 @@ private function processStmtNode( $gatheredReturnStatements[] = new ReturnStatement($scope, $node); }, StatementContext::createTopLevel()); - $classReflection = $scope->getClassReflection(); - $methodReflection = $methodScope->getFunction(); if (!$methodReflection instanceof PhpMethodFromParserNodeReflection) { throw new ShouldNotHappenException(); @@ -893,29 +905,38 @@ private function processStmtNode( $impurePoints = []; $this->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $nodeCallback); + $nativePropertyType = $stmt->type !== null ? ParserNodeTypeToPHPStanType::resolve($stmt->type, $scope->getClassReflection()) : null; + + [,,,,,,,,,,,,$isReadOnly, $docComment, ,,,$varTags, $isAllowedPrivateMutation] = $this->getPhpDocs($scope, $stmt); + $phpDocType = null; + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } + foreach ($stmt->props as $prop) { $nodeCallback($prop, $scope); if ($prop->default !== null) { $this->processExprNode($stmt, $prop->default, $scope, $nodeCallback, ExpressionContext::createDeep()); } - [,,,,,,,,,,,,$isReadOnly, $docComment, ,,,$varTags, $isAllowedPrivateMutation] = $this->getPhpDocs($scope, $stmt); + if (!$scope->isInClass()) { throw new ShouldNotHappenException(); } $propertyName = $prop->name->toString(); - $phpDocType = null; - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } elseif (isset($varTags[$propertyName])) { - $phpDocType = $varTags[$propertyName]->getType(); + + if ($phpDocType === null) { + if (isset($varTags[$propertyName])) { + $phpDocType = $varTags[$propertyName]->getType(); + } } + $propStmt = clone $stmt; $propStmt->setAttributes($prop->getAttributes()); $nodeCallback( new ClassPropertyNode( $propertyName, $stmt->flags, - $stmt->type !== null ? ParserNodeTypeToPHPStanType::resolve($stmt->type, $scope->getClassReflection()) : null, + $nativePropertyType, $prop->default, $docComment, $phpDocType, @@ -932,6 +953,21 @@ private function processStmtNode( ); } + if (count($stmt->hooks) > 0) { + if (!isset($propertyName)) { + throw new ShouldNotHappenException('Property name should be known when analysing hooks.'); + } + $this->processPropertyHooks( + $stmt, + $stmt->type, + $phpDocType, + $propertyName, + $stmt->hooks, + $scope, + $nodeCallback, + ); + } + if ($stmt->type !== null) { $nodeCallback($stmt->type, $scope); } @@ -4614,6 +4650,60 @@ private function processAttributeGroups( } } + /** + * @param Node\PropertyHook[] $hooks + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function processPropertyHooks( + Node\Stmt $stmt, + Identifier|Name|ComplexType|null $nativeTypeNode, + ?Type $phpDocType, + string $propertyName, + array $hooks, + MutatingScope $scope, + callable $nodeCallback, + ): void + { + if (!$scope->isInClass()) { + throw new ShouldNotHappenException(); + } + + $classReflection = $scope->getClassReflection(); + + foreach ($hooks as $hook) { + $nodeCallback($hook, $scope); + $this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $nodeCallback); + + [, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->getPhpDocs($scope, $hook); + + foreach ($hook->params as $param) { + $this->processParamNode($stmt, $param, $scope, $nodeCallback); + } + + $hookScope = $scope->enterPropertyHook( + $hook, + $propertyName, + $nativeTypeNode, + $phpDocType, + $phpDocParameterTypes, + $phpDocThrowType, + $phpDocComment, + ); + $hookReflection = $hookScope->getFunction(); + if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) { + throw new ShouldNotHappenException(); + } + $nodeCallback(new InPropertyHookNode($classReflection, $hookReflection, $hook), $hookScope); + + if ($hook->body instanceof Expr) { + $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); + } elseif (is_array($hook->body)) { + $this->processStmtNodes($stmt, $hook->body, $hookScope, $nodeCallback, StatementContext::createTopLevel()); + } + + } + } + /** * @param MethodReflection|FunctionReflection|null $calleeReflection * @param callable(Node $node, Scope $scope): void $nodeCallback diff --git a/src/Node/InPropertyHookNode.php b/src/Node/InPropertyHookNode.php new file mode 100644 index 00000000000..0484c2568f8 --- /dev/null +++ b/src/Node/InPropertyHookNode.php @@ -0,0 +1,53 @@ +getAttributes()); + } + + public function getClassReflection(): ClassReflection + { + return $this->classReflection; + } + + public function getMethodReflection(): PhpMethodFromParserNodeReflection + { + return $this->hookReflection; + } + + public function getOriginalNode(): Node\PropertyHook + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Node_InPropertyHookNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 1d157476df5..809e32f8881 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -30,14 +30,14 @@ class PhpFunctionFromParserNodeReflection implements FunctionReflection, ExtendedParametersAcceptor { - /** @var Function_|ClassMethod */ + /** @var Function_|ClassMethod|Node\PropertyHook */ private Node\FunctionLike $functionLike; /** @var list|null */ private ?array $variants = null; /** - * @param Function_|ClassMethod $functionLike + * @param Function_|ClassMethod|Node\PropertyHook $functionLike * @param Type[] $realParameterTypes * @param Type[] $phpDocParameterTypes * @param Type[] $realParameterDefaultValues @@ -86,6 +86,11 @@ public function getName(): string return $this->functionLike->name->name; } + if (!$this->functionLike instanceof Function_) { + // PropertyHook is handled in PhpMethodFromParserNodeReflection subclass + throw new ShouldNotHappenException(); + } + if ($this->functionLike->namespacedName === null) { throw new ShouldNotHappenException(); } diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 8cd703c8b22..52ab304f983 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\Assertions; @@ -9,6 +10,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\MissingMethodFromReflectionException; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; @@ -21,6 +23,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\VoidType; use function in_array; +use function sprintf; use function strtolower; /** @@ -38,7 +41,8 @@ final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeR */ public function __construct( private ClassReflection $declaringClass, - private ClassMethod $classMethod, + private ClassMethod|Node\PropertyHook $classMethod, + private ?string $hookForProperty, string $fileName, TemplateTypeMap $templateTypeMap, array $realParameterTypes, @@ -61,6 +65,14 @@ public function __construct( array $phpDocClosureThisTypeParameters, ) { + if ($this->classMethod instanceof Node\PropertyHook) { + if ($this->hookForProperty === null) { + throw new ShouldNotHappenException('Hook was provided but property was not'); + } + } elseif ($this->hookForProperty !== null) { + throw new ShouldNotHappenException('Hooked property was provided but hook was not'); + } + $name = strtolower($classMethod->name->name); if (in_array($name, ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { $realReturnType = new VoidType(); @@ -131,36 +143,75 @@ public function getPrototype(): ClassMemberReflection } } - private function getClassMethod(): ClassMethod + private function getClassMethod(): ClassMethod|Node\PropertyHook { - /** @var Node\Stmt\ClassMethod $functionLike */ + /** @var Node\Stmt\ClassMethod|Node\PropertyHook $functionLike */ $functionLike = $this->getFunctionLike(); return $functionLike; } + public function getName(): string + { + $function = $this->getFunctionLike(); + if (!$function instanceof Node\PropertyHook) { + return parent::getName(); + } + + if ($this->hookForProperty === null) { + throw new ShouldNotHappenException('Hook was provided but property was not'); + } + + return sprintf('$%s::%s', $this->hookForProperty, $function->name->toString()); + } + public function isStatic(): bool { - return $this->getClassMethod()->isStatic(); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return false; + } + + return $method->isStatic(); } public function isPrivate(): bool { - return $this->getClassMethod()->isPrivate(); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return false; + } + + return $method->isPrivate(); } public function isPublic(): bool { - return $this->getClassMethod()->isPublic(); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return true; + } + + return $method->isPublic(); } public function isFinal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->classMethod->isFinal() || $this->isFinal); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return TrinaryLogic::createFromBoolean((bool) ($method->flags & Modifiers::FINAL)); + } + + return TrinaryLogic::createFromBoolean($method->isFinal() || $this->isFinal); } public function isFinalByKeyword(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->classMethod->isFinal()); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return TrinaryLogic::createFromBoolean((bool) ($method->flags & Modifiers::FINAL)); + } + + return TrinaryLogic::createFromBoolean($method->isFinal()); } public function isBuiltin(): bool @@ -180,7 +231,12 @@ public function returnsByReference(): TrinaryLogic public function isAbstract(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->getClassMethod()->isAbstract()); + $method = $this->getClassMethod(); + if ($method instanceof Node\PropertyHook) { + return TrinaryLogic::createFromBoolean($method->body === null); + } + + return TrinaryLogic::createFromBoolean($method->isAbstract()); } public function hasSideEffects(): TrinaryLogic diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php new file mode 100644 index 00000000000..b169e7e7a3b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -0,0 +1,150 @@ += 8.4 + +declare(strict_types=1); + +namespace PropertyHooksTypes; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + public int $i { + set { + assertType('int', $value); + } + } + + public int $j { + set (int $val) { + assertType('int', $val); + } + } + + public int $k { + set (int|string $val) { + assertType('int|string', $val); + } + } + + /** @var array */ + public array $l { + set { + assertType('array', $value); + } + } + + /** @var array */ + public array $m { + set (array $val) { + assertType('array', $val); + } + } + + public int $n { + /** @param int|array $val */ + set (int|array $val) { + assertType('array|int', $val); + } + } + +} + +class FooShort +{ + + public int $i { + set => assertType('int', $value); + } + + public int $j { + set (int $val) => assertType('int', $val); + } + + public int $k { + set (int|string $val) => assertType('int|string', $val); + } + + /** @var array */ + public array $l { + set => assertType('array', $value); + } + + /** @var array */ + public array $m { + set (array $val) => assertType('array', $val); + } + + public int $n { + /** @param int|array $val */ + set (int|array $val) => assertType('array|int', $val); + } + +} + +class FooConstructor +{ + + public function __construct( + public int $i { + set { + assertType('int', $value); + } + }, + public int $j { + set (int $val) { + assertType('int', $val); + } + }, + public int $k { + set (int|string $val) { + assertType('int|string', $val); + } + }, + /** @var array */ + public array $l { + set { + assertType('array', $value); + } + }, + /** @var array */ + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + public int $n { + /** @param int|array $val */ + set (int|array $val) { + assertType('array|int', $val); + } + }, + ) { + + } + +} + +class FooConstructorWithParam +{ + + /** + * @param array $l + * @param array $m + */ + public function __construct( + public array $l { + set { + assertType('array', $value); + } + }, + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + ) { + + } + +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 94f0b0ffebf..17aa83b245d 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1068,4 +1068,34 @@ public function testBug10228(): void $this->analyse([__DIR__ . '/data/bug-10228.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/property-hooks.php'], [ + [ + 'Undefined variable: $val', + 16, + ], + [ + 'Undefined variable: $value', + 28, + ], + [ + 'Undefined variable: $val', + 43, + ], + [ + 'Undefined variable: $value', + 51, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/property-hooks.php b/tests/PHPStan/Rules/Variables/data/property-hooks.php new file mode 100644 index 00000000000..1fc6f744b20 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/property-hooks.php @@ -0,0 +1,54 @@ += 8.4 + +namespace PropertyHooksVariables; + +class Foo +{ + + public int $i { + set { + $this->i = $value + 10; + } + } + + public int $iErr { + set { + $this->iErr = $val + 10; + } + } + + public int $j { + set (int $val) { + $this->j = $val + 10; + } + } + + public int $jErr { + set (int $val) { + $this->jErr = $value + 10; + } + } + +} + + +class FooShort +{ + + public int $i { + set => $value + 10; + } + + public int $iErr { + set => $val + 10; + } + + public int $j { + set (int $val) => $val + 10; + } + + public int $jErr { + set (int $val) => $value + 10; + } + +} From e8a3027f279b9bd1cd83e87bbf00f452222beda4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 13 Dec 2024 10:56:14 +0100 Subject: [PATCH 0912/3097] Fix PHPDocs with generics in property hooks --- conf/config.neon | 5 + src/Analyser/NodeScopeResolver.php | 6 + src/Parser/PropertyHookNameVisitor.php | 60 ++++++++ src/Parser/SimpleParser.php | 2 + src/Type/FileTypeMapper.php | 66 +++++++-- .../PHPStan/Analyser/nsrt/property-hooks.php | 136 ++++++++++++++++++ tests/PHPStan/Parser/CleaningParserTest.php | 1 + 7 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 src/Parser/PropertyHookNameVisitor.php diff --git a/conf/config.neon b/conf/config.neon index d77e8895316..1501a7a253e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -318,6 +318,11 @@ services: tags: - phpstan.parser.richParserNodeVisitor + - + class: PHPStan\Parser\PropertyHookNameVisitor + tags: + - phpstan.parser.richParserNodeVisitor + - class: PHPStan\Node\Printer\ExprPrinter diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 4dd117aab83..98de22733b3 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -123,6 +123,7 @@ use PHPStan\Parser\ClosureArgVisitor; use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\Parser; +use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; @@ -6243,6 +6244,11 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n } } elseif ($node instanceof Node\Stmt\Function_) { $functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + $functionName = sprintf('$%s::%s', $propertyName, $node->name->toString()); + } } if ($docComment !== null && $resolvedPhpDoc === null) { diff --git a/src/Parser/PropertyHookNameVisitor.php b/src/Parser/PropertyHookNameVisitor.php new file mode 100644 index 00000000000..5a49a709153 --- /dev/null +++ b/src/Parser/PropertyHookNameVisitor.php @@ -0,0 +1,60 @@ +hooks) === 0) { + return null; + } + + $propertyName = null; + foreach ($node->props as $prop) { + $propertyName = $prop->name->toString(); + break; + } + + if (!isset($propertyName)) { + return null; + } + + foreach ($node->hooks as $hook) { + $hook->setAttribute(self::ATTRIBUTE_NAME, $propertyName); + } + + return $node; + } + + if ($node instanceof Node\Param) { + if (count($node->hooks) === 0) { + return null; + } + if (!$node->var instanceof Node\Expr\Variable) { + return null; + } + if (!is_string($node->var->name)) { + return null; + } + + foreach ($node->hooks as $hook) { + $hook->setAttribute(self::ATTRIBUTE_NAME, $node->var->name); + } + + return $node; + } + + return null; + } + +} diff --git a/src/Parser/SimpleParser.php b/src/Parser/SimpleParser.php index 8fbd1127420..71bab19964f 100644 --- a/src/Parser/SimpleParser.php +++ b/src/Parser/SimpleParser.php @@ -17,6 +17,7 @@ public function __construct( private NameResolver $nameResolver, private VariadicMethodsVisitor $variadicMethodsVisitor, private VariadicFunctionsVisitor $variadicFunctionsVisitor, + private PropertyHookNameVisitor $propertyHookNameVisitor, ) { } @@ -52,6 +53,7 @@ public function parseString(string $sourceCode): array $nodeTraverser->addVisitor($this->nameResolver); $nodeTraverser->addVisitor($this->variadicMethodsVisitor); $nodeTraverser->addVisitor($this->variadicFunctionsVisitor); + $nodeTraverser->addVisitor($this->propertyHookNameVisitor); /** @var array */ return $nodeTraverser->traverse($nodes); diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 9a14e3aec4b..3cc5e6c62ed 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -9,6 +9,7 @@ use PHPStan\Broker\AnonymousClassNameHelper; use PHPStan\File\FileHelper; use PHPStan\Parser\Parser; +use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; @@ -279,6 +280,11 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA } } elseif ($node instanceof Node\Stmt\Function_) { $functionStack[] = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + $functionStack[] = sprintf('$%s::%s', $propertyName, $node->name->toString()); + } } $className = $classStack[count($classStack) - 1] ?? null; @@ -291,6 +297,17 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA $phpDocNodeMap[$nameScopeKey] = $this->phpDocStringResolver->resolve($docComment); } + return null; + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + $docComment = GetLastDocComment::forNode($node); + if ($docComment !== null) { + $nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName); + $phpDocNodeMap[$nameScopeKey] = $this->phpDocStringResolver->resolve($docComment); + } + } + return null; } @@ -376,6 +393,15 @@ static function (Node $node) use (&$namespace, &$functionStack, &$classStack): v } array_pop($functionStack); + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + if (count($functionStack) === 0) { + throw new ShouldNotHappenException(); + } + + array_pop($functionStack); + } } }, ); @@ -476,6 +502,11 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } } elseif ($node instanceof Node\Stmt\Function_) { $functionStack[] = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + $functionStack[] = sprintf('$%s::%s', $propertyName, $node->name->toString()); + } } $className = $classStack[count($classStack) - 1] ?? null; @@ -483,6 +514,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName); if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { + // property hook skipped on purpose, it does not support @template if (array_key_exists($nameScopeKey, $phpDocNodeMap)) { $phpDocNode = $phpDocNodeMap[$nameScopeKey]; $typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocNode, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap { @@ -512,16 +544,20 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; if ( - $node instanceof Node\Stmt - && !$node instanceof Node\Stmt\Namespace_ - && !$node instanceof Node\Stmt\Declare_ - && !$node instanceof Node\Stmt\Use_ - && !$node instanceof Node\Stmt\GroupUse - && !$node instanceof Node\Stmt\TraitUse - && !$node instanceof Node\Stmt\TraitUseAdaptation - && !$node instanceof Node\Stmt\InlineHTML - && !($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Include_) - && !array_key_exists($nameScopeKey, $nameScopeMap) + ( + $node instanceof Node\PropertyHook + || ( + $node instanceof Node\Stmt + && !$node instanceof Node\Stmt\Namespace_ + && !$node instanceof Node\Stmt\Declare_ + && !$node instanceof Node\Stmt\Use_ + && !$node instanceof Node\Stmt\GroupUse + && !$node instanceof Node\Stmt\TraitUse + && !$node instanceof Node\Stmt\TraitUseAdaptation + && !$node instanceof Node\Stmt\InlineHTML + && !($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Include_) + ) + ) && !array_key_exists($nameScopeKey, $nameScopeMap) ) { $nameScopeMap[$nameScopeKey] = static fn (): NameScope => new NameScope( $namespace, @@ -537,6 +573,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { + // property hook skipped on purpose, it does not support @template if (array_key_exists($nameScopeKey, $phpDocNodeMap)) { return self::POP_TYPE_MAP_STACK; } @@ -704,6 +741,15 @@ static function (Node $node, $callbackResult) use (&$namespace, &$functionStack, } array_pop($functionStack); + } elseif ($node instanceof Node\PropertyHook) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + if (count($functionStack) === 0) { + throw new ShouldNotHappenException(); + } + + array_pop($functionStack); + } } if ($callbackResult !== self::POP_TYPE_MAP_STACK) { return; diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php index b169e7e7a3b..0e49e0e981c 100644 --- a/tests/PHPStan/Analyser/nsrt/property-hooks.php +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -148,3 +148,139 @@ public function __construct( } } + +/** + * @template T of \stdClass + */ +class FooGenerics +{ + + /** @var array */ + public array $m { + set (array $val) { + assertType('array', $val); + } + } + + public int $n { + /** @param int|array $val */ + set (int|array $val) { + assertType('array|int', $val); + } + } + +} + +/** + * @template T of \stdClass + */ +class FooGenericsConstructor +{ + + public function __construct( + /** @var array */ + public array $l { + set { + assertType('array', $value); + } + }, + /** @var array */ + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + public int $n { + /** @param int|array $val */ + set (int|array $val) { + assertType('array|int', $val); + } + }, + ) { + + } + +} + +/** + * @template T of \stdClass + */ +class FooGenericsConstructor2 +{ + + /** + * @param array $l + * @param array $m + */ + public function __construct( + public array $l { + set { + assertType('array', $value); + } + }, + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + public int $n { + /** @param int|array $val */ + set (int|array $val) { + assertType('array|int', $val); + } + }, + ) { + + } + +} + +class FooGenericsConstructorWithT +{ + + /** + * @template T of \stdClass + */ + public function __construct( + /** @var array */ + public array $l { + set { + assertType('array', $value); + } + }, + /** @var array */ + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + ) { + + } + +} + +class FooGenericsConstructorWithT2 +{ + + /** + * @template T of \stdClass + * @param array $l + * @param array $m + */ + public function __construct( + public array $l { + set { + assertType('array', $value); + } + }, + public array $m { + set (array $val) { + assertType('array', $val); + } + }, + ) { + + } + +} diff --git a/tests/PHPStan/Parser/CleaningParserTest.php b/tests/PHPStan/Parser/CleaningParserTest.php index 835486fdc2c..e0afccabb76 100644 --- a/tests/PHPStan/Parser/CleaningParserTest.php +++ b/tests/PHPStan/Parser/CleaningParserTest.php @@ -70,6 +70,7 @@ public function testParse( new NameResolver(), new VariadicMethodsVisitor(), new VariadicFunctionsVisitor(), + new PropertyHookNameVisitor(), ), new PhpVersion($phpVersionId), ); From d8382d5536a5eddd63959661010d3af4cc1cfec3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Dec 2024 13:52:03 +0100 Subject: [PATCH 0913/3097] Adjust method's ReturnTypeRule for property hooks --- .../Php/PhpMethodFromParserNodeReflection.php | 32 +++++++ src/Rules/Methods/ReturnTypeRule.php | 32 ++++--- .../Rules/Methods/ReturnTypeRuleTest.php | 37 +++++++++ .../Methods/data/property-hooks-return.php | 83 +++++++++++++++++++ 4 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/property-hooks-return.php diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 52ab304f983..af7d9a80f40 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -164,6 +164,38 @@ public function getName(): string return sprintf('$%s::%s', $this->hookForProperty, $function->name->toString()); } + /** + * @phpstan-assert-if-true !null $this->getHookedPropertyName() + * @phpstan-assert-if-true !null $this->getPropertyHookName() + */ + public function isPropertyHook(): bool + { + return $this->hookForProperty !== null; + } + + public function getHookedPropertyName(): ?string + { + return $this->hookForProperty; + } + + /** + * @return 'get'|'set'|null + */ + public function getPropertyHookName(): ?string + { + $function = $this->getFunctionLike(); + if (!$function instanceof Node\PropertyHook) { + return null; + } + + $name = $function->name->toLowerString(); + if (!in_array($name, ['get', 'set'], true)) { + throw new ShouldNotHappenException(sprintf('Unknown property hook: %s', $name)); + } + + return $name; + } + public function isStatic(): bool { $method = $this->getClassMethod(); diff --git a/src/Rules/Methods/ReturnTypeRule.php b/src/Rules/Methods/ReturnTypeRule.php index 9f851ce9c6e..58abdef6a63 100644 --- a/src/Rules/Methods/ReturnTypeRule.php +++ b/src/Rules/Methods/ReturnTypeRule.php @@ -21,6 +21,7 @@ use function count; use function sprintf; use function strtolower; +use function ucfirst; /** * @implements Rule @@ -52,6 +53,17 @@ public function processNode(Node $node, Scope $scope): array return []; } + if ($method->isPropertyHook()) { + $methodDescription = sprintf( + '%s hook for property %s::$%s', + ucfirst($method->getPropertyHookName()), + $method->getDeclaringClass()->getDisplayName(), + $method->getHookedPropertyName(), + ); + } else { + $methodDescription = sprintf('Method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()); + } + $returnType = $method->getReturnType(); $errors = $this->returnTypeCheck->checkReturnType( $scope, @@ -59,24 +71,20 @@ public function processNode(Node $node, Scope $scope): array $node->expr, $node, sprintf( - 'Method %s::%s() should return %%s but empty return statement found.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), + '%s should return %%s but empty return statement found.', + $methodDescription, ), sprintf( - 'Method %s::%s() with return type void returns %%s but should not return anything.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), + '%s with return type void returns %%s but should not return anything.', + $methodDescription, ), sprintf( - 'Method %s::%s() should return %%s but returns %%s.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), + '%s should return %%s but returns %%s.', + $methodDescription, ), sprintf( - 'Method %s::%s() should never return but return statement found.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), + '%s should never return but return statement found.', + $methodDescription, ), $method->isGenerator(), ); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 1fd11fe100e..c5243821742 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1101,4 +1101,41 @@ public function testBug12223(): void $this->analyse([__DIR__ . '/data/bug-12223.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + self::markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/property-hooks-return.php'], [ + [ + 'Get hook for property PropertyHooksReturn\Foo::$i should return int but returns string.', + 11, + ], + [ + 'Set hook for property PropertyHooksReturn\Foo::$i with return type void returns int but should not return anything.', + 21, + ], + [ + 'Get hook for property PropertyHooksReturn\Foo::$s should return non-empty-string but returns \'\'.', + 29, + ], + [ + 'Get hook for property PropertyHooksReturn\GenericFoo::$a should return T of PropertyHooksReturn\Foo but returns PropertyHooksReturn\Foo.', + 48, + 'Type PropertyHooksReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property PropertyHooksReturn\GenericFoo::$b should return T of PropertyHooksReturn\Foo but returns PropertyHooksReturn\Foo.', + 63, + 'Type PropertyHooksReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property PropertyHooksReturn\GenericFoo::$c should return T of PropertyHooksReturn\Foo but returns PropertyHooksReturn\Foo.', + 73, + 'Type PropertyHooksReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/property-hooks-return.php b/tests/PHPStan/Rules/Methods/data/property-hooks-return.php new file mode 100644 index 00000000000..206298551ec --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/property-hooks-return.php @@ -0,0 +1,83 @@ += 8.4 + +namespace PropertyHooksReturn; + +class Foo +{ + + public int $i { + get { + if (rand(0, 1)) { + return 'foo'; + } + + return 1; + } + set { + if (rand(0, 1)) { + return; + } + + return 1; + } + } + + /** @var non-empty-string */ + public string $s { + get { + if (rand(0, 1)) { + return ''; + } + + return 'foo'; + } + } + +} + +/** + * @template T of Foo + */ +class GenericFoo +{ + + /** @var T */ + public Foo $a { + get { + if (rand(0, 1)) { + return new Foo(); + } + + return $this->a; + } + } + + /** + * @param T $c + */ + public function __construct( + /** @var T */ + public Foo $b { + get { + if (rand(0, 1)) { + return new Foo(); + } + + return $this->b; + } + }, + + public Foo $c { + get { + if (rand(0, 1)) { + return new Foo(); + } + + return $this->c; + } + } + ) + { + } + +} From c55186078514689c392e50f1253879146934010e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Dec 2024 21:45:03 +0100 Subject: [PATCH 0914/3097] ShortGetPropertyHookReturnTypeRule - level 3 --- conf/config.level3.neon | 1 + src/Node/InPropertyHookNode.php | 2 +- .../ShortGetPropertyHookReturnTypeRule.php | 74 +++++++++++++++++++ ...ShortGetPropertyHookReturnTypeRuleTest.php | 56 ++++++++++++++ .../data/short-get-property-hook-return.php | 69 +++++++++++++++++ 5 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php create mode 100644 tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 250d545f153..31e065bf6f2 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -20,6 +20,7 @@ rules: - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule + - PHPStan\Rules\Properties\ShortGetPropertyHookReturnTypeRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule - PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule diff --git a/src/Node/InPropertyHookNode.php b/src/Node/InPropertyHookNode.php index 0484c2568f8..b27899949d8 100644 --- a/src/Node/InPropertyHookNode.php +++ b/src/Node/InPropertyHookNode.php @@ -27,7 +27,7 @@ public function getClassReflection(): ClassReflection return $this->classReflection; } - public function getMethodReflection(): PhpMethodFromParserNodeReflection + public function getHookReflection(): PhpMethodFromParserNodeReflection { return $this->hookReflection; } diff --git a/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php b/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php new file mode 100644 index 00000000000..1651ddfdcda --- /dev/null +++ b/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php @@ -0,0 +1,74 @@ + + */ +final class ShortGetPropertyHookReturnTypeRule implements Rule +{ + + public function __construct(private FunctionReturnTypeCheck $returnTypeCheck) + { + } + + public function getNodeType(): string + { + return InPropertyHookNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + // return statements in long property hook bodies are checked by Methods\ReturnTypeRule + // short set property hook type is checked by TypesAssignedToPropertiesRule + $hookReflection = $node->getHookReflection(); + if ($hookReflection->getPropertyHookName() !== 'get') { + return []; + } + + $originalHookNode = $node->getOriginalNode(); + $hookBody = $originalHookNode->body; + if (!$hookBody instanceof Node\Expr) { + return []; + } + + $methodDescription = sprintf( + 'Get hook for property %s::$%s', + $hookReflection->getDeclaringClass()->getDisplayName(), + $hookReflection->getHookedPropertyName(), + ); + + $returnType = $hookReflection->getReturnType(); + + return $this->returnTypeCheck->checkReturnType( + $scope, + $returnType, + $hookBody, + $node, + sprintf( + '%s should return %%s but empty return statement found.', + $methodDescription, + ), + sprintf( + '%s with return type void returns %%s but should not return anything.', + $methodDescription, + ), + sprintf( + '%s should return %%s but returns %%s.', + $methodDescription, + ), + sprintf( + '%s should never return but return statement found.', + $methodDescription, + ), + $hookReflection->isGenerator(), + ); + } + +} diff --git a/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php b/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php new file mode 100644 index 00000000000..0f4190b4337 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php @@ -0,0 +1,56 @@ + + */ +final class ShortGetPropertyHookReturnTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ShortGetPropertyHookReturnTypeRule( + new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false)) + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/short-get-property-hook-return.php'], [ + [ + 'Get hook for property ShortGetPropertyHookReturn\Foo::$i should return int but returns string.', + 9, + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\Foo::$s should return non-empty-string but returns \'\'.', + 18, + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$a should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 36, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$b should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 50, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$c should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 59, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php b/tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php new file mode 100644 index 00000000000..9271b944bcb --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php @@ -0,0 +1,69 @@ += 8.4 + +namespace ShortGetPropertyHookReturn; + +class Foo +{ + + public int $i { + get => 'foo'; + } + + public int $i2 { + get => 1; + } + + /** @var non-empty-string */ + public string $s { + get => ''; + } + + /** @var non-empty-string */ + public string $s2 { + get => 'foo'; + } + +} + +/** + * @template T of Foo + */ +class GenericFoo +{ + + /** @var T */ + public Foo $a { + get => new Foo(); + } + + /** @var T */ + public Foo $a2 { + get => $this->a2; + } + + /** + * @param T $c + */ + public function __construct( + /** @var T */ + public Foo $b { + get => new Foo(); + }, + + /** @var T */ + public Foo $b2 { + get => $this->b2; + }, + + public Foo $c { + get => new Foo(); + }, + + public Foo $c2 { + get => $this->c2; + } + ) + { + } + +} From 181491395baaeb94d23160a06e411421937680f1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 14 Dec 2024 22:02:41 +0100 Subject: [PATCH 0915/3097] Invoke PropertyAssignNode in short set property hook --- src/Analyser/NodeScopeResolver.php | 1 + .../TypesAssignedToPropertiesRuleTest.php | 34 +++++++++ .../data/short-set-property-hook-assign.php | 69 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/short-set-property-hook-assign.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 98de22733b3..0056af9f3fe 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4698,6 +4698,7 @@ private function processPropertyHooks( if ($hook->body instanceof Expr) { $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); + $nodeCallback(new PropertyAssignNode(new PropertyFetch(new Variable('this'), $propertyName, $hook->body->getAttributes()), $hook->body, false), $hookScope); } elseif (is_array($hook->body)) { $this->processStmtNodes($stmt, $hook->body, $hookScope, $nodeCallback, StatementContext::createTopLevel()); } diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index dede3e92113..061a8af5106 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -688,4 +688,38 @@ public function testBug12131(): void ]); } + public function testShortBodySetHook(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/short-set-property-hook-assign.php'], [ + [ + 'Property ShortSetPropertyHookAssign\Foo::$i (int) does not accept string.', + 9, + ], + [ + 'Property ShortSetPropertyHookAssign\Foo::$s (non-empty-string) does not accept \'\'.', + 18, + ], + [ + 'Property ShortSetPropertyHookAssign\GenericFoo::$a (T of ShortSetPropertyHookAssign\Foo) does not accept ShortSetPropertyHookAssign\Foo.', + 36, + 'Type ShortSetPropertyHookAssign\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Property ShortSetPropertyHookAssign\GenericFoo::$b (T of ShortSetPropertyHookAssign\Foo) does not accept ShortSetPropertyHookAssign\Foo.', + 50, + 'Type ShortSetPropertyHookAssign\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Property ShortSetPropertyHookAssign\GenericFoo::$c (T of ShortSetPropertyHookAssign\Foo) does not accept ShortSetPropertyHookAssign\Foo.', + 59, + 'Type ShortSetPropertyHookAssign\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/short-set-property-hook-assign.php b/tests/PHPStan/Rules/Properties/data/short-set-property-hook-assign.php new file mode 100644 index 00000000000..0e305bf1046 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/short-set-property-hook-assign.php @@ -0,0 +1,69 @@ += 8.4 + +namespace ShortSetPropertyHookAssign; + +class Foo +{ + + public int $i { + set => 'foo'; + } + + public int $i2 { + set => 1; + } + + /** @var non-empty-string */ + public string $s { + set => ''; + } + + /** @var non-empty-string */ + public string $s2 { + set => 'foo'; + } + +} + +/** + * @template T of Foo + */ +class GenericFoo +{ + + /** @var T */ + public Foo $a { + set => new Foo(); + } + + /** @var T */ + public Foo $a2 { + set => $this->a2; + } + + /** + * @param T $c + */ + public function __construct( + /** @var T */ + public Foo $b { + set => new Foo(); + }, + + /** @var T */ + public Foo $b2 { + set => $this->b2; + }, + + public Foo $c { + set => new Foo(); + }, + + public Foo $c2 { + set => $this->c2; + } + ) + { + } + +} From 0d5006a88211d6f9652e1c62d18cb38a744e32ce Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Dec 2024 15:23:22 +0100 Subject: [PATCH 0916/3097] Set checkUninitializedProperties to false in UnusedPrivatePropertyRuleTest --- .../Rules/DeadCode/UnusedPrivatePropertyRuleTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 76504181413..0a5bb7eff34 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -22,6 +22,8 @@ class UnusedPrivatePropertyRuleTest extends RuleTestCase /** @var string[] */ private array $alwaysReadTags; + private bool $checkUninitializedProperties = false; + protected function getRule(): Rule { return new UnusedPrivatePropertyRule( @@ -55,7 +57,7 @@ public function isInitialized(PropertyReflection $property, string $propertyName ]), $this->alwaysWrittenTags, $this->alwaysReadTags, - true, + $this->checkUninitializedProperties, ); } @@ -63,6 +65,7 @@ public function testRule(): void { $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; + $this->checkUninitializedProperties = true; $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; @@ -236,6 +239,7 @@ public function testBug5337(): void { $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; + $this->checkUninitializedProperties = true; $this->analyse([__DIR__ . '/data/bug-5337.php'], []); } From 1b41ebfb970e34786b1e22a9da0615585d5d632a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Dec 2024 14:52:32 +0100 Subject: [PATCH 0917/3097] Fix UnusedPrivatePropertyRule in regard to property hooks --- .../DeadCode/UnusedPrivatePropertyRule.php | 33 +++++- .../UnusedPrivatePropertyRuleTest.php | 40 +++++++ .../data/property-hooks-unused-property.php | 112 ++++++++++++++++++ 3 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/property-hooks-unused-property.php diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index b41185b5c96..137bd8f00e3 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\ClassPropertiesNode; use PHPStan\Node\Property\PropertyRead; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -14,6 +15,7 @@ use function array_key_exists; use function array_map; use function count; +use function is_string; use function sprintf; use function str_contains; @@ -113,11 +115,29 @@ public function processNode(Node $node, Scope $scope): array } foreach ($node->getPropertyUsages() as $usage) { + $usageScope = $usage->getScope(); $fetch = $usage->getFetch(); if ($fetch->name instanceof Node\Identifier) { - $propertyNames = [$fetch->name->toString()]; + $propertyName = $fetch->name->toString(); + $propertyNames = [$propertyName]; + if ( + $usageScope->getFunction() !== null + && $fetch instanceof Node\Expr\PropertyFetch + && $fetch->var instanceof Node\Expr\Variable + && is_string($fetch->var->name) + && $fetch->var->name === 'this' + ) { + $methodReflection = $usageScope->getFunction(); + if ( + $methodReflection instanceof PhpMethodFromParserNodeReflection + && $methodReflection->isPropertyHook() + && $methodReflection->getHookedPropertyName() === $propertyName + ) { + continue; + } + } } else { - $propertyNameType = $usage->getScope()->getType($fetch->name); + $propertyNameType = $usageScope->getType($fetch->name); $strings = $propertyNameType->getConstantStrings(); if (count($strings) === 0) { // handle subtractions of a dynamic property fetch @@ -134,13 +154,14 @@ public function processNode(Node $node, Scope $scope): array $propertyNames = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $strings); } + if ($fetch instanceof Node\Expr\PropertyFetch) { - $fetchedOnType = $usage->getScope()->getType($fetch->var); + $fetchedOnType = $usageScope->getType($fetch->var); } else { if ($fetch->class instanceof Node\Name) { - $fetchedOnType = $usage->getScope()->resolveTypeByName($fetch->class); + $fetchedOnType = $usageScope->resolveTypeByName($fetch->class); } else { - $fetchedOnType = $usage->getScope()->getType($fetch->class); + $fetchedOnType = $usageScope->getType($fetch->class); } } @@ -148,7 +169,7 @@ public function processNode(Node $node, Scope $scope): array if (!array_key_exists($propertyName, $properties)) { continue; } - $propertyReflection = $usage->getScope()->getPropertyReflection($fetchedOnType, $propertyName); + $propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName); if ($propertyReflection === null) { if (!$classType->isSuperTypeOf($fetchedOnType)->no()) { if ($usage instanceof PropertyRead) { diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 0a5bb7eff34..c56a986f1c0 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -347,4 +347,44 @@ public function testBug11802(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + + $this->analyse([__DIR__ . '/data/property-hooks-unused-property.php'], [ + [ + 'Property PropertyHooksUnusedProperty\FooUnused::$a is unused.', + 32, + $tip, + ], + [ + 'Property PropertyHooksUnusedProperty\FooOnlyRead::$a is never written, only read.', + 46, + $tip, + ], + [ + 'Property PropertyHooksUnusedProperty\FooOnlyWritten::$a is never read, only written.', + 65, + $tip, + ], + [ + 'Property PropertyHooksUnusedProperty\ReadInAnotherPropertyHook2::$bar is never written, only read.', + 95, + $tip, + ], + [ + 'Property PropertyHooksUnusedProperty\WrittenInAnotherPropertyHook::$bar is never read, only written.', + 105, + $tip, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/property-hooks-unused-property.php b/tests/PHPStan/Rules/DeadCode/data/property-hooks-unused-property.php new file mode 100644 index 00000000000..6b856eb1329 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/property-hooks-unused-property.php @@ -0,0 +1,112 @@ += 8.4 + +namespace PropertyHooksUnusedProperty; + +class FooUsed +{ + + private int $a { + get { + return $this->a + 100; + } + set { + $this->a = $value - 100; + } + } + + public function setA(int $a): void + { + $this->a = $a; + } + + public function getA(): int + { + return $this->a; + } + +} + +class FooUnused +{ + + private int $a { + get { + return $this->a + 100; + } + set { + $this->a = $value - 100; + } + } + +} + +class FooOnlyRead +{ + + private int $a { + get { + return $this->a + 100; + } + set { + $this->a = $value - 100; + } + } + + public function getA(): int + { + return $this->a; + } + +} + +class FooOnlyWritten +{ + + private int $a { + get { + return $this->a + 100; + } + set { + $this->a = $value - 100; + } + } + + public function setA(int $a): void + { + $this->a = $a; + } + +} + +class ReadInAnotherPropertyHook +{ + public function __construct( + private readonly string $bar, + ) {} + + public string $virtualProperty { + get => $this->bar; + } +} + +class ReadInAnotherPropertyHook2 +{ + + private string $bar; + + public string $virtualProperty { + get => $this->bar; + } +} + +class WrittenInAnotherPropertyHook +{ + + private string $bar; + + public string $virtualProperty { + set { + $this->bar = 'test'; + } + } +} From c91df5c9582b1657e2eaa34c1e658af29aa93245 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Dec 2024 15:51:05 +0100 Subject: [PATCH 0918/3097] Fix MissingReturnRule for property hooks --- src/Analyser/NodeScopeResolver.php | 14 ++--- src/Node/PropertyHookStatementNode.php | 52 +++++++++++++++++++ src/Rules/Missing/MissingReturnRule.php | 11 ++-- .../Rules/Missing/MissingReturnRuleTest.php | 19 +++++++ .../data/property-hooks-missing-return.php | 34 ++++++++++++ 5 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 src/Node/PropertyHookStatementNode.php create mode 100644 tests/PHPStan/Rules/Missing/data/property-hooks-missing-return.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0056af9f3fe..7ad28508acc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -114,6 +114,7 @@ use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Node\NoopExpressionNode; use PHPStan\Node\PropertyAssignNode; +use PHPStan\Node\PropertyHookStatementNode; use PHPStan\Node\ReturnStatement; use PHPStan\Node\StaticMethodCallableNode; use PHPStan\Node\UnreachableStatementNode; @@ -343,6 +344,7 @@ public function processStmtNodes( $stmtCount = count($stmts); $shouldCheckLastStatement = $parentNode instanceof Node\Stmt\Function_ || $parentNode instanceof Node\Stmt\ClassMethod + || $parentNode instanceof PropertyHookStatementNode || $parentNode instanceof Expr\Closure; foreach ($stmts as $i => $stmt) { if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) { @@ -360,7 +362,7 @@ public function processStmtNodes( $hasYield = $hasYield || $statementResult->hasYield(); if ($shouldCheckLastStatement && $isLast) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ + /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|PropertyHookStatementNode|Expr\Closure $parentNode */ $parentNode = $parentNode; $endStatements = $statementResult->getEndStatements(); @@ -377,7 +379,7 @@ public function processStmtNodes( $endStatementResult->getThrowPoints(), $endStatementResult->getImpurePoints(), ), - $parentNode->returnType !== null, + $parentNode->getReturnType() !== null, ), $endStatementResult->getScope()); } } else { @@ -391,7 +393,7 @@ public function processStmtNodes( $statementResult->getThrowPoints(), $statementResult->getImpurePoints(), ), - $parentNode->returnType !== null, + $parentNode->getReturnType() !== null, ), $scope); } } @@ -414,9 +416,9 @@ public function processStmtNodes( $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); if ($stmtCount === 0 && $shouldCheckLastStatement) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|Expr\Closure $parentNode */ + /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|PropertyHookStatementNode|Expr\Closure $parentNode */ $parentNode = $parentNode; - $returnTypeNode = $parentNode->returnType; + $returnTypeNode = $parentNode->getReturnType(); if ($parentNode instanceof Expr\Closure) { $parentNode = new Node\Stmt\Expression($parentNode, $parentNode->getAttributes()); } @@ -4700,7 +4702,7 @@ private function processPropertyHooks( $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); $nodeCallback(new PropertyAssignNode(new PropertyFetch(new Variable('this'), $propertyName, $hook->body->getAttributes()), $hook->body, false), $hookScope); } elseif (is_array($hook->body)) { - $this->processStmtNodes($stmt, $hook->body, $hookScope, $nodeCallback, StatementContext::createTopLevel()); + $this->processStmtNodes(new PropertyHookStatementNode($hook), $hook->body, $hookScope, $nodeCallback, StatementContext::createTopLevel()); } } diff --git a/src/Node/PropertyHookStatementNode.php b/src/Node/PropertyHookStatementNode.php new file mode 100644 index 00000000000..301d8359e4a --- /dev/null +++ b/src/Node/PropertyHookStatementNode.php @@ -0,0 +1,52 @@ +propertyHook->getAttributes()); + } + + public function getPropertyHook(): PropertyHook + { + return $this->propertyHook; + } + + /** + * @return null + */ + public function getReturnType() + { + return null; + } + + public function getType(): string + { + return 'PHPStan_Node_PropertyHookStatementNode'; + } + + public function getSubNodeNames(): array + { + return []; + } + + +} diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 9a3e64e3ac8..ef53fa16c84 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -6,7 +6,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\ExecutionEndNode; -use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -19,6 +19,7 @@ use PHPStan\Type\VerbosityLevel; use PHPStan\Type\VoidType; use function sprintf; +use function ucfirst; /** * @implements Rule @@ -55,8 +56,12 @@ public function processNode(Node $node, Scope $scope): array } } elseif ($scopeFunction !== null) { $returnType = $scopeFunction->getReturnType(); - if ($scopeFunction instanceof MethodReflection) { - $description = sprintf('Method %s::%s()', $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getName()); + if ($scopeFunction instanceof PhpMethodFromParserNodeReflection) { + if (!$scopeFunction->isPropertyHook()) { + $description = sprintf('Method %s::%s()', $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getName()); + } else { + $description = sprintf('%s hook for property %s::$%s', ucfirst($scopeFunction->getPropertyHookName()), $scopeFunction->getDeclaringClass()->getDisplayName(), $scopeFunction->getHookedPropertyName()); + } } else { $description = sprintf('Function %s()', $scopeFunction->getName()); } diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index 79eb736c16b..9c170899f24 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -351,4 +351,23 @@ public function testBug9374(): void $this->analyse([__DIR__ . '/data/bug-9374.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/property-hooks-missing-return.php'], [ + [ + 'Get hook for property PropertyHooksMissingReturn\Foo::$i should return int but return statement is missing.', + 10, + ], + [ + 'Get hook for property PropertyHooksMissingReturn\Foo::$j should return int but return statement is missing.', + 23, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Missing/data/property-hooks-missing-return.php b/tests/PHPStan/Rules/Missing/data/property-hooks-missing-return.php new file mode 100644 index 00000000000..eff880e46c4 --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/property-hooks-missing-return.php @@ -0,0 +1,34 @@ += 8.4 + +namespace PropertyHooksMissingReturn; + +class Foo +{ + + public int $i { + get { + if (rand(0, 1)) { + + } else { + return 1; + } + } + + set { + // set hook returns void + } + } + + public int $j { + get { + + } + } + + public int $ok { + get { + return $this->ok + 1; + } + } + +} From 2e7cb7d09b89ca391b70a21f31e965429fc98317 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Dec 2024 16:01:05 +0100 Subject: [PATCH 0919/3097] ExtendedPropertyReflection - methods describing hooked properties --- src/Node/PropertyHookStatementNode.php | 1 - .../AnnotationPropertyReflection.php | 27 ++ .../Dummy/ChangedTypePropertyReflection.php | 26 ++ .../Dummy/DummyPropertyReflection.php | 27 ++ src/Reflection/ExtendedPropertyReflection.php | 22 ++ src/Reflection/Php/EnumPropertyReflection.php | 27 ++ .../Php/PhpClassReflectionExtension.php | 63 +++- src/Reflection/Php/PhpMethodReflection.php | 59 +++ src/Reflection/Php/PhpPropertyReflection.php | 46 +++ .../Php/SimpleXMLElementProperty.php | 27 ++ .../Php/UniversalObjectCrateProperty.php | 27 ++ src/Reflection/ResolvedPropertyReflection.php | 29 ++ .../IntersectionTypePropertyReflection.php | 69 +++- .../Type/UnionTypePropertyReflection.php | 69 +++- .../WrappedExtendedPropertyReflection.php | 26 ++ .../Properties/FoundPropertyReflection.php | 26 ++ .../ShortGetPropertyHookReturnTypeRule.php | 1 + src/Type/ObjectShapePropertyReflection.php | 27 ++ .../PHPStan/Analyser/nsrt/property-hooks.php | 27 ++ .../Reflection/ClassReflectionTest.php | 340 ++++++++++++++++++ ...ShortGetPropertyHookReturnTypeRuleTest.php | 3 +- 21 files changed, 940 insertions(+), 29 deletions(-) diff --git a/src/Node/PropertyHookStatementNode.php b/src/Node/PropertyHookStatementNode.php index 301d8359e4a..34bdbcfd319 100644 --- a/src/Node/PropertyHookStatementNode.php +++ b/src/Node/PropertyHookStatementNode.php @@ -48,5 +48,4 @@ public function getSubNodeNames(): array return []; } - } diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 78b822ca3af..e6747a153cf 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -3,7 +3,9 @@ namespace PHPStan\Reflection\Annotations; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -85,4 +87,29 @@ public function getDocComment(): ?string return null; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index c4715616e74..cc431c7a5b6 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Dummy; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\WrapperPropertyReflection; use PHPStan\TrinaryLogic; @@ -85,4 +86,29 @@ public function getOriginalReflection(): ExtendedPropertyReflection return $this->reflection; } + public function isAbstract(): TrinaryLogic + { + return $this->reflection->isAbstract(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isVirtual(): TrinaryLogic + { + return $this->reflection->isVirtual(); + } + + public function hasHook(string $hookType): bool + { + return $this->reflection->hasHook($hookType); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + return $this->reflection->getHook($hookType); + } + } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 55addb6d907..a98855249a5 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -3,8 +3,10 @@ namespace PHPStan\Reflection\Dummy; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -80,4 +82,29 @@ public function getDocComment(): ?string return null; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index fbeac4f3a75..85b16a86d63 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -2,6 +2,8 @@ namespace PHPStan\Reflection; +use PHPStan\TrinaryLogic; + /** * The purpose of this interface is to be able to * answer more questions about properties @@ -19,4 +21,24 @@ interface ExtendedPropertyReflection extends PropertyReflection { + public const HOOK_GET = 'get'; + + public const HOOK_SET = 'set'; + + public function isAbstract(): TrinaryLogic; + + public function isFinal(): TrinaryLogic; + + public function isVirtual(): TrinaryLogic; + + /** + * @param self::HOOK_* $hookType + */ + public function hasHook(string $hookType): bool; + + /** + * @param self::HOOK_* $hookType + */ + public function getHook(string $hookType): ExtendedMethodReflection; + } diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index ef9ea9a2be1..c9540c7b644 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -3,7 +3,9 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -79,4 +81,29 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index c42863737c0..333f88bd33b 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -42,6 +42,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\GeneralizePrecision; +use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -212,7 +213,7 @@ private function createProperty( $types[] = $value; } - return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, false, false, false, false); + return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false); } } @@ -353,12 +354,72 @@ private function createProperty( $declaringTrait = $reflectionProvider->getClass($declaringTraitName); } + $getHook = null; + $setHook = null; + + $betterReflection = $propertyReflection->getBetterReflection(); + if ($betterReflection->hasHook('get')) { + $betterReflectionGetHook = $betterReflection->getHook('get'); + if ($betterReflectionGetHook === null) { + throw new ShouldNotHappenException(); + } + $getHook = $this->createUserlandMethodReflection( + $declaringClassReflection, + $declaringClassReflection, + new ReflectionMethod($betterReflectionGetHook), + $declaringTraitName, + ); + + if ($phpDocType !== null) { + $getHookMethodReflectionVariant = $getHook->getOnlyVariant(); + $getHookMethodReflectionVariantPhpDocReturnType = $getHookMethodReflectionVariant->getPhpDocReturnType(); + if ( + $getHookMethodReflectionVariantPhpDocReturnType instanceof MixedType + && !$getHookMethodReflectionVariantPhpDocReturnType instanceof TemplateMixedType + && !$getHookMethodReflectionVariantPhpDocReturnType->isExplicitMixed() + ) { + $getHook = $getHook->changePropertyGetHookPhpDocType($phpDocType); + } + } + } + + if ($betterReflection->hasHook('set')) { + $betterReflectionSetHook = $betterReflection->getHook('set'); + if ($betterReflectionSetHook === null) { + throw new ShouldNotHappenException(); + } + $setHook = $this->createUserlandMethodReflection( + $declaringClassReflection, + $declaringClassReflection, + new ReflectionMethod($betterReflectionSetHook), + $declaringTraitName, + ); + + if ($phpDocType !== null) { + $setHookMethodReflectionVariant = $setHook->getOnlyVariant(); + $setHookMethodReflectionParameters = $setHookMethodReflectionVariant->getParameters(); + if (isset($setHookMethodReflectionParameters[0])) { + $setHookMethodReflectionParameter = $setHookMethodReflectionParameters[0]; + $setHookMethodReflectionParameterPhpDocType = $setHookMethodReflectionParameter->getPhpDocType(); + if ( + $setHookMethodReflectionParameterPhpDocType instanceof MixedType + && !$setHookMethodReflectionParameterPhpDocType instanceof TemplateMixedType + && !$setHookMethodReflectionParameterPhpDocType->isExplicitMixed() + ) { + $setHook = $setHook->changePropertySetHookPhpDocType($setHookMethodReflectionParameter->getName(), $phpDocType); + } + } + } + } + return new PhpPropertyReflection( $declaringClassReflection, $declaringTrait, $nativeType, $phpDocType, $propertyReflection, + $getHook, + $setHook, $deprecatedDescription, $isDeprecated, $isInternal, diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 888530f2705..6209a57bc8d 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -450,4 +450,63 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->isPure); } + public function changePropertyGetHookPhpDocType(Type $phpDocType): self + { + return new self( + $this->initializerExprTypeResolver, + $this->declaringClass, + $this->declaringTrait, + $this->reflection, + $this->reflectionProvider, + $this->parser, + $this->templateTypeMap, + $this->phpDocParameterTypes, + $phpDocType, + $this->phpDocThrowType, + $this->deprecatedDescription, + $this->isDeprecated, + $this->isInternal, + $this->isFinal, + $this->isPure, + $this->asserts, + $this->acceptsNamedArguments, + $this->selfOutType, + $this->phpDocComment, + $this->phpDocParameterOutTypes, + $this->immediatelyInvokedCallableParameters, + $this->phpDocClosureThisTypeParameters, + ); + } + + public function changePropertySetHookPhpDocType(string $parameterName, Type $phpDocType): self + { + $phpDocParameterTypes = $this->phpDocParameterTypes; + $phpDocParameterTypes[$parameterName] = $phpDocType; + + return new self( + $this->initializerExprTypeResolver, + $this->declaringClass, + $this->declaringTrait, + $this->reflection, + $this->reflectionProvider, + $this->parser, + $this->templateTypeMap, + $phpDocParameterTypes, + $this->phpDocReturnType, + $this->phpDocThrowType, + $this->deprecatedDescription, + $this->isDeprecated, + $this->isInternal, + $this->isFinal, + $this->isPure, + $this->asserts, + $this->acceptsNamedArguments, + $this->selfOutType, + $this->phpDocComment, + $this->phpDocParameterOutTypes, + $this->immediatelyInvokedCallableParameters, + $this->phpDocClosureThisTypeParameters, + ); + } + } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 28e559719aa..3926a397892 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -7,11 +7,14 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\Reflection\MissingMethodFromReflectionException; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; +use function sprintf; /** * @api @@ -29,6 +32,8 @@ public function __construct( private ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null $nativeType, private ?Type $phpDocType, private ReflectionProperty $reflection, + private ?ExtendedMethodReflection $getHook, + private ?ExtendedMethodReflection $setHook, private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, @@ -182,4 +187,45 @@ public function getNativeReflection(): ReflectionProperty return $this->reflection; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isAbstract()); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isVirtual()); + } + + public function hasHook(string $hookType): bool + { + if ($hookType === 'get') { + return $this->getHook !== null; + } + + return $this->setHook !== null; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + if ($hookType === 'get') { + if ($this->getHook === null) { + throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::get', $this->reflection->getName())); + } + + return $this->getHook; + } + + if ($this->setHook === null) { + throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::set', $this->reflection->getName())); + } + + return $this->setHook; + } + } diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index 4073809a23f..a06da4df47e 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -3,7 +3,9 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; @@ -93,4 +95,29 @@ public function getDocComment(): ?string return null; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index ae1e86fe8cf..6013bcfa3be 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -3,7 +3,9 @@ namespace PHPStan\Reflection\Php; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -83,4 +85,29 @@ public function getDocComment(): ?string return null; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 8e8447ecace..43435261f6b 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -144,4 +144,33 @@ public function isInternal(): TrinaryLogic return $this->reflection->isInternal(); } + public function isAbstract(): TrinaryLogic + { + return $this->reflection->isAbstract(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isVirtual(): TrinaryLogic + { + return $this->reflection->isVirtual(); + } + + public function hasHook(string $hookType): bool + { + return $this->reflection->hasHook($hookType); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + return new ResolvedMethodReflection( + $this->reflection->getHook($hookType), + $this->templateTypeMap, + $this->callSiteVarianceMap, + ); + } + } diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 0db311786e7..40988deb29a 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -3,8 +3,9 @@ namespace PHPStan\Reflection\Type; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -16,7 +17,7 @@ final class IntersectionTypePropertyReflection implements ExtendedPropertyReflec { /** - * @param PropertyReflection[] $properties + * @param ExtendedPropertyReflection[] $properties */ public function __construct(private array $properties) { @@ -29,22 +30,22 @@ public function getDeclaringClass(): ClassReflection public function isStatic(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isStatic()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isStatic()); } public function isPrivate(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isPrivate()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivate()); } public function isPublic(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isPublic()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPublic()); } public function isDeprecated(): TrinaryLogic { - return TrinaryLogic::lazyMaxMin($this->properties, static fn (PropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isDeprecated()); + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isDeprecated()); } public function getDeprecatedDescription(): ?string @@ -71,7 +72,7 @@ public function getDeprecatedDescription(): ?string public function isInternal(): TrinaryLogic { - return TrinaryLogic::lazyMaxMin($this->properties, static fn (PropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isInternal()); + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isInternal()); } public function getDocComment(): ?string @@ -81,31 +82,31 @@ public function getDocComment(): ?string public function getReadableType(): Type { - return TypeCombinator::intersect(...array_map(static fn (PropertyReflection $property): Type => $property->getReadableType(), $this->properties)); + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); } public function getWritableType(): Type { - return TypeCombinator::intersect(...array_map(static fn (PropertyReflection $property): Type => $property->getWritableType(), $this->properties)); + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getWritableType(), $this->properties)); } public function canChangeTypeAfterAssignment(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->canChangeTypeAfterAssignment()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->canChangeTypeAfterAssignment()); } public function isReadable(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isReadable()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isReadable()); } public function isWritable(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isWritable()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isWritable()); } /** - * @param callable(PropertyReflection): bool $cb + * @param callable(ExtendedPropertyReflection): bool $cb */ private function computeResult(callable $cb): bool { @@ -117,4 +118,46 @@ private function computeResult(callable $cb): bool return $result; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isAbstract()); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinal()); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isVirtual()); + } + + public function hasHook(string $hookType): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasHook($hookType)); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + $hooks = []; + foreach ($this->properties as $property) { + if (!$property->hasHook($hookType)) { + continue; + } + + $hooks[] = $property->getHook($hookType); + } + + if (count($hooks) === 0) { + throw new ShouldNotHappenException(); + } + + if (count($hooks) === 1) { + return $hooks[0]; + } + + return new IntersectionTypeMethodReflection($hooks[0]->getName(), $hooks); + } + } diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 91964e8eda1..0f1c6c5162b 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -3,8 +3,9 @@ namespace PHPStan\Reflection\Type; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\PropertyReflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -16,7 +17,7 @@ final class UnionTypePropertyReflection implements ExtendedPropertyReflection { /** - * @param PropertyReflection[] $properties + * @param ExtendedPropertyReflection[] $properties */ public function __construct(private array $properties) { @@ -29,22 +30,22 @@ public function getDeclaringClass(): ClassReflection public function isStatic(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isStatic()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isStatic()); } public function isPrivate(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isPrivate()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivate()); } public function isPublic(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isPublic()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPublic()); } public function isDeprecated(): TrinaryLogic { - return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (PropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isDeprecated()); + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isDeprecated()); } public function getDeprecatedDescription(): ?string @@ -71,7 +72,7 @@ public function getDeprecatedDescription(): ?string public function isInternal(): TrinaryLogic { - return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (PropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isInternal()); + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isInternal()); } public function getDocComment(): ?string @@ -81,31 +82,31 @@ public function getDocComment(): ?string public function getReadableType(): Type { - return TypeCombinator::union(...array_map(static fn (PropertyReflection $property): Type => $property->getReadableType(), $this->properties)); + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); } public function getWritableType(): Type { - return TypeCombinator::union(...array_map(static fn (PropertyReflection $property): Type => $property->getWritableType(), $this->properties)); + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getWritableType(), $this->properties)); } public function canChangeTypeAfterAssignment(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->canChangeTypeAfterAssignment()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->canChangeTypeAfterAssignment()); } public function isReadable(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isReadable()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isReadable()); } public function isWritable(): bool { - return $this->computeResult(static fn (PropertyReflection $property) => $property->isWritable()); + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isWritable()); } /** - * @param callable(PropertyReflection): bool $cb + * @param callable(ExtendedPropertyReflection): bool $cb */ private function computeResult(callable $cb): bool { @@ -117,4 +118,46 @@ private function computeResult(callable $cb): bool return $result; } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isAbstract()); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinal()); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isVirtual()); + } + + public function hasHook(string $hookType): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasHook($hookType)); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + $hooks = []; + foreach ($this->properties as $property) { + if (!$property->hasHook($hookType)) { + continue; + } + + $hooks[] = $property->getHook($hookType); + } + + if (count($hooks) === 0) { + throw new ShouldNotHappenException(); + } + + if (count($hooks) === 1) { + return $hooks[0]; + } + + return new UnionTypeMethodReflection($hooks[0]->getName(), $hooks); + } + } diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 9b941088bc7..1469cd3f439 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -77,4 +78,29 @@ public function isInternal(): TrinaryLogic return $this->property->isInternal(); } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index a05182fa668..b74c62975a3 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -4,6 +4,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Reflection\WrapperPropertyReflection; @@ -127,4 +128,29 @@ public function getNativeReflection(): ?PhpPropertyReflection return $reflection; } + public function isAbstract(): TrinaryLogic + { + return $this->originalPropertyReflection->isAbstract(); + } + + public function isFinal(): TrinaryLogic + { + return $this->originalPropertyReflection->isFinal(); + } + + public function isVirtual(): TrinaryLogic + { + return $this->originalPropertyReflection->isVirtual(); + } + + public function hasHook(string $hookType): bool + { + return $this->originalPropertyReflection->hasHook($hookType); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + return $this->originalPropertyReflection->getHook($hookType); + } + } diff --git a/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php b/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php index 1651ddfdcda..cf30777b199 100644 --- a/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php +++ b/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php @@ -7,6 +7,7 @@ use PHPStan\Node\InPropertyHookNode; use PHPStan\Rules\FunctionReturnTypeCheck; use PHPStan\Rules\Rule; +use function sprintf; /** * @implements Rule diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index ca96ebae940..7c05525aaef 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -3,8 +3,10 @@ namespace PHPStan\Type; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use stdClass; @@ -82,4 +84,29 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isAbstract(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isVirtual(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function hasHook(string $hookType): bool + { + return false; + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + throw new ShouldNotHappenException(); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php index 0e49e0e981c..cc143f855d4 100644 --- a/tests/PHPStan/Analyser/nsrt/property-hooks.php +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -13,6 +13,9 @@ class Foo set { assertType('int', $value); } + get { + return 1; + } } public int $j { @@ -32,6 +35,9 @@ class Foo set { assertType('array', $value); } + get { + return []; + } } /** @var array */ @@ -106,6 +112,9 @@ public function __construct( set { assertType('array', $value); } + get { + return []; + } }, /** @var array */ public array $m { @@ -137,6 +146,9 @@ public function __construct( set { assertType('array', $value); } + get { + return []; + } }, public array $m { set (array $val) { @@ -160,6 +172,9 @@ class FooGenerics set (array $val) { assertType('array', $val); } + get { + + } } public int $n { @@ -167,6 +182,9 @@ class FooGenerics set (int|array $val) { assertType('array|int', $val); } + get { + + } } } @@ -183,18 +201,27 @@ public function __construct( set { assertType('array', $value); } + get { + + } }, /** @var array */ public array $m { set (array $val) { assertType('array', $val); } + get { + + } }, public int $n { /** @param int|array $val */ set (int|array $val) { assertType('array|int', $val); } + get { + + } }, ) { diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index d798e65b66f..7058b8b5105 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -29,12 +29,15 @@ use NestedTraits\NoTrait; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerType; +use PHPStan\Type\VerbosityLevel; use PHPUnit\Framework\TestCase; use ReflectionClass; use WrongClassConstantFile\SecuredRouter; use function array_map; use function array_values; +use function count; use const PHP_VERSION_ID; class ClassReflectionTest extends PHPStanTestCase @@ -322,4 +325,341 @@ public function testIs(): void $this->assertFalse($classReflection->is(RuleTestCase::class)); } + public function dataPropertyHooks(): iterable + { + if (PHP_VERSION_ID < 80400) { + return; + } + + $reflectionProvider = $this->createReflectionProvider(); + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\Foo'), + 'i', + 'set', + ['int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\Foo'), + 'i', + 'get', + [], + 'int', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\Foo'), + 'l', + 'get', + [], + 'array', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\Foo'), + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooShort'), + 'i', + 'set', + ['int'], + 'void', + false, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooShort'), + 'k', + 'set', + ['int|string'], + 'void', + false, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooShort'), + 'l', + 'set', + ['array'], + 'void', + false, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooShort'), + 'm', + 'set', + ['array'], + 'void', + false, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooShort'), + 'n', + 'set', + ['array|int'], + 'void', + false, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'i', + 'set', + ['int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'j', + 'set', + ['int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'k', + 'set', + ['int|string'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'l', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'l', + 'get', + [], + 'array', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructor'), + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructorWithParam'), + 'l', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructorWithParam'), + 'l', + 'get', + [], + 'array', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooConstructorWithParam'), + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'm', + 'get', + [], + 'array', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'n', + 'get', + [], + 'int', + true, + ]; + + $specificFooGenerics = (new GenericObjectType('PropertyHooksTypes\\FooGenerics', [new IntegerType()]))->getClassReflection(); + + yield [ + $specificFooGenerics, + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'n', + 'get', + [], + 'int', + true, + ]; + + yield [ + $specificFooGenerics, + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenerics'), + 'm', + 'get', + [], + 'array', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenericsConstructor'), + 'l', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenericsConstructor'), + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $reflectionProvider->getClass('PropertyHooksTypes\\FooGenericsConstructor'), + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + $specificFooGenericsConstructor = (new GenericObjectType('PropertyHooksTypes\\FooGenericsConstructor', [new IntegerType()]))->getClassReflection(); + + yield [ + $specificFooGenericsConstructor, + 'n', + 'set', + ['array|int'], + 'void', + true, + ]; + + yield [ + $specificFooGenericsConstructor, + 'm', + 'set', + ['array'], + 'void', + true, + ]; + + yield [ + $specificFooGenericsConstructor, + 'm', + 'get', + [], + 'array', + true, + ]; + } + + /** + * @dataProvider dataPropertyHooks + * @param ExtendedPropertyReflection::HOOK_* $hookName + * @param string[] $parameterTypes + */ + public function testPropertyHooks( + ClassReflection $classReflection, + string $propertyName, + string $hookName, + array $parameterTypes, + string $returnType, + bool $isVirtual, + ): void + { + $propertyReflection = $classReflection->getNativeProperty($propertyName); + $this->assertSame($isVirtual, $propertyReflection->isVirtual()->yes()); + + $hookReflection = $propertyReflection->getHook($hookName); + $hookVariant = $hookReflection->getOnlyVariant(); + $this->assertSame($returnType, $hookVariant->getReturnType()->describe(VerbosityLevel::precise())); + $this->assertCount(count($parameterTypes), $hookVariant->getParameters()); + + foreach ($hookVariant->getParameters() as $i => $parameter) { + $this->assertSame($parameterTypes[$i], $parameter->getType()->describe(VerbosityLevel::precise())); + } + } + } diff --git a/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php b/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php index 0f4190b4337..8a318db7eda 100644 --- a/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -16,7 +17,7 @@ final class ShortGetPropertyHookReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { return new ShortGetPropertyHookReturnTypeRule( - new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false)) + new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false)), ); } From 0b7104f6c1fb67551cdaf390ebe401459e47d7ad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 15:04:21 +0100 Subject: [PATCH 0920/3097] Allow wider set hook parameter type when assigning properties outside of their hooks --- src/Reflection/Php/PhpPropertyReflection.php | 8 +++++ .../TypesAssignedToPropertiesRule.php | 26 ++++++++++++++-- .../TypesAssignedToPropertiesRuleTest.php | 22 ++++++++++++++ .../data/assign-hooked-properties.php | 30 +++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 3926a397892..babe91bb3b4 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -103,6 +103,14 @@ public function getReadableType(): Type public function getWritableType(): Type { + if ($this->hasHook('set')) { + $setHookVariant = $this->getHook('set')->getOnlyVariant(); + $parameters = $setHookVariant->getParameters(); + if (isset($parameters[0])) { + return $parameters[0]->getType(); + } + } + return $this->getReadableType(); } diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index f043cf3c3e3..50d15024011 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -3,8 +3,11 @@ namespace PHPStan\Rules\Properties; use PhpParser\Node; +use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\StaticPropertyFetch; use PHPStan\Analyser\Scope; use PHPStan\Node\PropertyAssignNode; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\PropertyReflection; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -12,6 +15,7 @@ use PHPStan\Rules\RuleLevelHelper; use PHPStan\Type\VerbosityLevel; use function array_merge; +use function is_string; use function sprintf; /** @@ -34,12 +38,14 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($node->getPropertyFetch(), $scope); + $propertyFetch = $node->getPropertyFetch(); + $propertyReflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope); $errors = []; foreach ($propertyReflections as $propertyReflection) { $errors = array_merge($errors, $this->processSingleProperty( $propertyReflection, + $propertyFetch, $node->getAssignedExpr(), )); } @@ -52,6 +58,7 @@ public function processNode(Node $node, Scope $scope): array */ private function processSingleProperty( FoundPropertyReflection $propertyReflection, + PropertyFetch|StaticPropertyFetch $fetch, Node\Expr $assignedExpr, ): array { @@ -59,8 +66,23 @@ private function processSingleProperty( return []; } - $propertyType = $propertyReflection->getWritableType(); $scope = $propertyReflection->getScope(); + $inFunction = $scope->getFunction(); + if ( + $fetch instanceof PropertyFetch + && $fetch->var instanceof Node\Expr\Variable + && is_string($fetch->var->name) + && $fetch->var->name === 'this' + && $fetch->name instanceof Node\Identifier + && $inFunction instanceof PhpMethodFromParserNodeReflection + && $inFunction->isPropertyHook() + && $inFunction->getHookedPropertyName() === $fetch->name->toString() + ) { + $propertyType = $propertyReflection->getReadableType(); + } else { + $propertyType = $propertyReflection->getWritableType(); + } + $assignedValueType = $scope->getType($assignedExpr); $accepts = $this->ruleLevelHelper->accepts($propertyType, $assignedValueType, $scope->isDeclareStrictTypes()); diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 061a8af5106..dd5c31608dc 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -722,4 +722,26 @@ public function testShortBodySetHook(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/assign-hooked-properties.php'], [ + [ + 'Property AssignHookedProperties\Foo::$i (int) does not accept array|int.', + 11, + ], + [ + 'Property AssignHookedProperties\Foo::$j (int) does not accept array|int.', + 19, + ], + [ + 'Property AssignHookedProperties\Foo::$i (array|int) does not accept array.', + 27, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php new file mode 100644 index 00000000000..d25a9f8b4ba --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php @@ -0,0 +1,30 @@ += 8.4 + +namespace AssignHookedProperties; + +class Foo +{ + + public int $i { + /** @param array|int $val */ + set (array|int $val) { + $this->i = $val; // only int allowed + } + } + + public int $j { + /** @param array|int $val */ + set (array|int $val) { + $this->i = $val; // this is okay - hook called + $this->j = $val; // only int allowed + } + } + + public function doFoo(): void + { + $this->i = ['foo']; // okay + $this->i = 1; // okay + $this->i = [1]; // not okay + } + +} From 0d0cd13bed0ce91ad6450cb69155e4238ff760ec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 15:18:15 +0100 Subject: [PATCH 0921/3097] Fix canChangeTypeAfterAssignment --- src/Reflection/Php/PhpPropertyReflection.php | 16 +++++++ .../PHPStan/Analyser/nsrt/property-hooks.php | 46 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index babe91bb3b4..2879f2a9ea6 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -116,6 +116,22 @@ public function getWritableType(): Type public function canChangeTypeAfterAssignment(): bool { + if ($this->isStatic()) { + return true; + } + + if ($this->isVirtual()->yes()) { + return false; + } + + if ($this->hasHook('get')) { + return false; + } + + if ($this->hasHook('set')) { + return false; + } + return true; } diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php index cc143f855d4..4a7f0d9f3a0 100644 --- a/tests/PHPStan/Analyser/nsrt/property-hooks.php +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -311,3 +311,49 @@ public function __construct( } } + +class CanChangeTypeAfterAssignment +{ + + public int $i; + + public function doFoo(): void + { + assertType('int', $this->i); + $this->i = 1; + assertType('1', $this->i); + } + + public int $virtual { + get { + return 1; + } + set { + $this->i = 1; + } + } + + public function doFoo2(): void + { + assertType('int', $this->virtual); + $this->virtual = 1; + assertType('int', $this->virtual); + } + + public int $backedWithHook { + get { + return $this->backedWithHook + 100; + } + set { + $this->backedWithHook = $this->backedWithHook - 200; + } + } + + public function doFoo3(): void + { + assertType('int', $this->backedWithHook); + $this->backedWithHook = 1; + assertType('int', $this->backedWithHook); + } + +} From a0e05b8eaab2f81fff390595cc47abd2acd47729 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 15:58:18 +0100 Subject: [PATCH 0922/3097] Test WritingToReadOnlyPropertiesRule for hooked properties --- src/Reflection/Php/PhpPropertyReflection.php | 10 +++- .../WritingToReadOnlyPropertiesRuleTest.php | 20 ++++++++ ...writing-to-read-only-hooked-properties.php | 49 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/writing-to-read-only-hooked-properties.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 2879f2a9ea6..d8ce00cdf65 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -179,7 +179,15 @@ public function isReadable(): bool public function isWritable(): bool { - return true; + if ($this->isStatic()) { + return true; + } + + if (!$this->isVirtual()->yes()) { + return true; + } + + return $this->hasHook('set'); } public function getDeprecatedDescription(): ?string diff --git a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php index fb64553a3b2..3dd095b13fd 100644 --- a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -87,4 +88,23 @@ public function testConflictingAnnotationProperty(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/writing-to-read-only-hooked-properties.php'], [ + [ + 'Property WritingToReadOnlyHookedProperties\Foo::$i is not writable.', + 16, + ], + [ + 'Property WritingToReadOnlyHookedProperties\Bar::$i is not writable.', + 32, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/writing-to-read-only-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/writing-to-read-only-hooked-properties.php new file mode 100644 index 00000000000..06953a96611 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/writing-to-read-only-hooked-properties.php @@ -0,0 +1,49 @@ += 8.4 + +namespace WritingToReadOnlyHookedProperties; + +interface Foo +{ + + public int $i { + // virtual, not writable + get; + } + +} + +function (Foo $f): void { + $f->i = 1; +}; + +class Bar +{ + + public int $i { + // virtual, not writable + get { + return 1; + } + } + +} + +function (Bar $b): void { + $b->i = 1; +}; + +class Baz +{ + + public int $i { + // backed, writable + get { + return $this->i + 1; + } + } + +} + +function (Baz $b): void { + $b->i = 1; +}; From 9835f92feccfe9b25659cfc76f2a85b1f543a0b2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 16:06:04 +0100 Subject: [PATCH 0923/3097] Test ReadingWriteOnlyPropertiesRule for hooked properties --- src/Reflection/Php/PhpPropertyReflection.php | 10 +++- .../ReadingWriteOnlyPropertiesRuleTest.php | 20 ++++++++ .../reading-write-only-hooked-properties.php | 51 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/reading-write-only-hooked-properties.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index d8ce00cdf65..e9aaa764bca 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -174,7 +174,15 @@ public function getNativeType(): Type public function isReadable(): bool { - return true; + if ($this->isStatic()) { + return true; + } + + if (!$this->isVirtual()->yes()) { + return true; + } + + return $this->hasHook('get'); } public function isWritable(): bool diff --git a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php index f3ba064bac2..0857934cb5f 100644 --- a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -88,4 +89,23 @@ public function testConflictingAnnotationProperty(): void $this->analyse([__DIR__ . '/data/conflicting-annotation-property.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/reading-write-only-hooked-properties.php'], [ + [ + 'Property ReadingWriteOnlyHookedProperties\Foo::$i is not readable.', + 16, + ], + [ + 'Property ReadingWriteOnlyHookedProperties\Bar::$i is not readable.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/reading-write-only-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/reading-write-only-hooked-properties.php new file mode 100644 index 00000000000..9f695e4573c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/reading-write-only-hooked-properties.php @@ -0,0 +1,51 @@ += 8.4 + +namespace ReadingWriteOnlyHookedProperties; + +interface Foo +{ + + public int $i { + // virtual, not readable + set; + } + +} + +function (Foo $f): void { + echo $f->i; +}; + +class Bar +{ + + public int $other; + + public int $i { + // virtual, not readable + set { + $this->other = 1; + } + } + +} + +function (Bar $b): void { + echo $b->i; +}; + +class Baz +{ + + public int $i { + // backed, readable + set { + $this->i = 1; + } + } + +} + +function (Baz $b): void { + $b->i = 1; +}; From 2240ece6a9eca6abee369aa190fb42dcb308152f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 17:02:51 +0100 Subject: [PATCH 0924/3097] Test generics in TypesAssignedToPropertiesRule for hooked properties --- .../TypesAssignedToPropertiesRuleTest.php | 17 +++++ .../data/assign-hooked-properties.php | 73 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index dd5c31608dc..b374da4943b 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -741,6 +741,23 @@ public function testPropertyHooks(): void 'Property AssignHookedProperties\Foo::$i (array|int) does not accept array.', 27, ], + [ + 'Property AssignHookedProperties\FooGenerics::$a (int) does not accept string.', + 52, + ], + [ + 'Property AssignHookedProperties\FooGenerics::$a (T) does not accept int.', + 61, + 'Type int is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Property AssignHookedProperties\FooGenericsParam::$a (array) does not accept array|int.', + 76, + ], + [ + 'Property AssignHookedProperties\FooGenericsParam::$a (array|int) does not accept array.', + 91, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php index d25a9f8b4ba..6c6872df24b 100644 --- a/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php +++ b/tests/PHPStan/Rules/Properties/data/assign-hooked-properties.php @@ -28,3 +28,76 @@ public function doFoo(): void } } + +/** + * @template T + */ +class FooGenerics +{ + + /** @var T */ + public $a { + set { + $this->a = $value; + } + } + + /** + * @param FooGenerics $f + * @return void + */ + public static function doFoo(self $f): void + { + $f->a = 1; + $f->a = 'foo'; + } + + /** + * @param T $t + */ + public function doBar($t): void + { + $this->a = $t; + $this->a = 1; + } + +} + +/** + * @template T + */ +class FooGenericsParam +{ + + /** @var array */ + public array $a { + /** @param array|int $value */ + set (array|int $value) { + $this->a = $value; // not ok + + if (is_array($value)) { + $this->a = $value; // ok + } + } + } + + /** + * @param FooGenericsParam $f + * @return void + */ + public static function doFoo(self $f): void + { + $f->a = [1]; // ok + $f->a = ['foo']; // not ok + } + + /** + * @param T $t + */ + public function doBar($t): void + { + $this->a = [$t]; // ok + $this->a = 1; // ok + } + +} From 4c74572fb0d910b2984f007ed34c6ed45cef5658 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Dec 2024 17:14:10 +0100 Subject: [PATCH 0925/3097] CleaningParser - clean up property hooks --- Makefile | 2 + build/collision-detector.json | 2 + src/Parser/CleaningVisitor.php | 32 +++++++++++--- tests/PHPStan/Parser/CleaningParserTest.php | 9 +++- .../data/cleaning-property-hooks-after.php | 21 ++++++++++ .../data/cleaning-property-hooks-before.php | 42 +++++++++++++++++++ 6 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Parser/data/cleaning-property-hooks-after.php create mode 100644 tests/PHPStan/Parser/data/cleaning-property-hooks-before.php diff --git a/Makefile b/Makefile index 407333c955a..8a8077a3e1b 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,8 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ + --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-before.php \ + --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ src tests cs: diff --git a/build/collision-detector.json b/build/collision-detector.json index 12de9af1d39..03c717dfff6 100644 --- a/build/collision-detector.json +++ b/build/collision-detector.json @@ -5,6 +5,8 @@ "../tests/PHPStan/Analyser/data/multipleParseErrors.php", "../tests/PHPStan/Parser/data/cleaning-1-before.php", "../tests/PHPStan/Parser/data/cleaning-1-after.php", + "../tests/PHPStan/Parser/data/cleaning-property-hooks-before.php", + "../tests/PHPStan/Parser/data/cleaning-property-hooks-after.php", "../tests/PHPStan/Rules/Functions/data/duplicate-function.php", "../tests/PHPStan/Rules/Classes/data/duplicate-class.php", "../tests/PHPStan/Rules/Names/data/multiple-namespaces.php", diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index 773c36f6e4f..eb9492f3cde 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -7,6 +7,7 @@ use PhpParser\NodeVisitorAbstract; use PHPStan\Reflection\ParametersAcceptor; use function in_array; +use function is_array; final class CleaningVisitor extends NodeVisitorAbstract { @@ -21,20 +22,28 @@ public function __construct() public function enterNode(Node $node): ?Node { if ($node instanceof Node\Stmt\Function_) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYields($node->stmts, null); return $node; } if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYields($node->stmts, null); return $node; } if ($node instanceof Node\Expr\Closure) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYields($node->stmts, null); return $node; } + if ($node instanceof Node\PropertyHook && is_array($node->body)) { + $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + if ($propertyName !== null) { + $node->body = $this->keepVariadicsAndYields($node->body, $propertyName); + return $node; + } + } + return null; } @@ -42,9 +51,9 @@ public function enterNode(Node $node): ?Node * @param Node\Stmt[] $stmts * @return Node\Stmt[] */ - private function keepVariadicsAndYields(array $stmts): array + private function keepVariadicsAndYields(array $stmts, ?string $hookedPropertyName): array { - $results = $this->nodeFinder->find($stmts, static function (Node $node): bool { + $results = $this->nodeFinder->find($stmts, static function (Node $node) use ($hookedPropertyName): bool { if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) { return true; } @@ -56,6 +65,18 @@ private function keepVariadicsAndYields(array $stmts): array return true; } + if ($hookedPropertyName !== null) { + if ( + $node instanceof Node\Expr\PropertyFetch + && $node->var instanceof Node\Expr\Variable + && $node->var->name === 'this' + && $node->name instanceof Node\Identifier + && $node->name->toString() === $hookedPropertyName + ) { + return true; + } + } + return false; }); $newStmts = []; @@ -65,6 +86,7 @@ private function keepVariadicsAndYields(array $stmts): array || $result instanceof Node\Expr\YieldFrom || $result instanceof Node\Expr\Closure || $result instanceof Node\Expr\ArrowFunction + || $result instanceof Node\Expr\PropertyFetch ) { $newStmts[] = new Node\Stmt\Expression($result); continue; diff --git a/tests/PHPStan/Parser/CleaningParserTest.php b/tests/PHPStan/Parser/CleaningParserTest.php index e0afccabb76..8dbf5691716 100644 --- a/tests/PHPStan/Parser/CleaningParserTest.php +++ b/tests/PHPStan/Parser/CleaningParserTest.php @@ -4,7 +4,7 @@ use PhpParser\Lexer\Emulative; use PhpParser\NodeVisitor\NameResolver; -use PhpParser\Parser\Php7; +use PhpParser\Parser\Php8; use PHPStan\File\FileReader; use PHPStan\Node\Printer\Printer; use PHPStan\Php\PhpVersion; @@ -52,6 +52,11 @@ public function dataParse(): iterable __DIR__ . '/data/cleaning-php-version-after-74.php', 70400, ], + [ + __DIR__ . '/data/cleaning-property-hooks-before.php', + __DIR__ . '/data/cleaning-property-hooks-after.php', + 80400, + ], ]; } @@ -66,7 +71,7 @@ public function testParse( { $parser = new CleaningParser( new SimpleParser( - new Php7(new Emulative()), + new Php8(new Emulative()), new NameResolver(), new VariadicMethodsVisitor(), new VariadicFunctionsVisitor(), diff --git a/tests/PHPStan/Parser/data/cleaning-property-hooks-after.php b/tests/PHPStan/Parser/data/cleaning-property-hooks-after.php new file mode 100644 index 00000000000..105bcf5d764 --- /dev/null +++ b/tests/PHPStan/Parser/data/cleaning-property-hooks-after.php @@ -0,0 +1,21 @@ +i; + } + } +} +class FooParam +{ + public function __construct(public int $i { + get { + $this->i; + } + }) + { + } +} diff --git a/tests/PHPStan/Parser/data/cleaning-property-hooks-before.php b/tests/PHPStan/Parser/data/cleaning-property-hooks-before.php new file mode 100644 index 00000000000..7b73ef3d092 --- /dev/null +++ b/tests/PHPStan/Parser/data/cleaning-property-hooks-before.php @@ -0,0 +1,42 @@ +j; + + // backed property, leave this here + return $this->i; + } + } + +} + +class FooParam +{ + + public function __construct( + public int $i { + get { + echo 'irrelevant'; + + // other property, clean up + echo $this->j; + + // backed property, leave this here + return $this->i; + } + } + ) + { + + } + +} From 36e806238df8535b22fe9957a8f1b27f6bddf8a8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Dec 2024 10:18:05 +0100 Subject: [PATCH 0926/3097] Fix ReadOnlyByPhpDocPropertyAssignRule for hooked properties --- .../ReadOnlyByPhpDocPropertyAssignRule.php | 13 ++++++++++ ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 18 +++++++++++++ ...operty-hooks-readonly-by-phpdoc-assign.php | 25 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/property-hooks-readonly-by-phpdoc-assign.php diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index c71ce58bbcd..29d913d7790 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -7,6 +7,7 @@ use PHPStan\Node\PropertyAssignNode; use PHPStan\Reflection\ConstructorsHelper; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -40,6 +41,18 @@ public function processNode(Node $node, Scope $scope): array return []; } + $inFunction = $scope->getFunction(); + if ( + $inFunction instanceof PhpMethodFromParserNodeReflection + && $inFunction->isPropertyHook() + && $propertyFetch->var instanceof Node\Expr\Variable + && $propertyFetch->var->name === 'this' + && $propertyFetch->name instanceof Node\Identifier + && $inFunction->getHookedPropertyName() === $propertyFetch->name->toString() + ) { + return []; + } + $errors = []; $reflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope); foreach ($reflections as $propertyReflection) { diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php index 70d53797015..c7ec6ed0ad8 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -178,4 +178,22 @@ public function testFeature11775(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/property-hooks-readonly-by-phpdoc-assign.php'], [ + [ + '@readonly property PropertyHooksReadonlyByPhpDocAssign\Foo::$i is assigned outside of the constructor.', + 15, + ], + [ + '@readonly property PropertyHooksReadonlyByPhpDocAssign\Foo::$j is assigned outside of the constructor.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/property-hooks-readonly-by-phpdoc-assign.php b/tests/PHPStan/Rules/Properties/data/property-hooks-readonly-by-phpdoc-assign.php new file mode 100644 index 00000000000..179dfdde042 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-hooks-readonly-by-phpdoc-assign.php @@ -0,0 +1,25 @@ += 8.4 + +namespace PropertyHooksReadonlyByPhpDocAssign; + +class Foo +{ + + /** @readonly */ + public int $i { + get { + return $this->i + 1; + } + set { + $self = new self(); + $self->i = 1; + + $this->j = 2; + $this->i = $value - 1; + } + } + + /** @readonly */ + public int $j; + +} From 66393edfd566450b4febdd9756e452ae29c6002f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Dec 2024 11:16:27 +0100 Subject: [PATCH 0927/3097] Introduce PropertyHookReturnStatementsNode similar to MethodReturnStatementsNode --- src/Analyser/NodeScopeResolver.php | 43 +++++++- src/Node/PropertyHookReturnStatementsNode.php | 104 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/Node/PropertyHookReturnStatementsNode.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7ad28508acc..53f0f978052 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -114,6 +114,7 @@ use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Node\NoopExpressionNode; use PHPStan\Node\PropertyAssignNode; +use PHPStan\Node\PropertyHookReturnStatementsNode; use PHPStan\Node\PropertyHookStatementNode; use PHPStan\Node\ReturnStatement; use PHPStan\Node\StaticMethodCallableNode; @@ -4702,7 +4703,47 @@ private function processPropertyHooks( $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); $nodeCallback(new PropertyAssignNode(new PropertyFetch(new Variable('this'), $propertyName, $hook->body->getAttributes()), $hook->body, false), $hookScope); } elseif (is_array($hook->body)) { - $this->processStmtNodes(new PropertyHookStatementNode($hook), $hook->body, $hookScope, $nodeCallback, StatementContext::createTopLevel()); + $gatheredReturnStatements = []; + $executionEnds = []; + $methodImpurePoints = []; + $statementResult = $this->processStmtNodes(new PropertyHookStatementNode($hook), $hook->body, $hookScope, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { + $nodeCallback($node, $scope); + if ($scope->getFunction() !== $hookScope->getFunction()) { + return; + } + if ($scope->isInAnonymousFunction()) { + return; + } + if ($node instanceof PropertyAssignNode) { + $hookImpurePoints[] = new ImpurePoint( + $scope, + $node, + 'propertyAssign', + 'property assignment', + true, + ); + return; + } + if ($node instanceof ExecutionEndNode) { + $executionEnds[] = $node; + return; + } + if (!$node instanceof Return_) { + return; + } + + $gatheredReturnStatements[] = new ReturnStatement($scope, $node); + }, StatementContext::createTopLevel()); + + $nodeCallback(new PropertyHookReturnStatementsNode( + $hook, + $gatheredReturnStatements, + $statementResult, + $executionEnds, + array_merge($statementResult->getImpurePoints(), $methodImpurePoints), + $classReflection, + $hookReflection, + ), $hookScope); } } diff --git a/src/Node/PropertyHookReturnStatementsNode.php b/src/Node/PropertyHookReturnStatementsNode.php new file mode 100644 index 00000000000..7d97a140b91 --- /dev/null +++ b/src/Node/PropertyHookReturnStatementsNode.php @@ -0,0 +1,104 @@ + $returnStatements + * @param list $executionEnds + * @param ImpurePoint[] $impurePoints + */ + public function __construct( + private PropertyHook $hook, + private array $returnStatements, + private StatementResult $statementResult, + private array $executionEnds, + private array $impurePoints, + private ClassReflection $classReflection, + private PhpMethodFromParserNodeReflection $hookReflection, + ) + { + parent::__construct($hook->getAttributes()); + } + + public function getPropertyHookNode(): PropertyHook + { + return $this->hook; + } + + public function returnsByRef(): bool + { + return $this->hook->byRef; + } + + public function hasNativeReturnTypehint(): bool + { + return false; + } + + public function getYieldStatements(): array + { + return []; + } + + public function isGenerator(): bool + { + return false; + } + + public function getReturnStatements(): array + { + return $this->returnStatements; + } + + public function getStatementResult(): StatementResult + { + return $this->statementResult; + } + + public function getExecutionEnds(): array + { + return $this->executionEnds; + } + + public function getImpurePoints(): array + { + return $this->impurePoints; + } + + public function getClassReflection(): ClassReflection + { + return $this->classReflection; + } + + public function getHookReflection(): PhpMethodFromParserNodeReflection + { + return $this->hookReflection; + } + + public function getType(): string + { + return 'PHPStan_Node_PropertyHookReturnStatementsNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} From 00ad9be9ad930fa1a18e3b7b55a62cea223962c5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Dec 2024 11:21:26 +0100 Subject: [PATCH 0928/3097] SetNonVirtualPropertyHookAssignRule - level 3 --- conf/config.level3.neon | 1 + .../SetNonVirtualPropertyHookAssignRule.php | 97 +++++++++++++++++++ ...etNonVirtualPropertyHookAssignRuleTest.php | 38 ++++++++ .../set-non-virtual-property-hook-assign.php | 68 +++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php create mode 100644 tests/PHPStan/Rules/Properties/SetNonVirtualPropertyHookAssignRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 31e065bf6f2..6522c1eb5ba 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -20,6 +20,7 @@ rules: - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule + - PHPStan\Rules\Properties\SetNonVirtualPropertyHookAssignRule - PHPStan\Rules\Properties\ShortGetPropertyHookReturnTypeRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule diff --git a/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php b/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php new file mode 100644 index 00000000000..67f8f134bb7 --- /dev/null +++ b/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php @@ -0,0 +1,97 @@ + + */ +final class SetNonVirtualPropertyHookAssignRule implements Rule +{ + + public function getNodeType(): string + { + return PropertyHookReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $hookNode = $node->getPropertyHookNode(); + if ($hookNode->name->toLowerString() !== 'set') { + return []; + } + + $hookReflection = $node->getHookReflection(); + if (!$hookReflection->isPropertyHook()) { + throw new ShouldNotHappenException(); + } + + $propertyName = $hookReflection->getHookedPropertyName(); + $classReflection = $node->getClassReflection(); + if (!$classReflection->hasNativeProperty($propertyName)) { + throw new ShouldNotHappenException(); + } + + $propertyReflection = $classReflection->getNativeProperty($propertyName); + if ($propertyReflection->isVirtual()->yes()) { + return []; + } + + $finalHookScope = null; + foreach ($node->getExecutionEnds() as $executionEnd) { + $statementResult = $executionEnd->getStatementResult(); + $endNode = $executionEnd->getNode(); + if ($statementResult->isAlwaysTerminating()) { + if ($endNode instanceof Node\Stmt\Expression) { + $exprType = $statementResult->getScope()->getType($endNode->expr); + if ($exprType instanceof NeverType && $exprType->isExplicit()) { + continue; + } + } + } + if ($finalHookScope === null) { + $finalHookScope = $statementResult->getScope(); + continue; + } + + $finalHookScope = $finalHookScope->mergeWith($statementResult->getScope()); + } + + foreach ($node->getReturnStatements() as $returnStatement) { + if ($finalHookScope === null) { + $finalHookScope = $returnStatement->getScope(); + continue; + } + $finalHookScope = $finalHookScope->mergeWith($returnStatement->getScope()); + } + + if ($finalHookScope === null) { + return []; + } + + $initExpr = new PropertyInitializationExpr($propertyName); + $hasInit = $finalHookScope->hasExpressionType($initExpr); + if ($hasInit->yes()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Set hook for non-virtual property %s::$%s does not %sassign value to it.', + $classReflection->getDisplayName(), + $propertyName, + $hasInit->maybe() ? 'always ' : '', + ))->identifier('propertySetHook.noAssign')->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Properties/SetNonVirtualPropertyHookAssignRuleTest.php b/tests/PHPStan/Rules/Properties/SetNonVirtualPropertyHookAssignRuleTest.php new file mode 100644 index 00000000000..bdfcaf5f272 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/SetNonVirtualPropertyHookAssignRuleTest.php @@ -0,0 +1,38 @@ + + */ +class SetNonVirtualPropertyHookAssignRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new SetNonVirtualPropertyHookAssignRule(); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/set-non-virtual-property-hook-assign.php'], [ + [ + 'Set hook for non-virtual property SetNonVirtualPropertyHookAssign\Foo::$k does not assign value to it.', + 24, + ], + [ + 'Set hook for non-virtual property SetNonVirtualPropertyHookAssign\Foo::$k2 does not always assign value to it.', + 34, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php b/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php new file mode 100644 index 00000000000..ffc0e559f6c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php @@ -0,0 +1,68 @@ += 8.4 + +namespace SetNonVirtualPropertyHookAssign; + +class Foo +{ + + public int $i { + get { + return 1; + } + set { + // virtual property + $this->j = $value; + } + } + + public int $j; + + public int $k { + get { + return $this->k + 1; + } + set { + // backed property, missing assign should be reported + $this->j = $value; + } + } + + public int $k2 { + get { + return $this->k2 + 1; + } + set { + // backed property, missing assign should be reported + if (rand(0, 1)) { + return; + } + + $this->k2 = $value; + } + } + + public int $k3 { + get { + return $this->k3 + 1; + } + set { + // backed property, always assigned (or throws) + if (rand(0, 1)) { + throw new \Exception(); + } + + $this->k3 = $value; + } + } + + public int $k4 { + get { + return $this->k4 + 1; + } + set { + // backed property, always assigned + $this->k4 = $value; + } + } + +} From 0a2b600451ea602561b43f25a2ed19b0814c91c4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 10:25:12 +0100 Subject: [PATCH 0929/3097] GetNonVirtualPropertyHookReadRule - level 3 --- conf/config.level3.neon | 1 + .../GetNonVirtualPropertyHookReadRule.php | 118 ++++++++++++++++++ .../GetNonVirtualPropertyHookReadRuleTest.php | 38 ++++++ .../get-non-virtual-property-hook-read.php | 48 +++++++ 4 files changed, 205 insertions(+) create mode 100644 src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php create mode 100644 tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 6522c1eb5ba..b7d1a4c15e0 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -16,6 +16,7 @@ rules: - PHPStan\Rules\Generators\YieldTypeRule - PHPStan\Rules\Methods\ReturnTypeRule - PHPStan\Rules\Properties\DefaultValueTypesAssignedToPropertiesRule + - PHPStan\Rules\Properties\GetNonVirtualPropertyHookReadRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule diff --git a/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php new file mode 100644 index 00000000000..61b1c32a6c9 --- /dev/null +++ b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php @@ -0,0 +1,118 @@ + + */ +final class GetNonVirtualPropertyHookReadRule implements Rule +{ + + public function getNodeType(): string + { + return ClassPropertiesNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $reads = []; + $classReflection = $node->getClassReflection(); + foreach ($node->getPropertyUsages() as $propertyUsage) { + if (!$propertyUsage instanceof PropertyRead) { + continue; + } + + $fetch = $propertyUsage->getFetch(); + if (!$fetch instanceof Node\Expr\PropertyFetch) { + continue; + } + + if (!$fetch->name instanceof Node\Identifier) { + continue; + } + + $propertyName = $fetch->name->toString(); + if (!$fetch->var instanceof Node\Expr\Variable || $fetch->var->name !== 'this') { + continue; + } + + $usageScope = $propertyUsage->getScope(); + $inFunction = $usageScope->getFunction(); + if (!$inFunction instanceof PhpMethodFromParserNodeReflection) { + continue; + } + + if (!$inFunction->isPropertyHook()) { + continue; + } + + if ($inFunction->getPropertyHookName() !== 'get') { + continue; + } + + if ($propertyName !== $inFunction->getHookedPropertyName()) { + continue; + } + + $reads[$propertyName] = true; + } + + $errors = []; + foreach ($node->getProperties() as $propertyNode) { + if (!$propertyNode->hasHooks()) { + continue; + } + + if (array_key_exists($propertyNode->getName(), $reads)) { + continue; + } + + $propertyReflection = $classReflection->getNativeProperty($propertyNode->getName()); + if ($propertyReflection->isVirtual()->yes()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Get hook for non-virtual property %s::$%s does not read its value.', + $classReflection->getDisplayName(), + $propertyNode->getName(), + )) + ->line($this->getGetHookLine($propertyNode)) + ->identifier('propertyGetHook.noRead') + ->build(); + } + + return $errors; + } + + private function getGetHookLine(ClassPropertyNode $propertyNode): int + { + $getHook = null; + foreach ($propertyNode->getHooks() as $hook) { + if ($hook->name->toLowerString() !== 'get') { + continue; + } + + $getHook = $hook; + break; + } + + if ($getHook === null) { + return $propertyNode->getStartLine(); + } + + return $getHook->getStartLine(); + } + +} diff --git a/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php b/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php new file mode 100644 index 00000000000..8eedc782144 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php @@ -0,0 +1,38 @@ + + */ +class GetNonVirtualPropertyHookReadRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new GetNonVirtualPropertyHookReadRule(); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/get-non-virtual-property-hook-read.php'], [ + [ + 'Get hook for non-virtual property GetNonVirtualPropertyHookRead\Foo::$k does not read its value.', + 24, + ], + [ + 'Get hook for non-virtual property GetNonVirtualPropertyHookRead\Foo::$l does not read its value.', + 30, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php b/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php new file mode 100644 index 00000000000..077792c4060 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php @@ -0,0 +1,48 @@ += 8.4 + +namespace GetNonVirtualPropertyHookRead; + +class Foo +{ + + public int $i { + // backed, read and written + get => $this->i + 1; + set => $this->i + $value; + } + + public int $j { + // virtual + get => 1; + set { + $this->a = $value; + } + } + + public int $k { + // backed, not read + get => 1; + set => $value + 1; + } + + public int $l { + // backed, not read, long get + get { + return 1; + } + set => $value + 1; + } + + public int $m { + // it is okay to only read it sometimes + get { + if (rand(0, 1)) { + return 1; + } + + return $this->m; + } + set => $value + 1; + } + +} From 793c95df19f58f9a4ce304ff7bcdc85ef69f9fc2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 13:11:50 +0100 Subject: [PATCH 0930/3097] Test ReturnNullsafeByRefRule with property hooks --- .../Functions/ReturnNullsafeByRefRuleTest.php | 15 +++++++++++++++ .../return-null-safe-by-ref-property-hooks.php | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref-property-hooks.php diff --git a/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php index 92b6429eb09..a3b8f1db9e2 100644 --- a/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnNullsafeByRefRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\NullsafeCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -35,4 +36,18 @@ public function testRule(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/return-null-safe-by-ref-property-hooks.php'], [ + [ + 'Nullsafe cannot be returned by reference.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref-property-hooks.php b/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref-property-hooks.php new file mode 100644 index 00000000000..f902fa9f001 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/return-null-safe-by-ref-property-hooks.php @@ -0,0 +1,16 @@ += 8.4 + +namespace ReturnNullSafeByRefPropertyHools; + +use stdClass; + +class Foo +{ + public int $i { + &get { + $foo = new stdClass(); + + return $foo?->foo; + } + } +} From e99fc4f3cd8adde10684a63dddaf09dad87b80f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 13:23:11 +0100 Subject: [PATCH 0931/3097] Support `#[Deprecated]` attribute in property hooks --- src/Analyser/MutatingScope.php | 6 ++- src/Analyser/NodeScopeResolver.php | 6 ++- src/Reflection/InitializerExprContext.php | 3 +- .../Annotations/DeprecatedAnnotationsTest.php | 50 +++++++++++++++++++ ...hpFunctionFromParserReflectionRuleTest.php | 36 +++++++++++-- .../deprecated-attribute-property-hooks.php | 37 ++++++++++++++ 6 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index bfa07d782d3..5bd3f0a86e9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2999,6 +2999,8 @@ public function enterPropertyHook( ?Type $phpDocPropertyType, array $phpDocParameterTypes, ?Type $throwType, + ?string $deprecatedDescription, + bool $isDeprecated, ?string $phpDocComment, ): self { @@ -3054,8 +3056,8 @@ public function enterPropertyHook( $realReturnType, $phpDocReturnType, $throwType, - null, - false, + $deprecatedDescription, + $isDeprecated, false, false, false, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 53f0f978052..1639a125224 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1985,7 +1985,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { /** * @return array{bool, string|null} */ - private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod $stmt): array + private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod|Node\PropertyHook $stmt): array { $initializerExprContext = InitializerExprContext::fromStubParameter( null, @@ -4684,6 +4684,8 @@ private function processPropertyHooks( $this->processParamNode($stmt, $param, $scope, $nodeCallback); } + [$isDeprecated, $deprecatedDescription] = $this->getDeprecatedAttribute($scope, $hook); + $hookScope = $scope->enterPropertyHook( $hook, $propertyName, @@ -4691,6 +4693,8 @@ private function processPropertyHooks( $phpDocType, $phpDocParameterTypes, $phpDocThrowType, + $deprecatedDescription, + $isDeprecated, $phpDocComment, ); $hookReflection = $hookScope->getFunction(); diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index 9d7d9aa5bf8..f1587ef242b 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection; +use PhpParser\Node\PropertyHook; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PHPStan\Analyser\Scope; @@ -115,7 +116,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter): public static function fromStubParameter( ?string $className, string $stubFile, - ClassMethod|Function_ $function, + ClassMethod|Function_|PropertyHook $function, ): self { $namespace = null; diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index ff57846f969..8daa5fc1ee2 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -342,4 +342,54 @@ public function testDeprecatedAttributeAboveEnumCase(string $className, string $ $this->assertSame($deprecatedDescription, $case->getDeprecatedDescription()); } + public function dataDeprecatedAttributeAbovePropertyHook(): iterable + { + yield [ + 'DeprecatedAttributePropertyHooks\\Foo', + 'i', + 'get', + TrinaryLogic::createNo(), + null, + ]; + yield [ + 'DeprecatedAttributePropertyHooks\\Foo', + 'j', + 'get', + TrinaryLogic::createYes(), + null, + ]; + yield [ + 'DeprecatedAttributePropertyHooks\\Foo', + 'k', + 'get', + TrinaryLogic::createYes(), + 'msg', + ]; + yield [ + 'DeprecatedAttributePropertyHooks\\Foo', + 'l', + 'get', + TrinaryLogic::createYes(), + 'msg2', + ]; + } + + /** + * @dataProvider dataDeprecatedAttributeAbovePropertyHook + * @param 'get'|'set' $hookName + */ + public function testDeprecatedAttributeAbovePropertyHook(string $className, string $propertyName, string $hookName, TrinaryLogic $isDeprecated, ?string $deprecatedDescription): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); + $property = $class->getNativeProperty($propertyName); + $hook = $property->getHook($hookName); + $this->assertSame($isDeprecated->describe(), $hook->isDeprecated()->describe()); + $this->assertSame($deprecatedDescription, $hook->getDeprecatedDescription()); + } + } diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php index ce520ef7aa3..337713a18a5 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php @@ -6,10 +6,12 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InFunctionNode; +use PHPStan\Node\InPropertyHookNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Testing\RuleTestCase; use function sprintf; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -18,15 +20,15 @@ class DeprecatedAttributePhpFunctionFromParserReflectionRuleTest extends RuleTes { /** - * @return Rule + * @return Rule */ protected function getRule(): Rule { - return new /** @implements Rule */ class implements Rule { + return new /** @implements Rule */ class implements Rule { public function getNodeType(): string { - return Node\Stmt::class; + return Node::class; } public function processNode(Node $node, Scope $scope): array @@ -35,6 +37,8 @@ public function processNode(Node $node, Scope $scope): array $reflection = $node->getFunctionReflection(); } elseif ($node instanceof InClassMethodNode) { $reflection = $node->getMethodReflection(); + } elseif ($node instanceof InPropertyHookNode) { + $reflection = $node->getHookReflection(); } else { return []; } @@ -108,4 +112,30 @@ public function testMethodRule(): void ]); } + public function testPropertyHookRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/deprecated-attribute-property-hooks.php'], [ + [ + 'Not deprecated', + 11, + ], + [ + 'Deprecated', + 17, + ], + [ + 'Deprecated: msg', + 24, + ], + [ + 'Deprecated: msg2', + 31, + ], + ]); + } + } diff --git a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php new file mode 100644 index 00000000000..3caf94adf36 --- /dev/null +++ b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php @@ -0,0 +1,37 @@ += 8.4 + +namespace DeprecatedAttributePropertyHooks; + +use Deprecated; + +class Foo +{ + + public int $i { + get { + return 1; + } + } + + public int $j { + #[Deprecated] + get { + return 1; + } + } + + public int $k { + #[Deprecated('msg')] + get { + return 1; + } + } + + public int $l { + #[Deprecated(since: '1.0', message: 'msg2')] + get { + return 1; + } + } + +} From 8f9e4ba3528056e5c3da4794520e14e830c7b12c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 13:46:16 +0100 Subject: [PATCH 0932/3097] Support magic `__PROPERTY__` constant in hooks --- src/Analyser/NodeScopeResolver.php | 2 +- src/Reflection/InitializerExprContext.php | 54 +++++++++++++++---- .../InitializerExprTypeResolver.php | 9 ++++ .../PHPStan/Analyser/nsrt/property-hooks.php | 18 +++++++ .../Annotations/DeprecatedAnnotationsTest.php | 7 +++ ...hpFunctionFromParserReflectionRuleTest.php | 4 ++ .../deprecated-attribute-property-hooks.php | 7 +++ 7 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1639a125224..3bc323341c6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1988,7 +1988,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod|Node\PropertyHook $stmt): array { $initializerExprContext = InitializerExprContext::fromStubParameter( - null, + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->getFile(), $stmt, ); diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index f1587ef242b..eb64cacdbb9 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -9,6 +9,8 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\BetterReflection\Reflection\ReflectionConstant; +use PHPStan\Parser\PropertyHookNameVisitor; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\ShouldNotHappenException; use function array_slice; use function count; @@ -32,21 +34,25 @@ private function __construct( private ?string $traitName, private ?string $function, private ?string $method, + private ?string $property, ) { } public static function fromScope(Scope $scope): self { + $function = $scope->getFunction(); + return new self( $scope->getFile(), $scope->getNamespace(), $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() !== null ? $scope->getFunction()->getName() : null), - $scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() instanceof MethodReflection - ? sprintf('%s::%s', $scope->getFunction()->getDeclaringClass()->getName(), $scope->getFunction()->getName()) - : ($scope->getFunction() instanceof FunctionReflection ? $scope->getFunction()->getName() : null)), + $scope->isInAnonymousFunction() ? '{closure}' : ($function !== null ? $function->getName() : null), + $scope->isInAnonymousFunction() ? '{closure}' : ($function instanceof MethodReflection + ? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) + : ($function instanceof FunctionReflection ? $function->getName() : null)), + $function instanceof PhpMethodFromParserNodeReflection && $function->isPropertyHook() ? $function->getHookedPropertyName() : null, ); } @@ -81,6 +87,7 @@ public static function fromClass(string $className, ?string $fileName): self null, null, null, + null, ); } @@ -96,6 +103,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter): null, $declaringFunction->getName(), $declaringFunction->getName(), + null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that ); } @@ -110,6 +118,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter): $betterReflection->getDeclaringClass()->isTrait() ? $betterReflection->getDeclaringClass()->getName() : null, $declaringFunction->getName(), sprintf('%s::%s', $declaringFunction->getDeclaringClass()->getName(), $declaringFunction->getName()), + null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that ); } @@ -127,15 +136,36 @@ public static function fromStubParameter( $namespace = self::parseNamespace($function->namespacedName->toString()); } } + + $functionName = null; + $propertyName = null; + if ($function instanceof Function_ && $function->namespacedName !== null) { + $functionName = $function->namespacedName->toString(); + } elseif ($function instanceof ClassMethod) { + $functionName = $function->name->toString(); + } elseif ($function instanceof PropertyHook) { + $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $functionName = sprintf('$%s::%s', $propertyName, $function->name->toString()); + } + + $methodName = null; + if ($function instanceof ClassMethod && $className !== null) { + $methodName = sprintf('%s::%s', $className, $function->name->toString()); + } elseif ($function instanceof PropertyHook) { + $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString()); + } elseif ($function instanceof Function_ && $function->namespacedName !== null) { + $methodName = $function->namespacedName->toString(); + } + return new self( $stubFile, $namespace, $className, null, - $function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : ($function instanceof ClassMethod ? $function->name->toString() : null), - $function instanceof ClassMethod && $className !== null - ? sprintf('%s::%s', $className, $function->name->toString()) - : ($function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : null), + $functionName, + $methodName, + $propertyName, ); } @@ -148,12 +178,13 @@ public static function fromGlobalConstant(ReflectionConstant $constant): self null, null, null, + null, ); } public static function createEmpty(): self { - return new self(null, null, null, null, null, null); + return new self(null, null, null, null, null, null, null); } public function getFile(): ?string @@ -186,4 +217,9 @@ public function getMethod(): ?string return $this->method; } + public function getProperty(): ?string + { + return $this->property; + } + } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index fc8ad21c17e..9fef24a5875 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -392,6 +392,15 @@ public function getType(Expr $expr, InitializerExprContext $context): Type return new ConstantStringType($context->getTraitName(), true); } + if ($expr instanceof MagicConst\Property) { + $contextProperty = $context->getProperty(); + if ($contextProperty === null) { + return new ConstantStringType(''); + } + + return new ConstantStringType($contextProperty); + } + if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) { $fetchedOnType = $this->getType($expr->var, $context); if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php index 4a7f0d9f3a0..8e32e4c96d7 100644 --- a/tests/PHPStan/Analyser/nsrt/property-hooks.php +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -357,3 +357,21 @@ public function doFoo3(): void } } + +class MagicConstants +{ + + public int $i { + get { + assertType("'\$i::get'", __FUNCTION__); + assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::get'", __METHOD__); + assertType("'i'", __PROPERTY__); + } + set { + assertType("'\$i::set'", __FUNCTION__); + assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::set'", __METHOD__); + assertType("'i'", __PROPERTY__); + } + } + +} diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 8daa5fc1ee2..be18a8fb4b1 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -372,6 +372,13 @@ public function dataDeprecatedAttributeAbovePropertyHook(): iterable TrinaryLogic::createYes(), 'msg2', ]; + yield [ + 'DeprecatedAttributePropertyHooks\\Foo', + 'm', + 'get', + TrinaryLogic::createYes(), + '$m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m', + ]; } /** diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php index 337713a18a5..efdfaece702 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php @@ -135,6 +135,10 @@ public function testPropertyHookRule(): void 'Deprecated: msg2', 31, ], + [ + 'Deprecated: $m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m', + 38, + ], ]); } diff --git a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php index 3caf94adf36..0da2fbe4e53 100644 --- a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php +++ b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php @@ -34,4 +34,11 @@ class Foo } } + public int $m { + #[Deprecated(message: __FUNCTION__ . '+' . __METHOD__ . '+' . __PROPERTY__)] + get { + return 1; + } + } + } From ea47edfc6790be93df7429c7f339c378d6788582 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Dec 2024 16:03:54 +0100 Subject: [PATCH 0933/3097] Test ContinueBreakInLoopRule for property hooks --- Makefile | 1 + .../Keywords/ContinueBreakInLoopRule.php | 6 +- .../Keywords/ContinueBreakInLoopRuleTest.php | 31 ++++++ .../data/continue-break-property-hook.php | 102 ++++++++++++++++++ 4 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php diff --git a/Makefile b/Makefile index 8a8077a3e1b..fc4a0fe42ec 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ lint: --exclude tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php \ --exclude tests/PHPStan/Levels/data/namedArguments.php \ --exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \ + --exclude tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php \ --exclude tests/PHPStan/Rules/Properties/data/invalid-callable-property-type.php \ --exclude tests/PHPStan/Rules/Properties/data/properties-in-interface.php \ --exclude tests/PHPStan/Rules/Properties/data/read-only-property.php \ diff --git a/src/Rules/Keywords/ContinueBreakInLoopRule.php b/src/Rules/Keywords/ContinueBreakInLoopRule.php index 75657f232f1..4f421e5a6ce 100644 --- a/src/Rules/Keywords/ContinueBreakInLoopRule.php +++ b/src/Rules/Keywords/ContinueBreakInLoopRule.php @@ -39,11 +39,7 @@ public function processNode(Node $node, Scope $scope): array if ($parentStmtType === Stmt\Case_::class) { continue; } - if ( - $parentStmtType === Stmt\Function_::class - || $parentStmtType === Stmt\ClassMethod::class - || $parentStmtType === Node\Expr\Closure::class - ) { + if ($parentStmtType === Node\Expr\Closure::class) { return [ RuleErrorBuilder::message(sprintf( 'Keyword %s used outside of a loop or a switch statement.', diff --git a/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php b/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php index 493e23592b9..bca307593c7 100644 --- a/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php +++ b/tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -46,4 +47,34 @@ public function testRule(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/continue-break-property-hook.php'], [ + [ + 'Keyword break used outside of a loop or a switch statement.', + 13, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 15, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 24, + ], + [ + 'Keyword continue used outside of a loop or a switch statement.', + 26, + ], + [ + 'Keyword break used outside of a loop or a switch statement.', + 35, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php b/tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php new file mode 100644 index 00000000000..2cc1ba297bf --- /dev/null +++ b/tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php @@ -0,0 +1,102 @@ += 8.4 + +namespace ContinueBreakPropertyHook; + +class Foo +{ + + public int $bar { + set (int $foo) { + foreach ([1, 2, 3] as $val) { + switch ($foo) { + case 1: + break 3; + default: + break 3; + } + } + } + } + + public int $baz { + get { + if (rand(0, 1)) { + break; + } else { + continue; + } + } + } + + public int $ipsum { + get { + foreach ([1, 2, 3] as $val) { + function (): void { + break; + }; + } + } + } + +} + +class ValidUsages +{ + + public int $i { + set (int $foo) { + switch ($foo) { + case 1: + break; + default: + break; + } + + foreach ([1, 2, 3] as $val) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } + + for ($i = 0; $i < 5; $i++) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } + + while (true) { + if (rand(0, 1)) { + break; + } else { + continue; + } + } + + do { + if (rand(0, 1)) { + break; + } else { + continue; + } + } while (true); + } + } + + public int $j { + set (int $foo) { + foreach ([1, 2, 3] as $val) { + switch ($foo) { + case 1: + break 2; + default: + break 2; + } + } + } + } + +} From 4ba8fcb346b0b6210c90b66b5dfc4d57d41b2091 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 10:43:04 +0100 Subject: [PATCH 0934/3097] PropertyHookAttributesRule - level 0 --- conf/config.level0.neon | 1 + .../Properties/PropertyHookAttributesRule.php | 37 +++++++++++ .../PropertyHookAttributesRuleTest.php | 62 +++++++++++++++++++ .../data/property-hook-attributes.php | 57 +++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 src/Rules/Properties/PropertyHookAttributesRule.php create mode 100644 tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-hook-attributes.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index c84cf8f5f79..1a46352922c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -97,6 +97,7 @@ rules: - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\PropertiesInInterfaceRule - PHPStan\Rules\Properties\PropertyAttributesRule + - PHPStan\Rules\Properties\PropertyHookAttributesRule - PHPStan\Rules\Properties\PropertyInClassRule - PHPStan\Rules\Properties\ReadOnlyPropertyRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyRule diff --git a/src/Rules/Properties/PropertyHookAttributesRule.php b/src/Rules/Properties/PropertyHookAttributesRule.php new file mode 100644 index 00000000000..bd4968e8bfe --- /dev/null +++ b/src/Rules/Properties/PropertyHookAttributesRule.php @@ -0,0 +1,37 @@ + + */ +final class PropertyHookAttributesRule implements Rule +{ + + public function __construct(private AttributesCheck $attributesCheck) + { + } + + public function getNodeType(): string + { + return InPropertyHookNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->getOriginalNode()->attrGroups, + Attribute::TARGET_METHOD, + 'method', + ); + } + +} diff --git a/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php new file mode 100644 index 00000000000..5f627c1902f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php @@ -0,0 +1,62 @@ + + */ +class PropertyHookAttributesRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new PropertyHookAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new NullsafeCheck(), + new UnresolvableTypeHelper(), + new PropertyReflectionFinder(), + true, + true, + true, + true, + ), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, false), + new ClassForbiddenNameCheck(self::getContainer()), + ), + true, + ), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/property-hook-attributes.php'], [ + [ + 'Attribute class PropertyHookAttributes\Foo does not have the method target.', + 27, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/property-hook-attributes.php b/tests/PHPStan/Rules/Properties/data/property-hook-attributes.php new file mode 100644 index 00000000000..495cc793b05 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-hook-attributes.php @@ -0,0 +1,57 @@ += 8.4 + +namespace PropertyHookAttributes; + +#[\Attribute(\Attribute::TARGET_CLASS)] +class Foo +{ + +} + +#[\Attribute(\Attribute::TARGET_METHOD)] +class Bar +{ + +} + +#[\Attribute(\Attribute::TARGET_ALL)] +class Baz +{ + +} + +class Lorem +{ + + public int $i { + #[Foo] + get { + + } + } + +} + +class Ipsum +{ + + public int $i { + #[Bar] + get { + + } + } + +} + +class Dolor +{ + + public int $i { + #[Baz] + get { + + } + } + +} From c5c5839fe9893134e9e89490ce047ff8958095ee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 11:24:21 +0100 Subject: [PATCH 0935/3097] Hooked properties can throw custom exceptions --- src/Analyser/NodeScopeResolver.php | 100 +++++++++ .../AbilityToDisableImplicitThrowsTest.php | 39 ++++ .../CatchWithUnthrownExceptionRuleTest.php | 42 ++++ .../Rules/Exceptions/data/bug-5903.php | 2 +- .../Rules/Exceptions/data/bug-6791.php | 2 +- .../Exceptions/data/union-type-error.php | 2 +- ...roperty-hooks-implicit-throws-disabled.php | 120 +++++++++++ .../unthrown-exception-property-hooks.php | 200 ++++++++++++++++++ 8 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks-implicit-throws-disabled.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3bc323341c6..7339f0a0424 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -151,6 +151,7 @@ use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\Php\PhpMethodReflection; +use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; @@ -2973,6 +2974,7 @@ static function (): void { $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); } elseif ($expr instanceof PropertyFetch) { + $scopeBeforeVar = $scope; $result = $this->processExprNode($stmt, $expr->var, $scope, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); $throwPoints = $result->getThrowPoints(); @@ -2984,6 +2986,20 @@ static function (): void { $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); $scope = $result->getScope(); + if ($this->phpVersion->supportsPropertyHooks()) { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + } + } else { + $propertyName = $expr->name->toString(); + $propertyHolderType = $scopeBeforeVar->getType($expr->var); + $propertyReflection = $scopeBeforeVar->getPropertyReflection($propertyHolderType, $propertyName); + if ($propertyReflection !== null) { + $propertyDeclaringClass = $propertyReflection->getDeclaringClass(); + if ($propertyDeclaringClass->hasNativeProperty($propertyName)) { + $nativeProperty = $propertyDeclaringClass->getNativeProperty($propertyName); + $throwPoints = array_merge($throwPoints, $this->getPropertyReadThrowPointsFromGetHook($scopeBeforeVar, $expr, $nativeProperty)); + } + } } } elseif ($expr instanceof Expr\NullsafePropertyFetch) { $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); @@ -4224,6 +4240,83 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P return null; } + /** + * @return ThrowPoint[] + */ + private function getPropertyReadThrowPointsFromGetHook( + MutatingScope $scope, + PropertyFetch $propertyFetch, + PhpPropertyReflection $propertyReflection, + ): array + { + return $this->getThrowPointsFromPropertyHook($scope, $propertyFetch, $propertyReflection, 'get'); + } + + /** + * @return ThrowPoint[] + */ + private function getPropertyAssignThrowPointsFromSetHook( + MutatingScope $scope, + PropertyFetch $propertyFetch, + PhpPropertyReflection $propertyReflection, + ): array + { + return $this->getThrowPointsFromPropertyHook($scope, $propertyFetch, $propertyReflection, 'set'); + } + + /** + * @param 'get'|'set' $hookName + * @return ThrowPoint[] + */ + private function getThrowPointsFromPropertyHook( + MutatingScope $scope, + PropertyFetch $propertyFetch, + PhpPropertyReflection $propertyReflection, + string $hookName, + ): array + { + $scopeFunction = $scope->getFunction(); + if ( + $scopeFunction instanceof PhpMethodFromParserNodeReflection + && $scopeFunction->isPropertyHook() + && $propertyFetch->var instanceof Variable + && $propertyFetch->var->name === 'this' + && $propertyFetch->name instanceof Identifier + && $propertyFetch->name->toString() === $scopeFunction->getHookedPropertyName() + ) { + return []; + } + $declaringClass = $propertyReflection->getDeclaringClass(); + if (!$propertyReflection->hasHook($hookName)) { + if ( + $propertyReflection->isPrivate() + || $propertyReflection->isFinal()->yes() + || $declaringClass->isFinal() + ) { + return []; + } + + if ($this->implicitThrows) { + return [ThrowPoint::createImplicit($scope, $propertyFetch)]; + } + + return []; + } + + $getHook = $propertyReflection->getHook($hookName); + $throwType = $getHook->getThrowType(); + + if ($throwType !== null) { + if (!$throwType->isVoid()->yes()) { + return [ThrowPoint::createExplicit($scope, $throwType, $propertyFetch, true)]; + } + } elseif ($this->implicitThrows) { + return [ThrowPoint::createImplicit($scope, $propertyFetch)]; + } + + return []; + } + /** * @return string[] */ @@ -5408,6 +5501,10 @@ static function (): void { $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); $scope = $result->getScope(); + if ($var->name instanceof Expr && $this->phpVersion->supportsPropertyHooks()) { + $throwPoints[] = ThrowPoint::createImplicit($scope, $var); + } + $propertyHolderType = $scope->getType($var->var); if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { $propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); @@ -5424,6 +5521,9 @@ static function (): void { ) { $throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $assignedExpr, false); } + if ($this->phpVersion->supportsPropertyHooks()) { + $throwPoints = array_merge($throwPoints, $this->getPropertyAssignThrowPointsFromSetHook($scope, $var, $nativeProperty)); + } if ($enterExpressionAssign) { $scope = $scope->assignInitializedProperty($propertyHolderType, $propertyName); } diff --git a/tests/PHPStan/Rules/Exceptions/AbilityToDisableImplicitThrowsTest.php b/tests/PHPStan/Rules/Exceptions/AbilityToDisableImplicitThrowsTest.php index 08f48850674..33a117fd17e 100644 --- a/tests/PHPStan/Rules/Exceptions/AbilityToDisableImplicitThrowsTest.php +++ b/tests/PHPStan/Rules/Exceptions/AbilityToDisableImplicitThrowsTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function array_merge; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -33,6 +34,44 @@ public function testRule(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/unthrown-exception-property-hooks-implicit-throws-disabled.php'], [ + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 23, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 38, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 53, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 68, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 74, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 94, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooksImplicitThrowsDisabled\MyCustomException is never thrown in the try block.', + 115, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return array_merge( diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 834bac42ff7..6eacf1535d4 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -612,4 +612,46 @@ public function testBug9568(): void $this->analyse([__DIR__ . '/data/bug-9568.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + self::markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/unthrown-exception-property-hooks.php'], [ + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 27, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\SomeException is never thrown in the try block.', + 39, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 53, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\SomeException is never thrown in the try block.', + 65, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 107, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 128, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 154, + ], + [ + 'Dead catch - UnthrownExceptionPropertyHooks\MyCustomException is never thrown in the try block.', + 175, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-5903.php b/tests/PHPStan/Rules/Exceptions/data/bug-5903.php index b4c12e38776..0300b6ecc87 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-5903.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-5903.php @@ -2,7 +2,7 @@ namespace Bug5903; -class Test +final class Test { /** @var \Traversable */ protected $traversable; diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-6791.php b/tests/PHPStan/Rules/Exceptions/data/bug-6791.php index 73b9f59106a..300aad76b20 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-6791.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-6791.php @@ -2,7 +2,7 @@ namespace Bug6791; -class Foo { +final class Foo { /** @var int[] */ public array $intArray; /** @var \Ds\Set */ diff --git a/tests/PHPStan/Rules/Exceptions/data/union-type-error.php b/tests/PHPStan/Rules/Exceptions/data/union-type-error.php index 1c16fec53d2..cad8c5348ca 100644 --- a/tests/PHPStan/Rules/Exceptions/data/union-type-error.php +++ b/tests/PHPStan/Rules/Exceptions/data/union-type-error.php @@ -4,7 +4,7 @@ namespace UnionTypeError; -class Foo { +final class Foo { public string|int $stringOrInt; public string|array $stringOrArray; diff --git a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks-implicit-throws-disabled.php b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks-implicit-throws-disabled.php new file mode 100644 index 00000000000..6d47003630a --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks-implicit-throws-disabled.php @@ -0,0 +1,120 @@ += 8.4 + +namespace UnthrownExceptionPropertyHooksImplicitThrowsDisabled; + +class MyCustomException extends \Exception +{ + +} + +class SomeException extends \Exception +{ + +} + +class Foo +{ + public int $i; + + public function doFoo(): void + { + try { + echo $this->i; + } catch (MyCustomException) { // unthrown - implicit @throws disabled + + } + } + + public int $k { + get { + return 1; + } + } + + public function doBaz(): void + { + try { + echo $this->k; + } catch (MyCustomException) { // unthrown - implicit @throws disabled + + } + } + + private int $l { + get { + return $this->l; + } + } + + public function doLorem(): void + { + try { + echo $this->l; + } catch (MyCustomException) { // unthrown - implicit @throws disabled + + } + } + + final public int $m { + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // unthrown - implicit @throws disabled + + } + + try { + $this->m = 1; + } catch (MyCustomException) { // unthrown - set hook does not exist + + } + } + +} + +final class FinalFoo +{ + + public int $m { + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // unthrown - implicit @throws disabled + + } + } + +} + +class ThrowsVoid +{ + + public int $m { + /** @throws void */ + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // unthrown + + } + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks.php b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks.php new file mode 100644 index 00000000000..547a47eece4 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception-property-hooks.php @@ -0,0 +1,200 @@ += 8.4 + +namespace UnthrownExceptionPropertyHooks; + +class MyCustomException extends \Exception +{ + +} + +class SomeException extends \Exception +{ + +} + +class Foo +{ + + public int $i { + /** @throws MyCustomException */ + get { + if (rand(0, 1)) { + throw new MyCustomException(); + } + + try { + return $this->i; + } catch (MyCustomException) { // unthrown - @throws does not apply to direct access in the hook + + } + } + } + + public function doFoo(): void + { + try { + $a = $this->i; + } catch (MyCustomException) { + + } catch (SomeException) { // unthrown + + } + } + + public int $j { + /** @throws MyCustomException */ + set { + if (rand(0, 1)) { + throw new MyCustomException(); + } + + try { + $this->j = $value; + } catch (MyCustomException) { // unthrown - @throws does not apply to direct access in the hook + + } + } + } + + public function doBar(int $v): void + { + try { + $this->j = $v; + } catch (MyCustomException) { + + } catch (SomeException) { // unthrown + + } + } + + public int $k { + get { + return 1; + } + } + + public function doBaz(): void + { + try { + echo $this->k; + } catch (MyCustomException) { // can be thrown - implicit @throws + + } + + try { + $this->k = 1; + } catch (MyCustomException) { // can be thrown - subclass might introduce a set hook + + } + } + + private int $l { + get { + return $this->l; + } + } + + public function doLorem(): void + { + try { + echo $this->l; + } catch (MyCustomException) { // can be thrown - implicit @throws + + } + + try { + $this->l = 1; + } catch (MyCustomException) { // unthrown - set hook does not exist + + } + } + + final public int $m { + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // can be thrown - implicit @throws + + } + + try { + $this->m = 1; + } catch (MyCustomException) { // unthrown - set hook does not exist + + } + } + +} + +final class FinalFoo +{ + + public int $m { + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // can be thrown - implicit @throws + + } + + try { + $this->m = 1; + } catch (MyCustomException) { // unthrown - set hook does not exist + + } + } + +} + +class ThrowsVoid +{ + + public int $m { + /** @throws void */ + get { + return $this->m; + } + } + + public function doIpsum(): void + { + try { + echo $this->m; + } catch (MyCustomException) { // unthrown + + } + } + +} + +class Dynamic +{ + + public function doFoo(object $o, string $s): void + { + try { + echo $o->$s; + } catch (MyCustomException) { // implicit throw point + + } + + try { + $o->$s = 1; + } catch (MyCustomException) { // implicit throw point + + } + } + +} From 691994e1b7147b1320bb6da2fde3cf599c18fd22 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 13:58:29 +0100 Subject: [PATCH 0936/3097] TooWidePropertyHookThrowTypeRule - level 4 --- conf/config.level4.neon | 5 ++ .../TooWidePropertyHookThrowTypeRule.php | 74 +++++++++++++++++ .../TooWidePropertyHookThrowTypeRuleTest.php | 49 +++++++++++ .../data/too-wide-throws-property-hook.php | 81 +++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 src/Rules/Exceptions/TooWidePropertyHookThrowTypeRule.php create mode 100644 tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index bda46632c20..b026238cfb0 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -31,6 +31,8 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.tooWideThrowType% PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule: phpstan.rules.rule: %exceptions.check.tooWideThrowType% + PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule: + phpstan.rules.rule: %exceptions.check.tooWideThrowType% parameters: checkAdvancedIsset: true @@ -241,6 +243,9 @@ services: - class: PHPStan\Rules\Exceptions\TooWideMethodThrowTypeRule + - + class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule + - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/src/Rules/Exceptions/TooWidePropertyHookThrowTypeRule.php b/src/Rules/Exceptions/TooWidePropertyHookThrowTypeRule.php new file mode 100644 index 00000000000..00ed4bacd42 --- /dev/null +++ b/src/Rules/Exceptions/TooWidePropertyHookThrowTypeRule.php @@ -0,0 +1,74 @@ + + */ +final class TooWidePropertyHookThrowTypeRule implements Rule +{ + + public function __construct(private FileTypeMapper $fileTypeMapper, private TooWideThrowTypeCheck $check) + { + } + + public function getNodeType(): string + { + return PropertyHookReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $statementResult = $node->getStatementResult(); + $hookReflection = $node->getHookReflection(); + if ($hookReflection->getPropertyHookName() === null) { + throw new ShouldNotHappenException(); + } + + $classReflection = $node->getClassReflection(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $hookReflection->getName(), + $docComment->getText(), + ); + + if ($resolvedPhpDoc->getThrowsTag() === null) { + return []; + } + + $throwType = $resolvedPhpDoc->getThrowsTag()->getType(); + + $errors = []; + foreach ($this->check->check($throwType, $statementResult->getThrowPoints()) as $throwClass) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s hook for property %s::$%s has %s in PHPDoc @throws tag but it\'s not thrown.', + ucfirst($hookReflection->getPropertyHookName()), + $hookReflection->getDeclaringClass()->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $throwClass, + )) + ->identifier('throws.unusedType') + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php new file mode 100644 index 00000000000..0c3d0f75a14 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -0,0 +1,49 @@ + + */ +class TooWidePropertyHookThrowTypeRuleTest extends RuleTestCase +{ + + private bool $implicitThrows = true; + + protected function getRule(): Rule + { + return new TooWidePropertyHookThrowTypeRule(self::getContainer()->getByType(FileTypeMapper::class), new TooWideThrowTypeCheck($this->implicitThrows)); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/too-wide-throws-property-hook.php'], [ + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$d has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 33, + ], + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$g has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 58, + ], + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$h has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 68, + ], + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$j has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 76, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php new file mode 100644 index 00000000000..92998bafa45 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php @@ -0,0 +1,81 @@ += 8.4 + +namespace TooWideThrowsPropertyHook; + +use DomainException; + +class Foo +{ + + public int $a { + /** @throws \InvalidArgumentException */ + get { + throw new \InvalidArgumentException(); + } + } + + public int $b { + /** @throws \LogicException */ + get { + throw new \InvalidArgumentException(); + } + } + + public int $c { + /** @throws \InvalidArgumentException */ + get { + throw new \LogicException(); + } + } + + public int $d { + /** @throws \InvalidArgumentException|\DomainException */ + get { // error - DomainException unused + throw new \InvalidArgumentException(); + } + } + + public int $e { + /** @throws void */ + get { // ok - picked up by different rule + throw new \InvalidArgumentException(); + } + } + + public int $f { + /** @throws \InvalidArgumentException|\DomainException */ + get { + if (rand(0, 1)) { + throw new \InvalidArgumentException(); + } + + throw new DomainException(); + } + } + + public int $g { + /** @throws \DomainException */ + get { // error - DomainException unused + throw new \InvalidArgumentException(); + } + } + + public int $h { + /** + * @throws \InvalidArgumentException + * @throws \DomainException + */ + get { // error - DomainException unused + throw new \InvalidArgumentException(); + } + } + + + public int $j { + /** @throws \DomainException */ + get { // error - DomainException unused + + } + } + +} From e0a6b9a7e39755405f58f4fbb66dc2c52499f670 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 14:35:01 +0100 Subject: [PATCH 0937/3097] ThrowsVoidPropertyHookWithExplicitThrowPointRule - level 3 --- conf/config.level3.neon | 8 ++ ...PropertyHookWithExplicitThrowPointRule.php | 79 ++++++++++++++ ...ertyHookWithExplicitThrowPointRuleTest.php | 103 ++++++++++++++++++ .../data/throws-void-property-hook.php | 22 ++++ 4 files changed, 212 insertions(+) create mode 100644 src/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRule.php create mode 100644 tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index b7d1a4c15e0..c946a5ee3fe 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -69,6 +69,14 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Exceptions\ThrowsVoidPropertyHookWithExplicitThrowPointRule + arguments: + exceptionTypeResolver: @exceptionTypeResolver + missingCheckedExceptionInThrows: %exceptions.check.missingCheckedExceptionInThrows% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Generators\YieldFromTypeRule arguments: diff --git a/src/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRule.php new file mode 100644 index 00000000000..71b7cd9c2de --- /dev/null +++ b/src/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRule.php @@ -0,0 +1,79 @@ + + */ +final class ThrowsVoidPropertyHookWithExplicitThrowPointRule implements Rule +{ + + public function __construct( + private ExceptionTypeResolver $exceptionTypeResolver, + private bool $missingCheckedExceptionInThrows, + ) + { + } + + public function getNodeType(): string + { + return PropertyHookReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $hookReflection = $node->getHookReflection(); + + if ($hookReflection->getThrowType() === null || !$hookReflection->getThrowType()->isVoid()->yes()) { + return []; + } + + if ($hookReflection->getPropertyHookName() === null) { + throw new ShouldNotHappenException(); + } + + $errors = []; + foreach ($statementResult->getThrowPoints() as $throwPoint) { + if (!$throwPoint->isExplicit()) { + continue; + } + + foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { + $isCheckedException = TrinaryLogic::createFromBoolean($this->missingCheckedExceptionInThrows)->lazyAnd( + $throwPointType->getObjectClassNames(), + fn (string $objectClassName) => TrinaryLogic::createFromBoolean($this->exceptionTypeResolver->isCheckedException($objectClassName, $throwPoint->getScope())), + ); + if ($isCheckedException->yes()) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + '%s hook for property %s::$%s throws exception %s but the PHPDoc contains @throws void.', + ucfirst($hookReflection->getPropertyHookName()), + $hookReflection->getDeclaringClass()->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $throwPointType->describe(VerbosityLevel::typeOnly()), + )) + ->line($throwPoint->getNode()->getStartLine()) + ->identifier('throws.void') + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php new file mode 100644 index 00000000000..fecb9cfdc52 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php @@ -0,0 +1,103 @@ + + */ +class ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest extends RuleTestCase +{ + + private bool $missingCheckedExceptionInThrows; + + /** @var string[] */ + private array $checkedExceptionClasses; + + protected function getRule(): Rule + { + return new ThrowsVoidPropertyHookWithExplicitThrowPointRule(new DefaultExceptionTypeResolver( + $this->createReflectionProvider(), + [], + [], + [], + $this->checkedExceptionClasses, + ), $this->missingCheckedExceptionInThrows); + } + + public function dataRule(): array + { + return [ + [ + true, + [], + [], + ], + [ + false, + ['DifferentException'], + [ + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + true, + ['ThrowsVoidPropertyHook\\MyException'], + [], + ], + [ + true, + ['DifferentException'], + [ + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + false, + [], + [ + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + false, + ['ThrowsVoidPropertyHook\\MyException'], + [ + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param string[] $checkedExceptionClasses + * @param list $errors + */ + public function testRule(bool $missingCheckedExceptionInThrows, array $checkedExceptionClasses, array $errors): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; + $this->checkedExceptionClasses = $checkedExceptionClasses; + $this->analyse([__DIR__ . '/data/throws-void-property-hook.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php b/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php new file mode 100644 index 00000000000..82c4c2381f5 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php @@ -0,0 +1,22 @@ += 8.4 + +namespace ThrowsVoidPropertyHook; + +class MyException extends \Exception +{ + +} + +class Foo +{ + + public int $i { + /** + * @throws void + */ + get { + throw new MyException(); + } + } + +} From 31cfe22331d1aec0b873d2ecf5e98357b5d9b507 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Dec 2024 14:50:33 +0100 Subject: [PATCH 0938/3097] MissingCheckedExceptionInPropertyHookThrowsRule --- conf/config.neon | 5 ++ ...eckedExceptionInPropertyHookThrowsRule.php | 55 +++++++++++++++++++ ...dExceptionInPropertyHookThrowsRuleTest.php | 51 +++++++++++++++++ ...missing-exception-property-hook-throws.php | 42 ++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 src/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRule.php create mode 100644 tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php diff --git a/conf/config.neon b/conf/config.neon index 1501a7a253e..ec1c876661d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -209,6 +209,8 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% + PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule: + phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% services: - @@ -906,6 +908,9 @@ services: - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule + - + class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule + - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInThrowsCheck arguments: diff --git a/src/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRule.php b/src/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRule.php new file mode 100644 index 00000000000..d9b7a6b864e --- /dev/null +++ b/src/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRule.php @@ -0,0 +1,55 @@ + + */ +final class MissingCheckedExceptionInPropertyHookThrowsRule implements Rule +{ + + public function __construct(private MissingCheckedExceptionInThrowsCheck $check) + { + } + + public function getNodeType(): string + { + return PropertyHookReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $statementResult = $node->getStatementResult(); + $hookReflection = $node->getHookReflection(); + + if (!$hookReflection->isPropertyHook()) { + throw new ShouldNotHappenException(); + } + + $errors = []; + foreach ($this->check->check($hookReflection->getThrowType(), $statementResult->getThrowPoints()) as [$className, $throwPointNode]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s hook for property %s::$%s throws checked exception %s but it\'s missing from the PHPDoc @throws tag.', + ucfirst($hookReflection->getPropertyHookName()), + $hookReflection->getDeclaringClass()->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $className, + )) + ->line($throwPointNode->getStartLine()) + ->identifier('missingType.checkedException') + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php new file mode 100644 index 00000000000..e7f6130d670 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php @@ -0,0 +1,51 @@ + + */ +class MissingCheckedExceptionInPropertyHookThrowsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new MissingCheckedExceptionInPropertyHookThrowsRule( + new MissingCheckedExceptionInThrowsCheck(new DefaultExceptionTypeResolver( + $this->createReflectionProvider(), + [], + [ShouldNotHappenException::class], + [], + [], + )), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/missing-exception-property-hook-throws.php'], [ + [ + 'Get hook for property MissingExceptionPropertyHookThrows\Foo::$k throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 25, + ], + [ + 'Set hook for property MissingExceptionPropertyHookThrows\Foo::$l throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 32, + ], + [ + 'Get hook for property MissingExceptionPropertyHookThrows\Foo::$m throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 38, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php b/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php new file mode 100644 index 00000000000..d9fba8d0f1e --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php @@ -0,0 +1,42 @@ += 8.4 + +namespace MissingExceptionPropertyHookThrows; + +class Foo +{ + + public int $i { + /** @throws \InvalidArgumentException */ + get { + throw new \InvalidArgumentException(); // ok + } + } + + public int $j { + /** @throws \LogicException */ + set { + throw new \InvalidArgumentException(); // ok + } + } + + public int $k { + /** @throws \RuntimeException */ + get { + throw new \InvalidArgumentException(); // error + } + } + + public int $l { + /** @throws \RuntimeException */ + set { + throw new \InvalidArgumentException(); // error + } + } + + public int $m { + get { + throw new \InvalidArgumentException(); // error + } + } + +} From b775f8db3118b7a50c591ca78e8e4c08c47ec5d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 13:51:30 +0100 Subject: [PATCH 0939/3097] Adjust InvalidThrowsPhpDocValueRule for property hooks --- .../PhpDoc/InvalidThrowsPhpDocValueRule.php | 14 ++++++++---- .../InvalidThrowsPhpDocValueRuleTest.php | 15 +++++++++++++ .../data/invalid-throws-property-hook.php | 22 +++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/invalid-throws-property-hook.php diff --git a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php index 087c89b6ed1..33a2e120c3d 100644 --- a/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php +++ b/src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php @@ -3,7 +3,9 @@ namespace PHPStan\Rules\PhpDoc; use PhpParser\Node; +use PhpParser\NodeAbstract; use PHPStan\Analyser\Scope; +use PHPStan\Node\InPropertyHookNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\FileTypeMapper; @@ -16,7 +18,7 @@ use function sprintf; /** - * @implements Rule + * @implements Rule */ final class InvalidThrowsPhpDocValueRule implements Rule { @@ -27,13 +29,17 @@ public function __construct(private FileTypeMapper $fileTypeMapper) public function getNodeType(): string { - return Node\Stmt::class; + return NodeAbstract::class; } public function processNode(Node $node, Scope $scope): array { - if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) { - return []; // is handled by virtual nodes + if ($node instanceof Node\Stmt) { + if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) { + return []; // is handled by virtual nodes + } + } elseif (!$node instanceof InPropertyHookNode) { + return []; } $docComment = $node->getDocComment(); diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php index 2328aeb0d74..e378f873b67 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php @@ -9,6 +9,7 @@ use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\VerbosityLevel; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -137,4 +138,18 @@ public function testMergeInheritedPhpDocs( $this->assertSame($expectedType, $throwsType->describe(VerbosityLevel::precise())); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/invalid-throws-property-hook.php'], [ + [ + 'PHPDoc tag @throws with type DateTimeImmutable is not subtype of Throwable', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-throws-property-hook.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-throws-property-hook.php new file mode 100644 index 00000000000..c40b13aa9f1 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-throws-property-hook.php @@ -0,0 +1,22 @@ += 8.4 + +namespace InvalidThrowsPropertyHook; + +class Foo +{ + + public int $i { + /** @throws \InvalidArgumentException */ + get { + return 1; + } + } + + public int $j { + /** @throws \DateTimeImmutable */ + get { + return 1; + } + } + +} From 7f9538c1fd142cdf78620268e03aeaac2f819b6a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 14:05:32 +0100 Subject: [PATCH 0940/3097] Adjust InvalidPhpDocTagValueRule and InvalidPHPStanDocTagRule for property hooks --- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 8 ++++++-- src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php | 8 ++++++-- .../Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php | 15 +++++++++++++++ .../PhpDoc/InvalidPhpDocTagValueRuleTest.php | 15 +++++++++++++++ .../PhpDoc/data/invalid-phpdoc-property-hooks.php | 15 +++++++++++++++ .../data/invalid-phpstan-tag-property-hooks.php | 15 +++++++++++++++ 6 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-property-hooks.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-tag-property-hooks.php diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index 51b22dd5642..c9e27ca74a1 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\PhpDoc; use PhpParser\Node; +use PhpParser\NodeAbstract; use PHPStan\Analyser\Scope; use PHPStan\Node\VirtualNode; use PHPStan\PhpDocParser\Lexer\Lexer; @@ -15,7 +16,7 @@ use function str_starts_with; /** - * @implements Rule + * @implements Rule */ final class InvalidPHPStanDocTagRule implements Rule { @@ -69,7 +70,7 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt::class; + return NodeAbstract::class; } public function processNode(Node $node, Scope $scope): array @@ -78,6 +79,9 @@ public function processNode(Node $node, Scope $scope): array if ($node instanceof VirtualNode) { return []; } + if (!$node instanceof Node\Stmt && !$node instanceof Node\PropertyHook) { + return []; + } if ($node instanceof Node\Stmt\Expression) { if (!$node->expr instanceof Node\Expr\Assign && !$node->expr instanceof Node\Expr\AssignRef) { return []; diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index 2caa53394ee..5e99af64f59 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\PhpDoc; use PhpParser\Node; +use PhpParser\NodeAbstract; use PHPStan\Analyser\Scope; use PHPStan\Node\VirtualNode; use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; @@ -17,7 +18,7 @@ use function str_starts_with; /** - * @implements Rule + * @implements Rule */ final class InvalidPhpDocTagValueRule implements Rule { @@ -31,7 +32,7 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt::class; + return NodeAbstract::class; } public function processNode(Node $node, Scope $scope): array @@ -40,6 +41,9 @@ public function processNode(Node $node, Scope $scope): array if ($node instanceof VirtualNode) { return []; } + if (!$node instanceof Node\Stmt && !$node instanceof Node\PropertyHook) { + return []; + } if ($node instanceof Node\Stmt\Expression) { if (!$node->expr instanceof Node\Expr\Assign && !$node->expr instanceof Node\Expr\AssignRef) { return []; diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php index c664e1658af..e91e6470548 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPHPStanDocTagRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -52,4 +53,18 @@ public function testBug8697(): void $this->analyse([__DIR__ . '/data/bug-8697.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/invalid-phpstan-tag-property-hooks.php'], [ + [ + 'Unknown PHPDoc tag: @phpstan-what', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index 0047c107edd..be63bff8e2a 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -6,6 +6,7 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -144,4 +145,18 @@ public function testBug6692(): void ]); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/invalid-phpdoc-property-hooks.php'], [ + [ + 'PHPDoc tag @return has invalid value (Test(): Unexpected token "(", expected TOKEN_HORIZONTAL_WS at offset 16 on line 1', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-property-hooks.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-property-hooks.php new file mode 100644 index 00000000000..f145c5d437f --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc-property-hooks.php @@ -0,0 +1,15 @@ += 8.4 + +namespace InvalidPhpDocPropertyHooks; + +class Foo +{ + + public int $i { + /** @return Test( */ + get { + + } + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-tag-property-hooks.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-tag-property-hooks.php new file mode 100644 index 00000000000..1221fe7b438 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpstan-tag-property-hooks.php @@ -0,0 +1,15 @@ += 8.4 + +namespace InvalidPHPStanTagPropertyHooks; + +class Foo +{ + + public int $i { + /** @phpstan-what what */ + get { + + } + } + +} From bc044b73e73412a42ef3929517b7e1332eadfbb6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 14:27:24 +0100 Subject: [PATCH 0941/3097] Test MatchExpressionRule with property hooks --- .../Comparison/MatchExpressionRuleTest.php | 14 ++++++++ .../data/match-expr-property-hooks.php | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/match-expr-property-hooks.php diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index af0107c2a6c..d7a005c5896 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -503,4 +503,18 @@ public function testBug11852(): void $this->analyse([__DIR__ . '/data/bug-11852.php'], []); } + public function testPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/match-expr-property-hooks.php'], [ + [ + 'Match expression does not handle remaining value: 3', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/match-expr-property-hooks.php b/tests/PHPStan/Rules/Comparison/data/match-expr-property-hooks.php new file mode 100644 index 00000000000..b59eb1dc3ef --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/match-expr-property-hooks.php @@ -0,0 +1,33 @@ += 8.4 + +namespace MatchExprPropertyHooks; + +use UnhandledMatchError; + +class Foo +{ + + /** @var 1|2|3 */ + public int $i { + get { + return match ($this->i) { + 1 => 'foo', + 2 => 'bar', + }; + } + } + + /** + * @var 1|2|3 + */ + public int $j { + /** @throws UnhandledMatchError */ + get { + return match ($this->j) { + 1 => 10, + 2 => 20, + }; + } + } + +} From 50ff7bc5ee2cb0d99bac91560bc3873fa8af6f4a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 14:47:36 +0100 Subject: [PATCH 0942/3097] Extract IncompatiblePhpDocTypeCheck from IncompatiblePhpDocTypeRule --- conf/config.neon | 3 + src/PhpDoc/StubValidator.php | 3 +- .../PhpDoc/IncompatiblePhpDocTypeCheck.php | 236 ++++++++++++++++++ .../PhpDoc/IncompatiblePhpDocTypeRule.php | 215 +--------------- .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 24 +- 5 files changed, 265 insertions(+), 216 deletions(-) create mode 100644 src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php diff --git a/conf/config.neon b/conf/config.neon index ec1c876661d..4d7c3b4e99c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1021,6 +1021,9 @@ services: - class: PHPStan\Rules\PhpDoc\GenericCallableRuleHelper + - + class: PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeCheck + - class: PHPStan\Rules\PhpDoc\VarTagTypeRuleHelper arguments: diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 39ebcf09b34..33fde1e46e0 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -80,6 +80,7 @@ use PHPStan\Rules\PhpDoc\GenericCallableRuleHelper; use PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule; +use PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeCheck; use PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatibleSelfOutTypeRule; @@ -225,7 +226,7 @@ private function getRuleRegistry(Container $container): RuleRegistry new MethodTagTemplateTypeRule($methodTagTemplateTypeCheck), new MethodSignatureVarianceRule($varianceCheck), new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck), - new IncompatiblePhpDocTypeRule($fileTypeMapper, $genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper), + new IncompatiblePhpDocTypeRule($fileTypeMapper, new IncompatiblePhpDocTypeCheck($genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper)), new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper), new InvalidPhpDocTagValueRule( $container->getByType(Lexer::class), diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php new file mode 100644 index 00000000000..56c0ac529e7 --- /dev/null +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php @@ -0,0 +1,236 @@ + $nativeParameterTypes + * @param array $byRefParameters + * @return list + */ + public function check( + Scope $scope, + Node $node, + ResolvedPhpDocBlock $resolvedPhpDoc, + string $functionName, + array $nativeParameterTypes, + array $byRefParameters, + Type $nativeReturnType, + ): array + { + $errors = []; + + foreach (['@param' => $resolvedPhpDoc->getParamTags(), '@param-out' => $resolvedPhpDoc->getParamOutTags(), '@param-closure-this' => $resolvedPhpDoc->getParamClosureThisTags()] as $tagName => $parameters) { + foreach ($parameters as $parameterName => $phpDocParamTag) { + $phpDocParamType = $phpDocParamTag->getType(); + + if (!isset($nativeParameterTypes[$parameterName])) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s references unknown parameter: $%s', + $tagName, + $parameterName, + ))->identifier('parameter.notFound')->build(); + + } elseif ( + $this->unresolvableTypeHelper->containsUnresolvableType($phpDocParamType) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for parameter $%s contains unresolvable type.', + $tagName, + $parameterName, + ))->identifier('parameter.unresolvableType')->build(); + + } else { + $nativeParamType = $nativeParameterTypes[$parameterName]; + if ( + $phpDocParamTag instanceof ParamTag + && $phpDocParamTag->isVariadic() + && $phpDocParamType->isArray()->yes() + && $nativeParamType->isArray()->no() + ) { + $phpDocParamType = $phpDocParamType->getIterableValueType(); + } + + $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); + $escapedTagName = SprintfHelper::escapeFormatString($tagName); + + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $phpDocParamType, + sprintf( + 'PHPDoc tag %s for parameter $%s contains generic type %%s but %%s %%s is not generic.', + $escapedTagName, + $escapedParameterName, + ), + sprintf( + 'Generic type %%s in PHPDoc tag %s for parameter $%s does not specify all template types of %%s %%s: %%s', + $escapedTagName, + $escapedParameterName, + ), + sprintf( + 'Generic type %%s in PHPDoc tag %s for parameter $%s specifies %%d template types, but %%s %%s supports only %%d: %%s', + $escapedTagName, + $escapedParameterName, + ), + sprintf( + 'Type %%s in generic type %%s in PHPDoc tag %s for parameter $%s is not subtype of template type %%s of %%s %%s.', + $escapedTagName, + $escapedParameterName, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s for parameter $%s is in conflict with %%s template type %%s of %%s %%s.', + $escapedTagName, + $escapedParameterName, + ), + sprintf( + 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s for parameter $%s is redundant, template type %%s of %%s %%s has the same variance.', + $escapedTagName, + $escapedParameterName, + ), + )); + + $errors = array_merge($errors, $this->genericCallableRuleHelper->check( + $node, + $scope, + sprintf('%s for parameter $%s', $escapedTagName, $escapedParameterName), + $phpDocParamType, + $functionName, + $resolvedPhpDoc->getTemplateTags(), + $scope->isInClass() ? $scope->getClassReflection() : null, + )); + + if ($phpDocParamTag instanceof ParamOutTag) { + if (!$byRefParameters[$parameterName]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Parameter $%s for PHPDoc tag %s is not passed by reference.', + $parameterName, + $tagName, + ))->identifier('parameter.notByRef')->build(); + + } + continue; + } + + if (in_array($tagName, ['@param', '@param-out'], true)) { + $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); + if ($isParamSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', + $tagName, + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('parameter.phpDocType')->build(); + + } elseif ($isParamSuperType->maybe()) { + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', + $tagName, + $parameterName, + $phpDocParamType->describe(VerbosityLevel::typeOnly()), + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('parameter.phpDocType'); + if ($phpDocParamType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); + } + + $errors[] = $errorBuilder->build(); + } + } + + if ($tagName === '@param-closure-this') { + $isNonClosure = (new ClosureType())->isSuperTypeOf($nativeParamType)->no(); + if ($isNonClosure) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag %s is for parameter $%s with non-Closure type %s.', + $tagName, + $parameterName, + $nativeParamType->describe(VerbosityLevel::typeOnly()), + ))->identifier('paramClosureThis.nonClosure')->build(); + } + } + } + } + } + + if ($resolvedPhpDoc->getReturnTag() !== null) { + $phpDocReturnType = $resolvedPhpDoc->getReturnTag()->getType(); + + if ( + $this->unresolvableTypeHelper->containsUnresolvableType($phpDocReturnType) + ) { + $errors[] = RuleErrorBuilder::message('PHPDoc tag @return contains unresolvable type.')->identifier('return.unresolvableType')->build(); + + } else { + $isReturnSuperType = $nativeReturnType->isSuperTypeOf($phpDocReturnType); + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( + $phpDocReturnType, + 'PHPDoc tag @return contains generic type %s but %s %s is not generic.', + 'Generic type %s in PHPDoc tag @return does not specify all template types of %s %s: %s', + 'Generic type %s in PHPDoc tag @return specifies %d template types, but %s %s supports only %d: %s', + 'Type %s in generic type %s in PHPDoc tag @return is not subtype of template type %s of %s %s.', + 'Call-site variance of %s in generic type %s in PHPDoc tag @return is in conflict with %s template type %s of %s %s.', + 'Call-site variance of %s in generic type %s in PHPDoc tag @return is redundant, template type %s of %s %s has the same variance.', + )); + if ($isReturnSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @return with type %s is incompatible with native type %s.', + $phpDocReturnType->describe(VerbosityLevel::typeOnly()), + $nativeReturnType->describe(VerbosityLevel::typeOnly()), + ))->identifier('return.phpDocType')->build(); + + } elseif ($isReturnSuperType->maybe()) { + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @return with type %s is not subtype of native type %s.', + $phpDocReturnType->describe(VerbosityLevel::typeOnly()), + $nativeReturnType->describe(VerbosityLevel::typeOnly()), + ))->identifier('return.phpDocType'); + if ($phpDocReturnType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocReturnType->getName(), $nativeReturnType->describe(VerbosityLevel::typeOnly()))); + } + + $errors[] = $errorBuilder->build(); + } + + $errors = array_merge($errors, $this->genericCallableRuleHelper->check( + $node, + $scope, + '@return', + $phpDocReturnType, + $functionName, + $resolvedPhpDoc->getTemplateTags(), + $scope->isInClass() ? $scope->getClassReflection() : null, + )); + } + } + + return $errors; + } + +} diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index acdbeef79f2..47b3a78248b 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -5,22 +5,11 @@ use PhpParser\Node; use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; -use PHPStan\Internal\SprintfHelper; -use PHPStan\PhpDoc\Tag\ParamOutTag; -use PHPStan\PhpDoc\Tag\ParamTag; -use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\ClosureType; use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; -use PHPStan\Type\VerbosityLevel; -use function array_merge; -use function in_array; use function is_string; -use function sprintf; use function trim; /** @@ -31,9 +20,7 @@ final class IncompatiblePhpDocTypeRule implements Rule public function __construct( private FileTypeMapper $fileTypeMapper, - private GenericObjectTypeCheck $genericObjectTypeCheck, - private UnresolvableTypeHelper $unresolvableTypeHelper, - private GenericCallableRuleHelper $genericCallableRuleHelper, + private IncompatiblePhpDocTypeCheck $check, ) { } @@ -65,200 +52,20 @@ public function processNode(Node $node, Scope $scope): array $functionName, $docComment->getText(), ); - $nativeParameterTypes = $this->getNativeParameterTypes($node, $scope); - $byRefParameters = $this->getByRefParameters($node); - $errors = []; - - foreach (['@param' => $resolvedPhpDoc->getParamTags(), '@param-out' => $resolvedPhpDoc->getParamOutTags(), '@param-closure-this' => $resolvedPhpDoc->getParamClosureThisTags()] as $tagName => $parameters) { - foreach ($parameters as $parameterName => $phpDocParamTag) { - $phpDocParamType = $phpDocParamTag->getType(); - - if (!isset($nativeParameterTypes[$parameterName])) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s references unknown parameter: $%s', - $tagName, - $parameterName, - ))->identifier('parameter.notFound')->build(); - - } elseif ( - $this->unresolvableTypeHelper->containsUnresolvableType($phpDocParamType) - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for parameter $%s contains unresolvable type.', - $tagName, - $parameterName, - ))->identifier('parameter.unresolvableType')->build(); - - } else { - $nativeParamType = $nativeParameterTypes[$parameterName]; - if ( - $phpDocParamTag instanceof ParamTag - && $phpDocParamTag->isVariadic() - && $phpDocParamType->isArray()->yes() - && $nativeParamType->isArray()->no() - ) { - $phpDocParamType = $phpDocParamType->getIterableValueType(); - } - - $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); - $escapedTagName = SprintfHelper::escapeFormatString($tagName); - - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $phpDocParamType, - sprintf( - 'PHPDoc tag %s for parameter $%s contains generic type %%s but %%s %%s is not generic.', - $escapedTagName, - $escapedParameterName, - ), - sprintf( - 'Generic type %%s in PHPDoc tag %s for parameter $%s does not specify all template types of %%s %%s: %%s', - $escapedTagName, - $escapedParameterName, - ), - sprintf( - 'Generic type %%s in PHPDoc tag %s for parameter $%s specifies %%d template types, but %%s %%s supports only %%d: %%s', - $escapedTagName, - $escapedParameterName, - ), - sprintf( - 'Type %%s in generic type %%s in PHPDoc tag %s for parameter $%s is not subtype of template type %%s of %%s %%s.', - $escapedTagName, - $escapedParameterName, - ), - sprintf( - 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s for parameter $%s is in conflict with %%s template type %%s of %%s %%s.', - $escapedTagName, - $escapedParameterName, - ), - sprintf( - 'Call-site variance of %%s in generic type %%s in PHPDoc tag %s for parameter $%s is redundant, template type %%s of %%s %%s has the same variance.', - $escapedTagName, - $escapedParameterName, - ), - )); - - $errors = array_merge($errors, $this->genericCallableRuleHelper->check( - $node, - $scope, - sprintf('%s for parameter $%s', $escapedTagName, $escapedParameterName), - $phpDocParamType, - $functionName, - $resolvedPhpDoc->getTemplateTags(), - $scope->isInClass() ? $scope->getClassReflection() : null, - )); - - if ($phpDocParamTag instanceof ParamOutTag) { - if (!$byRefParameters[$parameterName]) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Parameter $%s for PHPDoc tag %s is not passed by reference.', - $parameterName, - $tagName, - ))->identifier('parameter.notByRef')->build(); - - } - continue; - } - - if (in_array($tagName, ['@param', '@param-out'], true)) { - $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); - if ($isParamSuperType->no()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for parameter $%s with type %s is incompatible with native type %s.', - $tagName, - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()), - ))->identifier('parameter.phpDocType')->build(); - - } elseif ($isParamSuperType->maybe()) { - $errorBuilder = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s for parameter $%s with type %s is not subtype of native type %s.', - $tagName, - $parameterName, - $phpDocParamType->describe(VerbosityLevel::typeOnly()), - $nativeParamType->describe(VerbosityLevel::typeOnly()), - ))->identifier('parameter.phpDocType'); - if ($phpDocParamType instanceof TemplateType) { - $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); - } - - $errors[] = $errorBuilder->build(); - } - } - - if ($tagName === '@param-closure-this') { - $isNonClosure = (new ClosureType())->isSuperTypeOf($nativeParamType)->no(); - if ($isNonClosure) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag %s is for parameter $%s with non-Closure type %s.', - $tagName, - $parameterName, - $nativeParamType->describe(VerbosityLevel::typeOnly()), - ))->identifier('paramClosureThis.nonClosure')->build(); - } - } - } - } - } - - if ($resolvedPhpDoc->getReturnTag() !== null) { - $phpDocReturnType = $resolvedPhpDoc->getReturnTag()->getType(); - - if ( - $this->unresolvableTypeHelper->containsUnresolvableType($phpDocReturnType) - ) { - $errors[] = RuleErrorBuilder::message('PHPDoc tag @return contains unresolvable type.')->identifier('return.unresolvableType')->build(); - - } else { - $nativeReturnType = $this->getNativeReturnType($node, $scope); - $isReturnSuperType = $nativeReturnType->isSuperTypeOf($phpDocReturnType); - $errors = array_merge($errors, $this->genericObjectTypeCheck->check( - $phpDocReturnType, - 'PHPDoc tag @return contains generic type %s but %s %s is not generic.', - 'Generic type %s in PHPDoc tag @return does not specify all template types of %s %s: %s', - 'Generic type %s in PHPDoc tag @return specifies %d template types, but %s %s supports only %d: %s', - 'Type %s in generic type %s in PHPDoc tag @return is not subtype of template type %s of %s %s.', - 'Call-site variance of %s in generic type %s in PHPDoc tag @return is in conflict with %s template type %s of %s %s.', - 'Call-site variance of %s in generic type %s in PHPDoc tag @return is redundant, template type %s of %s %s has the same variance.', - )); - if ($isReturnSuperType->no()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @return with type %s is incompatible with native type %s.', - $phpDocReturnType->describe(VerbosityLevel::typeOnly()), - $nativeReturnType->describe(VerbosityLevel::typeOnly()), - ))->identifier('return.phpDocType')->build(); - - } elseif ($isReturnSuperType->maybe()) { - $errorBuilder = RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @return with type %s is not subtype of native type %s.', - $phpDocReturnType->describe(VerbosityLevel::typeOnly()), - $nativeReturnType->describe(VerbosityLevel::typeOnly()), - ))->identifier('return.phpDocType'); - if ($phpDocReturnType instanceof TemplateType) { - $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocReturnType->getName(), $nativeReturnType->describe(VerbosityLevel::typeOnly()))); - } - - $errors[] = $errorBuilder->build(); - } - - $errors = array_merge($errors, $this->genericCallableRuleHelper->check( - $node, - $scope, - '@return', - $phpDocReturnType, - $functionName, - $resolvedPhpDoc->getTemplateTags(), - $scope->isInClass() ? $scope->getClassReflection() : null, - )); - } - } - - return $errors; + return $this->check->check( + $scope, + $node, + $resolvedPhpDoc, + $functionName, + $this->getNativeParameterTypes($node, $scope), + $this->getByRefParameters($node), + $this->getNativeReturnType($node, $scope), + ); } /** - * @return Type[] + * @return array */ private function getNativeParameterTypes(Node\FunctionLike $node, Scope $scope): array { diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index ace29c0b955..9c9c5965ef6 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -25,18 +25,20 @@ protected function getRule(): Rule return new IncompatiblePhpDocTypeRule( self::getContainer()->getByType(FileTypeMapper::class), - new GenericObjectTypeCheck(), - new UnresolvableTypeHelper(), - new GenericCallableRuleHelper( - new TemplateTypeCheck( - $reflectionProvider, - new ClassNameCheck( - new ClassCaseSensitivityCheck($reflectionProvider, true), - new ClassForbiddenNameCheck(self::getContainer()), + new IncompatiblePhpDocTypeCheck( + new GenericObjectTypeCheck(), + new UnresolvableTypeHelper(), + new GenericCallableRuleHelper( + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, ), - new GenericObjectTypeCheck(), - $typeAliasResolver, - true, ), ), ); From 92e9d4398c9d37463efe5404ed17694d90d56d9e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 14:56:51 +0100 Subject: [PATCH 0943/3097] IncompatiblePropertyHookPhpDocTypeRule - level 2 --- conf/config.level2.neon | 1 + ...IncompatiblePropertyHookPhpDocTypeRule.php | 85 ++++++++++++++++++ ...mpatiblePropertyHookPhpDocTypeRuleTest.php | 89 +++++++++++++++++++ ...ncompatible-property-hook-phpdoc-types.php | 66 ++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 src/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRule.php create mode 100644 tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/incompatible-property-hook-phpdoc-types.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 2d547cb94e8..9cd92e09e71 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -52,6 +52,7 @@ rules: - PHPStan\Rules\PhpDoc\IncompatibleSelfOutTypeRule - PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule + - PHPStan\Rules\PhpDoc\IncompatiblePropertyHookPhpDocTypeRule - PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule - PHPStan\Rules\PhpDoc\IncompatibleParamImmediatelyInvokedCallableRule diff --git a/src/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRule.php new file mode 100644 index 00000000000..dffebfa1c89 --- /dev/null +++ b/src/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRule.php @@ -0,0 +1,85 @@ + + */ +final class IncompatiblePropertyHookPhpDocTypeRule implements Rule +{ + + public function __construct( + private FileTypeMapper $fileTypeMapper, + private IncompatiblePhpDocTypeCheck $check, + ) + { + } + + public function getNodeType(): string + { + return InPropertyHookNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return []; + } + + $hookReflection = $node->getHookReflection(); + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $node->getClassReflection()->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $hookReflection->getName(), + $docComment->getText(), + ); + + return $this->check->check( + $scope, + $node, + $resolvedPhpDoc, + $hookReflection->getName(), + $this->getNativeParameterTypes($hookReflection), + $this->getByRefParameters($hookReflection), + $hookReflection->getNativeReturnType(), + ); + } + + /** + * @return array + */ + private function getNativeParameterTypes(PhpMethodFromParserNodeReflection $node): array + { + $parameters = []; + foreach ($node->getParameters() as $parameter) { + $parameters[$parameter->getName()] = $parameter->getNativeType(); + } + + return $parameters; + } + + /** + * @return array + */ + private function getByRefParameters(PhpMethodFromParserNodeReflection $node): array + { + $parameters = []; + foreach ($node->getParameters() as $parameter) { + $parameters[$parameter->getName()] = false; + } + + return $parameters; + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php new file mode 100644 index 00000000000..b0d4d718ad5 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php @@ -0,0 +1,89 @@ + + */ +class IncompatiblePropertyHookPhpDocTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + $typeAliasResolver = $this->createTypeAliasResolver([], $reflectionProvider); + + return new IncompatiblePropertyHookPhpDocTypeRule( + self::getContainer()->getByType(FileTypeMapper::class), + new IncompatiblePhpDocTypeCheck( + new GenericObjectTypeCheck(), + new UnresolvableTypeHelper(), + new GenericCallableRuleHelper( + new TemplateTypeCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new GenericObjectTypeCheck(), + $typeAliasResolver, + true, + ), + ), + ), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/incompatible-property-hook-phpdoc-types.php'], [ + [ + 'PHPDoc tag @return with type string is incompatible with native type int.', + 10, + ], + [ + 'PHPDoc tag @return with type string is incompatible with native type void.', + 17, + ], + [ + 'PHPDoc tag @param for parameter $value with type string is incompatible with native type int.', + 27, + ], + [ + 'Parameter $value for PHPDoc tag @param-out is not passed by reference.', + 27, + ], + [ + 'PHPDoc tag @param for parameter $value contains unresolvable type.', + 34, + ], + [ + 'PHPDoc tag @param for parameter $value contains generic type Exception but class Exception is not generic.', + 41, + ], + [ + 'PHPDoc tag @param for parameter $value template T of callable(T): T shadows @template T for class IncompatiblePropertyHookPhpDocTypes\GenericFoo.', + 54, + ], + [ + 'PHPDoc tag @param for parameter $value template of callable<\stdClass of mixed>(T): T cannot have existing class \stdClass as its name.', + 61, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-hook-phpdoc-types.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-hook-phpdoc-types.php new file mode 100644 index 00000000000..b1ce3b87624 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-hook-phpdoc-types.php @@ -0,0 +1,66 @@ += 8.4 + +namespace IncompatiblePropertyHookPhpDocTypes; + +class Foo +{ + + public int $i { + /** @return string */ + get { + return $this->i; + } + } + + public int $j { + /** @return string */ + set { + $this->j = 1; + } + } + + public int $k { + /** + * @param string $value + * @param-out int $value + */ + set { + $this->k = 1; + } + } + + public int $l { + /** @param \stdClass&\Exception $value */ + set { + + } + } + + public \Exception $m { + /** @param \Exception $value */ + set { + + } + } + +} + +/** @template T */ +class GenericFoo +{ + + public int $n { + /** @param int|callable(T): T $value */ + set (int|callable $value) { + + } + } + + public int $o { + /** @param int|callable<\stdClass>(T): T $value */ + set (int|callable $value) { + + } + } + +} From ef832135968ec1dfa95057e4108ef83a18db858f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Dec 2024 15:34:29 +0100 Subject: [PATCH 0944/3097] ExistingClassesInPropertyHookTypehintsRule - level 0 --- Makefile | 1 + conf/config.level0.neon | 1 + src/Rules/FunctionDefinitionCheck.php | 2 +- ...tingClassesInPropertyHookTypehintsRule.php | 87 +++++++++++++++++++ ...ClassesInPropertyHookTypehintsRuleTest.php | 65 ++++++++++++++ .../data/existing-classes-property-hooks.php | 34 ++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php create mode 100644 tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php diff --git a/Makefile b/Makefile index fc4a0fe42ec..bc702f32d49 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,7 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-before.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ + --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ src tests cs: diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1a46352922c..ff1a67c728c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -92,6 +92,7 @@ rules: - PHPStan\Rules\Operators\InvalidIncDecOperationRule - PHPStan\Rules\Properties\AccessPropertiesInAssignRule - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule + - PHPStan\Rules\Properties\ExistingClassesInPropertyHookTypehintsRule - PHPStan\Rules\Properties\InvalidCallablePropertyTypeRule - PHPStan\Rules\Properties\MissingReadOnlyPropertyAssignRule - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 6874582743c..700f6e7b71d 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -245,7 +245,7 @@ public function checkAnonymousFunction( */ public function checkClassMethod( PhpMethodFromParserNodeReflection $methodReflection, - ClassMethod $methodNode, + ClassMethod|Node\PropertyHook $methodNode, string $parameterMessage, string $returnMessage, string $unionTypesMessage, diff --git a/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php b/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php new file mode 100644 index 00000000000..be668710f66 --- /dev/null +++ b/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php @@ -0,0 +1,87 @@ + + */ +final class ExistingClassesInPropertyHookTypehintsRule implements Rule +{ + + public function __construct(private FunctionDefinitionCheck $check) + { + } + + public function getNodeType(): string + { + return InPropertyHookNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $hookReflection = $node->getHookReflection(); + if (!$hookReflection->isPropertyHook()) { + throw new ShouldNotHappenException(); + } + $className = SprintfHelper::escapeFormatString($node->getClassReflection()->getDisplayName()); + $hookName = $hookReflection->getPropertyHookName(); + $propertyName = SprintfHelper::escapeFormatString($hookReflection->getHookedPropertyName()); + + $originalHookNode = $node->getOriginalNode(); + if ($hookReflection->getPropertyHookName() === 'set' && $originalHookNode->params === []) { + $originalHookNode = clone $originalHookNode; + $originalHookNode->params = [ + new Node\Param(new Variable('value'), null, null), + ]; + } + + return $this->check->checkClassMethod( + $hookReflection, + $originalHookNode, + sprintf( + 'Parameter $%%s of %s hook for property %s::$%s has invalid type %%s.', + $hookName, + $className, + $propertyName, + ), + sprintf( + '%s hook for property %s::$%s has invalid return type %%s.', + ucfirst($hookName), + $className, + $propertyName, + ), + sprintf('%s hook for property %s::$%s uses native union types but they\'re supported only on PHP 8.0 and later.', $hookName, $className, $propertyName), + sprintf('Template type %%s of %s hook for property %s::$%s is not referenced in a parameter.', $hookName, $className, $propertyName), + sprintf( + 'Parameter $%%s of %s hook for property %s::$%s has unresolvable native type.', + $hookName, + $className, + $propertyName, + ), + sprintf( + '%s hook for property %s::$%s has unresolvable native return type.', + ucfirst($hookName), + $className, + $propertyName, + ), + sprintf( + '%s hook for property %s::$%s has invalid @phpstan-self-out type %%s.', + ucfirst($hookName), + $className, + $propertyName, + ), + ); + } + +} diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php new file mode 100644 index 00000000000..cab45fe36a6 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php @@ -0,0 +1,65 @@ + + */ +class ExistingClassesInPropertyHookTypehintsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new ExistingClassesInPropertyHookTypehintsRule( + new FunctionDefinitionCheck( + $reflectionProvider, + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + ), + new UnresolvableTypeHelper(), + new PhpVersion(PHP_VERSION_ID), + true, + false, + ), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/existing-classes-property-hooks.php'], [ + [ + 'Parameter $v of set hook for property ExistingClassesPropertyHooks\Foo::$i has invalid type ExistingClassesPropertyHooks\Nonexistent.', + 9, + ], + [ + 'Parameter $v of set hook for property ExistingClassesPropertyHooks\Foo::$j has unresolvable native type.', + 15, + ], + [ + 'Get hook for property ExistingClassesPropertyHooks\Foo::$k has invalid return type ExistingClassesPropertyHooks\Undefined.', + 22, + ], + [ + 'Parameter $value of set hook for property ExistingClassesPropertyHooks\Foo::$l has invalid type ExistingClassesPropertyHooks\Undefined.', + 29, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php b/tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php new file mode 100644 index 00000000000..a818f22c1e6 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php @@ -0,0 +1,34 @@ += 8.4 + +namespace ExistingClassesPropertyHooks; + +class Foo +{ + + public int $i { + set (Nonexistent $v) { + + } + } + + public \stdClass $j { + set (\stdClass&\Exception $v) { + + } + } + + /** @var Undefined */ + public $k { + get { + + } + } + + /** @var Undefined */ + public $l { + set { + + } + } + +} From 35fce623880a5a365303376e9696d1386d3750f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Dec 2024 14:19:13 +0100 Subject: [PATCH 0945/3097] Useful `getPropertyReflection()` shortcut in property hook virtual nodes --- src/Analyser/NodeScopeResolver.php | 15 ++++++++++++++- src/Node/InPropertyHookNode.php | 7 +++++++ src/Node/PropertyHookReturnStatementsNode.php | 7 +++++++ .../SetNonVirtualPropertyHookAssignRule.php | 6 +----- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7339f0a0424..495879eaeae 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4794,7 +4794,19 @@ private function processPropertyHooks( if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) { throw new ShouldNotHappenException(); } - $nodeCallback(new InPropertyHookNode($classReflection, $hookReflection, $hook), $hookScope); + + if (!$classReflection->hasNativeProperty($propertyName)) { + throw new ShouldNotHappenException(); + } + + $propertyReflection = $classReflection->getNativeProperty($propertyName); + + $nodeCallback(new InPropertyHookNode( + $classReflection, + $hookReflection, + $propertyReflection, + $hook, + ), $hookScope); if ($hook->body instanceof Expr) { $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); @@ -4840,6 +4852,7 @@ private function processPropertyHooks( array_merge($statementResult->getImpurePoints(), $methodImpurePoints), $classReflection, $hookReflection, + $propertyReflection, ), $hookScope); } diff --git a/src/Node/InPropertyHookNode.php b/src/Node/InPropertyHookNode.php index b27899949d8..99de6b73a0b 100644 --- a/src/Node/InPropertyHookNode.php +++ b/src/Node/InPropertyHookNode.php @@ -6,6 +6,7 @@ use PhpParser\NodeAbstract; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; +use PHPStan\Reflection\Php\PhpPropertyReflection; /** * @api @@ -16,6 +17,7 @@ final class InPropertyHookNode extends NodeAbstract implements VirtualNode public function __construct( private ClassReflection $classReflection, private PhpMethodFromParserNodeReflection $hookReflection, + private PhpPropertyReflection $propertyReflection, private Node\PropertyHook $originalNode, ) { @@ -32,6 +34,11 @@ public function getHookReflection(): PhpMethodFromParserNodeReflection return $this->hookReflection; } + public function getPropertyReflection(): PhpPropertyReflection + { + return $this->propertyReflection; + } + public function getOriginalNode(): Node\PropertyHook { return $this->originalNode; diff --git a/src/Node/PropertyHookReturnStatementsNode.php b/src/Node/PropertyHookReturnStatementsNode.php index 7d97a140b91..42db85ee6d7 100644 --- a/src/Node/PropertyHookReturnStatementsNode.php +++ b/src/Node/PropertyHookReturnStatementsNode.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\StatementResult; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; +use PHPStan\Reflection\Php\PhpPropertyReflection; /** * @api @@ -28,6 +29,7 @@ public function __construct( private array $impurePoints, private ClassReflection $classReflection, private PhpMethodFromParserNodeReflection $hookReflection, + private PhpPropertyReflection $propertyReflection, ) { parent::__construct($hook->getAttributes()); @@ -88,6 +90,11 @@ public function getHookReflection(): PhpMethodFromParserNodeReflection return $this->hookReflection; } + public function getPropertyReflection(): PhpPropertyReflection + { + return $this->propertyReflection; + } + public function getType(): string { return 'PHPStan_Node_PropertyHookReturnStatementsNode'; diff --git a/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php b/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php index 67f8f134bb7..aeedaeb4a97 100644 --- a/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php +++ b/src/Rules/Properties/SetNonVirtualPropertyHookAssignRule.php @@ -37,11 +37,7 @@ public function processNode(Node $node, Scope $scope): array $propertyName = $hookReflection->getHookedPropertyName(); $classReflection = $node->getClassReflection(); - if (!$classReflection->hasNativeProperty($propertyName)) { - throw new ShouldNotHappenException(); - } - - $propertyReflection = $classReflection->getNativeProperty($propertyName); + $propertyReflection = $node->getPropertyReflection(); if ($propertyReflection->isVirtual()->yes()) { return []; } From 22c80b1624c2fb4aed21a197a2bc76ca96be39d5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Dec 2024 08:23:42 +0100 Subject: [PATCH 0946/3097] ExtendedParameterReflection::hasNativeType() --- .../Annotations/AnnotationsMethodParameterReflection.php | 5 +++++ src/Reflection/ExtendedParameterReflection.php | 2 ++ src/Reflection/Native/ExtendedNativeParameterReflection.php | 6 ++++++ src/Reflection/Php/ExtendedDummyParameter.php | 6 ++++++ src/Reflection/Php/PhpParameterFromParserNodeReflection.php | 5 +++++ src/Reflection/Php/PhpParameterReflection.php | 5 +++++ 6 files changed, 29 insertions(+) diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index 51bddcaabe2..4f6b6407859 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -35,6 +35,11 @@ public function getPhpDocType(): Type return $this->type; } + public function hasNativeType(): bool + { + return false; + } + public function getNativeType(): Type { return new MixedType(); diff --git a/src/Reflection/ExtendedParameterReflection.php b/src/Reflection/ExtendedParameterReflection.php index db8df05ab8f..aff5f65822b 100644 --- a/src/Reflection/ExtendedParameterReflection.php +++ b/src/Reflection/ExtendedParameterReflection.php @@ -11,6 +11,8 @@ interface ExtendedParameterReflection extends ParameterReflection public function getPhpDocType(): Type; + public function hasNativeType(): bool; + public function getNativeType(): Type; public function getOutType(): ?Type; diff --git a/src/Reflection/Native/ExtendedNativeParameterReflection.php b/src/Reflection/Native/ExtendedNativeParameterReflection.php index 7e1388bf5a5..90c653484bb 100644 --- a/src/Reflection/Native/ExtendedNativeParameterReflection.php +++ b/src/Reflection/Native/ExtendedNativeParameterReflection.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class ExtendedNativeParameterReflection implements ExtendedParameterReflection @@ -46,6 +47,11 @@ public function getPhpDocType(): Type return $this->phpDocType; } + public function hasNativeType(): bool + { + return !$this->nativeType instanceof MixedType || $this->nativeType->isExplicitMixed(); + } + public function getNativeType(): Type { return $this->nativeType; diff --git a/src/Reflection/Php/ExtendedDummyParameter.php b/src/Reflection/Php/ExtendedDummyParameter.php index 91238c18b91..43151a7a7f4 100644 --- a/src/Reflection/Php/ExtendedDummyParameter.php +++ b/src/Reflection/Php/ExtendedDummyParameter.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class ExtendedDummyParameter extends DummyParameter implements ExtendedParameterReflection @@ -32,6 +33,11 @@ public function getPhpDocType(): Type return $this->phpDocType; } + public function hasNativeType(): bool + { + return !$this->nativeType instanceof MixedType || $this->nativeType->isExplicitMixed(); + } + public function getNativeType(): Type { return $this->nativeType; diff --git a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php index 8ebb272bfd4..f9bdddc13e0 100644 --- a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php @@ -63,6 +63,11 @@ public function getPhpDocType(): Type return $this->phpDocType ?? new MixedType(); } + public function hasNativeType(): bool + { + return !$this->realType instanceof MixedType || $this->realType->isExplicitMixed(); + } + public function getNativeType(): Type { return $this->realType; diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index 40b28e9ff6e..c4c2713c4ba 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -92,6 +92,11 @@ public function getPhpDocType(): Type return new MixedType(); } + public function hasNativeType(): bool + { + return $this->reflection->getType() !== null; + } + public function getNativeType(): Type { if ($this->nativeType === null) { From acd559e5aad014e7d7621a7dc2f62df4a105b0d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Dec 2024 13:44:53 +0100 Subject: [PATCH 0947/3097] SetPropertyHookParameterRule - level 0 and 3 --- Makefile | 1 + conf/config.level0.neon | 7 ++ .../SetPropertyHookParameterRule.php | 105 ++++++++++++++++++ .../SetPropertyHookParameterRuleTest.php | 54 +++++++++ .../data/set-property-hook-parameter.php | 78 +++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 src/Rules/Properties/SetPropertyHookParameterRule.php create mode 100644 tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php diff --git a/Makefile b/Makefile index bc702f32d49..1452d74f71d 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,7 @@ lint: --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-before.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ + --exclude tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php \ src tests cs: diff --git a/conf/config.level0.neon b/conf/config.level0.neon index ff1a67c728c..fc3bfc84f29 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -212,6 +212,13 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Properties\SetPropertyHookParameterRule + arguments: + checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Properties\UninitializedPropertyRule diff --git a/src/Rules/Properties/SetPropertyHookParameterRule.php b/src/Rules/Properties/SetPropertyHookParameterRule.php new file mode 100644 index 00000000000..941dd849737 --- /dev/null +++ b/src/Rules/Properties/SetPropertyHookParameterRule.php @@ -0,0 +1,105 @@ + + */ +final class SetPropertyHookParameterRule implements Rule +{ + + public function __construct(private bool $checkPhpDocMethodSignatures) + { + } + + public function getNodeType(): string + { + return InPropertyHookNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $hookReflection = $node->getHookReflection(); + if (!$hookReflection->isPropertyHook()) { + return []; + } + + if ($hookReflection->getPropertyHookName() !== 'set') { + return []; + } + + $propertyReflection = $node->getPropertyReflection(); + $parameters = $hookReflection->getParameters(); + if (!isset($parameters[0])) { + throw new ShouldNotHappenException(); + } + + $classReflection = $node->getClassReflection(); + + $errors = []; + $parameter = $parameters[0]; + if (!$propertyReflection->hasNativeType()) { + if ($parameter->hasNativeType()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Parameter $%s of set hook has a native type but the property %s::$%s does not.', + $parameter->getName(), + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + ))->identifier('propertySetHook.nativeParameterType') + ->nonIgnorable() + ->build(); + } + } elseif (!$parameter->hasNativeType()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Parameter $%s of set hook does not have a native type but the property %s::$%s does.', + $parameter->getName(), + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + ))->identifier('propertySetHook.nativeParameterType') + ->nonIgnorable() + ->build(); + } else { + if (!$parameter->getNativeType()->isSuperTypeOf($propertyReflection->getNativeType())->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Native type %s of set hook parameter $%s is not contravariant with native type %s of property %s::$%s.', + $parameter->getNativeType()->describe(VerbosityLevel::typeOnly()), + $parameter->getName(), + $propertyReflection->getNativeType()->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + ))->identifier('propertySetHook.nativeParameterType') + ->nonIgnorable() + ->build(); + } + } + + if (!$this->checkPhpDocMethodSignatures || count($errors) > 0) { + return $errors; + } + + if (!$parameter->getType()->isSuperTypeOf($propertyReflection->getReadableType())->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of set hook parameter $%s is not contravariant with type %s of property %s::$%s.', + $parameter->getType()->describe(VerbosityLevel::value()), + $parameter->getName(), + $propertyReflection->getReadableType()->describe(VerbosityLevel::value()), + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + ))->identifier('propertySetHook.parameterType') + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php b/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php new file mode 100644 index 00000000000..76e7b06b8ce --- /dev/null +++ b/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php @@ -0,0 +1,54 @@ + + */ +class SetPropertyHookParameterRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new SetPropertyHookParameterRule(true); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/set-property-hook-parameter.php'], [ + [ + 'Parameter $v of set hook has a native type but the property SetPropertyHookParameter\Bar::$a does not.', + 41, + ], + [ + 'Parameter $v of set hook does not have a native type but the property SetPropertyHookParameter\Bar::$b does.', + 47, + ], + [ + 'Native type string of set hook parameter $v is not contravariant with native type int of property SetPropertyHookParameter\Bar::$c.', + 53, + ], + [ + 'Native type string of set hook parameter $v is not contravariant with native type int|string of property SetPropertyHookParameter\Bar::$d.', + 59, + ], + [ + 'Type int<1, max> of set hook parameter $v is not contravariant with type int of property SetPropertyHookParameter\Bar::$e.', + 66, + ], + [ + 'Type array|int<1, max> of set hook parameter $v is not contravariant with type int of property SetPropertyHookParameter\Bar::$f.', + 73, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php b/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php new file mode 100644 index 00000000000..a8279832b2e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php @@ -0,0 +1,78 @@ + */ + set (int|array $v) { + + } + } + + public $ok4 { + set ($v) { + + } + } + +} + +class Bar +{ + + public $a { + set (int $v) { + + } + } + + public int $b { + set ($v) { + + } + } + + public int $c { + set (string $v) { + + } + } + + public int|string $d { + set (string $v) { + + } + } + + public int $e { + /** @param positive-int $v */ + set (int $v) { + + } + } + + public int $f { + /** @param positive-int|array $v */ + set (int|array $v) { + + } + } + +} From 70572a17d592b6a7c148d9b2188408127378a4c2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 26 Dec 2024 14:47:21 +0100 Subject: [PATCH 0948/3097] Report missing types in SetPropertyHookParameterRule - level 6 --- conf/config.level0.neon | 1 + .../SetPropertyHookParameterRule.php | 58 ++++++++++++++++- .../SetPropertyHookParameterRuleTest.php | 16 ++++- .../data/set-property-hook-parameter.php | 64 ++++++++++++++++++- 4 files changed, 134 insertions(+), 5 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index fc3bfc84f29..6493abd8685 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -216,6 +216,7 @@ services: class: PHPStan\Rules\Properties\SetPropertyHookParameterRule arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + checkMissingTypehints: %checkMissingTypehints% tags: - phpstan.rules.rule diff --git a/src/Rules/Properties/SetPropertyHookParameterRule.php b/src/Rules/Properties/SetPropertyHookParameterRule.php index 941dd849737..e8de30667e4 100644 --- a/src/Rules/Properties/SetPropertyHookParameterRule.php +++ b/src/Rules/Properties/SetPropertyHookParameterRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\InPropertyHookNode; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -18,7 +19,11 @@ final class SetPropertyHookParameterRule implements Rule { - public function __construct(private bool $checkPhpDocMethodSignatures) + public function __construct( + private MissingTypehintCheck $missingTypehintCheck, + private bool $checkPhpDocMethodSignatures, + private bool $checkMissingTypehints, + ) { } @@ -87,10 +92,12 @@ public function processNode(Node $node, Scope $scope): array return $errors; } - if (!$parameter->getType()->isSuperTypeOf($propertyReflection->getReadableType())->yes()) { + $parameterType = $parameter->getType(); + + if (!$parameterType->isSuperTypeOf($propertyReflection->getReadableType())->yes()) { $errors[] = RuleErrorBuilder::message(sprintf( 'Type %s of set hook parameter $%s is not contravariant with type %s of property %s::$%s.', - $parameter->getType()->describe(VerbosityLevel::value()), + $parameterType->describe(VerbosityLevel::value()), $parameter->getName(), $propertyReflection->getReadableType()->describe(VerbosityLevel::value()), $classReflection->getDisplayName(), @@ -99,6 +106,51 @@ public function processNode(Node $node, Scope $scope): array ->build(); } + if (!$this->checkMissingTypehints) { + return $errors; + } + + if ($parameter->getNativeType()->equals($propertyReflection->getReadableType())) { + return $errors; + } + + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + 'Set hook for property %s::$%s has parameter $%s with no value type specified in iterable type %s.', + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $parameter->getName(), + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Set hook for property %s::$%s has parameter $%s with generic %s but does not specify its types: %s', + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $parameter->getName(), + $name, + $genericTypeNames, + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Set hook for property %s::$%s has parameter $%s with no signature specified for %s.', + $classReflection->getDisplayName(), + $hookReflection->getHookedPropertyName(), + $parameter->getName(), + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + return $errors; } diff --git a/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php b/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php index 76e7b06b8ce..0b879f0ad5c 100644 --- a/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php +++ b/tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule as TRule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -14,7 +15,7 @@ class SetPropertyHookParameterRuleTest extends RuleTestCase protected function getRule(): TRule { - return new SetPropertyHookParameterRule(true); + return new SetPropertyHookParameterRule(new MissingTypehintCheck(true, []), true, true); } public function testRule(): void @@ -48,6 +49,19 @@ public function testRule(): void 'Type array|int<1, max> of set hook parameter $v is not contravariant with type int of property SetPropertyHookParameter\Bar::$f.', 73, ], + [ + 'Set hook for property SetPropertyHookParameter\MissingTypes::$f has parameter $v with no value type specified in iterable type array.', + 123, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Set hook for property SetPropertyHookParameter\MissingTypes::$g has parameter $value with generic class SetPropertyHookParameter\GenericFoo but does not specify its types: T', + 129, + ], + [ + 'Set hook for property SetPropertyHookParameter\MissingTypes::$h has parameter $value with no signature specified for callable.', + 135, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php b/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php index a8279832b2e..12c82ddc0af 100644 --- a/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php +++ b/tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php @@ -20,7 +20,7 @@ class Foo /** @var positive-int */ public int $ok3 { - /** @param positive-int|array */ + /** @param positive-int|array $v */ set (int|array $v) { } @@ -76,3 +76,65 @@ class Bar } } + +/** + * @template T + */ +class GenericFoo +{ + +} + +class MissingTypes +{ + + public array $a { + set { // do not report, taken care of above the property + } + } + + /** @var array */ + public array $b { + set { // do not report, inherited from property + } + } + + public array $c { + set (array $v) { // do not report, taken care of above the property + + } + } + + /** @var array */ + public array $d { + set (array $v) { // do not report, inherited from property + + } + } + + public int $e { + /** @param array $v */ + set (int|array $v) { // do not report, type specified + + } + } + + public int $f { + set (int|array $v) { // report + + } + } + + public int $g { + set (int|GenericFoo $value) { // report + + } + } + + public int $h { + set (int|callable $value) { // report + + } + } + +} From 41837b490b12e3c71b4ca50003690f2900f74876 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 28 Dec 2024 22:29:35 +0100 Subject: [PATCH 0949/3097] AccessStaticPropertiesRule - fixed blindspot about `parent::` --- .../Properties/AccessStaticPropertiesRule.php | 11 ----------- .../AccessStaticPropertiesRuleTest.php | 12 ++++++++++++ .../data/access-static-properties.php | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 67a03643f9e..a5a5c16c819 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -15,7 +15,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\StringType; @@ -108,16 +107,6 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]; } - if ($scope->getFunctionName() === null) { - throw new ShouldNotHappenException(); - } - - $currentMethodReflection = $scope->getClassReflection()->getNativeMethod($scope->getFunctionName()); - if (!$currentMethodReflection->isStatic()) { - // calling parent::method() from instance method - return []; - } - $classType = $scope->resolveTypeByName($node->class); } else { if (!$this->reflectionProvider->hasClass($class)) { diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index acbfca290c2..7060aeecea7 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -47,6 +47,10 @@ public function testAccessStaticProperties(): void 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', 26, ], + [ + 'Static access to instance property FooAccessStaticProperties::$loremIpsum.', + 32, + ], [ 'IpsumAccessStaticProperties::ipsum() accesses parent::$lorem but IpsumAccessStaticProperties does not extend any class.', 42, @@ -250,6 +254,14 @@ public function testAccessStaticProperties(): void 'Access to an undefined static property AllowsDynamicProperties::$foo.', 248, ], + [ + 'Static access to instance property ParentClassWithInstanceProperty::$i.', + 267, + ], + [ + 'Access to an undefined static property ParentClassWithInstanceProperty::$j.', + 268, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/access-static-properties.php b/tests/PHPStan/Rules/Properties/data/access-static-properties.php index 2ee62db90b0..9abc02950fb 100644 --- a/tests/PHPStan/Rules/Properties/data/access-static-properties.php +++ b/tests/PHPStan/Rules/Properties/data/access-static-properties.php @@ -251,3 +251,21 @@ public function doFoo() } } + +class ParentClassWithInstanceProperty +{ + + public int $i = 0; + +} + +class ChildClassAccessingParentProperty extends ParentClassWithInstanceProperty +{ + + public function doFoo(): void + { + echo parent::$i; + echo parent::$j; + } + +} From 06d592d410ca18af5628935238a7089687b29eaa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 28 Dec 2024 22:44:26 +0100 Subject: [PATCH 0950/3097] Fix --- src/Analyser/NodeScopeResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 495879eaeae..791a8920b72 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2993,7 +2993,7 @@ static function (): void { $propertyName = $expr->name->toString(); $propertyHolderType = $scopeBeforeVar->getType($expr->var); $propertyReflection = $scopeBeforeVar->getPropertyReflection($propertyHolderType, $propertyName); - if ($propertyReflection !== null) { + if ($propertyReflection !== null && $this->phpVersion->supportsPropertyHooks()) { $propertyDeclaringClass = $propertyReflection->getDeclaringClass(); if ($propertyDeclaringClass->hasNativeProperty($propertyName)) { $nativeProperty = $propertyDeclaringClass->getNativeProperty($propertyName); From 7a263de581fec2934dde8f6fb2c052a9c5d838e6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 10:01:15 +0100 Subject: [PATCH 0951/3097] Introduce AccessPropertiesCheck --- conf/config.level0.neon | 3 - conf/config.neon | 6 + .../Properties/AccessPropertiesCheck.php | 175 ++++++++++++++++++ .../AccessPropertiesInAssignRule.php | 4 +- src/Rules/Properties/AccessPropertiesRule.php | 155 +--------------- .../AccessPropertiesInAssignRuleTest.php | 2 +- .../Properties/AccessPropertiesRuleTest.php | 2 +- 7 files changed, 187 insertions(+), 160 deletions(-) create mode 100644 src/Rules/Properties/AccessPropertiesCheck.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 6493abd8685..980324fc542 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -179,9 +179,6 @@ services: class: PHPStan\Rules\Properties\AccessPropertiesRule tags: - phpstan.rules.rule - arguments: - reportMagicProperties: %reportMagicProperties% - checkDynamicProperties: %checkDynamicProperties% - class: PHPStan\Rules\Properties\AccessStaticPropertiesRule diff --git a/conf/config.neon b/conf/config.neon index 4d7c3b4e99c..b2d222ebb39 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1033,6 +1033,12 @@ services: - class: PHPStan\Rules\Playground\NeverRuleHelper + - + class: PHPStan\Rules\Properties\AccessPropertiesCheck + arguments: + reportMagicProperties: %reportMagicProperties% + checkDynamicProperties: %checkDynamicProperties% + - class: PHPStan\Rules\Properties\LazyReadWritePropertiesExtensionProvider diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php new file mode 100644 index 00000000000..8609cba9d9a --- /dev/null +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -0,0 +1,175 @@ + + */ + public function check(PropertyFetch $node, Scope $scope): array + { + if ($node->name instanceof Identifier) { + $names = [$node->name->name]; + } else { + $names = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $scope->getType($node->name)->getConstantStrings()); + } + + $errors = []; + foreach ($names as $name) { + $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); + } + + return $errors; + } + + /** + * @return list + */ + private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), + sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), + static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + + if ($scope->isInExpressionAssign($node)) { + return []; + } + + $typeForDescribe = $type; + if ($type instanceof StaticType) { + $typeForDescribe = $type->getStaticObjectType(); + } + + if ($type->canAccessProperties()->no() || $type->canAccessProperties()->maybe() && !$scope->isUndefinedExpressionAllowed($node)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot access property $%s on %s.', + $name, + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + ))->identifier('property.nonObject')->build(), + ]; + } + + $has = $type->hasProperty($name); + if (!$has->no() && $this->canAccessUndefinedProperties($scope, $node)) { + return []; + } + + if (!$has->yes()) { + if ($scope->hasExpressionType($node)->yes()) { + return []; + } + + $classNames = $type->getObjectClassNames(); + if (!$this->reportMagicProperties) { + foreach ($classNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ( + $classReflection->hasNativeMethod('__get') + || $classReflection->hasNativeMethod('__set') + ) { + return []; + } + } + } + + if (count($classNames) === 1) { + $propertyClassReflection = $this->reflectionProvider->getClass($classNames[0]); + $parentClassReflection = $propertyClassReflection->getParentClass(); + while ($parentClassReflection !== null) { + if ($parentClassReflection->hasProperty($name)) { + if ($scope->canAccessProperty($parentClassReflection->getProperty($name, $scope))) { + return []; + } + return [ + RuleErrorBuilder::message(sprintf( + 'Access to private property $%s of parent class %s.', + $name, + $parentClassReflection->getDisplayName(), + ))->identifier('property.private')->build(), + ]; + } + + $parentClassReflection = $parentClassReflection->getParentClass(); + } + } + + $ruleErrorBuilder = RuleErrorBuilder::message(sprintf( + 'Access to an undefined property %s::$%s.', + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + $name, + ))->identifier('property.notFound'); + if ($typeResult->getTip() !== null) { + $ruleErrorBuilder->tip($typeResult->getTip()); + } else { + $ruleErrorBuilder->tip('Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property'); + } + + return [ + $ruleErrorBuilder->build(), + ]; + } + + $propertyReflection = $type->getProperty($name, $scope); + if (!$scope->canAccessProperty($propertyReflection)) { + return [ + RuleErrorBuilder::message(sprintf( + 'Access to %s property %s::$%s.', + $propertyReflection->isPrivate() ? 'private' : 'protected', + $type->describe(VerbosityLevel::typeOnly()), + $name, + ))->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected'))->build(), + ]; + } + + return []; + } + + private function canAccessUndefinedProperties(Scope $scope, Expr $node): bool + { + return $scope->isUndefinedExpressionAllowed($node) && !$this->checkDynamicProperties; + } + +} diff --git a/src/Rules/Properties/AccessPropertiesInAssignRule.php b/src/Rules/Properties/AccessPropertiesInAssignRule.php index 5d7a9abcc4c..80bc39e4ac4 100644 --- a/src/Rules/Properties/AccessPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessPropertiesInAssignRule.php @@ -13,7 +13,7 @@ final class AccessPropertiesInAssignRule implements Rule { - public function __construct(private AccessPropertiesRule $accessPropertiesRule) + public function __construct(private AccessPropertiesCheck $check) { } @@ -32,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->accessPropertiesRule->processNode($node->getPropertyFetch(), $scope); + return $this->check->check($node->getPropertyFetch(), $scope); } } diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 773c715d04d..9e2d8852be4 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -4,24 +4,8 @@ use PhpParser\Node; use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Identifier; -use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; -use PHPStan\Internal\SprintfHelper; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Rules\RuleLevelHelper; -use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ErrorType; -use PHPStan\Type\StaticType; -use PHPStan\Type\Type; -use PHPStan\Type\VerbosityLevel; -use function array_map; -use function array_merge; -use function count; -use function sprintf; /** * @implements Rule @@ -29,12 +13,7 @@ final class AccessPropertiesRule implements Rule { - public function __construct( - private ReflectionProvider $reflectionProvider, - private RuleLevelHelper $ruleLevelHelper, - private bool $reportMagicProperties, - private bool $checkDynamicProperties, - ) + public function __construct(private AccessPropertiesCheck $check) { } @@ -45,137 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if ($node->name instanceof Identifier) { - $names = [$node->name->name]; - } else { - $names = array_map(static fn (ConstantStringType $type): string => $type->getValue(), $scope->getType($node->name)->getConstantStrings()); - } - - $errors = []; - foreach ($names as $name) { - $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); - } - - return $errors; - } - - /** - * @return list - */ - private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name): array - { - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), - sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), - static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), - ); - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - - if ($scope->isInExpressionAssign($node)) { - return []; - } - - $typeForDescribe = $type; - if ($type instanceof StaticType) { - $typeForDescribe = $type->getStaticObjectType(); - } - - if ($type->canAccessProperties()->no() || $type->canAccessProperties()->maybe() && !$scope->isUndefinedExpressionAllowed($node)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot access property $%s on %s.', - $name, - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('property.nonObject')->build(), - ]; - } - - $has = $type->hasProperty($name); - if (!$has->no() && $this->canAccessUndefinedProperties($scope, $node)) { - return []; - } - - if (!$has->yes()) { - if ($scope->hasExpressionType($node)->yes()) { - return []; - } - - $classNames = $type->getObjectClassNames(); - if (!$this->reportMagicProperties) { - foreach ($classNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ( - $classReflection->hasNativeMethod('__get') - || $classReflection->hasNativeMethod('__set') - ) { - return []; - } - } - } - - if (count($classNames) === 1) { - $propertyClassReflection = $this->reflectionProvider->getClass($classNames[0]); - $parentClassReflection = $propertyClassReflection->getParentClass(); - while ($parentClassReflection !== null) { - if ($parentClassReflection->hasProperty($name)) { - if ($scope->canAccessProperty($parentClassReflection->getProperty($name, $scope))) { - return []; - } - return [ - RuleErrorBuilder::message(sprintf( - 'Access to private property $%s of parent class %s.', - $name, - $parentClassReflection->getDisplayName(), - ))->identifier('property.private')->build(), - ]; - } - - $parentClassReflection = $parentClassReflection->getParentClass(); - } - } - - $ruleErrorBuilder = RuleErrorBuilder::message(sprintf( - 'Access to an undefined property %s::$%s.', - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - $name, - ))->identifier('property.notFound'); - if ($typeResult->getTip() !== null) { - $ruleErrorBuilder->tip($typeResult->getTip()); - } else { - $ruleErrorBuilder->tip('Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property'); - } - - return [ - $ruleErrorBuilder->build(), - ]; - } - - $propertyReflection = $type->getProperty($name, $scope); - if (!$scope->canAccessProperty($propertyReflection)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Access to %s property %s::$%s.', - $propertyReflection->isPrivate() ? 'private' : 'protected', - $type->describe(VerbosityLevel::typeOnly()), - $name, - ))->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected'))->build(), - ]; - } - - return []; - } - - private function canAccessUndefinedProperties(Scope $scope, Node\Expr $node): bool - { - return $scope->isUndefinedExpressionAllowed($node) && !$this->checkDynamicProperties; + return $this->check->check($node, $scope); } } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index b6ea917903a..dd44445971c 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), true, true), + new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), true, true), ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 164aefaafed..db82d7fcf16 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -23,7 +23,7 @@ class AccessPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), true, $this->checkDynamicProperties); + return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), true, $this->checkDynamicProperties)); } public function testAccessProperties(): void From 9b86df979975759cee2267e94b08ca94da58a050 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 14:38:06 +0100 Subject: [PATCH 0952/3097] ReadOnlyPropertyAssignRefRule does not make sense for StaticPropertyFetch --- src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php index 30f72336145..11ac4de1a48 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php @@ -25,7 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->expr instanceof Node\Expr\PropertyFetch && !$node->expr instanceof Node\Expr\StaticPropertyFetch) { + if (!$node->expr instanceof Node\Expr\PropertyFetch) { return []; } From c34432b4ce5e3ca48d56b7cbbc9f7daa717701be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 28 Dec 2024 21:16:02 +0100 Subject: [PATCH 0953/3097] Asymmetric visibility basics --- conf/config.level0.neon | 1 + src/Analyser/MutatingScope.php | 57 ++++++++++++++- src/Analyser/OutOfClassScope.php | 13 ++++ src/Php/PhpVersion.php | 5 ++ .../AnnotationPropertyReflection.php | 10 +++ src/Reflection/ClassMemberAccessAnswerer.php | 7 ++ src/Reflection/ClassReflection.php | 2 +- .../Dummy/ChangedTypePropertyReflection.php | 10 +++ .../Dummy/DummyPropertyReflection.php | 10 +++ src/Reflection/ExtendedPropertyReflection.php | 4 + src/Reflection/Php/EnumPropertyReflection.php | 10 +++ src/Reflection/Php/PhpPropertyReflection.php | 30 ++++++++ .../Php/SimpleXMLElementProperty.php | 10 +++ .../Php/UniversalObjectCrateProperty.php | 10 +++ src/Reflection/ResolvedPropertyReflection.php | 10 +++ .../IntersectionTypePropertyReflection.php | 10 +++ .../Type/UnionTypePropertyReflection.php | 10 +++ .../WrappedExtendedPropertyReflection.php | 10 +++ .../Properties/AccessPropertiesCheck.php | 38 ++++++++-- .../AccessPropertiesInAssignRule.php | 2 +- src/Rules/Properties/AccessPropertiesRule.php | 2 +- .../Properties/AccessStaticPropertiesRule.php | 4 +- .../Properties/FoundPropertyReflection.php | 10 +++ .../Properties/PropertyAssignRefRule.php | 71 ++++++++++++++++++ .../ReadOnlyByPhpDocPropertyAssignRefRule.php | 2 +- .../ReadOnlyByPhpDocPropertyAssignRule.php | 2 +- .../ReadOnlyPropertyAssignRefRule.php | 2 +- .../Properties/ReadOnlyPropertyAssignRule.php | 2 +- .../ReadingWriteOnlyPropertiesRule.php | 2 +- .../WritingToReadOnlyPropertiesRule.php | 2 +- src/Type/ObjectShapePropertyReflection.php | 10 +++ ...PropertiesClassReflectionExtensionTest.php | 2 + .../Annotations/DeprecatedAnnotationsTest.php | 2 + .../Annotations/FinalAnnotationsTest.php | 2 + .../Annotations/InternalAnnotationsTest.php | 2 + .../Reflection/FunctionReflectionTest.php | 4 + .../AccessPropertiesInAssignRuleTest.php | 42 ++++++++++- .../Properties/AccessPropertiesRuleTest.php | 15 +++- .../Properties/PropertyAssignRefRuleTest.php | 61 ++++++++++++++++ .../ReadOnlyPropertyAssignRefRuleTest.php | 14 +++- .../ReadOnlyPropertyAssignRuleTest.php | 30 ++++++-- .../data/property-assign-ref-asymmetric.php | 39 ++++++++++ .../Properties/data/property-assign-ref.php | 29 ++++++++ .../data/read-asymmetric-visibility.php | 37 ++++++++++ .../data/write-asymmetric-visibility.php | 73 +++++++++++++++++++ 45 files changed, 688 insertions(+), 32 deletions(-) create mode 100644 src/Rules/Properties/PropertyAssignRefRule.php create mode 100644 tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-assign-ref-asymmetric.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-assign-ref.php create mode 100644 tests/PHPStan/Rules/Properties/data/read-asymmetric-visibility.php create mode 100644 tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 980324fc542..dbb2b4836cd 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -97,6 +97,7 @@ rules: - PHPStan\Rules\Properties\MissingReadOnlyPropertyAssignRule - PHPStan\Rules\Properties\MissingReadOnlyByPhpDocPropertyAssignRule - PHPStan\Rules\Properties\PropertiesInInterfaceRule + - PHPStan\Rules\Properties\PropertyAssignRefRule - PHPStan\Rules\Properties\PropertyAttributesRule - PHPStan\Rules\Properties\PropertyHookAttributesRule - PHPStan\Rules\Properties\PropertyInClassRule diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5bd3f0a86e9..4a0169048ac 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5381,12 +5381,67 @@ private function getBooleanExpressionDepth(Expr $expr, int $depth = 0): int return $depth; } - /** @api */ + /** + * @api + * @deprecated Use canReadProperty() or canWriteProperty() + */ public function canAccessProperty(PropertyReflection $propertyReflection): bool { return $this->canAccessClassMember($propertyReflection); } + /** @api */ + public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool + { + return $this->canAccessClassMember($propertyReflection); + } + + /** @api */ + public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool + { + if (!$propertyReflection->isPrivateSet() && !$propertyReflection->isProtectedSet()) { + return $this->canAccessClassMember($propertyReflection); + } + + if (!$this->phpVersion->supportsAsymmetricVisibility()) { + return $this->canAccessClassMember($propertyReflection); + } + + $classReflectionName = $propertyReflection->getDeclaringClass()->getName(); + $canAccessClassMember = static function (ClassReflection $classReflection) use ($propertyReflection, $classReflectionName) { + if ($propertyReflection->isPrivateSet()) { + return $classReflection->getName() === $classReflectionName; + } + + // protected set + + if ( + $classReflection->getName() === $classReflectionName + || $classReflection->isSubclassOf($classReflectionName) + ) { + return true; + } + + return $propertyReflection->getDeclaringClass()->isSubclassOf($classReflection->getName()); + }; + + foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { + if (!$this->reflectionProvider->hasClass($inClosureBindScopeClass)) { + continue; + } + + if ($canAccessClassMember($this->reflectionProvider->getClass($inClosureBindScopeClass))) { + return true; + } + } + + if ($this->isInClass()) { + return $canAccessClassMember($this->getClassReflection()); + } + + return false; + } + /** @api */ public function canCallMethod(MethodReflection $methodReflection): bool { diff --git a/src/Analyser/OutOfClassScope.php b/src/Analyser/OutOfClassScope.php index 925e35a50ea..a2215bc25c2 100644 --- a/src/Analyser/OutOfClassScope.php +++ b/src/Analyser/OutOfClassScope.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\PropertyReflection; @@ -31,6 +32,18 @@ public function canAccessProperty(PropertyReflection $propertyReflection): bool return $propertyReflection->isPublic(); } + public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool + { + return $propertyReflection->isPublic(); + } + + public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool + { + return $propertyReflection->isPublic() + && !$propertyReflection->isProtectedSet() + && !$propertyReflection->isPrivateSet(); + } + public function canCallMethod(MethodReflection $methodReflection): bool { return $methodReflection->isPublic(); diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 98f86eac4df..1f7c05a50c9 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -357,6 +357,11 @@ public function supportsPropertyHooks(): bool return $this->versionId >= 80400; } + public function supportsAsymmetricVisibility(): bool + { + return $this->versionId >= 80400; + } + public function hasDateTimeExceptions(): bool { return $this->versionId >= 80300; diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index e6747a153cf..9188ef77212 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -112,4 +112,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Reflection/ClassMemberAccessAnswerer.php b/src/Reflection/ClassMemberAccessAnswerer.php index e1c62c60cae..9eeb9798214 100644 --- a/src/Reflection/ClassMemberAccessAnswerer.php +++ b/src/Reflection/ClassMemberAccessAnswerer.php @@ -13,8 +13,15 @@ public function isInClass(): bool; public function getClassReflection(): ?ClassReflection; + /** + * @deprecated Use canReadProperty() or canWriteProperty() + */ public function canAccessProperty(PropertyReflection $propertyReflection): bool; + public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool; + + public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool; + public function canCallMethod(MethodReflection $methodReflection): bool; public function canAccessConstant(ClassConstantReflection $constantReflection): bool; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index cd7ca830b34..909a6b50b91 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -642,7 +642,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco } $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName)); - if ($scope->canAccessProperty($property)) { + if ($scope->canReadProperty($property)) { return $this->properties[$key] = $property; } $this->properties[$key] = $property; diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index cc431c7a5b6..07dc20ce683 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -111,4 +111,14 @@ public function getHook(string $hookType): ExtendedMethodReflection return $this->reflection->getHook($hookType); } + public function isProtectedSet(): bool + { + return $this->reflection->isProtectedSet(); + } + + public function isPrivateSet(): bool + { + return $this->reflection->isPrivateSet(); + } + } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index a98855249a5..40a48911e81 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -107,4 +107,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 85b16a86d63..c4a55163bb5 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -41,4 +41,8 @@ public function hasHook(string $hookType): bool; */ public function getHook(string $hookType): ExtendedMethodReflection; + public function isProtectedSet(): bool; + + public function isPrivateSet(): bool; + } diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index c9540c7b644..8a9a4eed281 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -106,4 +106,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index e9aaa764bca..1fa95c67aab 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -268,4 +268,34 @@ public function getHook(string $hookType): ExtendedMethodReflection return $this->setHook; } + public function isProtectedSet(): bool + { + if ($this->reflection->isProtectedSet()) { + return true; + } + + if ($this->isReadOnly()) { + return !$this->isPrivate() && !$this->reflection->isPrivateSet(); + } + + return false; + } + + public function isPrivateSet(): bool + { + if ($this->reflection->isPrivateSet()) { + return true; + } + + if ($this->reflection->isProtectedSet()) { + return false; + } + + if ($this->isReadOnly()) { + return $this->isPrivate(); + } + + return false; + } + } diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index a06da4df47e..a8cfff2cb3d 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -120,4 +120,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 6013bcfa3be..3382a493444 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -110,4 +110,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 43435261f6b..d5ffc248c66 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -173,4 +173,14 @@ public function getHook(string $hookType): ExtendedMethodReflection ); } + public function isProtectedSet(): bool + { + return $this->reflection->isProtectedSet(); + } + + public function isPrivateSet(): bool + { + return $this->reflection->isPrivateSet(); + } + } diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 40988deb29a..b2d1551e5c3 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -160,4 +160,14 @@ public function getHook(string $hookType): ExtendedMethodReflection return new IntersectionTypeMethodReflection($hooks[0]->getName(), $hooks); } + public function isProtectedSet(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isProtectedSet()); + } + + public function isPrivateSet(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); + } + } diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 0f1c6c5162b..bc3c4f4411e 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -160,4 +160,14 @@ public function getHook(string $hookType): ExtendedMethodReflection return new UnionTypeMethodReflection($hooks[0]->getName(), $hooks); } + public function isProtectedSet(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isProtectedSet()); + } + + public function isPrivateSet(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); + } + } diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 1469cd3f439..52e1571309c 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -103,4 +103,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index 8609cba9d9a..023cc167568 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -28,6 +29,7 @@ final class AccessPropertiesCheck public function __construct( private ReflectionProvider $reflectionProvider, private RuleLevelHelper $ruleLevelHelper, + private PhpVersion $phpVersion, private bool $reportMagicProperties, private bool $checkDynamicProperties, ) @@ -37,7 +39,7 @@ public function __construct( /** * @return list */ - public function check(PropertyFetch $node, Scope $scope): array + public function check(PropertyFetch $node, Scope $scope, bool $write): array { if ($node->name instanceof Identifier) { $names = [$node->name->name]; @@ -47,7 +49,7 @@ public function check(PropertyFetch $node, Scope $scope): array $errors = []; foreach ($names as $name) { - $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name)); + $errors = array_merge($errors, $this->processSingleProperty($scope, $node, $name, $write)); } return $errors; @@ -56,7 +58,7 @@ public function check(PropertyFetch $node, Scope $scope): array /** * @return list */ - private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name): array + private function processSingleProperty(Scope $scope, PropertyFetch $node, string $name, bool $write): array { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, @@ -120,9 +122,14 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $parentClassReflection = $propertyClassReflection->getParentClass(); while ($parentClassReflection !== null) { if ($parentClassReflection->hasProperty($name)) { - if ($scope->canAccessProperty($parentClassReflection->getProperty($name, $scope))) { + if ($write) { + if ($scope->canWriteProperty($parentClassReflection->getProperty($name, $scope))) { + return []; + } + } elseif ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) { return []; } + return [ RuleErrorBuilder::message(sprintf( 'Access to private property $%s of parent class %s.', @@ -153,7 +160,19 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string } $propertyReflection = $type->getProperty($name, $scope); - if (!$scope->canAccessProperty($propertyReflection)) { + if ($write) { + if ($scope->canWriteProperty($propertyReflection)) { + return []; + } + } elseif ($scope->canReadProperty($propertyReflection)) { + return []; + } + + if ( + !$this->phpVersion->supportsAsymmetricVisibility() + || !$write + || (!$propertyReflection->isPrivateSet() && !$propertyReflection->isProtectedSet()) + ) { return [ RuleErrorBuilder::message(sprintf( 'Access to %s property %s::$%s.', @@ -164,7 +183,14 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string ]; } - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'Assign to %s property %s::$%s.', + $propertyReflection->isPrivateSet() ? 'private(set)' : 'protected(set)', + $type->describe(VerbosityLevel::typeOnly()), + $name, + ))->identifier(sprintf('assign.property%s', $propertyReflection->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet'))->build(), + ]; } private function canAccessUndefinedProperties(Scope $scope, Expr $node): bool diff --git a/src/Rules/Properties/AccessPropertiesInAssignRule.php b/src/Rules/Properties/AccessPropertiesInAssignRule.php index 80bc39e4ac4..6577820611a 100644 --- a/src/Rules/Properties/AccessPropertiesInAssignRule.php +++ b/src/Rules/Properties/AccessPropertiesInAssignRule.php @@ -32,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->check->check($node->getPropertyFetch(), $scope); + return $this->check->check($node->getPropertyFetch(), $scope, true); } } diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 9e2d8852be4..e9b382c7f2c 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node, $scope); + return $this->check->check($node, $scope, false); } } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index a5a5c16c819..94e526da0ca 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -184,7 +184,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, while ($parentClassReflection !== null) { if ($parentClassReflection->hasProperty($name)) { - if ($scope->canAccessProperty($parentClassReflection->getProperty($name, $scope))) { + if ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) { return []; } return [ @@ -227,7 +227,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } - if (!$scope->canAccessProperty($property)) { + if (!$scope->canReadProperty($property)) { return array_merge($messages, [ RuleErrorBuilder::message(sprintf( 'Access to %s property $%s of class %s.', diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index b74c62975a3..36a286a4a02 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -153,4 +153,14 @@ public function getHook(string $hookType): ExtendedMethodReflection return $this->originalPropertyReflection->getHook($hookType); } + public function isProtectedSet(): bool + { + return $this->originalPropertyReflection->isProtectedSet(); + } + + public function isPrivateSet(): bool + { + return $this->originalPropertyReflection->isPrivateSet(); + } + } diff --git a/src/Rules/Properties/PropertyAssignRefRule.php b/src/Rules/Properties/PropertyAssignRefRule.php new file mode 100644 index 00000000000..f6d3cc0cd0e --- /dev/null +++ b/src/Rules/Properties/PropertyAssignRefRule.php @@ -0,0 +1,71 @@ + + */ +final class PropertyAssignRefRule implements Rule +{ + + public function __construct( + private PhpVersion $phpVersion, + private PropertyReflectionFinder $propertyReflectionFinder, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\AssignRef::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->phpVersion->supportsAsymmetricVisibility()) { + return []; + } + + if (!$node->expr instanceof Node\Expr\PropertyFetch) { + return []; + } + + $propertyFetch = $node->expr; + + $errors = []; + $reflections = $this->propertyReflectionFinder->findPropertyReflectionsFromNode($propertyFetch, $scope); + foreach ($reflections as $propertyReflection) { + $nativeReflection = $propertyReflection->getNativeReflection(); + if ($nativeReflection === null) { + continue; + } + if ($scope->canWriteProperty($propertyReflection)) { + continue; + } + + $declaringClass = $nativeReflection->getDeclaringClass(); + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s with %s visibility is assigned by reference.', + $declaringClass->getDisplayName(), + $propertyReflection->getName(), + $propertyReflection->isPrivateSet() ? 'private(set)' : ( + $propertyReflection->isProtectedSet() ? 'protected(set)' : ( + $propertyReflection->isPrivate() ? 'private' : 'protected' + ) + ), + )) + ->identifier('property.assignByRef') + ->build(); + } + + return $errors; + } + +} diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php index 981415e915b..52637bb509c 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRefRule.php @@ -38,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array if ($nativeReflection === null) { continue; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canWriteProperty($propertyReflection)) { continue; } if (!$nativeReflection->isReadOnlyByPhpDoc() || $nativeReflection->isReadOnly()) { diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index 29d913d7790..70f18bcbccb 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -60,7 +60,7 @@ public function processNode(Node $node, Scope $scope): array if ($nativeReflection === null) { continue; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canWriteProperty($propertyReflection)) { continue; } if (!$nativeReflection->isReadOnlyByPhpDoc() || $nativeReflection->isReadOnly()) { diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php index 11ac4de1a48..d5079dc353b 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRefRule.php @@ -38,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array if ($nativeReflection === null) { continue; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canWriteProperty($propertyReflection)) { continue; } if (!$nativeReflection->isReadOnly()) { diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php index 2b5c7d010ad..4e9673070fc 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array if ($nativeReflection === null) { continue; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canWriteProperty($propertyReflection)) { continue; } if (!$nativeReflection->isReadOnly()) { diff --git a/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php b/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php index 2d2ab20f6a8..a3ce3325f0b 100644 --- a/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php +++ b/src/Rules/Properties/ReadingWriteOnlyPropertiesRule.php @@ -54,7 +54,7 @@ public function processNode(Node $node, Scope $scope): array if ($propertyReflection === null) { return []; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canReadProperty($propertyReflection)) { return []; } diff --git a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php index bfe8b1f7bf2..137caf24ce7 100644 --- a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php +++ b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php @@ -46,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (!$scope->canAccessProperty($propertyReflection)) { + if (!$scope->canWriteProperty($propertyReflection)) { return []; } diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 7c05525aaef..37a98fa9ba9 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -109,4 +109,14 @@ public function getHook(string $hookType): ExtendedMethodReflection throw new ShouldNotHappenException(); } + public function isProtectedSet(): bool + { + return false; + } + + public function isPrivateSet(): bool + { + return false; + } + } diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php index d58eeb7217a..35d8075f8b7 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php @@ -283,6 +283,8 @@ public function testProperties(string $className, array $properties): void $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); foreach ($properties as $propertyName => $expectedPropertyData) { $this->assertTrue( $class->hasProperty($propertyName), diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index be18a8fb4b1..48c41978680 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -99,6 +99,8 @@ public function testDeprecatedAnnotations(bool $deprecated, string $className, ? $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); $this->assertSame($deprecated, $class->isDeprecated()); $this->assertSame($classDeprecation, $class->getDeprecatedDescription()); diff --git a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php index 6df44ad440f..77b9d3e008e 100644 --- a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php @@ -48,6 +48,8 @@ public function testFinalAnnotations(bool $final, string $className, array $fina $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); $this->assertSame($final, $class->isFinal()); diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index b734fac05c5..d7af0d248f6 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -121,6 +121,8 @@ public function testInternalAnnotations(bool $internal, string $className, array $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); $this->assertSame($internal, $class->isInternal()); diff --git a/tests/PHPStan/Reflection/FunctionReflectionTest.php b/tests/PHPStan/Reflection/FunctionReflectionTest.php index 0b57c66a846..9f0780f1130 100644 --- a/tests/PHPStan/Reflection/FunctionReflectionTest.php +++ b/tests/PHPStan/Reflection/FunctionReflectionTest.php @@ -125,6 +125,8 @@ public function testMethodHasPhpdoc(string $className, string $methodName, ?stri $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); $classReflection = $reflectionProvider->getClass($className); $methodReflection = $classReflection->getMethod($methodName, $scope); @@ -186,6 +188,8 @@ public function testMethodReturnsByReference(string $className, string $methodNa $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); $scope->method('canAccessProperty')->willReturn(true); + $scope->method('canReadProperty')->willReturn(true); + $scope->method('canWriteProperty')->willReturn(true); $classReflection = $reflectionProvider->getClass($className); $methodReflection = $classReflection->getMethod($methodName, $scope); diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index dd44445971c..cd318cfedc3 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -2,9 +2,11 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -16,7 +18,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), true, true), + new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new PhpVersion(PHP_VERSION_ID), true, true), ); } @@ -120,4 +122,42 @@ public function testBug10477(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10477.php'], []); } + public function testAsymmetricVisibility(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/write-asymmetric-visibility.php'], [ + [ + 'Assign to private(set) property $this(WriteAsymmetricVisibility\Bar)::$a.', + 26, + ], + [ + 'Assign to private(set) property WriteAsymmetricVisibility\Foo::$a.', + 34, + ], + [ + 'Assign to protected(set) property WriteAsymmetricVisibility\Foo::$b.', + 35, + ], + [ + 'Access to private property $c of parent class WriteAsymmetricVisibility\ReadonlyProps.', + 64, + ], + [ + 'Assign to protected(set) property WriteAsymmetricVisibility\ReadonlyProps::$a.', + 70, + ], + [ + 'Assign to protected(set) property WriteAsymmetricVisibility\ReadonlyProps::$b.', + 71, + ], + [ + 'Assign to private(set) property WriteAsymmetricVisibility\ReadonlyProps::$c.', + 72, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index db82d7fcf16..1b79507bad9 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; @@ -23,7 +24,7 @@ class AccessPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), true, $this->checkDynamicProperties)); + return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties)); } public function testAccessProperties(): void @@ -959,4 +960,16 @@ public function testTraitMixin(): void $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); } + public function testAsymmetricVisibility(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/read-asymmetric-visibility.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php new file mode 100644 index 00000000000..8bfcf37bd38 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php @@ -0,0 +1,61 @@ + + */ +class PropertyAssignRefRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new PropertyAssignRefRule(new PhpVersion(PHP_VERSION_ID), new PropertyReflectionFinder()); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/property-assign-ref.php'], [ + [ + 'Property PropertyAssignRef\Foo::$foo with private(set) visibility is assigned by reference.', + 25, + ], + [ + 'Property PropertyAssignRef\Foo::$bar with protected(set) visibility is assigned by reference.', + 26, + ], + ]); + } + + public function testAsymmetricVisibility(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/property-assign-ref-asymmetric.php'], [ + [ + 'Property PropertyAssignRefAsymmetric\Foo::$a with private(set) visibility is assigned by reference.', + 28, + ], + [ + 'Property PropertyAssignRefAsymmetric\Foo::$a with private(set) visibility is assigned by reference.', + 36, + ], + [ + 'Property PropertyAssignRefAsymmetric\Foo::$b with protected(set) visibility is assigned by reference.', + 37, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRefRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRefRuleTest.php index c0dae45d970..e8acef73f82 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRefRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRefRuleTest.php @@ -23,7 +23,7 @@ public function testRule(): void $this->markTestSkipped('Test requires PHP 8.1.'); } - $this->analyse([__DIR__ . '/data/readonly-assign-ref.php'], [ + $errors = [ [ 'Readonly property ReadOnlyPropertyAssignRef\Foo::$foo is assigned by reference.', 14, @@ -32,11 +32,17 @@ public function testRule(): void 'Readonly property ReadOnlyPropertyAssignRef\Foo::$bar is assigned by reference.', 15, ], - [ + ]; + + if (PHP_VERSION_ID < 80400) { + // reported by PropertyAssignRefRule on 8.4+ + $errors[] = [ 'Readonly property ReadOnlyPropertyAssignRef\Foo::$bar is assigned by reference.', 26, - ], - ]); + ]; + } + + $this->analyse([__DIR__ . '/data/readonly-assign-ref.php'], $errors); } } diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php index 90da9c44ecd..966f8e9e415 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ConstructorsHelper; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function array_merge; use const PHP_VERSION_ID; /** @@ -32,7 +33,7 @@ public function testRule(): void self::markTestSkipped('Test requires PHP 8.1'); } - $this->analyse([__DIR__ . '/data/readonly-assign.php'], [ + $errors = [ [ 'Readonly property ReadonlyPropertyAssign\Foo::$foo is assigned outside of the constructor.', 21, @@ -49,10 +50,17 @@ public function testRule(): void 'Readonly property ReadonlyPropertyAssign\Foo::$bar is assigned outside of its declaring class.', 39, ], - [ + ]; + + if (PHP_VERSION_ID < 80400) { + // reported by AccessPropertiesInAssignRule on 8.4+ + $errors[] = [ 'Readonly property ReadonlyPropertyAssign\Foo::$baz is assigned outside of its declaring class.', 46, - ], + ]; + } + + $errors = array_merge($errors, [ [ 'Readonly property ReadonlyPropertyAssign\FooArrays::$details is assigned outside of the constructor.', 64, @@ -101,15 +109,21 @@ public function testRule(): void 'Readonly property ReadonlyPropertyAssign\FooEnum::$value is assigned outside of its declaring class.', 152, ],*/ - [ + ]); + + if (PHP_VERSION_ID < 80400) { + // reported by AccessPropertiesInAssignRule on 8.4+ + $errors[] = [ 'Readonly property ReadonlyPropertyAssign\Foo::$baz is assigned outside of its declaring class.', 162, - ], - [ + ]; + $errors[] = [ 'Readonly property ReadonlyPropertyAssign\Foo::$baz is assigned outside of its declaring class.', 163, - ], - ]); + ]; + } + + $this->analyse([__DIR__ . '/data/readonly-assign.php'], $errors); } public function testFeature7648(): void diff --git a/tests/PHPStan/Rules/Properties/data/property-assign-ref-asymmetric.php b/tests/PHPStan/Rules/Properties/data/property-assign-ref-asymmetric.php new file mode 100644 index 00000000000..8163bb9f009 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-assign-ref-asymmetric.php @@ -0,0 +1,39 @@ += 8.4 + +namespace PropertyAssignRefAsymmetric; + +class Foo +{ + + private(set) int $a; + + protected(set) int $b; + + public(set) int $c; + + public function doFoo() + { + $foo = &$this->a; + $bar = &$this->b; + $bar = &$this->c; + } + +} + +class Bar extends Foo +{ + + public function doBar(Foo $foo) + { + $foo = &$this->a; + $bar = &$this->b; + $bar = &$this->c; + } + +} + +function (Foo $foo): void { + $a = &$foo->a; + $b = &$foo->b; + $c = &$foo->c; +}; diff --git a/tests/PHPStan/Rules/Properties/data/property-assign-ref.php b/tests/PHPStan/Rules/Properties/data/property-assign-ref.php new file mode 100644 index 00000000000..eaa2de7ec88 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-assign-ref.php @@ -0,0 +1,29 @@ += 8.1 + +namespace PropertyAssignRef; + +class Foo +{ + + private readonly int $foo; + + public readonly int $bar; + + public function doFoo() + { + $foo = &$this->foo; + $bar = &$this->bar; + } + +} + +class Bar +{ + + public function doBar(Foo $foo) + { + $a = &$foo->foo; // private + $b = &$foo->bar; + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/read-asymmetric-visibility.php b/tests/PHPStan/Rules/Properties/data/read-asymmetric-visibility.php new file mode 100644 index 00000000000..ee38e072ce9 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/read-asymmetric-visibility.php @@ -0,0 +1,37 @@ += 8.4 + +namespace ReadAsymmetricVisibility; + +class Foo +{ + + public private(set) int $a; + public protected(set) int $b; + public public(set) int $c; + + public function doFoo(): void + { + echo $this->a; + echo $this->b; + echo $this->c; + } + +} + +class Bar extends Foo +{ + + public function doBar(): void + { + echo $this->a; + echo $this->b; + echo $this->c; + } + +} + +function (Foo $foo): void { + echo $foo->a; + echo $foo->b; + echo $foo->c; +}; diff --git a/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php b/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php new file mode 100644 index 00000000000..6ea2ab154b7 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php @@ -0,0 +1,73 @@ += 8.4 + +namespace WriteAsymmetricVisibility; + +class Foo +{ + + public private(set) int $a; + public protected(set) int $b; + public public(set) int $c; + + public function doFoo(): void + { + $this->a = 1; + $this->b = 1; + $this->c = 1; + } + +} + +class Bar extends Foo +{ + + public function doBar(): void + { + $this->a = 1; + $this->b = 1; + $this->c = 1; + } + +} + +function (Foo $foo): void { + $foo->a = 1; + $foo->b = 1; + $foo->c = 1; +}; + +class ReadonlyProps +{ + + public readonly int $a; + + protected readonly int $b; + + private readonly int $c; + + public function doFoo(): void + { + $this->a = 1; + $this->b = 1; + $this->c = 1; + } + +} + +class ChildReadonlyProps extends ReadonlyProps +{ + + public function doBar(): void + { + $this->a = 1; + $this->b = 1; + $this->c = 1; + } + +} + +function (ReadonlyProps $foo): void { + $foo->a = 1; + $foo->b = 1; + $foo->c = 1; +}; From da12dc2d10aff3d1dcd79c8aa4cbdc24a5e5ed26 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 15:38:54 +0100 Subject: [PATCH 0954/3097] OverridingPropertyRule - check for final --- src/Reflection/Php/PhpPropertyReflection.php | 9 +++++- .../Properties/OverridingPropertyRule.php | 12 ++++++++ .../Properties/OverridingPropertyRuleTest.php | 28 ++++++++++++++++++ .../data/overriding-final-property.php | 29 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/overriding-final-property.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 1fa95c67aab..3ff948eb3af 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -234,7 +234,14 @@ public function isAbstract(): TrinaryLogic public function isFinal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); + if ($this->reflection->isFinal()) { + return TrinaryLogic::createYes(); + } + if ($this->reflection->isPrivate()) { + return TrinaryLogic::createNo(); + } + + return TrinaryLogic::createFromBoolean($this->isPrivateSet()); } public function isVirtual(): TrinaryLogic diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 5767635e27d..61325ed5081 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -102,6 +102,18 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.visibility')->nonIgnorable()->build(); } + if ($prototype->isFinal()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overrides final property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.parentPropertyFinal') + ->nonIgnorable() + ->build(); + } + $typeErrors = []; $nativeType = $node->getNativeType(); if ($prototype->hasNativeType()) { diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index dafac4e5f07..954eaae5115 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function sprintf; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -171,4 +172,31 @@ public function testBug7692(): void $this->analyse([__DIR__ . '/data/bug-7692.php'], []); } + public function testFinal(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->reportMaybes = true; + $this->analyse([__DIR__ . '/data/overriding-final-property.php'], [ + [ + 'Property OverridingFinalProperty\Bar::$a overrides final property OverridingFinalProperty\Foo::$a.', + 21, + ], + [ + 'Property OverridingFinalProperty\Bar::$b overrides final property OverridingFinalProperty\Foo::$b.', + 23, + ], + [ + 'Property OverridingFinalProperty\Bar::$c overrides final property OverridingFinalProperty\Foo::$c.', + 25, + ], + [ + 'Property OverridingFinalProperty\Bar::$d overrides final property OverridingFinalProperty\Foo::$d.', + 27, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php new file mode 100644 index 00000000000..662f285a7eb --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -0,0 +1,29 @@ += 8.4 + +namespace OverridingFinalProperty; + +class Foo +{ + + final public $a; + + final protected $b; + + public private(set) $c; + + protected private(set) $d; + +} + +class Bar extends Foo +{ + + public $a; + + public $b; + + public $c; + + public $d; + +} From daad756029ff23b987db017515c6ee1883c7b274 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 15:45:01 +0100 Subject: [PATCH 0955/3097] Fix build --- .../Rules/Properties/data/overriding-final-property.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php index 662f285a7eb..a5d78f27bc2 100644 --- a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -9,9 +9,9 @@ class Foo final protected $b; - public private(set) $c; + public private(set) int $c; - protected private(set) $d; + protected private(set) int $d; } From e8805bd352e2caa5574a84ffbc771ec3f4a93889 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 15:47:59 +0100 Subject: [PATCH 0956/3097] Fix build --- Makefile | 1 + .../PHPStan/Rules/Properties/data/overriding-final-property.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1452d74f71d..7ac68874b19 100644 --- a/Makefile +++ b/Makefile @@ -95,6 +95,7 @@ lint: --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php \ + --exclude tests/PHPStan/Rules/Properties/data/overriding-final-property.php \ src tests cs: diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php index a5d78f27bc2..89ed23c6dbb 100644 --- a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -1,4 +1,4 @@ -= 8.4 + Date: Sun, 29 Dec 2024 15:51:01 +0100 Subject: [PATCH 0957/3097] Test PropertyAssignRefRule --- .../Rules/Properties/PropertyAssignRefRuleTest.php | 8 ++++++++ .../Rules/Properties/data/property-assign-ref.php | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php index 8bfcf37bd38..7f24e6f628c 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php @@ -33,6 +33,14 @@ public function testRule(): void 'Property PropertyAssignRef\Foo::$bar with protected(set) visibility is assigned by reference.', 26, ], + [ + 'Property PropertyAssignRef\Baz::$a with protected visibility is assigned by reference.', + 41, + ], + [ + 'Property PropertyAssignRef\Baz::$b with private visibility is assigned by reference.', + 42, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/property-assign-ref.php b/tests/PHPStan/Rules/Properties/data/property-assign-ref.php index eaa2de7ec88..1b49683a019 100644 --- a/tests/PHPStan/Rules/Properties/data/property-assign-ref.php +++ b/tests/PHPStan/Rules/Properties/data/property-assign-ref.php @@ -27,3 +27,17 @@ public function doBar(Foo $foo) } } + +class Baz +{ + + protected $a; + + private $b; + +} + +function (Baz $b): void { + $z = &$b->a; + $zz = &$b->b; +}; From 5bfe8f1eb79c274ea673600556e8bf0434d6ede0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 15:52:22 +0100 Subject: [PATCH 0958/3097] Fix build --- .../Rules/Properties/data/overriding-final-property.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php index 89ed23c6dbb..02d7467e57c 100644 --- a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -22,8 +22,8 @@ class Bar extends Foo public $b; - public $c; + public int $c; - public $d; + public int $d; } From a83c3dcefef85bf648881d2f620c8d5bb0ca245c Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 30 Dec 2024 13:54:37 +0100 Subject: [PATCH 0959/3097] Remove duplicated PHPDoc from InternalScopeFactory classes --- src/Analyser/DirectInternalScopeFactory.php | 11 ----------- src/Analyser/InternalScopeFactory.php | 2 +- src/Analyser/LazyInternalScopeFactory.php | 11 ----------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 74b43d80c9c..22f709cbc96 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -7,10 +7,7 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; @@ -40,14 +37,6 @@ public function __construct( { } - /** - * @param array $expressionTypes - * @param array $nativeExpressionTypes - * @param array $conditionalExpressions - * @param list $inFunctionCallsStack - * @param array $currentlyAssignedExpressions - * @param array $currentlyAllowedUndefinedExpressions - */ public function create( ScopeContext $context, bool $declareStrictTypes = false, diff --git a/src/Analyser/InternalScopeFactory.php b/src/Analyser/InternalScopeFactory.php index 6d8608ec186..8d8daa714fe 100644 --- a/src/Analyser/InternalScopeFactory.php +++ b/src/Analyser/InternalScopeFactory.php @@ -18,7 +18,7 @@ interface InternalScopeFactory * @param list $inClosureBindScopeClasses * @param array $currentlyAssignedExpressions * @param array $currentlyAllowedUndefinedExpressions - * @param list $inFunctionCallsStack + * @param list $inFunctionCallsStack */ public function create( ScopeContext $context, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index ac5b757991b..657cb8c8656 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -7,10 +7,7 @@ use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; -use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; @@ -25,14 +22,6 @@ public function __construct( { } - /** - * @param array $expressionTypes - * @param array $nativeExpressionTypes - * @param array $conditionalExpressions - * @param array $currentlyAssignedExpressions - * @param array $currentlyAllowedUndefinedExpressions - * @param list $inFunctionCallsStack - */ public function create( ScopeContext $context, bool $declareStrictTypes = false, From 068be33ffe572a64647d369e7b63115b58ef1d40 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Dec 2024 16:06:05 +0100 Subject: [PATCH 0960/3097] Test AccessPropertiesInAssignRule private(set) with array append --- .../Properties/AccessPropertiesInAssignRuleTest.php | 4 ++++ .../Properties/data/write-asymmetric-visibility.php | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index cd318cfedc3..53f852ae8ee 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -157,6 +157,10 @@ public function testAsymmetricVisibility(): void 'Assign to private(set) property WriteAsymmetricVisibility\ReadonlyProps::$c.', 72, ], + [ + 'Assign to private(set) property WriteAsymmetricVisibility\ArrayProp::$a.', + 83, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php b/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php index 6ea2ab154b7..a6273caf09d 100644 --- a/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php +++ b/tests/PHPStan/Rules/Properties/data/write-asymmetric-visibility.php @@ -71,3 +71,14 @@ function (ReadonlyProps $foo): void { $foo->b = 1; $foo->c = 1; }; + +class ArrayProp +{ + + public private(set) array $a = []; + +} + +function (ArrayProp $foo): void { + $foo->a[] = 1; +}; From e36bb83477da13b9475efff78f663016c1622d53 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 2 Jan 2025 18:24:09 +0700 Subject: [PATCH 0961/3097] Introduce `getNextStatements` in UnreachableStatementNode Co-authored-by: Ondrej Mirtes --- src/Analyser/NodeScopeResolver.php | 61 +++++++++--- src/Node/UnreachableStatementNode.php | 13 ++- ...achableStatementNextStatementsRuleTest.php | 94 +++++++++++++++++++ .../DeadCode/UnreachableStatementRuleTest.php | 11 +++ .../DeadCode/data/multiple_unreachable.php | 23 +++++ .../data/multiple_unreachable_top_level.php | 17 ++++ 6 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php create mode 100644 tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php create mode 100644 tests/PHPStan/Rules/DeadCode/data/multiple_unreachable_top_level.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 791a8920b72..fb72cfeb6ba 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -316,13 +316,38 @@ public function processNodes( } $alreadyTerminated = true; - $nextStmt = $this->getFirstUnreachableNode(array_slice($nodes, $i + 1), true); - if (!$nextStmt instanceof Node\Stmt) { + $nextStmts = $this->getNextUnreachableStatements(array_slice($nodes, $i + 1), true); + $this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); + } + } + + /** + * @param Node\Stmt[] $nextStmts + * @param callable(Node $node, Scope $scope): void $nodeCallback + */ + private function processUnreachableStatement(array $nextStmts, MutatingScope $scope, callable $nodeCallback): void + { + if ($nextStmts === []) { + return; + } + + $unreachableStatement = null; + $nextStatements = []; + + foreach ($nextStmts as $key => $nextStmt) { + if ($key === 0) { + $unreachableStatement = $nextStmt; continue; } - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + $nextStatements[] = $nextStmt; + } + + if (!$unreachableStatement instanceof Node\Stmt) { + return; } + + $nodeCallback(new UnreachableStatementNode($unreachableStatement, $nextStatements), $scope); } /** @@ -409,11 +434,8 @@ public function processStmtNodes( } $alreadyTerminated = true; - $nextStmt = $this->getFirstUnreachableNode(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); - if ($nextStmt === null) { - continue; - } - $nodeCallback(new UnreachableStatementNode($nextStmt), $scope); + $nextStmts = $this->getNextUnreachableStatements(array_slice($stmts, $i + 1), $parentNode instanceof Node\Stmt\Namespace_); + $this->processUnreachableStatement($nextStmts, $scope, $nodeCallback); } $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); @@ -6514,22 +6536,31 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ } /** - * @template T of Node - * @param array $nodes - * @return T|null + * @param array $nodes + * @return list */ - private function getFirstUnreachableNode(array $nodes, bool $earlyBinding): ?Node + private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): array { + $stmts = []; + $isPassedUnreachableStatement = false; foreach ($nodes as $node) { + if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { + continue; + } + if ($isPassedUnreachableStatement && $node instanceof Node\Stmt) { + $stmts[] = $node; + continue; + } if ($node instanceof Node\Stmt\Nop) { continue; } - if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) { + if (!$node instanceof Node\Stmt) { continue; } - return $node; + $stmts[] = $node; + $isPassedUnreachableStatement = true; } - return null; + return $stmts; } } diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index e0c8cb0af9b..603b7d6f2fe 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -10,9 +10,12 @@ final class UnreachableStatementNode extends Stmt implements VirtualNode { - public function __construct(private Stmt $originalStatement) + /** @param Stmt[] $nextStatements */ + public function __construct(private Stmt $originalStatement, private array $nextStatements = []) { parent::__construct($originalStatement->getAttributes()); + + $this->nextStatements = $nextStatements; } public function getOriginalStatement(): Stmt @@ -33,4 +36,12 @@ public function getSubNodeNames(): array return []; } + /** + * @return Stmt[] + */ + public function getNextStatements(): array + { + return $this->nextStatements; + } + } diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php new file mode 100644 index 00000000000..36aa85b6bb1 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementNextStatementsRuleTest.php @@ -0,0 +1,94 @@ + + */ +class UnreachableStatementNextStatementsRuleTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new class implements Rule { + + public function getNodeType(): string + { + return UnreachableStatementNode::class; + } + + /** + * @param UnreachableStatementNode $node + */ + public function processNode(Node $node, Scope $scope): array + { + $errors = [ + RuleErrorBuilder::message('First unreachable') + ->identifier('tests.nextUnreachableStatements') + ->build(), + ]; + + foreach ($node->getNextStatements() as $nextStatement) { + $errors[] = RuleErrorBuilder::message('Another unreachable') + ->line($nextStatement->getStartLine()) + ->identifier('tests.nextUnreachableStatements') + ->build(); + } + + return $errors; + } + + }; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ + [ + 'First unreachable', + 14, + ], + [ + 'Another unreachable', + 15, + ], + [ + 'Another unreachable', + 17, + ], + [ + 'Another unreachable', + 22, + ], + ]); + } + + public function testRuleTopLevel(): void + { + $this->analyse([__DIR__ . '/data/multiple_unreachable_top_level.php'], [ + [ + 'First unreachable', + 9, + ], + [ + 'Another unreachable', + 10, + ], + [ + 'Another unreachable', + 17, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index da076db1c7d..ec97b0481af 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -230,4 +230,15 @@ public function testBug11992(): void $this->analyse([__DIR__ . '/data/bug-11992.php'], []); } + public function testMultipleUnreachable(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/multiple_unreachable.php'], [ + [ + 'Unreachable statement - code above always terminates.', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php new file mode 100644 index 00000000000..0e9ab15119e --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/multiple_unreachable.php @@ -0,0 +1,23 @@ + Date: Wed, 25 Dec 2024 13:00:35 +0100 Subject: [PATCH 0962/3097] Improve loose comparison on string types --- .../Accessory/AccessoryNonEmptyStringType.php | 5 + .../Accessory/AccessoryNonFalsyStringType.php | 7 ++ .../Accessory/AccessoryNumericStringType.php | 9 ++ src/Type/StringType.php | 5 + .../Analyser/nsrt/loose-comparisons-php7.php | 11 ++ .../Analyser/nsrt/loose-comparisons-php8.php | 11 ++ .../Analyser/nsrt/loose-comparisons.php | 110 +++++++++++++++++- 7 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index f31e3108b49..d630cad147b 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -323,9 +323,14 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isNull()->yes()) { + return new ConstantBooleanType(false); + } + if ($type->isString()->yes() && $type->isNonEmptyString()->no()) { return new ConstantBooleanType(false); } + return new BooleanType(); } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 04fd4fb60e5..faac90150ec 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -11,6 +11,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; @@ -19,6 +20,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\StringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\NonArrayTypeTrait; @@ -322,6 +324,11 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + $falseyTypes = StaticTypeFactory::falsey(); + if ($falseyTypes->isSuperTypeOf($type)->yes()) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index e0f4964c93d..9429c7ea73f 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -11,6 +11,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; @@ -324,6 +325,14 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isNull()->yes()) { + return new ConstantBooleanType(false); + } + + if ($type->isString()->yes() && $type->isNumericString()->no()) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 4605c92efed..eeedd4bc68a 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -10,6 +10,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -267,6 +268,10 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { + if ($type->isArray()->yes()) { + return new ConstantBooleanType(false); + } + return new BooleanType(); } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php index 9e00dd0f657..cc5b0f4eb4b 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php7.php @@ -61,4 +61,15 @@ public function sayInt( assertType('bool', $int == $phpStr); assertType('bool', $int == 'a'); } + + /** + * @param "abc"|"def" $constNonFalsy + */ + public function sayConstUnion( + $constNonFalsy, + ): void + { + assertType('true', $constNonFalsy == 0); + assertType('true', "" == 0); + } } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php index a3ca84cf64b..3c12092eb73 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons-php8.php @@ -68,4 +68,15 @@ public function sayInt( assertType('false', $intRange == 'a'); } + /** + * @param "abc"|"def" $constNonFalsy + */ + public function sayConstUnion( + $constNonFalsy, + ): void + { + assertType('false', $constNonFalsy == 0); + assertType('false', "" == 0); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index 16c414170b8..243b7672fd7 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -526,6 +526,7 @@ public function sayEmptyArray( * @param array{} $emptyArr * @param 'php' $phpStr * @param '' $emptyStr + * @param non-falsy-string $nonFalsyString */ public function sayNonFalsyStr( $true, @@ -540,7 +541,8 @@ public function sayNonFalsyStr( $null, $emptyArr, $phpStr, - $emptyStr + $emptyStr, + $nonFalsyString ): void { assertType('true', $phpStr == $true); @@ -555,6 +557,100 @@ public function sayNonFalsyStr( assertType('false', $phpStr == $emptyArr); assertType('true', $phpStr == $phpStr); assertType('false', $phpStr == $emptyStr); + + assertType('bool', $nonFalsyString == $true); + assertType('false', $nonFalsyString == $false); + assertType('bool', $nonFalsyString == $one); + assertType('false', $nonFalsyString == $zero); + assertType('bool', $nonFalsyString == $minusOne); + assertType('bool', $nonFalsyString == $oneStr); + assertType('false', $nonFalsyString == $zeroStr); + assertType('bool', $nonFalsyString == $minusOneStr); + assertType('bool', $nonFalsyString == $plusOneStr); + assertType('false', $nonFalsyString == $null); + assertType('false', $nonFalsyString == $emptyArr); + assertType('bool', $nonFalsyString == $phpStr); + assertType('false', $nonFalsyString == $emptyStr); + } + + /** + * @param true $true + * @param false $false + * @param 1 $one + * @param 0 $zero + * @param -1 $minusOne + * @param '1' $oneStr + * @param '0' $zeroStr + * @param '-1' $minusOneStr + * @param '+1' $plusOneStr + * @param null $null + * @param array{} $emptyArr + * @param 'php' $phpStr + * @param '' $emptyStr + * @param numeric-string $numericStr + */ + public function sayStr( + $true, + $false, + $one, + $zero, + $minusOne, + $oneStr, + $zeroStr, + $minusOneStr, + $plusOneStr, + $null, + $emptyArr, + string $string, + $phpStr, + $emptyStr, + $numericStr, + ?string $stringOrNull, + ): void + { + assertType('bool', $string == $true); + assertType('bool', $string == $false); + assertType('bool', $string == $one); + assertType('bool', $string == $zero); + assertType('bool', $string == $minusOne); + assertType('bool', $string == $oneStr); + assertType('bool', $string == $zeroStr); + assertType('bool', $string == $minusOneStr); + assertType('bool', $string == $plusOneStr); + assertType('bool', $string == $null); + assertType('bool', $string == $stringOrNull); + assertType('false', $string == $emptyArr); + assertType('bool', $string == $phpStr); + assertType('bool', $string == $emptyStr); + assertType('bool', $string == $numericStr); + + assertType('bool', $numericStr == $true); + assertType('bool', $numericStr == $false); + assertType('bool', $numericStr == $one); + assertType('bool', $numericStr == $zero); + assertType('bool', $numericStr == $minusOne); + assertType('bool', $numericStr == $oneStr); + assertType('bool', $numericStr == $zeroStr); + assertType('bool', $numericStr == $minusOneStr); + assertType('bool', $numericStr == $plusOneStr); + assertType('false', $numericStr == $null); + assertType('bool', $numericStr == $stringOrNull); + assertType('false', $numericStr == $emptyArr); + assertType('bool', $numericStr == $string); + assertType('false', $numericStr == $phpStr); + assertType('false', $numericStr == $emptyStr); + if (is_numeric($string)) { + assertType('bool', $numericStr == $string); + } + + assertType('false', "" == 1); + assertType('true', "" == null); + assertType('false', "" == true); + assertType('true', "" == false); + assertType('false', "" == "1"); + assertType('false', "" == "0"); + assertType('false', "" == "-1"); + assertType('false', "" == []); } /** @@ -657,11 +753,13 @@ public function sayInt( * @param true|1|"1" $looseOne * @param false|0|"0" $looseZero * @param false|1 $constMix + * @param "abc"|"def" $constNonFalsy */ public function sayConstUnion( $looseOne, $looseZero, - $constMix + $constMix, + $constNonFalsy, ): void { assertType('true', $looseOne == 1); @@ -696,6 +794,14 @@ public function sayConstUnion( assertType('bool', $constMix == $looseOne); assertType('bool', $looseZero == $constMix); assertType('bool', $constMix == $looseZero); + + assertType('false', $constNonFalsy == 1); + assertType('false', $constNonFalsy == null); + assertType('true', $constNonFalsy == true); + assertType('false', $constNonFalsy == false); + assertType('false', $constNonFalsy == "1"); + assertType('false', $constNonFalsy == "0"); + assertType('false', $constNonFalsy == []); } /** From a742e51a9f5f984141e30b9dd96cd68ba33ba59f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Jan 2025 12:33:30 +0100 Subject: [PATCH 0963/3097] Assignment not needed --- src/Node/UnreachableStatementNode.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Node/UnreachableStatementNode.php b/src/Node/UnreachableStatementNode.php index 603b7d6f2fe..3e2c72e29bd 100644 --- a/src/Node/UnreachableStatementNode.php +++ b/src/Node/UnreachableStatementNode.php @@ -14,8 +14,6 @@ final class UnreachableStatementNode extends Stmt implements VirtualNode public function __construct(private Stmt $originalStatement, private array $nextStatements = []) { parent::__construct($originalStatement->getAttributes()); - - $this->nextStatements = $nextStatements; } public function getOriginalStatement(): Stmt From b614f70e0154010f74e36dc9264962facac8122e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Jan 2025 17:19:09 +0100 Subject: [PATCH 0964/3097] GetNonVirtualPropertyHookReadRule - do not report if get hook is not present at all --- .../Properties/GetNonVirtualPropertyHookReadRule.php | 12 +++++++++++- .../data/get-non-virtual-property-hook-read.php | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php index 61b1c32a6c9..9a2fc917e7f 100644 --- a/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php +++ b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php @@ -70,7 +70,17 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($node->getProperties() as $propertyNode) { - if (!$propertyNode->hasHooks()) { + $hasGetHook = false; + foreach ($propertyNode->getHooks() as $hook) { + if ($hook->name->toLowerString() !== 'get') { + continue; + } + + $hasGetHook = true; + break; + } + + if (!$hasGetHook) { continue; } diff --git a/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php b/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php index 077792c4060..76ceabe4080 100644 --- a/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php +++ b/tests/PHPStan/Rules/Properties/data/get-non-virtual-property-hook-read.php @@ -46,3 +46,12 @@ class Foo } } + +class GetHookIsNotPresentAtAll +{ + public int $i { + set { + $this->i = $value + 10; + } + } +} From 3119fe90be7f055de2fe6848a14310e0040f8935 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 13:20:07 +0100 Subject: [PATCH 0965/3097] Update PHP-Parser and BetterReflection --- composer.json | 6 +++--- composer.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 6e2c1cb8c4d..6ac10b2742b 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,16 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#db675e059f57071e8209c99075128b92d8a727e7", + "jetbrains/phpstorm-stubs": "dev-master#dfcad4524db603bd20bdec3aab1a31c5f5128ea3", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", "nette/php-generator": "3.6.9", "nette/schema": "^1.2.2", "nette/utils": "^3.2.5", - "nikic/php-parser": "^5.3.0", + "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.49.0.0", + "ondrejmirtes/better-reflection": "6.51.0.1", "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 880e1134519..039bd64a30f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f3a19a9abe4cf8cfbe9a6a76cf161369", + "content-hash": "f5964f498aa14ffd6b984c06676417aa", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "db675e059f57071e8209c99075128b92d8a727e7" + "reference": "dfcad4524db603bd20bdec3aab1a31c5f5128ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/db675e059f57071e8209c99075128b92d8a727e7", - "reference": "db675e059f57071e8209c99075128b92d8a727e7", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/dfcad4524db603bd20bdec3aab1a31c5f5128ea3", + "reference": "dfcad4524db603bd20bdec3aab1a31c5f5128ea3", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2024-12-23T11:36:45+00:00" + "time": "2025-01-02T13:51:39+00:00" }, { "name": "nette/bootstrap", @@ -2057,16 +2057,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -2109,9 +2109,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "ondram/ci-detector", @@ -2187,22 +2187,22 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.49.0.0", + "version": "6.51.0.1", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "11abb6b4c9c8b29ee2730a3307ebae77b17fa94d" + "reference": "739c4cc0a01ef79055688606be07cff93551815d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/11abb6b4c9c8b29ee2730a3307ebae77b17fa94d", - "reference": "11abb6b4c9c8b29ee2730a3307ebae77b17fa94d", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/739c4cc0a01ef79055688606be07cff93551815d", + "reference": "739c4cc0a01ef79055688606be07cff93551815d", "shasum": "" }, "require": { "ext-json": "*", - "jetbrains/phpstorm-stubs": "dev-master#b61d4a5f40c3940be440d85355fef4e2416b8527", - "nikic/php-parser": "^5.3.1", + "jetbrains/phpstorm-stubs": "dev-master#dfcad4524db603bd20bdec3aab1a31c5f5128ea3", + "nikic/php-parser": "^5.4.0", "php": "^7.4 || ^8.0" }, "conflict": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.5.1", + "phpunit/phpunit": "^11.5.2", "rector/rector": "1.2.10" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.49.0.0" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.51.0.1" }, - "time": "2024-12-20T19:27:15+00:00" + "time": "2025-01-04T12:23:15+00:00" }, { "name": "phpstan/php-8-stubs", From d5a6a6310118249041b7126a19001756c98f125e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 13:39:35 +0100 Subject: [PATCH 0966/3097] Simplify code thanks to PHP-Parser update --- .../Php/PhpMethodFromParserNodeReflection.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index af7d9a80f40..9e36a632aef 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Php; -use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\Assertions; @@ -230,7 +229,7 @@ public function isFinal(): TrinaryLogic { $method = $this->getClassMethod(); if ($method instanceof Node\PropertyHook) { - return TrinaryLogic::createFromBoolean((bool) ($method->flags & Modifiers::FINAL)); + return TrinaryLogic::createFromBoolean($method->isFinal()); } return TrinaryLogic::createFromBoolean($method->isFinal() || $this->isFinal); @@ -238,12 +237,7 @@ public function isFinal(): TrinaryLogic public function isFinalByKeyword(): TrinaryLogic { - $method = $this->getClassMethod(); - if ($method instanceof Node\PropertyHook) { - return TrinaryLogic::createFromBoolean((bool) ($method->flags & Modifiers::FINAL)); - } - - return TrinaryLogic::createFromBoolean($method->isFinal()); + return TrinaryLogic::createFromBoolean($this->getClassMethod()->isFinal()); } public function isBuiltin(): bool From 01ade6ed1ffae4d96cdd23a783855e2f2a1e7dfd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 13:40:50 +0100 Subject: [PATCH 0967/3097] Simplify code thanks to BetterReflection update --- src/Reflection/Php/PhpPropertyReflection.php | 33 ++----------------- .../AccessPropertiesInAssignRuleTest.php | 4 +-- .../Properties/PropertyAssignRefRuleTest.php | 2 +- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 3ff948eb3af..1284d4a699e 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -234,14 +234,7 @@ public function isAbstract(): TrinaryLogic public function isFinal(): TrinaryLogic { - if ($this->reflection->isFinal()) { - return TrinaryLogic::createYes(); - } - if ($this->reflection->isPrivate()) { - return TrinaryLogic::createNo(); - } - - return TrinaryLogic::createFromBoolean($this->isPrivateSet()); + return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); } public function isVirtual(): TrinaryLogic @@ -277,32 +270,12 @@ public function getHook(string $hookType): ExtendedMethodReflection public function isProtectedSet(): bool { - if ($this->reflection->isProtectedSet()) { - return true; - } - - if ($this->isReadOnly()) { - return !$this->isPrivate() && !$this->reflection->isPrivateSet(); - } - - return false; + return $this->reflection->isProtectedSet(); } public function isPrivateSet(): bool { - if ($this->reflection->isPrivateSet()) { - return true; - } - - if ($this->reflection->isProtectedSet()) { - return false; - } - - if ($this->isReadOnly()) { - return $this->isPrivate(); - } - - return false; + return $this->reflection->isPrivateSet(); } } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index 53f852ae8ee..d1e43fd0e1e 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -150,11 +150,11 @@ public function testAsymmetricVisibility(): void 70, ], [ - 'Assign to protected(set) property WriteAsymmetricVisibility\ReadonlyProps::$b.', + 'Access to protected property WriteAsymmetricVisibility\ReadonlyProps::$b.', 71, ], [ - 'Assign to private(set) property WriteAsymmetricVisibility\ReadonlyProps::$c.', + 'Access to private property WriteAsymmetricVisibility\ReadonlyProps::$c.', 72, ], [ diff --git a/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php index 7f24e6f628c..3d78abbee9c 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAssignRefRuleTest.php @@ -26,7 +26,7 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/property-assign-ref.php'], [ [ - 'Property PropertyAssignRef\Foo::$foo with private(set) visibility is assigned by reference.', + 'Property PropertyAssignRef\Foo::$foo with private visibility is assigned by reference.', 25, ], [ From cc83891d25da951ada41ad1dba4995bf70827554 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 13:53:37 +0100 Subject: [PATCH 0968/3097] Always call processStmtNodes on property hook body thanks to `getStmts()` --- src/Analyser/NodeScopeResolver.php | 96 +++++++++++++++------------- src/Parser/LineAttributesVisitor.php | 28 ++++++++ 2 files changed, 80 insertions(+), 44 deletions(-) create mode 100644 src/Parser/LineAttributesVisitor.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fb72cfeb6ba..eb05b36f3b0 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -124,6 +124,7 @@ use PHPStan\Parser\ArrowFunctionArgVisitor; use PHPStan\Parser\ClosureArgVisitor; use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; +use PHPStan\Parser\LineAttributesVisitor; use PHPStan\Parser\Parser; use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\Php\PhpVersion; @@ -4830,54 +4831,61 @@ private function processPropertyHooks( $hook, ), $hookScope); + $stmts = $hook->getStmts(); + if ($stmts === null) { + return; + } + if ($hook->body instanceof Expr) { - $this->processExprNode($stmt, $hook->body, $hookScope, $nodeCallback, ExpressionContext::createTopLevel()); - $nodeCallback(new PropertyAssignNode(new PropertyFetch(new Variable('this'), $propertyName, $hook->body->getAttributes()), $hook->body, false), $hookScope); - } elseif (is_array($hook->body)) { - $gatheredReturnStatements = []; - $executionEnds = []; - $methodImpurePoints = []; - $statementResult = $this->processStmtNodes(new PropertyHookStatementNode($hook), $hook->body, $hookScope, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { - $nodeCallback($node, $scope); - if ($scope->getFunction() !== $hookScope->getFunction()) { - return; - } - if ($scope->isInAnonymousFunction()) { - return; - } - if ($node instanceof PropertyAssignNode) { - $hookImpurePoints[] = new ImpurePoint( - $scope, - $node, - 'propertyAssign', - 'property assignment', - true, - ); - return; - } - if ($node instanceof ExecutionEndNode) { - $executionEnds[] = $node; - return; - } - if (!$node instanceof Return_) { - return; - } + // enrich attributes of nodes in short hook body statements + $traverser = new NodeTraverser( + new LineAttributesVisitor($hook->body->getStartLine(), $hook->body->getEndLine()), + ); + $traverser->traverse($stmts); + } - $gatheredReturnStatements[] = new ReturnStatement($scope, $node); - }, StatementContext::createTopLevel()); + $gatheredReturnStatements = []; + $executionEnds = []; + $methodImpurePoints = []; + $statementResult = $this->processStmtNodes(new PropertyHookStatementNode($hook), $stmts, $hookScope, static function (Node $node, Scope $scope) use ($nodeCallback, $hookScope, &$gatheredReturnStatements, &$executionEnds, &$hookImpurePoints): void { + $nodeCallback($node, $scope); + if ($scope->getFunction() !== $hookScope->getFunction()) { + return; + } + if ($scope->isInAnonymousFunction()) { + return; + } + if ($node instanceof PropertyAssignNode) { + $hookImpurePoints[] = new ImpurePoint( + $scope, + $node, + 'propertyAssign', + 'property assignment', + true, + ); + return; + } + if ($node instanceof ExecutionEndNode) { + $executionEnds[] = $node; + return; + } + if (!$node instanceof Return_) { + return; + } - $nodeCallback(new PropertyHookReturnStatementsNode( - $hook, - $gatheredReturnStatements, - $statementResult, - $executionEnds, - array_merge($statementResult->getImpurePoints(), $methodImpurePoints), - $classReflection, - $hookReflection, - $propertyReflection, - ), $hookScope); - } + $gatheredReturnStatements[] = new ReturnStatement($scope, $node); + }, StatementContext::createTopLevel()); + $nodeCallback(new PropertyHookReturnStatementsNode( + $hook, + $gatheredReturnStatements, + $statementResult, + $executionEnds, + array_merge($statementResult->getImpurePoints(), $methodImpurePoints), + $classReflection, + $hookReflection, + $propertyReflection, + ), $hookScope); } } diff --git a/src/Parser/LineAttributesVisitor.php b/src/Parser/LineAttributesVisitor.php new file mode 100644 index 00000000000..53f3f3f50ff --- /dev/null +++ b/src/Parser/LineAttributesVisitor.php @@ -0,0 +1,28 @@ +getStartLine() === -1) { + $node->setAttribute('startLine', $this->startLine); + } + + if ($node->getEndLine() === -1) { + $node->setAttribute('endLine', $this->endLine); + } + + return $node; + } + +} From b2c257625fe9a3544fdcaafde72cc7a6b0b5c870 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 14:06:57 +0100 Subject: [PATCH 0969/3097] PropertyHookReturnStatementsNode is invoked for short body hooks --- ...ckedExceptionInPropertyHookThrowsRuleTest.php | 4 ++++ ...ropertyHookWithExplicitThrowPointRuleTest.php | 16 ++++++++++++++++ .../TooWidePropertyHookThrowTypeRuleTest.php | 4 ++++ .../missing-exception-property-hook-throws.php | 4 ++++ .../data/throws-void-property-hook.php | 7 +++++++ .../data/too-wide-throws-property-hook.php | 5 +++++ .../set-non-virtual-property-hook-assign.php | 7 +++++++ 7 files changed, 47 insertions(+) diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php index e7f6130d670..cf01fb3852c 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInPropertyHookThrowsRuleTest.php @@ -45,6 +45,10 @@ public function testRule(): void 'Get hook for property MissingExceptionPropertyHookThrows\Foo::$m throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', 38, ], + [ + 'Get hook for property MissingExceptionPropertyHookThrows\Foo::$n throws checked exception InvalidArgumentException but it\'s missing from the PHPDoc @throws tag.', + 43, + ], ]); } diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php index fecb9cfdc52..6072db088b6 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidPropertyHookWithExplicitThrowPointRuleTest.php @@ -44,6 +44,10 @@ public function dataRule(): array 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', 18, ], + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$j throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 26, + ], ], ], [ @@ -59,6 +63,10 @@ public function dataRule(): array 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', 18, ], + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$j throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 26, + ], ], ], [ @@ -69,6 +77,10 @@ public function dataRule(): array 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', 18, ], + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$j throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 26, + ], ], ], [ @@ -79,6 +91,10 @@ public function dataRule(): array 'Get hook for property ThrowsVoidPropertyHook\Foo::$i throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', 18, ], + [ + 'Get hook for property ThrowsVoidPropertyHook\Foo::$j throws exception ThrowsVoidPropertyHook\MyException but the PHPDoc contains @throws void.', + 26, + ], ], ], ]; diff --git a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php index 0c3d0f75a14..6fed25e6c22 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -43,6 +43,10 @@ public function testRule(): void 'Get hook for property TooWideThrowsPropertyHook\Foo::$j has DomainException in PHPDoc @throws tag but it\'s not thrown.', 76, ], + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$k has DomainException in PHPDoc @throws tag but it\'s not thrown.', + 83, + ], ]); } diff --git a/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php b/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php index d9fba8d0f1e..773f849d744 100644 --- a/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php +++ b/tests/PHPStan/Rules/Exceptions/data/missing-exception-property-hook-throws.php @@ -39,4 +39,8 @@ class Foo } } + public int $n { + get => throw new \InvalidArgumentException(); // error + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php b/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php index 82c4c2381f5..08e3f10940b 100644 --- a/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php +++ b/tests/PHPStan/Rules/Exceptions/data/throws-void-property-hook.php @@ -19,4 +19,11 @@ class Foo } } + public int $j { + /** + * @throws void + */ + get => throw new MyException(); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php index 92998bafa45..6cf4b550724 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php @@ -78,4 +78,9 @@ class Foo } } + public int $k { + /** @throws \DomainException */ + get => 11; // error - DomainException unused + } + } diff --git a/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php b/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php index ffc0e559f6c..56133fb5567 100644 --- a/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php +++ b/tests/PHPStan/Rules/Properties/data/set-non-virtual-property-hook-assign.php @@ -65,4 +65,11 @@ class Foo } } + public int $k5 { + get { + return $this->k4 + 1; + } + set => $value; // short body always assigns + } + } From 947e74e22356db4edb1ea7950367dd968523cd82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 14:13:22 +0100 Subject: [PATCH 0970/3097] ShortGetPropertyHookReturnTypeRule is no longer needed --- conf/config.level3.neon | 1 - .../ShortGetPropertyHookReturnTypeRule.php | 75 ------------------- .../Rules/Methods/ReturnTypeRuleTest.php | 33 ++++++++ .../data/short-get-property-hook-return.php | 0 ...ShortGetPropertyHookReturnTypeRuleTest.php | 57 -------------- 5 files changed, 33 insertions(+), 133 deletions(-) delete mode 100644 src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php rename tests/PHPStan/Rules/{Properties => Methods}/data/short-get-property-hook-return.php (100%) delete mode 100644 tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php diff --git a/conf/config.level3.neon b/conf/config.level3.neon index c946a5ee3fe..4e5f80c5efd 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -22,7 +22,6 @@ rules: - PHPStan\Rules\Properties\ReadOnlyPropertyAssignRefRule - PHPStan\Rules\Properties\ReadOnlyByPhpDocPropertyAssignRefRule - PHPStan\Rules\Properties\SetNonVirtualPropertyHookAssignRule - - PHPStan\Rules\Properties\ShortGetPropertyHookReturnTypeRule - PHPStan\Rules\Properties\TypesAssignedToPropertiesRule - PHPStan\Rules\Variables\ParameterOutAssignedTypeRule - PHPStan\Rules\Variables\ParameterOutExecutionEndTypeRule diff --git a/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php b/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php deleted file mode 100644 index cf30777b199..00000000000 --- a/src/Rules/Properties/ShortGetPropertyHookReturnTypeRule.php +++ /dev/null @@ -1,75 +0,0 @@ - - */ -final class ShortGetPropertyHookReturnTypeRule implements Rule -{ - - public function __construct(private FunctionReturnTypeCheck $returnTypeCheck) - { - } - - public function getNodeType(): string - { - return InPropertyHookNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - // return statements in long property hook bodies are checked by Methods\ReturnTypeRule - // short set property hook type is checked by TypesAssignedToPropertiesRule - $hookReflection = $node->getHookReflection(); - if ($hookReflection->getPropertyHookName() !== 'get') { - return []; - } - - $originalHookNode = $node->getOriginalNode(); - $hookBody = $originalHookNode->body; - if (!$hookBody instanceof Node\Expr) { - return []; - } - - $methodDescription = sprintf( - 'Get hook for property %s::$%s', - $hookReflection->getDeclaringClass()->getDisplayName(), - $hookReflection->getHookedPropertyName(), - ); - - $returnType = $hookReflection->getReturnType(); - - return $this->returnTypeCheck->checkReturnType( - $scope, - $returnType, - $hookBody, - $node, - sprintf( - '%s should return %%s but empty return statement found.', - $methodDescription, - ), - sprintf( - '%s with return type void returns %%s but should not return anything.', - $methodDescription, - ), - sprintf( - '%s should return %%s but returns %%s.', - $methodDescription, - ), - sprintf( - '%s should never return but return statement found.', - $methodDescription, - ), - $hookReflection->isGenerator(), - ); - } - -} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index c5243821742..98bb11b0b54 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1138,4 +1138,37 @@ public function testPropertyHooks(): void ]); } + public function testShortGetPropertyHook(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/short-get-property-hook-return.php'], [ + [ + 'Get hook for property ShortGetPropertyHookReturn\Foo::$i should return int but returns string.', + 9, + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\Foo::$s should return non-empty-string but returns \'\'.', + 18, + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$a should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 36, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$b should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 50, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + [ + 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$c should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', + 59, + 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php b/tests/PHPStan/Rules/Methods/data/short-get-property-hook-return.php similarity index 100% rename from tests/PHPStan/Rules/Properties/data/short-get-property-hook-return.php rename to tests/PHPStan/Rules/Methods/data/short-get-property-hook-return.php diff --git a/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php b/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php deleted file mode 100644 index 8a318db7eda..00000000000 --- a/tests/PHPStan/Rules/Properties/ShortGetPropertyHookReturnTypeRuleTest.php +++ /dev/null @@ -1,57 +0,0 @@ - - */ -final class ShortGetPropertyHookReturnTypeRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new ShortGetPropertyHookReturnTypeRule( - new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false)), - ); - } - - public function testRule(): void - { - if (PHP_VERSION_ID < 80400) { - $this->markTestSkipped('Test requires PHP 8.4.'); - } - - $this->analyse([__DIR__ . '/data/short-get-property-hook-return.php'], [ - [ - 'Get hook for property ShortGetPropertyHookReturn\Foo::$i should return int but returns string.', - 9, - ], - [ - 'Get hook for property ShortGetPropertyHookReturn\Foo::$s should return non-empty-string but returns \'\'.', - 18, - ], - [ - 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$a should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', - 36, - 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', - ], - [ - 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$b should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', - 50, - 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', - ], - [ - 'Get hook for property ShortGetPropertyHookReturn\GenericFoo::$c should return T of ShortGetPropertyHookReturn\Foo but returns ShortGetPropertyHookReturn\Foo.', - 59, - 'Type ShortGetPropertyHookReturn\Foo is not always the same as T. It breaks the contract for some argument types, typically subtypes.', - ], - ]); - } - -} From 317a9974e295838cb5083527f6061965ab70dca5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 14:13:53 +0100 Subject: [PATCH 0971/3097] PropertyHookNameVisitor is no longer needed, PHP-parser comes with `propertyName` attribute --- conf/config.neon | 5 -- src/Analyser/NodeScopeResolver.php | 3 +- src/Parser/CleaningVisitor.php | 2 +- src/Parser/PropertyHookNameVisitor.php | 60 --------------------- src/Parser/SimpleParser.php | 2 - src/Reflection/InitializerExprContext.php | 5 +- src/Type/FileTypeMapper.php | 11 ++-- tests/PHPStan/Parser/CleaningParserTest.php | 1 - 8 files changed, 9 insertions(+), 80 deletions(-) delete mode 100644 src/Parser/PropertyHookNameVisitor.php diff --git a/conf/config.neon b/conf/config.neon index b2d222ebb39..c3c2c7bdeda 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -320,11 +320,6 @@ services: tags: - phpstan.parser.richParserNodeVisitor - - - class: PHPStan\Parser\PropertyHookNameVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - class: PHPStan\Node\Printer\ExprPrinter diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index eb05b36f3b0..ab61ff14862 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -126,7 +126,6 @@ use PHPStan\Parser\ImmediatelyInvokedClosureVisitor; use PHPStan\Parser\LineAttributesVisitor; use PHPStan\Parser\Parser; -use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; @@ -6436,7 +6435,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n } elseif ($node instanceof Node\Stmt\Function_) { $functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { $functionName = sprintf('$%s::%s', $propertyName, $node->name->toString()); } diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index eb9492f3cde..80f5b2f5947 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -37,7 +37,7 @@ public function enterNode(Node $node): ?Node } if ($node instanceof Node\PropertyHook && is_array($node->body)) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { $node->body = $this->keepVariadicsAndYields($node->body, $propertyName); return $node; diff --git a/src/Parser/PropertyHookNameVisitor.php b/src/Parser/PropertyHookNameVisitor.php deleted file mode 100644 index 5a49a709153..00000000000 --- a/src/Parser/PropertyHookNameVisitor.php +++ /dev/null @@ -1,60 +0,0 @@ -hooks) === 0) { - return null; - } - - $propertyName = null; - foreach ($node->props as $prop) { - $propertyName = $prop->name->toString(); - break; - } - - if (!isset($propertyName)) { - return null; - } - - foreach ($node->hooks as $hook) { - $hook->setAttribute(self::ATTRIBUTE_NAME, $propertyName); - } - - return $node; - } - - if ($node instanceof Node\Param) { - if (count($node->hooks) === 0) { - return null; - } - if (!$node->var instanceof Node\Expr\Variable) { - return null; - } - if (!is_string($node->var->name)) { - return null; - } - - foreach ($node->hooks as $hook) { - $hook->setAttribute(self::ATTRIBUTE_NAME, $node->var->name); - } - - return $node; - } - - return null; - } - -} diff --git a/src/Parser/SimpleParser.php b/src/Parser/SimpleParser.php index 71bab19964f..8fbd1127420 100644 --- a/src/Parser/SimpleParser.php +++ b/src/Parser/SimpleParser.php @@ -17,7 +17,6 @@ public function __construct( private NameResolver $nameResolver, private VariadicMethodsVisitor $variadicMethodsVisitor, private VariadicFunctionsVisitor $variadicFunctionsVisitor, - private PropertyHookNameVisitor $propertyHookNameVisitor, ) { } @@ -53,7 +52,6 @@ public function parseString(string $sourceCode): array $nodeTraverser->addVisitor($this->nameResolver); $nodeTraverser->addVisitor($this->variadicMethodsVisitor); $nodeTraverser->addVisitor($this->variadicFunctionsVisitor); - $nodeTraverser->addVisitor($this->propertyHookNameVisitor); /** @var array */ return $nodeTraverser->traverse($nodes); diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index eb64cacdbb9..650408f3498 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -9,7 +9,6 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\BetterReflection\Reflection\ReflectionConstant; -use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\ShouldNotHappenException; use function array_slice; @@ -144,7 +143,7 @@ public static function fromStubParameter( } elseif ($function instanceof ClassMethod) { $functionName = $function->name->toString(); } elseif ($function instanceof PropertyHook) { - $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $function->getAttribute('propertyName'); $functionName = sprintf('$%s::%s', $propertyName, $function->name->toString()); } @@ -152,7 +151,7 @@ public static function fromStubParameter( if ($function instanceof ClassMethod && $className !== null) { $methodName = sprintf('%s::%s', $className, $function->name->toString()); } elseif ($function instanceof PropertyHook) { - $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $function->getAttribute('propertyName'); $methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString()); } elseif ($function instanceof Function_ && $function->namespacedName !== null) { $methodName = $function->namespacedName->toString(); diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 3cc5e6c62ed..614ef449108 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -9,7 +9,6 @@ use PHPStan\Broker\AnonymousClassNameHelper; use PHPStan\File\FileHelper; use PHPStan\Parser\Parser; -use PHPStan\Parser\PropertyHookNameVisitor; use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; @@ -281,7 +280,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA } elseif ($node instanceof Node\Stmt\Function_) { $functionStack[] = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { $functionStack[] = sprintf('$%s::%s', $propertyName, $node->name->toString()); } @@ -299,7 +298,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA return null; } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { $docComment = GetLastDocComment::forNode($node); if ($docComment !== null) { @@ -394,7 +393,7 @@ static function (Node $node) use (&$namespace, &$functionStack, &$classStack): v array_pop($functionStack); } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { if (count($functionStack) === 0) { throw new ShouldNotHappenException(); @@ -503,7 +502,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun } elseif ($node instanceof Node\Stmt\Function_) { $functionStack[] = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\'); } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { $functionStack[] = sprintf('$%s::%s', $propertyName, $node->name->toString()); } @@ -742,7 +741,7 @@ static function (Node $node, $callbackResult) use (&$namespace, &$functionStack, array_pop($functionStack); } elseif ($node instanceof Node\PropertyHook) { - $propertyName = $node->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $propertyName = $node->getAttribute('propertyName'); if ($propertyName !== null) { if (count($functionStack) === 0) { throw new ShouldNotHappenException(); diff --git a/tests/PHPStan/Parser/CleaningParserTest.php b/tests/PHPStan/Parser/CleaningParserTest.php index 8dbf5691716..69c0e8af105 100644 --- a/tests/PHPStan/Parser/CleaningParserTest.php +++ b/tests/PHPStan/Parser/CleaningParserTest.php @@ -75,7 +75,6 @@ public function testParse( new NameResolver(), new VariadicMethodsVisitor(), new VariadicFunctionsVisitor(), - new PropertyHookNameVisitor(), ), new PhpVersion($phpVersionId), ); From 17d6b2938621a42bc1f49d5d05eeadc06fde98aa Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 28 Nov 2024 10:56:02 +0100 Subject: [PATCH 0972/3097] Enforce safe constructor overrides with `@phpstan-consistent-constructor` --- conf/config.neon | 3 ++ src/PhpDoc/StubValidator.php | 11 +++- .../Methods/ConsistentConstructorRule.php | 7 ++- .../MethodVisibilityComparisonHelper.php | 51 +++++++++++++++++++ src/Rules/Methods/OverridingMethodRule.php | 28 +--------- .../Methods/ConsistentConstructorRuleTest.php | 15 +++++- .../Rules/Methods/MethodSignatureRuleTest.php | 1 + .../Methods/OverridingMethodRuleTest.php | 1 + .../PHPStan/Rules/Methods/data/bug-12137.php | 23 +++++++++ 9 files changed, 111 insertions(+), 29 deletions(-) create mode 100755 src/Rules/Methods/MethodVisibilityComparisonHelper.php create mode 100755 tests/PHPStan/Rules/Methods/data/bug-12137.php diff --git a/conf/config.neon b/conf/config.neon index c3c2c7bdeda..b9c52c9a571 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -986,6 +986,9 @@ services: - class: PHPStan\Rules\Methods\MethodParameterComparisonHelper + - + class: PHPStan\Rules\Methods\MethodVisibilityComparisonHelper + - class: PHPStan\Rules\MissingTypehintCheck arguments: diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 33fde1e46e0..0374aa268f3 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -68,6 +68,7 @@ use PHPStan\Rules\Methods\ExistingClassesInTypehintsRule; use PHPStan\Rules\Methods\MethodParameterComparisonHelper; use PHPStan\Rules\Methods\MethodSignatureRule; +use PHPStan\Rules\Methods\MethodVisibilityComparisonHelper; use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule; use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule; use PHPStan\Rules\Methods\MissingMethodSelfOutTypeRule; @@ -209,7 +210,15 @@ private function getRuleRegistry(Container $container): RuleRegistry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false), - new OverridingMethodRule($phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), true, new MethodParameterComparisonHelper($phpVersion), $phpClassReflectionExtension, $container->getParameter('checkMissingOverrideMethodAttribute')), + new OverridingMethodRule( + $phpVersion, + new MethodSignatureRule($phpClassReflectionExtension, true, true), + true, + new MethodParameterComparisonHelper($phpVersion), + new MethodVisibilityComparisonHelper(), + $phpClassReflectionExtension, + $container->getParameter('checkMissingOverrideMethodAttribute'), + ), new DuplicateDeclarationRule(), new LocalTypeAliasesRule($localTypeAliasesCheck), new LocalTypeTraitAliasesRule($localTypeAliasesCheck, $reflectionProvider), diff --git a/src/Rules/Methods/ConsistentConstructorRule.php b/src/Rules/Methods/ConsistentConstructorRule.php index ab8553b5cc0..16226a10745 100644 --- a/src/Rules/Methods/ConsistentConstructorRule.php +++ b/src/Rules/Methods/ConsistentConstructorRule.php @@ -7,6 +7,7 @@ use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\Dummy\DummyConstructorReflection; use PHPStan\Rules\Rule; +use function array_merge; use function strtolower; /** @implements Rule */ @@ -15,6 +16,7 @@ final class ConsistentConstructorRule implements Rule public function __construct( private MethodParameterComparisonHelper $methodParameterComparisonHelper, + private MethodVisibilityComparisonHelper $methodVisibilityComparisonHelper, ) { } @@ -47,7 +49,10 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->methodParameterComparisonHelper->compare($parentConstructor, $parentConstructor->getDeclaringClass(), $method, true); + return array_merge( + $this->methodParameterComparisonHelper->compare($parentConstructor, $parentConstructor->getDeclaringClass(), $method, true), + $this->methodVisibilityComparisonHelper->compare($parentConstructor, $parentConstructor->getDeclaringClass(), $method), + ); } } diff --git a/src/Rules/Methods/MethodVisibilityComparisonHelper.php b/src/Rules/Methods/MethodVisibilityComparisonHelper.php new file mode 100755 index 00000000000..4807453f2a9 --- /dev/null +++ b/src/Rules/Methods/MethodVisibilityComparisonHelper.php @@ -0,0 +1,51 @@ + */ + public function compare(ExtendedMethodReflection $prototype, ClassReflection $prototypeDeclaringClass, PhpMethodFromParserNodeReflection $method): array + { + /** @var list $messages */ + $messages = []; + + if ($prototype->isPublic()) { + if (!$method->isPublic()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s method %s::%s() overriding public method %s::%s() should also be public.', + $method->isPrivate() ? 'Private' : 'Protected', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototypeDeclaringClass->getDisplayName(true), + $prototype->getName(), + )) + ->nonIgnorable() + ->identifier('method.visibility') + ->build(); + } + } elseif ($method->isPrivate()) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Private method %s::%s() overriding protected method %s::%s() should be protected or public.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototypeDeclaringClass->getDisplayName(true), + $prototype->getName(), + )) + ->nonIgnorable() + ->identifier('method.visibility') + ->build(); + } + + return $messages; + } + +} diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 38c588f9db2..81a9b1b0e1d 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -35,6 +35,7 @@ public function __construct( private MethodSignatureRule $methodSignatureRule, private bool $checkPhpDocMethodSignatures, private MethodParameterComparisonHelper $methodParameterComparisonHelper, + private MethodVisibilityComparisonHelper $methodVisibilityComparisonHelper, private PhpClassReflectionExtension $phpClassReflectionExtension, private bool $checkMissingOverrideMethodAttribute, ) @@ -165,32 +166,7 @@ public function processNode(Node $node, Scope $scope): array } if ($checkVisibility) { - if ($prototype->isPublic()) { - if (!$method->isPublic()) { - $messages[] = RuleErrorBuilder::message(sprintf( - '%s method %s::%s() overriding public method %s::%s() should also be public.', - $method->isPrivate() ? 'Private' : 'Protected', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototypeDeclaringClass->getDisplayName(true), - $prototype->getName(), - )) - ->nonIgnorable() - ->identifier('method.visibility') - ->build(); - } - } elseif ($method->isPrivate()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Private method %s::%s() overriding protected method %s::%s() should be protected or public.', - $method->getDeclaringClass()->getDisplayName(), - $method->getName(), - $prototypeDeclaringClass->getDisplayName(true), - $prototype->getName(), - )) - ->nonIgnorable() - ->identifier('method.visibility') - ->build(); - } + $messages = array_merge($messages, $this->methodVisibilityComparisonHelper->compare($prototype, $prototypeDeclaringClass, $method)); } $prototypeVariants = $prototype->getVariants(); diff --git a/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php b/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php index 28b0091af95..adcb69cdbeb 100644 --- a/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ConsistentConstructorRuleTest.php @@ -12,7 +12,10 @@ class ConsistentConstructorRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ConsistentConstructorRule(self::getContainer()->getByType(MethodParameterComparisonHelper::class)); + return new ConsistentConstructorRule( + self::getContainer()->getByType(MethodParameterComparisonHelper::class), + self::getContainer()->getByType(MethodVisibilityComparisonHelper::class), + ); } public function testRule(): void @@ -42,4 +45,14 @@ public function testRuleNoErrors(): void $this->analyse([__DIR__ . '/data/consistent-constructor-no-errors.php'], []); } + public function testBug12137(): void + { + $this->analyse([__DIR__ . '/data/bug-12137.php'], [ + [ + 'Private method Bug12137\ChildClass::__construct() overriding protected method Bug12137\ParentClass::__construct() should be protected or public.', + 20, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 5f75cd05541..580d21787c5 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -29,6 +29,7 @@ protected function getRule(): Rule new MethodSignatureRule($phpClassReflectionExtension, $this->reportMaybes, $this->reportStatic), true, new MethodParameterComparisonHelper($phpVersion), + new MethodVisibilityComparisonHelper(), $phpClassReflectionExtension, false, ); diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index abbe2927ab3..9c65a99d354 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -31,6 +31,7 @@ protected function getRule(): Rule new MethodSignatureRule($phpClassReflectionExtension, true, true), false, new MethodParameterComparisonHelper($phpVersion), + new MethodVisibilityComparisonHelper(), $phpClassReflectionExtension, $this->checkMissingOverrideMethodAttribute, ); diff --git a/tests/PHPStan/Rules/Methods/data/bug-12137.php b/tests/PHPStan/Rules/Methods/data/bug-12137.php new file mode 100755 index 00000000000..eacb78bbf36 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12137.php @@ -0,0 +1,23 @@ + Date: Wed, 25 Dec 2024 11:39:10 +0100 Subject: [PATCH 0973/3097] Improve loose comparison on constant types --- src/Type/Constant/ConstantArrayType.php | 22 +++++++-- .../Analyser/nsrt/loose-comparisons.php | 45 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 1514781d0fe..114843f9933 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -418,9 +418,25 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - if ($this->isIterableAtLeastOnce()->no() && count($type->getConstantScalarValues()) === 1) { - // @phpstan-ignore equal.invalid, equal.notAllowed - return new ConstantBooleanType($type->getConstantScalarValues()[0] == []); // phpcs:ignore + if ($type->isInteger()->yes()) { + return new ConstantBooleanType(false); + } + + if ($this->isIterableAtLeastOnce()->no()) { + if ($type->isIterableAtLeastOnce()->yes()) { + return new ConstantBooleanType(false); + } + + $constantScalarValues = $type->getConstantScalarValues(); + if (count($constantScalarValues) > 0) { + $results = []; + foreach ($constantScalarValues as $constantScalarValue) { + // @phpstan-ignore equal.invalid, equal.notAllowed + $results[] = TrinaryLogic::createFromBoolean($constantScalarValue == []); // phpcs:ignore + } + + return TrinaryLogic::extremeIdentity(...$results)->toBooleanType(); + } } return new BooleanType(); diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index 243b7672fd7..415cc07a73d 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -729,6 +729,8 @@ public function sayInt( array $array, int $int, int $intRange, + string $emptyStr, + string $phpStr, ): void { assertType('bool', $int == $true); @@ -747,6 +749,20 @@ public function sayInt( assertType('false', $intRange == $emptyArr); assertType('false', $intRange == $array); + assertType('false', 5 == $emptyArr); + assertType('false', $emptyArr == 5); + assertType('false', 5 == $array); + assertType('false', $array == 5); + assertType('false', [] == 5); + assertType('false', 5 == []); + + assertType('false', 5 == $emptyStr); + assertType('false', 5 == $phpStr); + assertType('false', 5 == 'a'); + + assertType('false', $emptyStr == 5); + assertType('false', $phpStr == 5); + assertType('false', 'a' == 5); } /** @@ -754,12 +770,16 @@ public function sayInt( * @param false|0|"0" $looseZero * @param false|1 $constMix * @param "abc"|"def" $constNonFalsy + * @param array{abc: string, num?: int, nullable: ?string} $arrShape + * @param array{} $emptyArr */ public function sayConstUnion( $looseOne, $looseZero, $constMix, $constNonFalsy, + array $arrShape, + array $emptyArr ): void { assertType('true', $looseOne == 1); @@ -802,6 +822,14 @@ public function sayConstUnion( assertType('false', $constNonFalsy == "1"); assertType('false', $constNonFalsy == "0"); assertType('false', $constNonFalsy == []); + + assertType('false', $emptyArr == $looseOne); + assertType('bool', $emptyArr == $constMix); + assertType('bool', $emptyArr == $looseZero); + + assertType('bool', $arrShape == $looseOne); + assertType('bool', $arrShape == $constMix); + assertType('bool', $arrShape == $looseZero); } /** @@ -809,6 +837,7 @@ public function sayConstUnion( * @param lowercase-string $lower * @param array{} $emptyArr * @param non-empty-array $nonEmptyArr + * @param array{abc: string, num?: int, nullable: ?string} $arrShape * @param int<10, 20> $intRange */ public function sayIntersection( @@ -818,6 +847,7 @@ public function sayIntersection( array $emptyArr, array $nonEmptyArr, array $arr, + array $arrShape, int $i, int $intRange, ): void @@ -849,11 +879,24 @@ public function sayIntersection( assertType('false', $nonEmptyArr == $i); assertType('false', $arr == $intRange); assertType('false', $nonEmptyArr == $intRange); - assertType('bool', $emptyArr == $nonEmptyArr); // should be false + assertType('false', $emptyArr == $nonEmptyArr); assertType('false', $nonEmptyArr == $emptyArr); assertType('bool', $arr == $nonEmptyArr); assertType('bool', $nonEmptyArr == $arr); + assertType('false', 5 == $arr); + assertType('false', $arr == 5); + assertType('false', 5 == $emptyArr); + assertType('false', $emptyArr == 5); + assertType('false', 5 == $nonEmptyArr); + assertType('false', $nonEmptyArr == 5); + assertType('false', 5 == $arrShape); + assertType('false', $arrShape == 5); + if (count($arr) > 0) { + assertType('false', 5 == $arr); + assertType('false', $arr == 5); + } + assertType('bool', '' == $lower); if ($lower != '') { assertType('false', '' == $lower); From 13f6406cfe1fe1ff96e613004fa8c266c0c16b81 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Sat, 4 Jan 2025 14:57:51 +0100 Subject: [PATCH 0974/3097] ConstantArrayType: fix returned ConstantArrayTypeAndMethod --- src/Type/Constant/ConstantArrayType.php | 3 +- .../Type/Constant/ConstantArrayTypeTest.php | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 114843f9933..7659db1e607 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -499,6 +499,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) } $method = $typeAndMethodName->getType() + ->getObjectTypeOrClassStringObjectType() ->getMethod($typeAndMethodName->getMethod(), $scope); if (!$scope->canCallMethod($method)) { @@ -583,7 +584,7 @@ public function findTypeAndMethodNames(): array $has = $has->and(TrinaryLogic::createMaybe()); } - $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); + $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($classOrObject, $method->getValue(), $has); } return $typeAndMethods; diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index b047b86a691..5697dee6b2a 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -1051,4 +1051,34 @@ public function testValuesArray(ConstantArrayType $type, ConstantArrayType $expe $this->assertSame($expectedType->getNextAutoIndexes(), $actualType->getNextAutoIndexes()); } + public function testFindTypeAndMethodNames(): void + { + $classStringArray = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ConstantStringType(Closure::class, true), + new ConstantStringType('bind'), + ]); + $objectArray = new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + ], [ + new ObjectType(Closure::class, null, $this->createReflectionProvider()->getClass(Closure::class)), + new ConstantStringType('bind'), + ]); + + $classStringResult = $classStringArray->findTypeAndMethodNames(); + $objectResult = $objectArray->findTypeAndMethodNames(); + + $this->assertCount(1, $classStringResult); + $this->assertCount(1, $objectResult); + $this->assertInstanceOf(ConstantStringType::class, $classStringResult[0]->getType()); + $this->assertInstanceOf(ObjectType::class, $objectResult[0]->getType()); + $this->assertSame('bind', $classStringResult[0]->getMethod()); + $this->assertSame('bind', $objectResult[0]->getMethod()); + $this->assertSame(TrinaryLogic::createYes(), $classStringResult[0]->getCertainty()); + $this->assertSame(TrinaryLogic::createYes(), $objectResult[0]->getCertainty()); + } + } From b102d983419ac78cb173c3f120ded847de0933af Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sat, 4 Jan 2025 15:08:24 +0100 Subject: [PATCH 0975/3097] Support named arguments after unpacking --- src/Php/PhpVersions.php | 5 +++ src/Rules/FunctionCallParametersCheck.php | 23 +++++++------ .../CallToFunctionParametersRuleTest.php | 32 +++++++++++++++++++ .../Rules/Functions/data/bug-11418.php | 9 ++++++ .../PHPStan/Rules/Functions/data/bug-8046.php | 11 +++++++ .../data/named-arguments-after-unpacking.php | 14 ++++++++ 6 files changed, 84 insertions(+), 10 deletions(-) create mode 100755 tests/PHPStan/Rules/Functions/data/bug-11418.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-8046.php create mode 100755 tests/PHPStan/Rules/Functions/data/named-arguments-after-unpacking.php diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 229dccb72d9..96bf2332097 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -38,4 +38,9 @@ public function supportsNamedArguments(): TrinaryLogic return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result; } + public function supportsNamedArgumentAfterUnpackedArgument(): TrinaryLogic + { + return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result; + } + } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 50371ab23c1..bd637913bc1 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -101,6 +101,12 @@ public function check( $hasUnpackedArgument = false; $errors = []; foreach ($args as $arg) { + $argumentName = null; + if ($arg->name !== null) { + $hasNamedArguments = true; + $argumentName = $arg->name->toString(); + } + if ($hasNamedArguments && $arg->unpack) { $errors[] = RuleErrorBuilder::message('Named argument cannot be followed by an unpacked (...) argument.') ->identifier('argument.unpackAfterNamed') @@ -109,20 +115,17 @@ public function check( ->build(); } if ($hasUnpackedArgument && !$arg->unpack) { - $errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.') - ->identifier('argument.nonUnpackAfterUnpacked') - ->line($arg->getStartLine()) - ->nonIgnorable() - ->build(); + if ($argumentName === null || !$scope->getPhpVersion()->supportsNamedArgumentAfterUnpackedArgument()->yes()) { + $errors[] = RuleErrorBuilder::message('Unpacked argument (...) cannot be followed by a non-unpacked argument.') + ->identifier('argument.nonUnpackAfterUnpacked') + ->line($arg->getStartLine()) + ->nonIgnorable() + ->build(); + } } if ($arg->unpack) { $hasUnpackedArgument = true; } - $argumentName = null; - if ($arg->name !== null) { - $hasNamedArguments = true; - $argumentName = $arg->name->toString(); - } if ($arg->unpack) { $type = $scope->getType($arg->value); $arrays = $type->getConstantArrays(); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a6513f03cb1..2e8afa35a43 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -499,6 +499,20 @@ public function testNamedArguments(): void $this->analyse([__DIR__ . '/data/named-arguments.php'], $errors); } + public function testNamedArgumentsAfterUnpacking(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/named-arguments-after-unpacking.php'], [ + [ + 'Named parameter cannot overwrite already unpacked argument $b.', + 14, + ], + ]); + } + public function testBug4514(): void { $this->analyse([__DIR__ . '/data/bug-4514.php'], []); @@ -1936,4 +1950,22 @@ public function testBug12051(): void $this->analyse([__DIR__ . '/data/bug-12051.php'], []); } + public function testBug8046(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-8046.php'], []); + } + + public function testBug11418(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-11418.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11418.php b/tests/PHPStan/Rules/Functions/data/bug-11418.php new file mode 100755 index 00000000000..8172892d95c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11418.php @@ -0,0 +1,9 @@ + 7]; + +var_dump(add(...$args, b: 8)); diff --git a/tests/PHPStan/Rules/Functions/data/named-arguments-after-unpacking.php b/tests/PHPStan/Rules/Functions/data/named-arguments-after-unpacking.php new file mode 100755 index 00000000000..29d9ac8b4e7 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/named-arguments-after-unpacking.php @@ -0,0 +1,14 @@ + 2, 'a' => 1], d: 40)); // 46 + +var_dump(foo(...[1, 2], b: 20)); // Fatal error. Named parameter $b overwrites previous argument From a245a648d371d08b318d3c5310a6f18c33bdd0dd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 4 Jan 2025 15:09:10 +0100 Subject: [PATCH 0976/3097] Revert "ConstantArrayType: fix returned ConstantArrayTypeAndMethod" This reverts commit 13f6406cfe1fe1ff96e613004fa8c266c0c16b81. --- src/Type/Constant/ConstantArrayType.php | 3 +- .../Type/Constant/ConstantArrayTypeTest.php | 30 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7659db1e607..114843f9933 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -499,7 +499,6 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) } $method = $typeAndMethodName->getType() - ->getObjectTypeOrClassStringObjectType() ->getMethod($typeAndMethodName->getMethod(), $scope); if (!$scope->canCallMethod($method)) { @@ -584,7 +583,7 @@ public function findTypeAndMethodNames(): array $has = $has->and(TrinaryLogic::createMaybe()); } - $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($classOrObject, $method->getValue(), $has); + $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); } return $typeAndMethods; diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 5697dee6b2a..b047b86a691 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -1051,34 +1051,4 @@ public function testValuesArray(ConstantArrayType $type, ConstantArrayType $expe $this->assertSame($expectedType->getNextAutoIndexes(), $actualType->getNextAutoIndexes()); } - public function testFindTypeAndMethodNames(): void - { - $classStringArray = new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ConstantStringType(Closure::class, true), - new ConstantStringType('bind'), - ]); - $objectArray = new ConstantArrayType([ - new ConstantIntegerType(0), - new ConstantIntegerType(1), - ], [ - new ObjectType(Closure::class, null, $this->createReflectionProvider()->getClass(Closure::class)), - new ConstantStringType('bind'), - ]); - - $classStringResult = $classStringArray->findTypeAndMethodNames(); - $objectResult = $objectArray->findTypeAndMethodNames(); - - $this->assertCount(1, $classStringResult); - $this->assertCount(1, $objectResult); - $this->assertInstanceOf(ConstantStringType::class, $classStringResult[0]->getType()); - $this->assertInstanceOf(ObjectType::class, $objectResult[0]->getType()); - $this->assertSame('bind', $classStringResult[0]->getMethod()); - $this->assertSame('bind', $objectResult[0]->getMethod()); - $this->assertSame(TrinaryLogic::createYes(), $classStringResult[0]->getCertainty()); - $this->assertSame(TrinaryLogic::createYes(), $objectResult[0]->getCertainty()); - } - } From 44e28390db88e8e23c39af601eeb8ba8e476161b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 2 Jan 2025 16:46:03 +0100 Subject: [PATCH 0977/3097] Improve loose comparison on IntegerRange containing zero --- src/Type/IntegerRangeType.php | 55 ++++++++++++++++- .../Analyser/nsrt/loose-comparisons.php | 60 ++++++++++++++++++- .../ConstantLooseComparisonRuleTest.php | 10 +++- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index b90f4d31a7e..1c956015ddd 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -315,6 +315,16 @@ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryL $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThan($otherType, $phpVersion); } + // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti + $zeroInt = new ConstantIntegerType(0); + if (!$zeroInt->isSuperTypeOf($this)->no()) { + return TrinaryLogic::extremeIdentity( + $zeroInt->isSmallerThan($otherType, $phpVersion), + $minIsSmaller, + $maxIsSmaller, + ); + } + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } @@ -332,6 +342,16 @@ public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): T $maxIsSmaller = (new ConstantIntegerType($this->max))->isSmallerThanOrEqual($otherType, $phpVersion); } + // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti + $zeroInt = new ConstantIntegerType(0); + if (!$zeroInt->isSuperTypeOf($this)->no()) { + return TrinaryLogic::extremeIdentity( + $zeroInt->isSmallerThanOrEqual($otherType, $phpVersion), + $minIsSmaller, + $maxIsSmaller, + ); + } + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } @@ -349,6 +369,16 @@ public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryL $maxIsSmaller = $otherType->isSmallerThan((new ConstantIntegerType($this->max)), $phpVersion); } + // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti + $zeroInt = new ConstantIntegerType(0); + if (!$zeroInt->isSuperTypeOf($this)->no()) { + return TrinaryLogic::extremeIdentity( + $otherType->isSmallerThan($zeroInt, $phpVersion), + $minIsSmaller, + $maxIsSmaller, + ); + } + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } @@ -366,6 +396,16 @@ public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): T $maxIsSmaller = $otherType->isSmallerThanOrEqual((new ConstantIntegerType($this->max)), $phpVersion); } + // 0 can have different results in contrast to the interval edges, see https://3v4l.org/iGoti + $zeroInt = new ConstantIntegerType(0); + if (!$zeroInt->isSuperTypeOf($this)->no()) { + return TrinaryLogic::extremeIdentity( + $otherType->isSmallerThanOrEqual($zeroInt, $phpVersion), + $minIsSmaller, + $maxIsSmaller, + ); + } + return TrinaryLogic::extremeIdentity($minIsSmaller, $maxIsSmaller); } @@ -694,7 +734,20 @@ public function toPhpDocNode(): TypeNode public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - if ($this->isSmallerThan($type, $phpVersion)->yes() || $this->isGreaterThan($type, $phpVersion)->yes()) { + $zeroInt = new ConstantIntegerType(0); + if ($zeroInt->isSuperTypeOf($this)->no()) { + if ($type->isTrue()->yes()) { + return new ConstantBooleanType(true); + } + if ($type->isFalse()->yes()) { + return new ConstantBooleanType(false); + } + } + + if ( + $this->isSmallerThan($type, $phpVersion)->yes() + || $this->isGreaterThan($type, $phpVersion)->yes() + ) { return new ConstantBooleanType(false); } diff --git a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php index 415cc07a73d..c385548cf50 100644 --- a/tests/PHPStan/Analyser/nsrt/loose-comparisons.php +++ b/tests/PHPStan/Analyser/nsrt/loose-comparisons.php @@ -712,7 +712,9 @@ public function sayEmptyStr( * @param array{} $emptyArr * @param 'php' $phpStr * @param '' $emptyStr - * @param int<10, 20> $intRange + * @param int<10, 20> $positiveIntRange + * @param int<-20, -10> $negativeIntRange + * @param int<-10, 10> $minusTenToTen */ public function sayInt( $true, @@ -731,6 +733,9 @@ public function sayInt( int $intRange, string $emptyStr, string $phpStr, + int $positiveIntRange, + int $negativeIntRange, + int $minusTenToTen, ): void { assertType('bool', $int == $true); @@ -746,8 +751,57 @@ public function sayInt( assertType('false', $int == $emptyArr); assertType('false', $int == $array); - assertType('false', $intRange == $emptyArr); - assertType('false', $intRange == $array); + assertType('true', $positiveIntRange == $true); + assertType('false', $positiveIntRange == $false); + assertType('false', $positiveIntRange == $one); + assertType('false', $positiveIntRange == $zero); + assertType('false', $positiveIntRange == $minusOne); + assertType('false', $positiveIntRange == $oneStr); + assertType('false', $positiveIntRange == $zeroStr); + assertType('false', $positiveIntRange == $minusOneStr); + assertType('false', $positiveIntRange == $plusOneStr); + assertType('false', $positiveIntRange == $null); + assertType('false', $positiveIntRange == $emptyArr); + assertType('false', $positiveIntRange == $array); + + assertType('true', $negativeIntRange == $true); + assertType('false', $negativeIntRange == $false); + assertType('false', $negativeIntRange == $one); + assertType('false', $negativeIntRange == $zero); + assertType('false', $negativeIntRange == $minusOne); + assertType('false', $negativeIntRange == $oneStr); + assertType('false', $negativeIntRange == $zeroStr); + assertType('false', $negativeIntRange == $minusOneStr); + assertType('false', $negativeIntRange == $plusOneStr); + assertType('false', $negativeIntRange == $null); + assertType('false', $negativeIntRange == $emptyArr); + assertType('false', $negativeIntRange == $array); + + // see https://3v4l.org/VudDK + assertType('bool', $minusTenToTen == $true); + assertType('bool', $minusTenToTen == $false); + assertType('bool', $minusTenToTen == $one); + assertType('bool', $minusTenToTen == $zero); + assertType('bool', $minusTenToTen == $minusOne); + assertType('bool', $minusTenToTen == $oneStr); + assertType('bool', $minusTenToTen == $zeroStr); + assertType('bool', $minusTenToTen == $minusOneStr); + assertType('bool', $minusTenToTen == $plusOneStr); + assertType('bool', $minusTenToTen == $null); + assertType('false', $minusTenToTen == $emptyArr); + assertType('false', $minusTenToTen == $array); + + // see https://3v4l.org/oJl3K + assertType('false', $minusTenToTen < $null); + assertType('bool', $minusTenToTen > $null); + assertType('bool', $minusTenToTen <= $null); + assertType('true', $minusTenToTen >= $null); + + // see https://3v4l.org/oRSgU + assertType('bool', $null < $minusTenToTen); + assertType('false', $null > $minusTenToTen); + assertType('true', $null <= $minusTenToTen); + assertType('bool', $null >= $minusTenToTen); assertType('false', 5 == $emptyArr); assertType('false', $emptyArr == 5); diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index 6e56f9dfcdc..e49a6100f73 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -210,15 +210,21 @@ public function testBug11694(): void 39, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], + [ + 'Loose comparison using == between true and int<10, 20> will always evaluate to true.', + 41, + ], + [ + 'Loose comparison using == between int<10, 20> and true will always evaluate to true.', + 42, + ], [ 'Loose comparison using == between false and int<10, 20> will always evaluate to false.', 44, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Loose comparison using == between int<10, 20> and false will always evaluate to false.', 45, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ]); From cdf51107e19c8e90a3b64616eaf9bd60d7d8ae5b Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sat, 4 Jan 2025 15:29:35 +0100 Subject: [PATCH 0978/3097] Fix test --- .../Rules/Functions/CallToFunctionParametersRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 2e8afa35a43..7fde83eb122 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -507,7 +507,7 @@ public function testNamedArgumentsAfterUnpacking(): void $this->analyse([__DIR__ . '/data/named-arguments-after-unpacking.php'], [ [ - 'Named parameter cannot overwrite already unpacked argument $b.', + 'Argument for parameter $b has already been passed.', 14, ], ]); From 7c281ba7ba38186cd62df366ba62fe28037b98ef Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 3 Jan 2025 16:57:46 +0100 Subject: [PATCH 0979/3097] NodeScopeResolver: 10x Faster constant array processing --- src/Analyser/NodeScopeResolver.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ab61ff14862..f8a09a5c4e1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5771,7 +5771,10 @@ private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type foreach (array_reverse($offsetTypes) as $i => $offsetType) { /** @var Type $offsetValueType */ $offsetValueType = array_pop($offsetValueTypeStack); - if (!$offsetValueType instanceof MixedType) { + if ( + !$offsetValueType instanceof MixedType + && !$offsetValueType->isConstantArray()->yes() + ) { $types = [ new ArrayType(new MixedType(), new MixedType()), new ObjectType(ArrayAccess::class), From a063119ee422460615adaa7a37bc4c5d2e755971 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 13:34:35 +0100 Subject: [PATCH 0980/3097] Fix inferring type of `new` with generic type with constructor in parent class --- src/Analyser/MutatingScope.php | 66 ++++++++++- tests/PHPStan/Analyser/nsrt/bug-2735.php | 134 +++++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/generics.php | 2 +- 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-2735.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 52ae6763977..9f726ede71e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5525,13 +5525,77 @@ private function exactInstantiation(New_ $node, string $className): ?Type } } - if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) { + if ($constructorMethod instanceof DummyConstructorReflection) { return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), ); } + if ($constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) { + if (!$constructorMethod->getDeclaringClass()->isGeneric()) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + ); + } + $newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); + $ancestorType = $newType->getAncestorWithClassName($constructorMethod->getDeclaringClass()->getName()); + if ($ancestorType === null) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + ); + } + $ancestorClassReflections = $ancestorType->getObjectClassReflections(); + if (count($ancestorClassReflections) !== 1) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + ); + } + + $newParentNode = new New_(new Name($constructorMethod->getDeclaringClass()->getName()), $node->args); + $newParentType = $this->getType($newParentNode); + $newParentTypeClassReflections = $newParentType->getObjectClassReflections(); + if (count($newParentTypeClassReflections) !== 1) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + ); + } + $newParentTypeClassReflection = $newParentTypeClassReflections[0]; + + $ancestorClassReflection = $ancestorClassReflections[0]; + $ancestorMapping = []; + foreach ($ancestorClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $templateType) { + if (!$templateType instanceof TemplateType) { + continue; + } + + $ancestorMapping[$typeName] = $templateType->getName(); + } + + $resolvedTypeMap = []; + foreach ($newParentTypeClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $type) { + if (!array_key_exists($typeName, $ancestorMapping)) { + continue; + } + + if (!array_key_exists($ancestorMapping[$typeName], $resolvedTypeMap)) { + $resolvedTypeMap[$ancestorMapping[$typeName]] = $type; + continue; + } + + $resolvedTypeMap[$ancestorMapping[$typeName]] = TypeCombinator::union($resolvedTypeMap[$ancestorMapping[$typeName]], $type); + } + + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), + ); + } + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $this, $methodCall->getArgs(), diff --git a/tests/PHPStan/Analyser/nsrt/bug-2735.php b/tests/PHPStan/Analyser/nsrt/bug-2735.php new file mode 100644 index 00000000000..a486eda5c02 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-2735.php @@ -0,0 +1,134 @@ + */ + protected $arr = []; + + /** + * @param array $arr + */ + public function __construct(array $arr) { + $this->arr = $arr; + } + + /** + * @return T + */ + public function last() + { + if (!$this->arr) { + throw new \Exception('bad'); + } + return end($this->arr); + } +} + +/** + * @template T + * @extends Collection + */ +class CollectionChild extends Collection { +} + +$dogs = new CollectionChild([new Dog(), new Dog()]); +assertType('Bug2735\\CollectionChild', $dogs); + +/** + * @template X + * @template Y + */ +class ParentWithConstructor +{ + + /** + * @param X $x + * @param Y $y + */ + public function __construct($x, $y) + { + } + +} + +/** + * @template T + * @extends ParentWithConstructor + */ +class ChildOne extends ParentWithConstructor +{ + +} + +function (): void { + $a = new ChildOne(1, new Dog()); + assertType('Bug2735\\ChildOne', $a); +}; + +/** + * @template T + * @extends ParentWithConstructor + */ +class ChildTwo extends ParentWithConstructor +{ + +} + +function (): void { + $a = new ChildTwo(new Cat(), 2); + assertType('Bug2735\\ChildTwo', $a); +}; + +/** + * @template T + * @extends ParentWithConstructor + */ +class ChildThree extends ParentWithConstructor +{ + +} + +function (): void { + $a = new ChildThree(new Cat(), new Dog()); + assertType('Bug2735\\ChildThree', $a); +}; + +/** + * @template T + * @template U + * @extends ParentWithConstructor + */ +class ChildFour extends ParentWithConstructor +{ + +} + +function (): void { + $a = new ChildFour(new Cat(), new Dog()); + assertType('Bug2735\\ChildFour', $a); +}; + +/** + * @template T + * @template U + * @extends ParentWithConstructor + */ +class ChildFive extends ParentWithConstructor +{ + +} + +function (): void { + $a = new ChildFive(new Cat(), new Dog()); + assertType('Bug2735\\ChildFive', $a); +}; diff --git a/tests/PHPStan/Analyser/nsrt/generics.php b/tests/PHPStan/Analyser/nsrt/generics.php index 80f10030e8f..6f69f3b78fc 100644 --- a/tests/PHPStan/Analyser/nsrt/generics.php +++ b/tests/PHPStan/Analyser/nsrt/generics.php @@ -741,7 +741,7 @@ function testClasses() assertType('DateTime', $ab->getB(new \DateTime())); $noConstructor = new NoConstructor(1); - assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor', $noConstructor); + assertType('PHPStan\Generics\FunctionsAssertType\NoConstructor', $noConstructor); assertType('stdClass', acceptsClassString(\stdClass::class)); assertType('class-string', returnsClassString(new \stdClass())); From fd7bad3ef259b3f9990b6cf88c2c01bae24b725e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 5 Jan 2025 13:08:46 +0100 Subject: [PATCH 0981/3097] Cleanup `instanceof ConstantBooleanType` checks --- phpstan-baseline.neon | 10 ---------- src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php | 3 +-- .../ArraySearchFunctionDynamicReturnTypeExtension.php | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1662d1fa9af..1011e39a004 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1307,11 +1307,6 @@ parameters: count: 1 path: src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 @@ -1322,11 +1317,6 @@ parameters: count: 4 path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantBooleanType is error\\-prone and deprecated\\. Use Type\\:\\:isTrue\\(\\) or Type\\:\\:isFalse\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 16 diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php index 52916521295..8c158da87be 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php @@ -21,7 +21,6 @@ use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\MixedType; @@ -255,7 +254,7 @@ private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type return [ $keyVarName !== null ? $scope->getVariableType($keyVarName) : $keyType, $itemVarName !== null ? $scope->getVariableType($itemVarName) : $itemType, - !$booleanResult instanceof ConstantBooleanType, + !$booleanResult->isTrue()->yes(), ]; } diff --git a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php index 8560faf5a9b..762e577211c 100644 --- a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php @@ -43,7 +43,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $strictArgType = $scope->getType($functionCall->getArgs()[2]->value); - if (!$strictArgType instanceof ConstantBooleanType || $strictArgType->getValue() === false) { + if (!$strictArgType->isTrue()->yes()) { return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); } From 3ff4184f7319e20104a172a3669cdd6b2b99e112 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 14:07:46 +0100 Subject: [PATCH 0982/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/10580 Closes https://github.com/phpstan/phpstan/issues/11939 Closes https://github.com/phpstan/phpstan/issues/10338 Closes https://github.com/phpstan/phpstan/issues/12048 Closes https://github.com/phpstan/phpstan/issues/3107 --- tests/PHPStan/Analyser/nsrt/bug-10338.php | 12 +++++ .../CallToFunctionParametersRuleTest.php | 5 ++ .../PHPStan/Rules/Functions/data/bug-3107.php | 23 ++++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 46 +++++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-10580.php | 36 +++++++++++++++ .../MethodConditionalReturnTypeRuleTest.php | 5 ++ tests/PHPStan/Rules/PhpDoc/data/bug-11939.php | 36 +++++++++++++++ .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 6 +++ tests/PHPStan/Rules/Pure/data/bug-12048.php | 19 ++++++++ 9 files changed, 188 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10338.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3107.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10580.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-11939.php create mode 100644 tests/PHPStan/Rules/Pure/data/bug-12048.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10338.php b/tests/PHPStan/Analyser/nsrt/bug-10338.php new file mode 100644 index 00000000000..89934bb5029 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10338.php @@ -0,0 +1,12 @@ +analyse([__DIR__ . '/data/bug-11418.php'], []); } + public function testBug3107(): void + { + $this->analyse([__DIR__ . '/data/bug-3107.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3107.php b/tests/PHPStan/Rules/Functions/data/bug-3107.php new file mode 100644 index 00000000000..12ed0edfd0a --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3107.php @@ -0,0 +1,23 @@ +val = $mixed; + + $a = []; + $a[$holder->val] = 1; + take($a); +} + +/** @param array $a */ +function take($a): void {} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 98bb11b0b54..73350821e12 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1171,4 +1171,50 @@ public function testShortGetPropertyHook(): void ]); } + public function testBug1O580(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/bug-10580.php'], [ + [ + 'Method Bug10580\FooA::fooThisInterface() should return $this(Bug10580\FooA) but returns Bug10580\FooA.', + 18, + ], + [ + 'Method Bug10580\FooA::fooThisClass() should return $this(Bug10580\FooA) but returns Bug10580\FooA.', + 19, + ], + [ + 'Method Bug10580\FooA::fooThisSelf() should return $this(Bug10580\FooA) but returns Bug10580\FooA.', + 20, + ], + [ + 'Method Bug10580\FooA::fooThisStatic() should return $this(Bug10580\FooA) but returns Bug10580\FooA.', + 21, + ], + [ + 'Method Bug10580\FooB::fooThisInterface() should return $this(Bug10580\FooB) but returns Bug10580\FooB.', + 27, + ], + [ + 'Method Bug10580\FooB::fooThisClass() should return $this(Bug10580\FooB) but returns Bug10580\FooB.', + 29, + ], + [ + 'Method Bug10580\FooB::fooThisSelf() should return $this(Bug10580\FooB) but returns Bug10580\FooB.', + 31, + ], + [ + 'Method Bug10580\FooB::fooThisStatic() should return $this(Bug10580\FooB) but returns Bug10580\FooB.', + 33, + ], + [ + 'Method Bug10580\FooB::fooThis() should return $this(Bug10580\FooB) but returns Bug10580\FooB.', + 35, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10580.php b/tests/PHPStan/Rules/Methods/data/bug-10580.php new file mode 100644 index 00000000000..b9c479000a4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10580.php @@ -0,0 +1,36 @@ += 8.0 + +namespace Bug10580; + +interface FooI { + /** @return $this */ + public function fooThisInterface(): FooI; + /** @return $this */ + public function fooThisClass(): FooI; + /** @return $this */ + public function fooThisSelf(): self; + /** @return $this */ + public function fooThisStatic(): static; +} + +final class FooA implements FooI +{ + public function fooThisInterface(): FooI { return new FooA(); } + public function fooThisClass(): FooA { return new FooA(); } + public function fooThisSelf(): self { return new FooA(); } + public function fooThisStatic(): static { return new FooA(); } +} + +final class FooB implements FooI +{ + /** @return $this */ + public function fooThisInterface(): FooI { return new FooB(); } + /** @return $this */ + public function fooThisClass(): FooB { return new FooB(); } + /** @return $this */ + public function fooThisSelf(): self { return new FooB(); } + /** @return $this */ + public function fooThisStatic(): static { return new FooB(); } + /** @return $this */ + public function fooThis(): static { return new FooB(); } +} diff --git a/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php index 48258202dc4..1fa91d3fe63 100644 --- a/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php @@ -95,4 +95,9 @@ public function testBug7310(): void $this->analyse([__DIR__ . '/data/bug-7310.php'], []); } + public function testBug11939(): void + { + $this->analyse([__DIR__ . '/data/bug-11939.php'], []); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-11939.php b/tests/PHPStan/Rules/PhpDoc/data/bug-11939.php new file mode 100644 index 00000000000..759c3b5bb5f --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-11939.php @@ -0,0 +1,36 @@ += 8.1 + +declare(strict_types=1); + +namespace Bug11939; + +enum What +{ + case This; + case That; + + /** + * @return ($this is self::This ? 'here' : 'there') + */ + public function where(): string + { + return match ($this) { + self::This => 'here', + self::That => 'there' + }; + } +} + +class Where +{ + /** + * @return ($what is What::This ? 'here' : 'there') + */ + public function __invoke(What $what): string + { + return match ($what) { + What::This => 'here', + What::That => 'there' + }; + } +} diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 4ec5028172d..64ee811d819 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -194,4 +194,10 @@ public function dataBug11207(): array ]; } + public function testBug12048(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12048.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-12048.php b/tests/PHPStan/Rules/Pure/data/bug-12048.php new file mode 100644 index 00000000000..ab5d44154a3 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-12048.php @@ -0,0 +1,19 @@ + Date: Sat, 4 Jan 2025 21:48:11 +0100 Subject: [PATCH 0983/3097] Support tagged unions in `array_merge` --- phpstan-baseline.neon | 5 -- ...ergeFunctionDynamicReturnTypeExtension.php | 54 +++++++++---------- tests/PHPStan/Analyser/nsrt/array-merge2.php | 4 ++ .../CallToFunctionParametersRuleTest.php | 9 ++++ .../PHPStan/Rules/Functions/data/bug-9559.php | 12 +++++ .../Rules/Methods/ReturnTypeRuleTest.php | 10 ++++ tests/PHPStan/Rules/Methods/data/bug-7857.php | 17 ++++++ tests/PHPStan/Rules/Methods/data/bug-8632.php | 26 +++++++++ 8 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-9559.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-7857.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-8632.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1011e39a004..9977a1992d8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1312,11 +1312,6 @@ parameters: count: 1 path: src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 4 - path: src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 16 diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 5b177a9f686..01d2143b25a 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -5,13 +5,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\NeverType; @@ -39,58 +40,53 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $argTypes = []; $optionalArgTypes = []; - $allConstant = true; foreach ($args as $arg) { $argType = $scope->getType($arg->value); if ($arg->unpack) { - if ($argType instanceof ConstantArrayType) { - $argTypesFound = $argType->getValueTypes(); - } else { - $argTypesFound = [$argType->getIterableValueType()]; - } - - foreach ($argTypesFound as $argTypeFound) { - $argTypes[] = $argTypeFound; - if ($argTypeFound instanceof ConstantArrayType) { - continue; + if ($argType->isConstantArray()->yes()) { + foreach ($argType->getConstantArrays() as $constantArray) { + foreach ($constantArray->getValueTypes() as $valueType) { + $argTypes[] = $valueType; + } } - $allConstant = false; + } else { + $argTypes[] = $argType->getIterableValueType(); } if (!$argType->isIterableAtLeastOnce()->yes()) { // unpacked params can be empty, making them optional $optionalArgTypesOffset = count($argTypes) - 1; - foreach (array_keys($argTypesFound) as $key) { + foreach (array_keys($argTypes) as $key) { $optionalArgTypes[] = $optionalArgTypesOffset + $key; } } } else { $argTypes[] = $argType; - if (!$argType instanceof ConstantArrayType) { - $allConstant = false; - } } } - if ($allConstant) { + $allConstant = TrinaryLogic::createYes()->lazyAnd( + $argTypes, + static fn (Type $argType) => $argType->isConstantArray(), + ); + + if ($allConstant->yes()) { $newArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach ($argTypes as $argType) { - if (!$argType instanceof ConstantArrayType) { - throw new ShouldNotHappenException(); + /** @var array $keyTypes */ + $keyTypes = []; + foreach ($argType->getConstantArrays() as $constantArray) { + foreach ($constantArray->getKeyTypes() as $keyType) { + $keyTypes[$keyType->getValue()] = $keyType; + } } - $keyTypes = $argType->getKeyTypes(); - $valueTypes = $argType->getValueTypes(); - $optionalKeys = $argType->getOptionalKeys(); - - foreach ($keyTypes as $k => $keyType) { - $isOptional = in_array($k, $optionalKeys, true); - + foreach ($keyTypes as $keyType) { $newArrayBuilder->setOffsetValueType( $keyType instanceof ConstantIntegerType ? null : $keyType, - $valueTypes[$k], - $isOptional, + $argType->getOffsetValueType($keyType), + !$argType->hasOffsetValueType($keyType)->yes(), ); } } diff --git a/tests/PHPStan/Analyser/nsrt/array-merge2.php b/tests/PHPStan/Analyser/nsrt/array-merge2.php index a52f640ef2a..f0d86e61b6a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-merge2.php +++ b/tests/PHPStan/Analyser/nsrt/array-merge2.php @@ -21,6 +21,10 @@ public function arrayMergeArrayShapes($array1, $array2): void assertType("array{foo: '1', bar: '2', lall2: '3', 0: '4', 1: '6', lall: '3', 2: '2', 3: '3'}", array_merge($array2, $array1)); assertType("array{foo: 3, bar: '2', lall2: '3', 0: '4', 1: '6', lall: '3', 2: '2', 3: '3'}", array_merge($array2, $array1, ['foo' => 3])); assertType("array{foo: 3, bar: '2', lall2: '3', 0: '4', 1: '6', lall: '3', 2: '2', 3: '3'}", array_merge($array2, $array1, ...[['foo' => 3]])); + assertType("array{foo: '1', bar: '2'|'4', lall?: '3', 0: '2'|'4', 1: '3'|'6', lall2?: '3'}", array_merge(rand(0, 1) ? $array1 : $array2, [])); + assertType("array{foo?: 3, bar?: 3}", array_merge([], ...[rand(0, 1) ? ['foo' => 3] : ['bar' => 3]])); + assertType("array{foo: '1', bar: '2'|'4', lall?: '3', 0: '2'|'4', 1: '3'|'6', lall2?: '3'}", array_merge([], ...[rand(0, 1) ? $array1 : $array2])); + assertType("array{foo: 1, bar: 2, 0: 2, 1: 3}", array_merge(['foo' => 4, 'bar' => 5], ...[['foo' => 1, 'bar' => 2], [2, 3]])); } /** diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a89705028b4..a3ffa071b79 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1598,6 +1598,15 @@ public function testBug9399(): void $this->analyse([__DIR__ . '/data/bug-9399.php'], []); } + public function testBug9559(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/bug-9559.php'], []); + } + public function testBug9923(): void { if (PHP_VERSION_ID < 80000) { diff --git a/tests/PHPStan/Rules/Functions/data/bug-9559.php b/tests/PHPStan/Rules/Functions/data/bug-9559.php new file mode 100644 index 00000000000..8f452e90f03 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-9559.php @@ -0,0 +1,12 @@ + "3" ])); +} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 13082d79b00..98e54bce0e8 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -846,6 +846,16 @@ public function testBug8573(): void $this->analyse([__DIR__ . '/data/bug-8573.php'], []); } + public function testBug8632(): void + { + $this->analyse([__DIR__ . '/data/bug-8632.php'], []); + } + + public function testBug7857(): void + { + $this->analyse([__DIR__ . '/data/bug-7857.php'], []); + } + public function testBug8879(): void { $this->analyse([__DIR__ . '/data/bug-8879.php'], []); diff --git a/tests/PHPStan/Rules/Methods/data/bug-7857.php b/tests/PHPStan/Rules/Methods/data/bug-7857.php new file mode 100644 index 00000000000..269bbd5a873 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-7857.php @@ -0,0 +1,17 @@ + $page], + $perPage !== null ? ['perPage' => $perPage] : [] + ); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-8632.php b/tests/PHPStan/Rules/Methods/data/bug-8632.php new file mode 100644 index 00000000000..17c2aa50f6d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8632.php @@ -0,0 +1,26 @@ + 1, + 'categories' => ['news'], + ]; + } else { + $arr = []; + } + + return array_merge($arr, []); + } +} From 33a45f4f64303ba31bb7407404f4b9c8cb8b058a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 14:28:29 +0100 Subject: [PATCH 0984/3097] Fix build --- tests/PHPStan/Analyser/nsrt/bug-10338.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10338.php b/tests/PHPStan/Analyser/nsrt/bug-10338.php index 89934bb5029..cb9103eae0a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10338.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10338.php @@ -8,5 +8,5 @@ function (): void { die; } - assertType('string', $content); + assertType('non-empty-string', $content); }; From 7dc98b6bfd02a76bbcf6c766972e1eba07e9c070 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 14:37:08 +0100 Subject: [PATCH 0985/3097] Playground rule - StaticVarWithoutTypeRule --- issue-bot/playground.neon | 8 ++ .../Playground/StaticVarWithoutTypeRule.php | 81 +++++++++++++++++++ .../StaticVarWithoutTypeRuleTest.php | 34 ++++++++ .../data/static-var-without-type.php | 31 +++++++ 4 files changed, 154 insertions(+) create mode 100644 src/Rules/Playground/StaticVarWithoutTypeRule.php create mode 100644 tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Playground/data/static-var-without-type.php diff --git a/issue-bot/playground.neon b/issue-bot/playground.neon index 2f9743d575e..d4072a41703 100644 --- a/issue-bot/playground.neon +++ b/issue-bot/playground.neon @@ -3,3 +3,11 @@ rules: - PHPStan\Rules\Playground\MethodNeverRule - PHPStan\Rules\Playground\NotAnalysedTraitRule - PHPStan\Rules\Playground\NoPhpCodeRule + +conditionalTags: + PHPStan\Rules\Playground\StaticVarWithoutTypeRule: + phpstan.rules.rule: %checkImplicitMixed% + +services: + - + class: PHPStan\Rules\Playground\StaticVarWithoutTypeRule diff --git a/src/Rules/Playground/StaticVarWithoutTypeRule.php b/src/Rules/Playground/StaticVarWithoutTypeRule.php new file mode 100644 index 00000000000..28f53699525 --- /dev/null +++ b/src/Rules/Playground/StaticVarWithoutTypeRule.php @@ -0,0 +1,81 @@ + + */ +final class StaticVarWithoutTypeRule implements Rule +{ + + public function __construct( + private FileTypeMapper $fileTypeMapper, + ) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Static_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $docComment = $node->getDocComment(); + $ruleError = RuleErrorBuilder::message('Static variable needs to be typed with PHPDoc @var tag.') + ->identifier('phpstanPlayground.staticWithoutType') + ->build(); + if ($docComment === null) { + return [$ruleError]; + } + $variableNames = []; + foreach ($node->vars as $var) { + if (!is_string($var->var->name)) { + throw new ShouldNotHappenException(); + } + + $variableNames[] = $var->var->name; + } + + $function = $scope->getFunction(); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + $docComment->getText(), + ); + $varTags = []; + foreach ($resolvedPhpDoc->getVarTags() as $key => $varTag) { + $varTags[$key] = $varTag; + } + + if (count($varTags) === 0) { + return [$ruleError]; + } + + if (count($variableNames) === 1 && count($varTags) === 1 && isset($varTags[0])) { + return []; + } + + foreach ($variableNames as $variableName) { + if (isset($varTags[$variableName])) { + continue; + } + + return [$ruleError]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php b/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php new file mode 100644 index 00000000000..4ef63348287 --- /dev/null +++ b/tests/PHPStan/Rules/Playground/StaticVarWithoutTypeRuleTest.php @@ -0,0 +1,34 @@ + + */ +class StaticVarWithoutTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new StaticVarWithoutTypeRule(self::getContainer()->getByType(FileTypeMapper::class)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/static-var-without-type.php'], [ + [ + 'Static variable needs to be typed with PHPDoc @var tag.', + 23, + ], + [ + 'Static variable needs to be typed with PHPDoc @var tag.', + 28, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Playground/data/static-var-without-type.php b/tests/PHPStan/Rules/Playground/data/static-var-without-type.php new file mode 100644 index 00000000000..10455d131de --- /dev/null +++ b/tests/PHPStan/Rules/Playground/data/static-var-without-type.php @@ -0,0 +1,31 @@ + Date: Sun, 5 Jan 2025 14:49:31 +0100 Subject: [PATCH 0986/3097] Fix build --- .../Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php index 1fa91d3fe63..ff465ea7e56 100644 --- a/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -97,6 +98,10 @@ public function testBug7310(): void public function testBug11939(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/bug-11939.php'], []); } From 59ccf550b002043e044f620d23432dbcbd3d6bfc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 14:51:28 +0100 Subject: [PATCH 0987/3097] Playground rule - PromoteParameterRule --- src/Rules/Playground/PromoteParameterRule.php | 57 +++++++++++++++++++ .../Playground/PromoteParameterRuleTest.php | 41 +++++++++++++ .../Playground/data/promote-parameter.php | 10 ++++ 3 files changed, 108 insertions(+) create mode 100644 src/Rules/Playground/PromoteParameterRule.php create mode 100644 tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php create mode 100644 tests/PHPStan/Rules/Playground/data/promote-parameter.php diff --git a/src/Rules/Playground/PromoteParameterRule.php b/src/Rules/Playground/PromoteParameterRule.php new file mode 100644 index 00000000000..10a7af56f13 --- /dev/null +++ b/src/Rules/Playground/PromoteParameterRule.php @@ -0,0 +1,57 @@ + + */ +final class PromoteParameterRule implements Rule +{ + + /** + * @param Rule $rule + * @param class-string $nodeType + */ + public function __construct( + private Rule $rule, + private string $nodeType, + private bool $parameterValue, + private string $parameterName, + ) + { + } + + public function getNodeType(): string + { + return $this->nodeType; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->parameterValue) { + return []; + } + + if ($this->nodeType !== $this->rule->getNodeType()) { + return []; + } + + $errors = []; + foreach ($this->rule->processNode($node, $scope) as $error) { + $errors[] = RuleErrorBuilder::message($error->getMessage()) + ->identifier('phpstanPlayground.configParameter') + ->tip(sprintf('This error would be reported if the %s: true parameter was enabled in your %%configurationFile%%.', $this->parameterName)) + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php new file mode 100644 index 00000000000..f95e1b5573e --- /dev/null +++ b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php @@ -0,0 +1,41 @@ +> + */ +class PromoteParameterRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new PromoteParameterRule( + new UninitializedPropertyRule(new ConstructorsHelper( + self::getContainer(), + [], + )), + ClassPropertiesNode::class, + false, + 'checkUninitializedProperties', + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/promote-parameter.php'], [ + [ + 'Class PromoteParameter\Foo has an uninitialized property $test. Give it default value or assign it in the constructor.', + 5, + 'This error would be reported if the checkUninitializedProperties: true parameter was enabled in your %configurationFile%.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Playground/data/promote-parameter.php b/tests/PHPStan/Rules/Playground/data/promote-parameter.php new file mode 100644 index 00000000000..da1ea8ad08f --- /dev/null +++ b/tests/PHPStan/Rules/Playground/data/promote-parameter.php @@ -0,0 +1,10 @@ + Date: Sun, 5 Jan 2025 17:08:44 +0100 Subject: [PATCH 0988/3097] UninitializedPropertyRule should be always reported when `checkUninitializedProperties` is enabled --- conf/config.level0.neon | 12 ------------ conf/config.neon | 10 ++++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index dbb2b4836cd..91602816517 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -1,10 +1,6 @@ parameters: customRulesetUsed: false -conditionalTags: - PHPStan\Rules\Properties\UninitializedPropertyRule: - phpstan.rules.rule: %checkUninitializedProperties% - rules: - PHPStan\Rules\Api\ApiInstanceofRule - PHPStan\Rules\Api\ApiInstanceofTypeRule @@ -218,9 +214,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Properties\UninitializedPropertyRule - - class: PHPStan\Rules\Properties\WritingToReadOnlyPropertiesRule arguments: @@ -250,11 +243,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Reflection\ConstructorsHelper - arguments: - additionalConstructors: %additionalConstructors% - - class: PHPStan\Rules\Keywords\RequireFileExistsRule arguments: diff --git a/conf/config.neon b/conf/config.neon index b9c52c9a571..9d62f32c601 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -211,6 +211,8 @@ conditionalTags: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% + PHPStan\Rules\Properties\UninitializedPropertyRule: + phpstan.rules.rule: %checkUninitializedProperties% services: - @@ -734,6 +736,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Reflection\ConstructorsHelper + arguments: + additionalConstructors: %additionalConstructors% + - class: PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension @@ -1037,6 +1044,9 @@ services: reportMagicProperties: %reportMagicProperties% checkDynamicProperties: %checkDynamicProperties% + - + class: PHPStan\Rules\Properties\UninitializedPropertyRule + - class: PHPStan\Rules\Properties\LazyReadWritePropertiesExtensionProvider From 2f712479fe1aa86f618a49fe4f36a3531230484b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 5 Jan 2025 17:16:28 +0100 Subject: [PATCH 0989/3097] PromoteParameterRule - more precise line for LineRuleError --- src/Rules/Playground/PromoteParameterRule.php | 10 +++++++--- .../Rules/Playground/PromoteParameterRuleTest.php | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Rules/Playground/PromoteParameterRule.php b/src/Rules/Playground/PromoteParameterRule.php index 10a7af56f13..cee351e3ffc 100644 --- a/src/Rules/Playground/PromoteParameterRule.php +++ b/src/Rules/Playground/PromoteParameterRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Rules\LineRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; @@ -45,10 +46,13 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($this->rule->processNode($node, $scope) as $error) { - $errors[] = RuleErrorBuilder::message($error->getMessage()) + $builder = RuleErrorBuilder::message($error->getMessage()) ->identifier('phpstanPlayground.configParameter') - ->tip(sprintf('This error would be reported if the %s: true parameter was enabled in your %%configurationFile%%.', $this->parameterName)) - ->build(); + ->tip(sprintf('This error would be reported if the %s: true parameter was enabled in your %%configurationFile%%.', $this->parameterName)); + if ($error instanceof LineRuleError) { + $builder->line($error->getLine()); + } + $errors[] = $builder->build(); } return $errors; diff --git a/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php index f95e1b5573e..e97673ff5f6 100644 --- a/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php +++ b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php @@ -32,7 +32,7 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/promote-parameter.php'], [ [ 'Class PromoteParameter\Foo has an uninitialized property $test. Give it default value or assign it in the constructor.', - 5, + 8, 'This error would be reported if the checkUninitializedProperties: true parameter was enabled in your %configurationFile%.', ], ]); From 1f64c159c599408fde0d7285fb89920c00b15446 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:03:54 +0000 Subject: [PATCH 0990/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 6ac10b2742b..6edf082303d 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#dfcad4524db603bd20bdec3aab1a31c5f5128ea3", + "jetbrains/phpstorm-stubs": "dev-master#62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 039bd64a30f..f9f801df4c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f5964f498aa14ffd6b984c06676417aa", + "content-hash": "bf40a89cec9c4598324b1e8394b7367c", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "dfcad4524db603bd20bdec3aab1a31c5f5128ea3" + "reference": "62a683f61d9ea11ef8caf8b2ad54e59e92b2c670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/dfcad4524db603bd20bdec3aab1a31c5f5128ea3", - "reference": "dfcad4524db603bd20bdec3aab1a31c5f5128ea3", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", + "reference": "62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-01-02T13:51:39+00:00" + "time": "2025-01-04T20:30:22+00:00" }, { "name": "nette/bootstrap", From 8b2794326fcfea43111df419a948d197219f589a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 6 Jan 2025 11:53:02 +0100 Subject: [PATCH 0991/3097] Calling to a constructor with promoted properties has side effects --- src/Analyser/MutatingScope.php | 2 ++ src/Analyser/NodeScopeResolver.php | 7 ++++-- .../Php/PhpMethodFromParserNodeReflection.php | 11 +++++++++- ...onstructorWithoutImpurePointsCollector.php | 3 +-- .../MethodWithoutImpurePointsCollector.php | 6 +---- src/Rules/Pure/FunctionPurityCheck.php | 9 +------- src/Rules/Pure/PureFunctionRule.php | 1 + src/Rules/Pure/PureMethodRule.php | 1 + ...odStatementWithoutImpurePointsRuleTest.php | 8 +++++++ .../PHPStan/Rules/DeadCode/data/bug-12379.php | 22 +++++++++++++++++++ 10 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-12379.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 9f726ede71e..274d8392e43 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2969,6 +2969,7 @@ public function enterClassMethod( array $parameterOutTypes = [], array $immediatelyInvokedCallableParameters = [], array $phpDocClosureThisTypeParameters = [], + bool $isConstructor = false, ): self { if (!$this->isInClass()) { @@ -2999,6 +3000,7 @@ public function enterClassMethod( array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $parameterOutTypes), $immediatelyInvokedCallableParameters, array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters), + $isConstructor, ), !$classMethod->isStatic(), ); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 5f518077a25..c0617856f7f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -614,6 +614,9 @@ private function processStmtNode( $nodeCallback($stmt->returnType, $scope); } + $isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct'; + $isConstructor = $isFromTrait || $stmt->name->toLowerString() === '__construct'; + $methodScope = $scope->enterClassMethod( $stmt, $templateTypeMap, @@ -632,14 +635,14 @@ private function processStmtNode( $phpDocParameterOutTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, + $isConstructor, ); if (!$scope->isInClass()) { throw new ShouldNotHappenException(); } - $isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct'; - if ($isFromTrait || $stmt->name->toLowerString() === '__construct') { + if ($isConstructor) { foreach ($stmt->params as $param) { if ($param->flags === 0) { continue; diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index a05f5501058..f105115df79 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -60,10 +60,14 @@ public function __construct( array $parameterOutTypes, array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, + private bool $isConstructor, ) { $name = strtolower($classMethod->name->name); - if (in_array($name, ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { + if ($this->isConstructor) { + $realReturnType = new VoidType(); + } + if (in_array($name, ['__destruct', '__unset', '__wakeup', '__clone'], true)) { $realReturnType = new VoidType(); } if ($name === '__tostring') { @@ -175,6 +179,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->getClassMethod()->isAbstract()); } + public function isConstructor(): bool + { + return $this->isConstructor; + } + public function hasSideEffects(): TrinaryLogic { if ( diff --git a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php index 2dca4eb85e2..47b2feb4dfd 100644 --- a/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/ConstructorWithoutImpurePointsCollector.php @@ -7,7 +7,6 @@ use PHPStan\Collectors\Collector; use PHPStan\Node\MethodReturnStatementsNode; use function count; -use function strtolower; /** * @implements Collector @@ -23,7 +22,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope) { $method = $node->getMethodReflection(); - if (strtolower($method->getName()) !== '__construct') { + if (!$method->isConstructor()) { return null; } diff --git a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php index 1ec55619b64..675d150d52e 100644 --- a/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php +++ b/src/Rules/DeadCode/MethodWithoutImpurePointsCollector.php @@ -49,11 +49,7 @@ public function processNode(Node $node, Scope $scope) return null; } - $declaringClass = $method->getDeclaringClass(); - if ( - $declaringClass->hasConstructor() - && $declaringClass->getConstructor()->getName() === $method->getName() - ) { + if ($method->isConstructor()) { return null; } diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index e70d2eb2927..56c78e874bf 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -40,18 +40,11 @@ public function check( array $impurePoints, array $throwPoints, array $statements, + bool $isConstructor, ): array { $errors = []; $isPure = $functionReflection->isPure(); - $isConstructor = false; - if ( - $functionReflection instanceof ExtendedMethodReflection - && $functionReflection->getDeclaringClass()->hasConstructor() - && $functionReflection->getDeclaringClass()->getConstructor()->getName() === $functionReflection->getName() - ) { - $isConstructor = true; - } if ($isPure->yes()) { foreach ($parameters as $parameter) { diff --git a/src/Rules/Pure/PureFunctionRule.php b/src/Rules/Pure/PureFunctionRule.php index 622a093e0bf..e05a0be9028 100644 --- a/src/Rules/Pure/PureFunctionRule.php +++ b/src/Rules/Pure/PureFunctionRule.php @@ -36,6 +36,7 @@ public function processNode(Node $node, Scope $scope): array $node->getImpurePoints(), $node->getStatementResult()->getThrowPoints(), $node->getStatements(), + false, ); } diff --git a/src/Rules/Pure/PureMethodRule.php b/src/Rules/Pure/PureMethodRule.php index 5dd972f7096..8ef6f87c662 100644 --- a/src/Rules/Pure/PureMethodRule.php +++ b/src/Rules/Pure/PureMethodRule.php @@ -36,6 +36,7 @@ public function processNode(Node $node, Scope $scope): array $node->getImpurePoints(), $node->getStatementResult()->getThrowPoints(), $node->getStatements(), + $method->isConstructor(), ); } diff --git a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php index 77feb89d7d3..67f6f42b4e3 100644 --- a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php @@ -72,6 +72,14 @@ public function testBug11011(): void ]); } + public function testBug12379(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/bug-12379.php'], []); + } + protected function getCollectors(): array { return [ diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-12379.php b/tests/PHPStan/Rules/DeadCode/data/bug-12379.php new file mode 100644 index 00000000000..f8dc4ede859 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-12379.php @@ -0,0 +1,22 @@ += 8.1 + +namespace Bug12379; + +class HelloWorld +{ + use myTrait{ + myTrait::__construct as private __myTraitConstruct; + } + + public function __construct( + int $entityManager + ){ + $this->__myTraitConstruct($entityManager); + } +} + +trait myTrait{ + public function __construct( + private readonly int $entityManager + ){} +} From 65be2b21be5614c7a1d8841a0381fcfb7a1330b8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 7 Jan 2025 11:40:15 +0100 Subject: [PATCH 0992/3097] Support arrays with union value-types in `implode()` --- phpstan-baseline.neon | 5 ---- .../ImplodeFunctionReturnTypeExtension.php | 23 +++++++++++--- tests/PHPStan/Analyser/nsrt/bug-11854.php | 18 +++++++++++ tests/PHPStan/Analyser/nsrt/implode.php | 30 +++++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11854.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9977a1992d8..93649e90879 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1362,11 +1362,6 @@ parameters: count: 1 path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" - count: 1 - path: src/Type/Php/ImplodeFunctionReturnTypeExtension.php - - message: """ #^Call to deprecated method getConstantScalars\\(\\) of class PHPStan\\\\Type\\\\TypeUtils\\: diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 5c680b5b62f..a052a43416c 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -4,7 +4,9 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -12,7 +14,6 @@ use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\ConstantScalarType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; @@ -114,14 +115,28 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT $valueTypes = $array->getValueTypes(); $arrayValues = []; + $combinationsCount = 1; foreach ($valueTypes as $valueType) { - if (!$valueType instanceof ConstantScalarType) { + $constScalars = $valueType->getConstantScalarValues(); + if (count($constScalars) === 0) { return null; } - $arrayValues[] = $valueType->getValue(); + $arrayValues[] = $constScalars; + $combinationsCount *= count($constScalars); } - $strings[] = new ConstantStringType(implode($separatorType->getValue(), $arrayValues)); + if ($combinationsCount > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; + } + + $combinations = CombinationsHelper::combinations($arrayValues); + foreach ($combinations as $combination) { + $strings[] = new ConstantStringType(implode($separatorType->getValue(), $combination)); + } + } + + if (count($strings) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return null; } return TypeCombinator::union(...$strings); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11854.php b/tests/PHPStan/Analyser/nsrt/bug-11854.php new file mode 100644 index 00000000000..48a49258cc5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11854.php @@ -0,0 +1,18 @@ + Date: Tue, 7 Jan 2025 14:35:06 +0100 Subject: [PATCH 0993/3097] Add support for result cache meta extensions --- .github/workflows/e2e-tests.yml | 9 +++++ composer.json | 3 ++ e2e/result-cache-meta-extension/hash.txt | 1 + e2e/result-cache-meta-extension/phpstan.neon | 10 +++++ .../src/DummyResultCacheMetaExtension.php | 21 ++++++++++ .../ResultCache/ResultCacheManager.php | 28 +++++++++++++ .../ResultCache/ResultCacheMetaExtension.php | 39 +++++++++++++++++++ .../ConditionalTagsExtension.php | 2 + 8 files changed, 113 insertions(+) create mode 100644 e2e/result-cache-meta-extension/hash.txt create mode 100644 e2e/result-cache-meta-extension/phpstan.neon create mode 100644 e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php create mode 100644 src/Analyser/ResultCache/ResultCacheMetaExtension.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 872d5363144..5b77182b86a 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -233,6 +233,15 @@ jobs: cd e2e/bug-11857 composer install ../../bin/phpstan + - script: | + cd e2e/result-cache-meta-extension + ../../bin/phpstan -vvv + ../../bin/phpstan -vvv --fail-without-result-cache + echo 'modified-hash' > hash.txt + OUTPUT=$(../bashunit -a exit_code "2" "../../bin/phpstan -vvv --fail-without-result-cache") + echo "$OUTPUT" + ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" + ../bashunit -a contains 'Result cache not used because the metadata do not match: metaExtensions' "$OUTPUT" steps: - name: "Checkout" diff --git a/composer.json b/composer.json index 6edf082303d..4a524d5ff79 100644 --- a/composer.json +++ b/composer.json @@ -140,6 +140,9 @@ "classmap": [ "tests/e2e", "tests/PHPStan" + ], + "files": [ + "e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php" ] }, "repositories": [ diff --git a/e2e/result-cache-meta-extension/hash.txt b/e2e/result-cache-meta-extension/hash.txt new file mode 100644 index 00000000000..1f34c8dfd28 --- /dev/null +++ b/e2e/result-cache-meta-extension/hash.txt @@ -0,0 +1 @@ +initial-hash diff --git a/e2e/result-cache-meta-extension/phpstan.neon b/e2e/result-cache-meta-extension/phpstan.neon new file mode 100644 index 00000000000..f2f9c411482 --- /dev/null +++ b/e2e/result-cache-meta-extension/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + level: 8 + paths: + - src + +services: + - + class: ResultCacheE2E\MetaExtension\DummyResultCacheMetaExtension + tags: + - phpstan.resultCacheMetaExtension diff --git a/e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php b/e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php new file mode 100644 index 00000000000..81b6332f96e --- /dev/null +++ b/e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php @@ -0,0 +1,21 @@ + self::CACHE_VERSION, 'phpstanVersion' => ComposerHelper::getPhpStanVersion(), + 'metaExtensions' => $this->getMetaFromPhpStanExtensions(), 'phpVersion' => PHP_VERSION_ID, 'projectConfig' => $projectConfigArray, 'analysedPaths' => $this->analysedPaths, @@ -1036,4 +1039,29 @@ private function getStubFiles(): array return $stubFiles; } + /** + * @return array + * @throws ShouldNotHappenException + */ + private function getMetaFromPhpStanExtensions(): array + { + $meta = []; + + /** @var ResultCacheMetaExtension $extension */ + foreach ($this->container->getServicesByTag(ResultCacheMetaExtension::EXTENSION_TAG) as $extension) { + if (array_key_exists($extension->getKey(), $meta)) { + throw new ShouldNotHappenException(sprintf( + 'Duplicate ResultCacheMetaExtension with key "%s" found.', + $extension->getKey(), + )); + } + + $meta[$extension->getKey()] = $extension->getHash(); + } + + ksort($meta); + + return $meta; + } + } diff --git a/src/Analyser/ResultCache/ResultCacheMetaExtension.php b/src/Analyser/ResultCache/ResultCacheMetaExtension.php new file mode 100644 index 00000000000..11aac2512fe --- /dev/null +++ b/src/Analyser/ResultCache/ResultCacheMetaExtension.php @@ -0,0 +1,39 @@ + $bool, LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG => $bool, DiagnoseExtension::EXTENSION_TAG => $bool, + ResultCacheMetaExtension::EXTENSION_TAG => $bool, ])->min(1)); } From 33dc7579dec5e6de1f72406ec0b4b0bbfd75533c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 7 Jan 2025 14:41:43 +0100 Subject: [PATCH 0994/3097] Moved the autoload-dev file to e2e/result-cache-meta-extension --- .github/workflows/e2e-tests.yml | 1 + composer.json | 3 --- e2e/result-cache-meta-extension/.gitignore | 1 + e2e/result-cache-meta-extension/composer.json | 5 +++++ e2e/result-cache-meta-extension/composer.lock | 18 ++++++++++++++++++ 5 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 e2e/result-cache-meta-extension/.gitignore create mode 100644 e2e/result-cache-meta-extension/composer.json create mode 100644 e2e/result-cache-meta-extension/composer.lock diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5b77182b86a..208df4952fd 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -235,6 +235,7 @@ jobs: ../../bin/phpstan - script: | cd e2e/result-cache-meta-extension + composer install ../../bin/phpstan -vvv ../../bin/phpstan -vvv --fail-without-result-cache echo 'modified-hash' > hash.txt diff --git a/composer.json b/composer.json index 4a524d5ff79..6edf082303d 100644 --- a/composer.json +++ b/composer.json @@ -140,9 +140,6 @@ "classmap": [ "tests/e2e", "tests/PHPStan" - ], - "files": [ - "e2e/result-cache-meta-extension/src/DummyResultCacheMetaExtension.php" ] }, "repositories": [ diff --git a/e2e/result-cache-meta-extension/.gitignore b/e2e/result-cache-meta-extension/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/e2e/result-cache-meta-extension/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/result-cache-meta-extension/composer.json b/e2e/result-cache-meta-extension/composer.json new file mode 100644 index 00000000000..a072011fe86 --- /dev/null +++ b/e2e/result-cache-meta-extension/composer.json @@ -0,0 +1,5 @@ +{ + "autoload-dev": { + "classmap": ["src/"] + } +} diff --git a/e2e/result-cache-meta-extension/composer.lock b/e2e/result-cache-meta-extension/composer.lock new file mode 100644 index 00000000000..b383d88ac57 --- /dev/null +++ b/e2e/result-cache-meta-extension/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "d751713988987e9331980363e24189ce", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} From eb0e0bcfe2e4947d06c5eb680f5cf568a688ff4a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Jan 2025 15:20:03 +0100 Subject: [PATCH 0995/3097] Overwrite property expression type only if it's subtype of the native type --- src/Analyser/MutatingScope.php | 15 +-- src/Analyser/NodeScopeResolver.php | 26 ++++- .../AnnotationPropertyReflection.php | 21 ++++ .../Dummy/ChangedTypePropertyReflection.php | 22 ++++- .../Dummy/DummyPropertyReflection.php | 20 ++++ src/Reflection/ExtendedPropertyReflection.php | 9 ++ src/Reflection/Php/EnumPropertyReflection.php | 21 ++++ .../Php/SimpleXMLElementProperty.php | 21 ++++ .../Php/UniversalObjectCrateProperty.php | 21 ++++ src/Reflection/ResolvedPropertyReflection.php | 20 ++++ ...kUnresolvedPropertyPrototypeReflection.php | 4 +- ...eUnresolvedPropertyPrototypeReflection.php | 4 +- .../IntersectionTypePropertyReflection.php | 20 ++++ .../Type/UnionTypePropertyReflection.php | 20 ++++ .../WrappedExtendedPropertyReflection.php | 21 ++++ .../Properties/FoundPropertyReflection.php | 30 ++++-- src/Type/ObjectShapePropertyReflection.php | 20 ++++ tests/PHPStan/Analyser/nsrt/bug-12393.php | 99 +++++++++++++++++++ 18 files changed, 393 insertions(+), 21 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 297697ee78d..3bd6db75568 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2119,11 +2119,13 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } @@ -2167,11 +2169,12 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $nativeType); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 58b6f34df5c..e91ca6748ef 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5556,7 +5556,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } $declaringClass = $propertyReflection->getDeclaringClass(); if ($declaringClass->hasNativeProperty($propertyName)) { @@ -5621,7 +5632,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } } else { // fallback diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 9188ef77212..cc1994bc9aa 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class AnnotationPropertyReflection implements ExtendedPropertyReflection @@ -42,6 +43,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->readableType; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 07dc20ce683..f235b2e6e3f 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -12,7 +12,7 @@ final class ChangedTypePropertyReflection implements WrapperPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType) + public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType, private Type $phpDocType, private Type $nativeType) { } @@ -41,6 +41,26 @@ public function getDocComment(): ?string return $this->reflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->phpDocType; + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 40a48911e81..c2c6d4c7682 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -37,6 +37,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return new MixedType(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index c4a55163bb5..63b6246dd33 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; /** * The purpose of this interface is to be able to @@ -25,6 +26,14 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; + public function hasPhpDocType(): bool; + + public function getPhpDocType(): Type; + + public function hasNativeType(): bool; + + public function getNativeType(): Type; + public function isAbstract(): TrinaryLogic; public function isFinal(): TrinaryLogic; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index 8a9a4eed281..a74bb419ff4 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class EnumPropertyReflection implements ExtendedPropertyReflection @@ -41,6 +42,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index a8cfff2cb3d..d354ae5fe27 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -10,6 +10,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -44,6 +45,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 3382a493444..0a2f8faf359 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class UniversalObjectCrateProperty implements ExtendedPropertyReflection @@ -40,6 +41,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index d5ffc248c66..e964d99b5ed 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -59,6 +59,26 @@ public function isPublic(): bool return $this->reflection->isPublic(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->reflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->reflection->getNativeType(); + } + public function getReadableType(): Type { $type = $this->readableType; diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 151945e921f..06069f8410f 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -79,8 +79,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 4b843829ad0..18beaf3f8e4 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -74,8 +74,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index b2d1551e5c3..9976bab57db 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index bc3c4f4411e..24e2e911561 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 52e1571309c..fe64beb0f0b 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -4,6 +4,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class WrappedExtendedPropertyReflection implements ExtendedPropertyReflection @@ -38,6 +39,26 @@ public function getDocComment(): ?string return $this->property->getDocComment(); } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->property->getReadableType(); diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 36a286a4a02..19e77db7e05 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -59,6 +59,26 @@ public function getDocComment(): ?string return $this->originalPropertyReflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->originalPropertyReflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->originalPropertyReflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->originalPropertyReflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->originalPropertyReflection->getNativeType(); + } + public function getReadableType(): Type { return $this->readableType; @@ -104,16 +124,6 @@ public function isNative(): bool return $this->getNativeReflection() !== null; } - public function getNativeType(): ?Type - { - $reflection = $this->getNativeReflection(); - if ($reflection === null) { - return null; - } - - return $reflection->getNativeType(); - } - public function getNativeReflection(): ?PhpPropertyReflection { $reflection = $this->originalPropertyReflection; diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 37a98fa9ba9..d5fb99f5463 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -44,6 +44,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->type; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php new file mode 100644 index 00000000000..5410dc21508 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393.php @@ -0,0 +1,99 @@ +name = $plugin["name"]; + assertType('string', $this->name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + $this->untypedName = $plugin["name"]; + assertType('mixed', $this->untypedName); + } + + public function doBar(int $i){ + $this->float = $i; + assertType('float', $this->float); + } + + public function doBaz(int $i){ + $this->untypedFloat = $i; + assertType('int', $this->untypedFloat); + } + + public function doLorem(): void + { + $this->a = ['a' => 1]; + assertType('array{a: 1}', $this->a); + } +} + +class HelloWorldStatic +{ + private static string $name; + + /** @var string */ + private static $untypedName; + + private static float $float; + + /** @var float */ + private static $untypedFloat; + + private static array $a; + + /** + * @param mixed[] $plugin + */ + public function __construct(array $plugin){ + self::$name = $plugin["name"]; + assertType('string', self::$name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + self::$untypedName = $plugin["name"]; + assertType('mixed', self::$untypedName); + } + + public function doBar(int $i){ + self::$float = $i; + assertType('float', self::$float); + } + + public function doBaz(int $i){ + self::$untypedFloat = $i; + assertType('int', self::$untypedFloat); + } + + public function doLorem(): void + { + self::$a = ['a' => 1]; + assertType('array{a: 1}', self::$a); + } +} From 0711bec076d632f1011e3535e7dec6b59c04d708 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 11 Jan 2025 13:48:16 +0100 Subject: [PATCH 0996/3097] Fix ImpossibleCheckTypeFunctionCallRule for `is_subclass_of` and `is_a` --- .../ImpossibleCheckTypeFunctionCallRule.php | 4 - .../IsAFunctionTypeSpecifyingExtension.php | 16 ++- ...classOfFunctionTypeSpecifyingExtension.php | 16 ++- tests/PHPStan/Analyser/TypeSpecifierTest.php | 4 +- ...mpossibleCheckTypeFunctionCallRuleTest.php | 7 + .../Rules/Comparison/data/bug-3979.php | 130 ++++++++++++++++++ 6 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3979.php diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 9033aa38658..29b0801eced 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; -use function strtolower; /** * @implements Rule @@ -38,9 +37,6 @@ public function processNode(Node $node, Scope $scope): array } $functionName = (string) $node->name; - if (strtolower($functionName) === 'is_a') { - return []; - } $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index c4000b9aff0..4d062efd56a 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -9,9 +9,12 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; +use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -47,9 +50,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(false); $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); + $superType = $allowString + ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) + : new ObjectWithoutClassType(); + + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true), + $resultType, $context, false, $scope, diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 2d52ee99e1b..0ca6cf4e764 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -9,9 +9,12 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -48,9 +51,20 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes([], []); } + $superType = $allowString + ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) + : new ObjectWithoutClassType(); + + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false), + $resultType, $context, false, $scope, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 999c7169a39..4ad6a7cec40 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1133,9 +1133,7 @@ public function dataCondition(): iterable new Arg(new Variable('stringOrNull')), new Arg(new Expr\ConstFetch(new Name('false'))), ]), - [ - '$object' => 'object', - ], + [], [], ], [ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 16529f3a744..b15cf528b0a 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1102,4 +1102,11 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } + public function testBug3979(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-3979.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3979.php b/tests/PHPStan/Rules/Comparison/data/bug-3979.php new file mode 100644 index 00000000000..f0f21220d11 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3979.php @@ -0,0 +1,130 @@ + Date: Fri, 10 Jan 2025 13:09:01 +0100 Subject: [PATCH 0997/3097] Pass ExtendedMethodReflection into AlwaysUsedMethodExtension --- src/Rules/Methods/AlwaysUsedMethodExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/Methods/AlwaysUsedMethodExtension.php b/src/Rules/Methods/AlwaysUsedMethodExtension.php index cfccf5b9721..f52b6a9c2bc 100644 --- a/src/Rules/Methods/AlwaysUsedMethodExtension.php +++ b/src/Rules/Methods/AlwaysUsedMethodExtension.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules\Methods; -use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ExtendedMethodReflection; /** * This is the extension interface to implement if you want to describe an always-used class method. @@ -22,6 +22,6 @@ interface AlwaysUsedMethodExtension { - public function isAlwaysUsed(MethodReflection $methodReflection): bool; + public function isAlwaysUsed(ExtendedMethodReflection $methodReflection): bool; } From 5b1bb99b3c87b9005b704444df1500d0e0cc57ad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Jan 2025 14:01:48 +0100 Subject: [PATCH 0998/3097] Fix build after merge --- .../Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 793015d51c2..a0d08561d77 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -957,7 +957,6 @@ public function testAlwaysTruePregMatch(): void public function testBug3979(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3979.php'], []); } From 5a87c6447b592e25421586e9a9392ed944d067d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Jan 2025 14:03:16 +0100 Subject: [PATCH 0999/3097] No need for Upload transformed sources anymore --- .github/workflows/static-analysis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 6c71c8d1f06..602152e12ff 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -56,13 +56,6 @@ jobs: shell: bash run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - - name: "Upload transformed sources" - if: matrix.php-version == '7.4' - uses: actions/upload-artifact@v3 - with: - name: transformed-src - path: src - - name: "PHPStan" run: "make phpstan" From 76740fd95bbe616331c66de45f489c697163a52b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 11 Jan 2025 16:37:31 +0100 Subject: [PATCH 1000/3097] BooleanType - implement getConstantScalarTypes --- src/Type/BooleanType.php | 10 ++++++++++ .../Php/MbStrlenFunctionReturnTypeExtension.php | 12 +----------- src/Type/Php/StrlenFunctionReturnTypeExtension.php | 13 +------------ tests/PHPStan/Analyser/nsrt/bug-10952b.php | 9 +++++++++ tests/PHPStan/Analyser/nsrt/bug-11201.php | 2 +- tests/PHPStan/Analyser/nsrt/implode.php | 5 +++++ 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 0b0eb798ec5..0e26b52a674 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -46,6 +46,16 @@ public function getConstantStrings(): array return []; } + public function getConstantScalarTypes(): array + { + return [new ConstantBooleanType(true), new ConstantBooleanType(false)]; + } + + public function getConstantScalarValues(): array + { + return [true, false]; + } + public function describe(VerbosityLevel $level): string { return 'bool'; diff --git a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php index 73bdfa483ff..84464f30bd1 100644 --- a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php @@ -8,7 +8,6 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -93,16 +92,7 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($args[0]->value); - - if ($argType->isSuperTypeOf(new BooleanType())->yes()) { - $constantScalars = TypeCombinator::remove($argType, new BooleanType())->getConstantScalarTypes(); - if (count($constantScalars) > 0) { - $constantScalars[] = new ConstantBooleanType(true); - $constantScalars[] = new ConstantBooleanType(false); - } - } else { - $constantScalars = $argType->getConstantScalarTypes(); - } + $constantScalars = $argType->getConstantScalarTypes(); $lengths = []; foreach ($constantScalars as $constantScalar) { diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index e50dc206762..40b1c855872 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -5,8 +5,6 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\BooleanType; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -44,16 +42,7 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($args[0]->value); - - if ($argType->isSuperTypeOf(new BooleanType())->yes()) { - $constantScalars = TypeCombinator::remove($argType, new BooleanType())->getConstantScalarTypes(); - if (count($constantScalars) > 0) { - $constantScalars[] = new ConstantBooleanType(true); - $constantScalars[] = new ConstantBooleanType(false); - } - } else { - $constantScalars = $argType->getConstantScalarTypes(); - } + $constantScalars = $argType->getConstantScalarTypes(); $lengths = []; foreach ($constantScalars as $constantScalar) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10952b.php b/tests/PHPStan/Analyser/nsrt/bug-10952b.php index f8f70e07d0c..02386aa4b70 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10952b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10952b.php @@ -37,5 +37,14 @@ public function test(): void mb_strlen($string) > 0 => assertType('non-empty-string', $string), default => assertType("''", $string), }; + + assertType('int<0, 1>', strlen($this->getBool())); + assertType('int<0, 1>', mb_strlen($this->getBool())); } + + public function getBool(): bool + { + return rand(0, 1) === 1; + } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11201.php b/tests/PHPStan/Analyser/nsrt/bug-11201.php index 202e5b1700a..17890a823ca 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11201.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11201.php @@ -53,4 +53,4 @@ function returnsBool(): bool { assertType("' 1'", $s); $s = sprintf('%20s', returnsBool()); -assertType("lowercase-string&non-falsy-string", $s); +assertType("' '|' 1'", $s); diff --git a/tests/PHPStan/Analyser/nsrt/implode.php b/tests/PHPStan/Analyser/nsrt/implode.php index 51e121a4c14..16f060465c4 100644 --- a/tests/PHPStan/Analyser/nsrt/implode.php +++ b/tests/PHPStan/Analyser/nsrt/implode.php @@ -51,4 +51,9 @@ public function constArrays5($constArr) { public function constArrays6($constArr) { assertType("string", implode('', $constArr)); } + + /** @param array{10: 1|2|bool, xy: 'a'|'b'|'c'} $constArr */ + public function constArrays7($constArr) { + assertType("'1a'|'1b'|'1c'|'2a'|'2b'|'2c'|'a'|'b'|'c'", implode('', $constArr)); + } } From ff1feeebbd32dd99983e148eb7b47c907359634b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Jan 2025 09:58:09 +0100 Subject: [PATCH 1001/3097] More specific return type for `stream_context_get_params` --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 1b8e4c28a4d..90fcb6e6b42 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11996,7 +11996,7 @@ 'stream_context_create' => ['resource', 'options='=>'array', 'params='=>'array'], 'stream_context_get_default' => ['resource', 'options='=>'array'], 'stream_context_get_options' => ['array', 'context'=>'resource'], -'stream_context_get_params' => ['array', 'context'=>'resource'], +'stream_context_get_params' => ['array{notification:string, options:array}', 'context'=>'resource'], 'stream_context_set_default' => ['resource', 'options'=>'array'], 'stream_context_set_option' => ['bool', 'context'=>'', 'wrappername'=>'string', 'optionname'=>'string', 'value'=>''], 'stream_context_set_option\'1' => ['bool', 'context'=>'', 'options'=>'array'], From d58874ec9167dfae3636772edc84394e0fb6e8d9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 15 Jan 2025 10:23:55 +0100 Subject: [PATCH 1002/3097] Avoid false inference with instanceof --- phpstan-baseline.neon | 10 ----- src/Analyser/TypeSpecifier.php | 7 ++- tests/PHPStan/Analyser/nsrt/bug-12107.php | 43 +++++++++++++++++++ .../Analyser/nsrt/instanceof-class-string.php | 2 +- tests/PHPStan/Analyser/nsrt/instanceof.php | 16 +++---- .../Classes/ImpossibleInstanceOfRuleTest.php | 10 ----- 6 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12107.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 93649e90879..e6e26f81203 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1040,11 +1040,6 @@ parameters: count: 3 path: src/Type/Generic/TemplateIntersectionType.php - - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" - count: 2 - path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 @@ -1145,11 +1140,6 @@ parameters: count: 3 path: src/Type/Generic/TemplateUnionType.php - - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" - count: 2 - path: src/Type/Generic/TemplateUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 088236bac5c..f7d01f2da68 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -156,14 +156,17 @@ public function specifyTypesInCondition( } $classType = $scope->getType($expr->class); - $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse): Type { + $uncertainty = false; + $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); } if ($type->getObjectClassNames() !== []) { + $uncertainty = true; return $type; } if ($type instanceof GenericClassStringType) { + $uncertainty = true; return $type->getGenericType(); } if ($type instanceof ConstantStringType) { @@ -179,7 +182,7 @@ public function specifyTypesInCondition( new ObjectWithoutClassType(), ); return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); - } elseif ($context->false()) { + } elseif ($context->false() && !$uncertainty) { $exprType = $scope->getType($expr->expr); if (!$type->isSuperTypeOf($exprType)->yes()) { return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12107.php b/tests/PHPStan/Analyser/nsrt/bug-12107.php new file mode 100644 index 00000000000..1a2c839c055 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12107.php @@ -0,0 +1,43 @@ + $e2 */ + public function sayHello2(Throwable $e1, string $e2): void + { + if ($e1 instanceof $e2) { + return; + } + + + assertType('Throwable', $e1); + assertType('bool', $e1 instanceof $e2); // could be false + } + + public function sayHello3(Throwable $e1): void + { + if ($e1 instanceof LogicException) { + return; + } + + assertType('Throwable~LogicException', $e1); + assertType('false', $e1 instanceof LogicException); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php b/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php index dc2bcaa2f32..22c58c4b209 100644 --- a/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php +++ b/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php @@ -32,7 +32,7 @@ public function doBar(Foo $foo, Bar $bar): void if ($foo instanceof $class) { assertType(self::class, $foo); } else { - assertType('InstanceOfClassString\Foo~InstanceOfClassString\Bar', $foo); + assertType('InstanceOfClassString\Foo', $foo); } } diff --git a/tests/PHPStan/Analyser/nsrt/instanceof.php b/tests/PHPStan/Analyser/nsrt/instanceof.php index 098b74cb47b..9ad5cceea7d 100644 --- a/tests/PHPStan/Analyser/nsrt/instanceof.php +++ b/tests/PHPStan/Analyser/nsrt/instanceof.php @@ -80,9 +80,9 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('true', $subject instanceof Foo); assertType('bool', $subject instanceof $classString); } else { - assertType('mixed~InstanceOfNamespace\Foo', $subject); - assertType('false', $subject instanceof Foo); - assertType('false', $subject instanceof $classString); + assertType('mixed', $subject); + assertType('bool', $subject instanceof Foo); + assertType('bool', $subject instanceof $classString); // could be false } $constantString = 'InstanceOfNamespace\BarParent'; @@ -132,7 +132,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); assertType('bool', $subject instanceof $objectT); } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $objectT); // can be false } @@ -140,7 +140,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); assertType('bool', $subject instanceof $objectTString); } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $objectTString); // can be false } @@ -148,7 +148,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)&object', $subject); assertType('bool', $subject instanceof $mixedTString); } else { - assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $mixedTString); // can be false } @@ -180,8 +180,8 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('InstanceOfNamespace\Foo', $object); assertType('bool', $object instanceof $classString); } else { - assertType('object~InstanceOfNamespace\Foo', $object); - assertType('false', $object instanceof $classString); + assertType('object', $object); + assertType('bool', $object instanceof $classString); // could be false } if ($instance instanceof $string) { diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index fde28cab1a6..5382491c027 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -167,11 +167,6 @@ public function testInstanceof(): void 388, $tipText, ], - [ - 'Instanceof between T of Exception and Error will always evaluate to false.', - 404, - $tipText, - ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418, @@ -270,11 +265,6 @@ public function testInstanceofWithoutAlwaysTrue(): void 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', 362, ],*/ - [ - 'Instanceof between T of Exception and Error will always evaluate to false.', - 404, - $tipText, - ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418, From abbae6a4ccb528e45338ecebd76cb3f371356393 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 11 Jan 2025 18:25:40 +0100 Subject: [PATCH 1003/3097] Add non regression tests --- ...mpossibleCheckTypeFunctionCallRuleTest.php | 14 ++++++++++ .../Rules/Comparison/data/bug-8464.php | 18 ++++++++++++ .../Rules/Comparison/data/bug-8954.php | 28 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8464.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8954.php diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index b15cf528b0a..2bfbc1592d4 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1109,4 +1109,18 @@ public function testBug3979(): void $this->analyse([__DIR__ . '/data/bug-3979.php'], []); } + public function testBug8464(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8464.php'], []); + } + + public function testBug8954(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8954.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8464.php b/tests/PHPStan/Rules/Comparison/data/bug-8464.php new file mode 100644 index 00000000000..23cd280d7aa --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8464.php @@ -0,0 +1,18 @@ += 8.0 + +namespace Bug8464; + +final class ObjectUtil +{ + /** + * @param class-string $type + */ + public static function instanceOf(mixed $object, string $type): bool + { + return \is_object($object) + && ( + $object::class === $type || + is_subclass_of($object, $type) + ); + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8954.php b/tests/PHPStan/Rules/Comparison/data/bug-8954.php new file mode 100644 index 00000000000..b89b47ba6d5 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8954.php @@ -0,0 +1,28 @@ + $class + * @param class-string $expected + * + * @return ?class-string + */ +function ensureSubclassOf(?string $class, string $expected): ?string { + if ($class === null) { + return $class; + } + + if (!class_exists($class)) { + throw new \Exception("Class “{$class}” does not exist."); + } + + if (!is_subclass_of($class, $expected)) { + throw new \Exception("Class “{$class}” is not a subclass of “{$expected}”."); + } + + return $class; +} From 4811a1a631d22994313c9b7641ed8cb1d64953a0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Jan 2025 10:48:12 +0100 Subject: [PATCH 1004/3097] Fix build after merge --- .../Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index f64ac08dd72..278c979a89c 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -963,14 +963,12 @@ public function testBug3979(): void public function testBug8464(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8464.php'], []); } public function testBug8954(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8954.php'], []); } From d38ed503c5c9ac19a4233952775daafa14db48f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Jan 2025 10:52:43 +0100 Subject: [PATCH 1005/3097] Casting ArrayObject to array should not lead to array shape --- src/Type/ObjectType.php | 2 ++ tests/PHPStan/Analyser/nsrt/bug-12182.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12182.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index fab7c056b08..96d30d09587 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use ArrayAccess; +use ArrayObject; use Closure; use Countable; use DateTime; @@ -634,6 +635,7 @@ public function toArray(): Type if ( !$classReflection->getNativeReflection()->isUserDefined() + || $classReflection->is(ArrayObject::class) || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( $reflectionProvider, Broker::getInstance()->getUniversalObjectCratesClasses(), diff --git a/tests/PHPStan/Analyser/nsrt/bug-12182.php b/tests/PHPStan/Analyser/nsrt/bug-12182.php new file mode 100644 index 00000000000..5566a2a2da6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12182.php @@ -0,0 +1,19 @@ += 8.0 + +namespace Bug12182; + +use ArrayObject; +use function PHPStan\Testing\assertType; + +/** + * @extends ArrayObject + */ +class HelloWorld extends ArrayObject +{ + public function __construct(private int $a = 42) { + } +} + +function (HelloWorld $hw): void { + assertType('array', (array) $hw); +}; From 4dd10f35014a2224bd37025a2210156abfa4e41f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Jan 2025 10:56:41 +0100 Subject: [PATCH 1006/3097] Fix build --- .../Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 2bfbc1592d4..896173a6ee0 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1111,6 +1111,10 @@ public function testBug3979(): void public function testBug8464(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8464.php'], []); From b9894fa84cebc3fa0e2fce926eeb7eb86bf7abb9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 16 Jan 2025 15:33:55 +0100 Subject: [PATCH 1007/3097] Add ArrayChangeKeyCaseFunctionReturnTypeExtension --- conf/config.neon | 5 + ...gumentBasedFunctionReturnTypeExtension.php | 1 - ...angeKeyCaseFunctionReturnTypeExtension.php | 159 ++++++++++++++++++ .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../Analyser/nsrt/array-change-key-case.php | 98 +++++++++++ .../Rules/Functions/ReturnTypeRuleTest.php | 7 + .../Rules/Functions/data/bug-10960.php | 26 +++ 7 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/array-change-key-case.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-10960.php diff --git a/conf/config.neon b/conf/config.neon index 19b6388858d..a605093fcb0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1177,6 +1177,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayChangeKeyCaseFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayIntersectKeyFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index b2508600f43..6e3c75b9a15 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -18,7 +18,6 @@ final class ArgumentBasedFunctionReturnTypeExtension implements DynamicFunctionR private const FUNCTION_NAMES = [ 'array_unique' => 0, - 'array_change_key_case' => 0, 'array_diff_assoc' => 0, 'array_diff_key' => 0, 'array_diff_uassoc' => 0, diff --git a/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php new file mode 100644 index 00000000000..391974231d9 --- /dev/null +++ b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php @@ -0,0 +1,159 @@ +getName() === 'array_change_key_case'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + if (!isset($functionCall->getArgs()[0])) { + return null; + } + + $arrayType = $scope->getType($functionCall->getArgs()[0]->value); + if (!isset($functionCall->getArgs()[1])) { + $case = CASE_LOWER; + } else { + $caseType = $scope->getType($functionCall->getArgs()[1]->value); + $scalarValues = $caseType->getConstantScalarValues(); + if (count($scalarValues) === 1) { + $case = (int) $scalarValues[0]; + } else { + $case = null; + } + } + + $constantArrays = $arrayType->getConstantArrays(); + if (count($constantArrays) > 0) { + $arrayTypes = []; + foreach ($constantArrays as $constantArray) { + $newConstantArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $valueTypes = $constantArray->getValueTypes(); + foreach ($constantArray->getKeyTypes() as $i => $keyType) { + $valueType = $valueTypes[$i]; + + $constantStrings = $keyType->getConstantStrings(); + if (count($constantStrings) > 0) { + $keyType = TypeCombinator::union( + ...array_map( + fn (ConstantStringType $type): Type => $this->mapConstantString($type, $case), + $constantStrings, + ), + ); + } + + $newConstantArrayBuilder->setOffsetValueType( + $keyType, + $valueType, + $constantArray->isOptionalKey($i), + ); + } + $newConstantArrayType = $newConstantArrayBuilder->getArray(); + if ($constantArray->isList()->yes()) { + $newConstantArrayType = AccessoryArrayListType::intersectWith($newConstantArrayType); + } + $arrayTypes[] = $newConstantArrayType; + } + + $newArrayType = TypeCombinator::union(...$arrayTypes); + } else { + $keysType = $arrayType->getIterableKeyType(); + + $keysType = TypeTraverser::map($keysType, function (Type $type, callable $traverse) use ($case): Type { + if ($type instanceof UnionType) { + return $traverse($type); + } + + $constantStrings = $type->getConstantStrings(); + if (count($constantStrings) > 0) { + return TypeCombinator::union( + ...array_map( + fn (ConstantStringType $type): Type => $this->mapConstantString($type, $case), + $constantStrings, + ), + ); + } + + if ($type->isString()->yes()) { + $types = [new StringType()]; + if ($type->isNonFalsyString()->yes()) { + $types[] = new AccessoryNonFalsyStringType(); + } elseif ($type->isNonEmptyString()->yes()) { + $types[] = new AccessoryNonEmptyStringType(); + } + if ($type->isNumericString()->yes()) { + $types[] = new AccessoryNumericStringType(); + } + if ($case === CASE_LOWER) { + $types[] = new AccessoryLowercaseStringType(); + } elseif ($case === CASE_UPPER) { + $types[] = new AccessoryUppercaseStringType(); + } + + return TypeCombinator::intersect(...$types); + } + + return $type; + }); + + $newArrayType = TypeCombinator::intersect(new ArrayType( + $keysType, + $arrayType->getIterableValueType(), + ), ...TypeUtils::getAccessoryTypes($arrayType)); + } + + if ($arrayType->isIterableAtLeastOnce()->yes()) { + $newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); + } + + return $newArrayType; + } + + private function mapConstantString(ConstantStringType $type, ?int $case): Type + { + if ($case === CASE_LOWER) { + return new ConstantStringType(strtolower($type->getValue())); + } elseif ($case === CASE_UPPER) { + return new ConstantStringType(strtoupper($type->getValue())); + } + + return TypeCombinator::union( + new ConstantStringType(strtolower($type->getValue())), + new ConstantStringType(strtoupper($type->getValue())), + ); + } + +} diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 42b0d24959a..64bb226b0f4 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -4577,7 +4577,7 @@ public function dataArrayFunctions(): array '$reducedToInt', ], [ - 'array<0|1|2, 1|2|3>', + 'array{1, 2, 3}', 'array_change_key_case($integers)', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/array-change-key-case.php b/tests/PHPStan/Analyser/nsrt/array-change-key-case.php new file mode 100644 index 00000000000..aef6c0ac75e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-change-key-case.php @@ -0,0 +1,98 @@ + $arr1 + * @param array $arr2 + * @param array $arr3 + * @param array $arr4 + * @param array $arr5 + * @param array $arr6 + * @param array $arr7 + * @param array $arr8 + * @param array{foo: 1, bar?: 2} $arr9 + * @param array<'foo'|'bar', string> $arr10 + * @param list $list + * @param non-empty-array $nonEmpty + */ + public function sayHello( + array $arr1, + array $arr2, + array $arr3, + array $arr4, + array $arr5, + array $arr6, + array $arr7, + array $arr8, + array $arr9, + array $arr10, + array $list, + array $nonEmpty, + int $case + ): void { + assertType('array', array_change_key_case($arr1)); + assertType('array', array_change_key_case($arr1, CASE_LOWER)); + assertType('array', array_change_key_case($arr1, CASE_UPPER)); + assertType('array', array_change_key_case($arr1, $case)); + + assertType('array', array_change_key_case($arr2)); + assertType('array', array_change_key_case($arr2, CASE_LOWER)); + assertType('array', array_change_key_case($arr2, CASE_UPPER)); + assertType('array', array_change_key_case($arr2, $case)); + + assertType('array', array_change_key_case($arr3)); + assertType('array', array_change_key_case($arr3, CASE_LOWER)); + assertType('array', array_change_key_case($arr3, CASE_UPPER)); + assertType('array', array_change_key_case($arr3, $case)); + + assertType('array', array_change_key_case($arr4)); + assertType('array', array_change_key_case($arr4, CASE_LOWER)); + assertType('array', array_change_key_case($arr4, CASE_UPPER)); + assertType('array', array_change_key_case($arr4, $case)); + + assertType('array', array_change_key_case($arr5)); + assertType('array', array_change_key_case($arr5, CASE_LOWER)); + assertType('array', array_change_key_case($arr5, CASE_UPPER)); + assertType('array', array_change_key_case($arr5, $case)); + + assertType('array', array_change_key_case($arr6)); + assertType('array', array_change_key_case($arr6, CASE_LOWER)); + assertType('array', array_change_key_case($arr6, CASE_UPPER)); + assertType('array', array_change_key_case($arr6, $case)); + + assertType('array', array_change_key_case($arr7)); + assertType('array', array_change_key_case($arr7, CASE_LOWER)); + assertType('array', array_change_key_case($arr7, CASE_UPPER)); + assertType('array', array_change_key_case($arr7, $case)); + + assertType('array', array_change_key_case($arr8)); + assertType('array', array_change_key_case($arr8, CASE_LOWER)); + assertType('array', array_change_key_case($arr8, CASE_UPPER)); + assertType('array', array_change_key_case($arr8, $case)); + + assertType('array{foo: 1, bar?: 2}', array_change_key_case($arr9)); + assertType('array{foo: 1, bar?: 2}', array_change_key_case($arr9, CASE_LOWER)); + assertType('array{FOO: 1, BAR?: 2}', array_change_key_case($arr9, CASE_UPPER)); + assertType("non-empty-array<'BAR'|'bar'|'FOO'|'foo', 1|2>", array_change_key_case($arr9, $case)); + + assertType("array<'bar'|'foo', string>", array_change_key_case($arr10)); + assertType("array<'bar'|'foo', string>", array_change_key_case($arr10, CASE_LOWER)); + assertType("array<'BAR'|'FOO', string>", array_change_key_case($arr10, CASE_UPPER)); + assertType("array<'BAR'|'bar'|'FOO'|'foo', string>", array_change_key_case($arr10, $case)); + + assertType('list', array_change_key_case($list)); + assertType('list', array_change_key_case($list, CASE_LOWER)); + assertType('list', array_change_key_case($list, CASE_UPPER)); + assertType('list', array_change_key_case($list, $case)); + + assertType('non-empty-array', array_change_key_case($nonEmpty)); + assertType('non-empty-array', array_change_key_case($nonEmpty, CASE_LOWER)); + assertType('non-empty-array', array_change_key_case($nonEmpty, CASE_UPPER)); + assertType('non-empty-array', array_change_key_case($nonEmpty, $case)); + } +} diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 28d766512f0..d8a9be4200f 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -283,6 +283,13 @@ public function testBug10732(): void $this->analyse([__DIR__ . '/data/bug-10732.php'], []); } + public function testBug10960(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/bug-10960.php'], []); + } + public function testBug11518(): void { $this->checkExplicitMixed = true; diff --git a/tests/PHPStan/Rules/Functions/data/bug-10960.php b/tests/PHPStan/Rules/Functions/data/bug-10960.php new file mode 100644 index 00000000000..4b64fae830c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10960.php @@ -0,0 +1,26 @@ + 'bar']); +lowerCaseKey(['FOO' => 'bar']); From 5914d32ebb80c44614b2de7c55abea5c3656d093 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Jan 2025 15:39:40 +0100 Subject: [PATCH 1008/3097] Fix after merge --- src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php index 391974231d9..5367744502b 100644 --- a/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php @@ -83,7 +83,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $newConstantArrayType = $newConstantArrayBuilder->getArray(); if ($constantArray->isList()->yes()) { - $newConstantArrayType = AccessoryArrayListType::intersectWith($newConstantArrayType); + $newConstantArrayType = TypeCombinator::intersect($newConstantArrayType, new AccessoryArrayListType()); } $arrayTypes[] = $newConstantArrayType; } From 8a3f8c4251ee8a41138ebf57485ce19a50f81265 Mon Sep 17 00:00:00 2001 From: sayuprc <41261915+sayuprc@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:45:05 +0900 Subject: [PATCH 1009/3097] Enabling constructor check for class-string variables --- src/Rules/Classes/InstantiationRule.php | 16 ++++ .../Rules/Classes/InstantiationRuleTest.php | 42 +++++++++ .../Rules/Classes/data/class-string.php | 87 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 tests/PHPStan/Rules/Classes/data/class-string.php diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 7dd65488a0f..4c0a424c1b1 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\New_; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; @@ -17,6 +18,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; +use function array_filter; use function array_map; use function array_merge; use function count; @@ -245,6 +247,20 @@ private function getClassNames(Node $node, Scope $scope): array $type = $scope->getType($node->class); + if ($type->isClassString()->yes()) { + $concretes = array_filter( + $type->getClassStringObjectType()->getObjectClassReflections(), + static fn (ClassReflection $classReflection): bool => !$classReflection->isAbstract() && !$classReflection->isInterface(), + ); + + if (count($concretes) > 0) { + return array_map( + static fn (ClassReflection $classReflection): array => [$classReflection->getName(), true], + $concretes, + ); + } + } + return array_merge( array_map( static fn (ConstantStringType $type): array => [$type->getValue(), true], diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index e4155ce55b0..7852d40555d 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -511,4 +511,46 @@ public function testBug11815(): void $this->analyse([__DIR__ . '/data/bug-11815.php'], []); } + public function testClassString(): void + { + $this->analyse([__DIR__ . '/data/class-string.php'], [ + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 65, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 66, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 67, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 75, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 76, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 77, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 85, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 86, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 87, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/class-string.php b/tests/PHPStan/Rules/Classes/data/class-string.php new file mode 100644 index 00000000000..bb07d5954a4 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/class-string.php @@ -0,0 +1,87 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClassString; + +class A +{ + public function __construct(public int $i) + { + } +} + +abstract class B +{ + public function __construct(public int $i) + { + } +} + +class C extends B +{ +} + +interface D +{ +} + +class Foo +{ + /** + * @return class-string + */ + public static function returnClassStringA(): string + { + return A::class; + } + + /** + * @return class-string + */ + public static function returnClassStringB(): string + { + return B::class; + } + + /** + * @return class-string + */ + public static function returnClassStringC(): string + { + return C::class; + } + + /** + * @return class-string + */ + public static function returnClassStringD(): string + { + return D::class; + } +} + +$classString = Foo::returnClassStringA(); +$error = new (Foo::returnClassStringA())('O_O'); +$error = new ($classString)('O_O'); +$error = new $classString('O_O'); + +$classString = Foo::returnClassStringB(); +$ok = new (Foo::returnClassStringB())('O_O'); +$ok = new ($classString)('O_O'); +$ok = new $classString('O_O'); + +$classString = Foo::returnClassStringC(); +$error = new (Foo::returnClassStringC())('O_O'); +$error = new ($classString)('O_O'); +$error = new $classString('O_O'); + +$classString = Foo::returnClassStringD(); +$ok = new (Foo::returnClassStringD())('O_O'); +$ok = new ($classString)('O_O'); +$ok = new $classString('O_O'); + +$className = A::class; +$error = new ($className)('O_O'); +$error = new $className('O_O'); +$error = new A('O_O'); From 30b9cd86c523b59739b609dc4e325d3275d71188 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Jan 2025 15:48:53 +0100 Subject: [PATCH 1010/3097] Remove private method `ConstantArrayType::findTypeAndMethodNames()` used only once --- src/Type/Constant/ConstantArrayType.php | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 114843f9933..76fdf42a5d4 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -512,10 +512,8 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) return $acceptors; } - /** - * @return array{Type, Type}|array{} - */ - private function getClassOrObjectAndMethods(): array + /** @return ConstantArrayTypeAndMethod[] */ + public function findTypeAndMethodNames(): array { if (count($this->keyTypes) !== 2) { return []; @@ -540,16 +538,7 @@ private function getClassOrObjectAndMethods(): array return []; } - return [$classOrObject, $method]; - } - - /** @return ConstantArrayTypeAndMethod[] */ - public function findTypeAndMethodNames(): array - { - $callableArray = $this->getClassOrObjectAndMethods(); - if ($callableArray === []) { - return []; - } + $callableArray = [$classOrObject, $method]; [$classOrObject, $methods] = $callableArray; if (count($methods->getConstantStrings()) === 0) { @@ -563,8 +552,8 @@ public function findTypeAndMethodNames(): array $typeAndMethods = []; $phpVersion = PhpVersionStaticAccessor::getInstance(); - foreach ($methods->getConstantStrings() as $method) { - $has = $type->hasMethod($method->getValue()); + foreach ($methods->getConstantStrings() as $methodName) { + $has = $type->hasMethod($methodName->getValue()); if ($has->no()) { continue; } @@ -573,7 +562,7 @@ public function findTypeAndMethodNames(): array $has->yes() && !$phpVersion->supportsCallableInstanceMethods() ) { - $methodReflection = $type->getMethod($method->getValue(), new OutOfClassScope()); + $methodReflection = $type->getMethod($methodName->getValue(), new OutOfClassScope()); if ($classOrObject->isString()->yes() && !$methodReflection->isStatic()) { continue; } @@ -583,7 +572,7 @@ public function findTypeAndMethodNames(): array $has = $has->and(TrinaryLogic::createMaybe()); } - $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $method->getValue(), $has); + $typeAndMethods[] = ConstantArrayTypeAndMethod::createConcrete($type, $methodName->getValue(), $has); } return $typeAndMethods; From 2cb89ae9a89bb4f33e76fb356a56c10b80376fce Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 16 Jan 2025 15:56:14 +0100 Subject: [PATCH 1011/3097] More precise hash return type --- resources/functionMap.php | 14 ++--- resources/functionMap_php80delta.php | 12 ++-- .../Php/HashFunctionsReturnTypeExtension.php | 60 +++++++++++-------- .../Analyser/nsrt/hash-functions-74.php | 11 ++-- .../Analyser/nsrt/hash-functions-80.php | 7 ++- .../PHPStan/Analyser/nsrt/hash-functions.php | 39 +++++++----- 6 files changed, 84 insertions(+), 59 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 90fcb6e6b42..5148ffeb6dd 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3909,18 +3909,18 @@ 'HaruPage::stroke' => ['bool', 'close_path='=>'bool'], 'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], 'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'], -'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], +'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], 'hash_algos' => ['non-empty-list'], 'hash_copy' => ['HashContext', 'context'=>'HashContext'], 'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'], -'hash_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'], -'hash_final' => ['non-empty-string', 'context'=>'HashContext', 'raw_output='=>'bool'], -'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], -'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], +'hash_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'], +'hash_final' => ['non-falsy-string', 'context'=>'HashContext', 'raw_output='=>'bool'], +'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], +'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], 'hash_hmac_algos' => ['non-empty-list'], -'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'], +'hash_hmac_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'], 'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'], -'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], +'hash_pbkdf2' => ['(non-falsy-string&lowercase-string)|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], 'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'], 'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'], 'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index d242a5410a4..c6a9dfe91ae 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -53,8 +53,8 @@ 'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'hash' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], 'hash_hkdf' => ['non-falsy-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], - 'hash_hmac' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], - 'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], + 'hash_hmac' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], + 'hash_pbkdf2' => ['non-falsy-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], 'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], 'imagecreate' => ['__benevolent', 'width'=>'int', 'height'=>'int'], 'imagecreatefrombmp' => ['false|object', 'filename'=>'string'], @@ -192,10 +192,10 @@ 'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'gmp_random' => ['GMP', 'limiter='=>'int'], 'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'], - 'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], - 'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], - 'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], - 'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], + 'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], + 'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'], + 'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'], + 'hash_pbkdf2' => ['non-falsy-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'], 'hebrevc' => ['string', 'str'=>'string', 'max_chars_per_line='=>'int'], 'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'], 'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'], diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 925873293da..85ba080957d 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -6,21 +6,22 @@ use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; +use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; -use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use function array_map; +use function count; use function hash_algos; use function in_array; +use function is_bool; use function strtolower; final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -30,26 +31,32 @@ final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTyp 'hash' => [ 'cryptographic' => false, 'possiblyFalse' => false, + 'binary' => 2, ], 'hash_file' => [ 'cryptographic' => false, 'possiblyFalse' => true, + 'binary' => 2, ], 'hash_hkdf' => [ 'cryptographic' => true, 'possiblyFalse' => false, + 'binary' => true, ], 'hash_hmac' => [ 'cryptographic' => true, 'possiblyFalse' => false, + 'binary' => 3, ], 'hash_hmac_file' => [ 'cryptographic' => true, 'possiblyFalse' => true, + 'binary' => 3, ], 'hash_pbkdf2' => [ 'cryptographic' => true, 'possiblyFalse' => false, + 'binary' => 5, ], ]; @@ -86,41 +93,46 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo return isset(self::SUPPORTED_FUNCTIONS[$name]); } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $functionCall->getArgs(), - $functionReflection->getVariants(), - )->getReturnType(); - if (!isset($functionCall->getArgs()[0])) { - return $defaultReturnType; + return null; } - $algorithmType = $scope->getType($functionCall->getArgs()[0]->value); - if ($algorithmType instanceof MixedType) { - return TypeUtils::toBenevolentUnion($defaultReturnType); + $functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())]; + if (is_bool($functionData['binary'])) { + $binaryType = new ConstantBooleanType($functionData['binary']); + } elseif (isset($functionCall->getArgs()[$functionData['binary']])) { + $binaryType = $scope->getType($functionCall->getArgs()[$functionData['binary']]->value); + } else { + $binaryType = new ConstantBooleanType(false); + } + + $stringTypes = [ + new StringType(), + new AccessoryNonFalsyStringType(), + ]; + if ($binaryType->isFalse()->yes()) { + $stringTypes[] = new AccessoryLowercaseStringType(); } + $stringReturnType = new IntersectionType($stringTypes); + $algorithmType = $scope->getType($functionCall->getArgs()[0]->value); $constantAlgorithmTypes = $algorithmType->getConstantStrings(); + if (count($constantAlgorithmTypes) === 0) { + if ($functionData['possiblyFalse'] || !$this->phpVersion->throwsValueErrorForInternalFunctions()) { + return TypeUtils::toBenevolentUnion(TypeCombinator::union($stringReturnType, new ConstantBooleanType(false))); + } - if ($constantAlgorithmTypes === []) { - return TypeUtils::toBenevolentUnion($defaultReturnType); + return $stringReturnType; } $neverType = new NeverType(); $falseType = new ConstantBooleanType(false); - $nonEmptyString = new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); - $invalidAlgorithmType = $this->phpVersion->throwsValueErrorForInternalFunctions() ? $neverType : $falseType; - $functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())]; $returnTypes = array_map( - function (ConstantStringType $type) use ($functionData, $nonEmptyString, $invalidAlgorithmType) { + function (ConstantStringType $type) use ($functionData, $stringReturnType, $invalidAlgorithmType) { $algorithm = strtolower($type->getValue()); if (!in_array($algorithm, $this->hashAlgorithms, true)) { return $invalidAlgorithmType; @@ -128,7 +140,7 @@ function (ConstantStringType $type) use ($functionData, $nonEmptyString, $invali if ($functionData['cryptographic'] && in_array($algorithm, self::NON_CRYPTOGRAPHIC_ALGORITHMS, true)) { return $invalidAlgorithmType; } - return $nonEmptyString; + return $stringReturnType; }, $constantAlgorithmTypes, ); diff --git a/tests/PHPStan/Analyser/nsrt/hash-functions-74.php b/tests/PHPStan/Analyser/nsrt/hash-functions-74.php index 0d068d37651..2ffcb920f82 100644 --- a/tests/PHPStan/Analyser/nsrt/hash-functions-74.php +++ b/tests/PHPStan/Analyser/nsrt/hash-functions-74.php @@ -11,7 +11,8 @@ public function hash_hmac(string $string): void { assertType('false', hash_hmac('crc32', 'data', 'key')); assertType('false', hash_hmac('invalid', 'data', 'key')); - assertType('(non-empty-string|false)', hash_hmac($string, 'data', 'key')); + assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac($string, 'data', 'key')); + assertType('(non-falsy-string|false)', hash_hmac($string, 'data', 'key', true)); } public function hash_hmac_file(): void @@ -23,7 +24,8 @@ public function hash_hmac_file(): void public function hash(string $string): void { assertType('false', hash('invalid', 'data', false)); - assertType('(non-empty-string|false)', hash($string, 'data')); + assertType('((lowercase-string&non-falsy-string)|false)', hash($string, 'data')); + assertType('(non-falsy-string|false)', hash($string, 'data', true)); } public function hash_file(): void @@ -35,14 +37,15 @@ public function hash_hkdf(string $string): void { assertType('false', hash_hkdf('crc32', 'key')); assertType('false', hash_hkdf('invalid', 'key')); - assertType('(non-empty-string|false)', hash_hkdf($string, 'key')); + assertType('(non-falsy-string|false)', hash_hkdf($string, 'key')); } public function hash_pbkdf2(string $string): void { assertType('false', hash_pbkdf2('crc32', 'password', 'salt', 1000)); assertType('false', hash_pbkdf2('invalid', 'password', 'salt', 1000)); - assertType('(non-empty-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000)); + assertType('((lowercase-string&non-falsy-string)|false)', hash_pbkdf2($string, 'password', 'salt', 1000)); + assertType('(non-falsy-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000, 0, true)); } public function caseSensitive() diff --git a/tests/PHPStan/Analyser/nsrt/hash-functions-80.php b/tests/PHPStan/Analyser/nsrt/hash-functions-80.php index fe8cd726f07..1d11247d53a 100644 --- a/tests/PHPStan/Analyser/nsrt/hash-functions-80.php +++ b/tests/PHPStan/Analyser/nsrt/hash-functions-80.php @@ -11,7 +11,8 @@ public function hash_hmac(string $string): void { assertType('*NEVER*', hash_hmac('crc32', 'data', 'key')); assertType('*NEVER*', hash_hmac('invalid', 'data', 'key')); - assertType('non-empty-string', hash_hmac($string, 'data', 'key')); + assertType('lowercase-string&non-falsy-string', hash_hmac($string, 'data', 'key')); + assertType('non-falsy-string', hash_hmac($string, 'data', 'key', true)); } public function hash_hmac_file(): void @@ -23,7 +24,7 @@ public function hash_hmac_file(): void public function hash(string $string): void { assertType('*NEVER*', hash('invalid', 'data', false)); - assertType('non-falsy-string', hash($string, 'data')); + assertType('lowercase-string&non-falsy-string', hash($string, 'data')); } public function hash_file(): void @@ -42,7 +43,7 @@ public function hash_pbkdf2(string $string): void { assertType('*NEVER*', hash_pbkdf2('crc32', 'password', 'salt', 1000)); assertType('*NEVER*', hash_pbkdf2('invalid', 'password', 'salt', 1000)); - assertType('non-empty-string', hash_pbkdf2($string, 'password', 'salt', 1000)); + assertType('lowercase-string&non-falsy-string', hash_pbkdf2($string, 'password', 'salt', 1000)); } public function caseSensitive() diff --git a/tests/PHPStan/Analyser/nsrt/hash-functions.php b/tests/PHPStan/Analyser/nsrt/hash-functions.php index 17b9eb4d8c8..72f977baae1 100644 --- a/tests/PHPStan/Analyser/nsrt/hash-functions.php +++ b/tests/PHPStan/Analyser/nsrt/hash-functions.php @@ -13,44 +13,52 @@ class HashFunctionTests public function hash_hmac(): void { - assertType('non-empty-string', hash_hmac('md5', 'data', 'key')); - assertType('non-empty-string', hash_hmac('sha256', 'data', 'key')); + assertType('lowercase-string&non-falsy-string', hash_hmac('md5', 'data', 'key')); + assertType('non-falsy-string', hash_hmac('md5', 'data', 'key', true)); + assertType('lowercase-string&non-falsy-string', hash_hmac('sha256', 'data', 'key')); + assertType('non-falsy-string', hash_hmac('sha256', 'data', 'key', true)); } public function hash_hmac_file(string $string): void { - assertType('non-empty-string|false', hash_hmac_file('md5', 'filename', 'key')); - assertType('non-empty-string|false', hash_hmac_file('sha256', 'filename', 'key')); - assertType('(non-empty-string|false)', hash_hmac_file($string, 'filename', 'key')); + assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('md5', 'filename', 'key')); + assertType('non-falsy-string|false', hash_hmac_file('md5', 'filename', 'key', true)); + assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('sha256', 'filename', 'key')); + assertType('non-falsy-string|false', hash_hmac_file('sha256', 'filename', 'key', true)); + assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac_file($string, 'filename', 'key')); + assertType('(non-falsy-string|false)', hash_hmac_file($string, 'filename', 'key', true)); } public function hash($mixed): void { - assertType('non-empty-string', hash('sha256', 'data', false)); - assertType('non-empty-string', hash('sha256', 'data', true)); - assertType('non-empty-string', hash('md5', $mixed, false)); + assertType('lowercase-string&non-falsy-string', hash('sha256', 'data', false)); + assertType('non-falsy-string', hash('sha256', 'data', true)); + assertType('lowercase-string&non-falsy-string', hash('md5', $mixed, false)); } public function hash_file(): void { - assertType('non-empty-string|false', hash_file('sha256', 'filename', false)); - assertType('non-empty-string|false', hash_file('sha256', 'filename', true)); - assertType('non-empty-string|false', hash_file('crc32', 'filename')); + assertType('(lowercase-string&non-falsy-string)|false', hash_file('sha256', 'filename', false)); + assertType('non-falsy-string|false', hash_file('sha256', 'filename', true)); + assertType('(lowercase-string&non-falsy-string)|false', hash_file('crc32', 'filename')); + assertType('non-falsy-string|false', hash_file('crc32', 'filename', true)); } public function hash_hkdf(): void { - assertType('non-empty-string', hash_hkdf('sha256', 'key')); + assertType('non-falsy-string', hash_hkdf('sha256', 'key')); } public function hash_pbkdf2(): void { - assertType('non-empty-string', hash_pbkdf2('sha256', 'password', 'salt', 1000)); + assertType('lowercase-string&non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000)); + assertType('non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000, 0, true)); } public function caseSensitive() { - assertType('non-empty-string', hash('SHA256', 'data')); + assertType('lowercase-string&non-falsy-string', hash('SHA256', 'data')); + assertType('non-falsy-string', hash('SHA256', 'data', true)); } public function constantStrings(int $type) @@ -69,7 +77,8 @@ public function constantStrings(int $type) return; } - assertType('non-empty-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000)); + assertType('lowercase-string&non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000)); + assertType('non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000, 0, true)); } } From 0cd6324ceed8b0c928383848ebdb166bd704c866 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 22 Sep 2024 22:05:42 +0200 Subject: [PATCH 1012/3097] Makefile: Disable xdebug in dev tools --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 11807088cac..a2239eba000 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ build: cs tests phpstan tests: - php vendor/bin/paratest --runner WrapperRunner --no-coverage + XDEBUG_MODE=off php vendor/bin/paratest --runner WrapperRunner --no-coverage tests-integration: php vendor/bin/paratest --runner WrapperRunner --no-coverage --group exec @@ -18,7 +18,7 @@ tests-golden-reflection: php vendor/bin/paratest --runner WrapperRunner --no-coverage tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php lint: - php vendor/bin/parallel-lint --colors \ + XDEBUG_MODE=off php vendor/bin/parallel-lint --colors \ --exclude tests/PHPStan/Analyser/data \ --exclude tests/PHPStan/Analyser/nsrt \ --exclude tests/PHPStan/Rules/Methods/data \ @@ -80,10 +80,10 @@ lint: src tests cs: - composer install --working-dir build-cs && php build-cs/vendor/bin/phpcs + composer install --working-dir build-cs && XDEBUG_MODE=off php build-cs/vendor/bin/phpcs cs-fix: - php build-cs/vendor/bin/phpcbf + XDEBUG_MODE=off php build-cs/vendor/bin/phpcbf phpstan: php bin/phpstan clear-result-cache -q && php -d memory_limit=448M bin/phpstan From f3ac9ea673d24aab4df94556a3e656521ea324d7 Mon Sep 17 00:00:00 2001 From: Sven Reichel Date: Sat, 14 Dec 2024 08:02:54 +0100 Subject: [PATCH 1013/3097] shell_exec --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b6caed90bda..f2b70427a49 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10490,7 +10490,7 @@ 'shapeObj::toWkt' => ['string'], 'shapeObj::union' => ['shapeObj', 'shape'=>'shapeObj'], 'shapeObj::within' => ['int', 'shape2'=>'shapeObj'], -'shell_exec' => ['?string', 'cmd'=>'string'], +'shell_exec' => ['string|false|null', 'cmd'=>'string'], 'shm_attach' => ['resource|false', 'key'=>'int', 'memsize='=>'int', 'perm='=>'int'], 'shm_detach' => ['bool', 'shm_identifier'=>'resource'], 'shm_get_var' => ['mixed', 'id'=>'resource', 'variable_key'=>'int'], From c99b24244dee1d7ce104c5e7834ef46aace1d49b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 16 Jan 2025 16:27:37 +0100 Subject: [PATCH 1014/3097] Fix union of lowercase/uppercase string with empty string --- src/Type/TypeCombinator.php | 38 +++++- tests/PHPStan/Analyser/nsrt/bug-12312.php | 140 ++++++++++++++++++++++ 2 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12312.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 03ddffd988b..cd776efa5fd 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -3,8 +3,10 @@ namespace PHPStan\Type; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\HasPropertyType; @@ -452,7 +454,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && ($b->describe(VerbosityLevel::value()) === 'non-empty-string' || $b->describe(VerbosityLevel::value()) === 'non-falsy-string') ) { - return [null, new StringType()]; + return [null, self::intersect( + new StringType(), + ...self::getAccessoryCaseStringTypes($b), + )]; } if ( @@ -461,7 +466,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && ($a->describe(VerbosityLevel::value()) === 'non-empty-string' || $a->describe(VerbosityLevel::value()) === 'non-falsy-string') ) { - return [new StringType(), null]; + return [self::intersect( + new StringType(), + ...self::getAccessoryCaseStringTypes($a), + ), null]; } if ( @@ -469,10 +477,11 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && $a->getValue() === '0' && $b->describe(VerbosityLevel::value()) === 'non-falsy-string' ) { - return [null, new IntersectionType([ + return [null, self::intersect( new StringType(), new AccessoryNonEmptyStringType(), - ])]; + ...self::getAccessoryCaseStringTypes($b), + )]; } if ( @@ -480,15 +489,32 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array && $b->getValue() === '0' && $a->describe(VerbosityLevel::value()) === 'non-falsy-string' ) { - return [new IntersectionType([ + return [self::intersect( new StringType(), new AccessoryNonEmptyStringType(), - ]), null]; + ...self::getAccessoryCaseStringTypes($a), + ), null]; } return null; } + /** + * @return array + */ + private static function getAccessoryCaseStringTypes(Type $type): array + { + $accessory = []; + if ($type->isLowercaseString()->yes()) { + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($type->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + + return $accessory; + } + private static function unionWithSubtractedType( Type $type, ?Type $subtractedType, diff --git a/tests/PHPStan/Analyser/nsrt/bug-12312.php b/tests/PHPStan/Analyser/nsrt/bug-12312.php new file mode 100644 index 00000000000..f67d1aa3cb3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12312.php @@ -0,0 +1,140 @@ + Date: Sun, 22 Dec 2024 23:36:05 +0800 Subject: [PATCH 1015/3097] Implement `OpenSslEncryptParameterOutTypeExtension` --- conf/config.neon | 5 ++ ...penSslEncryptParameterOutTypeExtension.php | 70 ++++++++++++++++++ .../PHPStan/Analyser/nsrt/openssl-encrypt.php | 73 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/openssl-encrypt.php diff --git a/conf/config.neon b/conf/config.neon index a605093fcb0..205827901ef 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1574,6 +1574,11 @@ services: tags: - phpstan.dynamicFunctionThrowTypeExtension + - + class: PHPStan\Type\Php\OpenSslEncryptParameterOutTypeExtension + tags: + - phpstan.functionParameterOutTypeExtension + - class: PHPStan\Type\Php\ParseStrParameterOutTypeExtension tags: diff --git a/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php new file mode 100644 index 00000000000..5d24f86f526 --- /dev/null +++ b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php @@ -0,0 +1,70 @@ +getName() === 'openssl_encrypt' && $parameter->getName() === 'tag'; + } + + public function getParameterOutTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, ParameterReflection $parameter, Scope $scope): ?Type + { + $args = $funcCall->getArgs(); + $cipherArg = $args[1] ?? null; + + if ($cipherArg === null) { + return null; + } + + $tagTypes = []; + + foreach ($scope->getType($cipherArg->value)->getConstantStrings() as $cipherType) { + $cipher = strtolower($cipherType->getValue()); + $mode = substr($cipher, -3); + + if (!in_array($cipher, openssl_get_cipher_methods(), true)) { + $tagTypes[] = new NullType(); + continue; + } + + if (in_array($mode, ['gcm', 'ccm'], true)) { + $tagTypes[] = TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ); + + continue; + } + + $tagTypes[] = new NullType(); + } + + if ($tagTypes === []) { + return TypeCombinator::addNull(TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + )); + } + + return TypeCombinator::union(...$tagTypes); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/openssl-encrypt.php b/tests/PHPStan/Analyser/nsrt/openssl-encrypt.php new file mode 100644 index 00000000000..91e178514c7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/openssl-encrypt.php @@ -0,0 +1,73 @@ + Date: Thu, 16 Jan 2025 22:23:46 +0100 Subject: [PATCH 1016/3097] ReflectionClass stub with lazy object methods --- conf/config.neon | 6 +- src/Php/PhpVersion.php | 5 + .../ReflectionClassStubFilesExtension.php | 27 +++++ .../ReflectionEnumStubFilesExtension.php | 6 +- stubs/ReflectionClassWithLazyObjects.stub | 113 ++++++++++++++++++ stubs/ReflectionEnum.stub | 2 +- stubs/ReflectionEnumWithLazyObjects.stub | 27 +++++ .../Analyser/nsrt/enum-reflection-backed.php | 16 +++ .../PHPStan/Analyser/nsrt/enum-reflection.php | 9 -- .../Type/Generic/GenericObjectTypeTest.php | 5 +- 10 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 src/PhpDoc/ReflectionClassStubFilesExtension.php create mode 100644 stubs/ReflectionClassWithLazyObjects.stub create mode 100644 stubs/ReflectionEnumWithLazyObjects.stub create mode 100644 tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php diff --git a/conf/config.neon b/conf/config.neon index 07c94887837..bd13fe06122 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -98,7 +98,6 @@ parameters: - stdClass stubFiles: - ../stubs/ReflectionAttribute.stub - - ../stubs/ReflectionClass.stub - ../stubs/ReflectionClassConstant.stub - ../stubs/ReflectionFunctionAbstract.stub - ../stubs/ReflectionMethod.stub @@ -418,6 +417,11 @@ services: tags: - phpstan.stubFilesExtension + - + class: PHPStan\PhpDoc\ReflectionClassStubFilesExtension + tags: + - phpstan.stubFilesExtension + - class: PHPStan\PhpDoc\ReflectionEnumStubFilesExtension tags: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 1f7c05a50c9..651aff52052 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -362,6 +362,11 @@ public function supportsAsymmetricVisibility(): bool return $this->versionId >= 80400; } + public function supportsLazyObjects(): bool + { + return $this->versionId >= 80400; + } + public function hasDateTimeExceptions(): bool { return $this->versionId >= 80300; diff --git a/src/PhpDoc/ReflectionClassStubFilesExtension.php b/src/PhpDoc/ReflectionClassStubFilesExtension.php new file mode 100644 index 00000000000..1abd672f68a --- /dev/null +++ b/src/PhpDoc/ReflectionClassStubFilesExtension.php @@ -0,0 +1,27 @@ +phpVersion->supportsLazyObjects()) { + return [ + __DIR__ . '/../../stubs/ReflectionClass.stub', + ]; + } + + return [ + __DIR__ . '/../../stubs/ReflectionClassWithLazyObjects.stub', + ]; + } + +} diff --git a/src/PhpDoc/ReflectionEnumStubFilesExtension.php b/src/PhpDoc/ReflectionEnumStubFilesExtension.php index 38ab53a1247..ed9b43b6beb 100644 --- a/src/PhpDoc/ReflectionEnumStubFilesExtension.php +++ b/src/PhpDoc/ReflectionEnumStubFilesExtension.php @@ -17,7 +17,11 @@ public function getFiles(): array return []; } - return [__DIR__ . '/../../stubs/ReflectionEnum.stub']; + if (!$this->phpVersion->supportsLazyObjects()) { + return [__DIR__ . '/../../stubs/ReflectionEnum.stub']; + } + + return [__DIR__ . '/../../stubs/ReflectionEnumWithLazyObjects.stub']; } } diff --git a/stubs/ReflectionClassWithLazyObjects.stub b/stubs/ReflectionClassWithLazyObjects.stub new file mode 100644 index 00000000000..1a53ae87504 --- /dev/null +++ b/stubs/ReflectionClassWithLazyObjects.stub @@ -0,0 +1,113 @@ + + */ + public $name; + + /** + * @param T|class-string $argument + * @throws ReflectionException + */ + public function __construct($argument) {} + + /** + * @return class-string + */ + public function getName() : string; + + /** + * @param mixed ...$args + * + * @return T + */ + public function newInstance(...$args) {} + + /** + * @param array $args + * + * @return T + */ + public function newInstanceArgs(array $args) {} + + /** + * @return T + */ + public function newInstanceWithoutConstructor(); + + /** + * @return list> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } + + /** + * @param callable(T): void $initializer + * @return T + */ + public function newLazyGhost(callable $initializer, int $options = 0): object + { + } + + /** + * @param callable(T): T $factory + * @return T + */ + public function newLazyProxy(callable $factory, int $options = 0): object + { + } + + /** + * @param T $object + * @param callable(T): void $initializer + */ + public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void + { + } + + /** + * @param T $object + * @param callable(T): T $factory + */ + public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void + { + } + + /** + * @param T $object + * @return T + */ + public function initializeLazyObject(object $object): object + { + } + + /** + * @param T $object + * @return T + */ + public function markLazyObjectAsInitialized(object $object): object + { + } + + /** + * @param T $object + */ + public function getLazyInitializer(object $object): ?callable + { + } + + /** + * @param T $object + */ + public function isUninitializedLazyObject(object $object): bool + { + } +} diff --git a/stubs/ReflectionEnum.stub b/stubs/ReflectionEnum.stub index 20396c04fc6..6a13532d9a7 100644 --- a/stubs/ReflectionEnum.stub +++ b/stubs/ReflectionEnum.stub @@ -19,7 +19,7 @@ class ReflectionEnum extends ReflectionClass public function getCase(string $name): ReflectionEnumUnitCase {} /** - * @phpstan-assert-if-true self $this + * @phpstan-assert-if-true self $this * @phpstan-assert-if-true !null $this->getBackingType() */ public function isBacked(): bool {} diff --git a/stubs/ReflectionEnumWithLazyObjects.stub b/stubs/ReflectionEnumWithLazyObjects.stub new file mode 100644 index 00000000000..3955a5013f5 --- /dev/null +++ b/stubs/ReflectionEnumWithLazyObjects.stub @@ -0,0 +1,27 @@ + + */ +class ReflectionEnum extends ReflectionClass +{ + + /** + * @return (T is BackedEnum ? ReflectionEnumBackedCase[] : ReflectionEnumUnitCase[]) + */ + public function getCases(): array {} + + /** + * @return (T is BackedEnum ? ReflectionEnumBackedCase : ReflectionEnumUnitCase) + * @throws ReflectionException + */ + public function getCase(string $name): ReflectionEnumUnitCase {} + + /** + * @phpstan-assert-if-true self $this + * @phpstan-assert-if-true !null $this->getBackingType() + */ + public function isBacked(): bool {} + +} diff --git a/tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php b/tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php new file mode 100644 index 00000000000..00f1b9634fd --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php @@ -0,0 +1,16 @@ + $class */ +function testNarrowGetNameTypeAfterIsBacked(string $class) { + $r = new ReflectionEnum($class); + assertType('class-string', $r->getName()); + if ($r->isBacked()) { + assertType('class-string', $r->getName()); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/enum-reflection.php b/tests/PHPStan/Analyser/nsrt/enum-reflection.php index 38f023a6375..85e98f4f8d4 100644 --- a/tests/PHPStan/Analyser/nsrt/enum-reflection.php +++ b/tests/PHPStan/Analyser/nsrt/enum-reflection.php @@ -41,12 +41,3 @@ public function doFoo(): void } } - -/** @param class-string $class */ -function testNarrowGetNameTypeAfterIsBacked(string $class) { - $r = new ReflectionEnum($class); - assertType('class-string', $r->getName()); - if ($r->isBacked()) { - assertType('class-string', $r->getName()); - } -} diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 30539fae857..3be9b7193d2 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -26,6 +26,7 @@ use Traversable; use function array_map; use function sprintf; +use const PHP_VERSION_ID; class GenericObjectTypeTest extends PHPStanTestCase { @@ -134,7 +135,7 @@ public function dataIsSuperTypeOf(): array new GenericObjectType(ReflectionClass::class, [ new ObjectType(stdClass::class), ]), - TrinaryLogic::createYes(), + PHP_VERSION_ID >= 80400 ? TrinaryLogic::createNo() : TrinaryLogic::createYes(), ], [ new GenericObjectType(ReflectionClass::class, [ @@ -143,7 +144,7 @@ public function dataIsSuperTypeOf(): array new GenericObjectType(ReflectionClass::class, [ new ObjectWithoutClassType(), ]), - TrinaryLogic::createMaybe(), + PHP_VERSION_ID >= 80400 ? TrinaryLogic::createNo() : TrinaryLogic::createMaybe(), ], [ new GenericObjectType(ReflectionClass::class, [ From 72a16076a585c3a501eb7c815c0e4de25bed7576 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Jan 2025 22:58:36 +0100 Subject: [PATCH 1017/3097] Fix build --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++++ .../Analyser/{nsrt => data}/enum-reflection-backed.php | 0 2 files changed, 4 insertions(+) rename tests/PHPStan/Analyser/{nsrt => data}/enum-reflection-backed.php (100%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1363deb2fe0..cc9f6112fc0 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -33,6 +33,10 @@ private static function findTestFiles(): iterable yield __DIR__ . '/data/enum-reflection-php81.php'; } + if (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80400) { + yield __DIR__ . '/data/enum-reflection-backed.php'; + } + if (PHP_VERSION_ID < 80000) { yield __DIR__ . '/data/bug-4902.php'; } diff --git a/tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php b/tests/PHPStan/Analyser/data/enum-reflection-backed.php similarity index 100% rename from tests/PHPStan/Analyser/nsrt/enum-reflection-backed.php rename to tests/PHPStan/Analyser/data/enum-reflection-backed.php From 24cdeac08c96cee15817e1d90a14c7521e767913 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 18 Jan 2025 14:48:29 +0100 Subject: [PATCH 1018/3097] Update Symfony polyfills --- composer.lock | 144 +++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/composer.lock b/composer.lock index 7a215f2f76f..26d8c72dc79 100644 --- a/composer.lock +++ b/composer.lock @@ -3405,20 +3405,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -3429,8 +3429,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3464,7 +3464,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -3480,24 +3480,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3505,8 +3505,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3542,7 +3542,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -3558,24 +3558,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3583,8 +3583,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3623,7 +3623,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -3639,24 +3639,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -3667,8 +3667,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3703,7 +3703,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -3719,30 +3719,30 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3779,7 +3779,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -3795,30 +3795,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php74", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php74.git", - "reference": "37f1d1a2fb3ebc494f9f9b0f7e92064b43332321" + "reference": "9589537d05325fb5d88a20d8926823e5b827a43e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/37f1d1a2fb3ebc494f9f9b0f7e92064b43332321", - "reference": "37f1d1a2fb3ebc494f9f9b0f7e92064b43332321", + "url": "https://api.github.com/repos/symfony/polyfill-php74/zipball/9589537d05325fb5d88a20d8926823e5b827a43e", + "reference": "9589537d05325fb5d88a20d8926823e5b827a43e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3856,7 +3856,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php74/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php74/tree/v1.31.0" }, "funding": [ { @@ -3872,30 +3872,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3936,7 +3936,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -3952,30 +3952,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4012,7 +4012,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -4028,7 +4028,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", From 537219bdc32360e1796e93e7a4f2480b885cf8a7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 14:44:03 +0100 Subject: [PATCH 1019/3097] MinMaxFunctionReturnTypeExtension: Cleanup `instanceof ConstantScalarType` --- phpstan-baseline.neon | 7 +--- .../Php/MinMaxFunctionReturnTypeExtension.php | 17 +++++----- .../Analyser/LegacyNodeScopeResolverTest.php | 34 +++++++++++++++++-- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e6e26f81203..be237d9d794 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1410,12 +1410,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" - count: 4 - path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - - - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantValue\\(\\) or Type\\:\\:generalize\\(\\) instead\\.$#" - count: 1 + count: 2 path: src/Type/Php/MinMaxFunctionReturnTypeExtension.php - diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index 20128f0a927..5beabcbcf39 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -11,7 +11,6 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; @@ -152,16 +151,16 @@ private function processType( { $resultType = null; foreach ($types as $type) { - if (!$type instanceof ConstantType) { - return TypeCombinator::union(...$types); - } - if ($resultType === null) { $resultType = $type; continue; } $compareResult = $this->compareTypes($resultType, $type); + if ($compareResult === null) { + return TypeCombinator::union(...$types); + } + if ($functionName === 'min') { if ($compareResult === $type) { $resultType = $type; @@ -186,15 +185,15 @@ private function compareTypes( ): ?Type { if ( - $firstType->isConstantArray()->yes() - && $secondType instanceof ConstantScalarType + $firstType->isArray()->yes() + && $secondType->isConstantScalarValue()->yes() ) { return $secondType; } if ( - $firstType instanceof ConstantScalarType - && $secondType->isConstantArray()->yes() + $firstType->isConstantScalarValue()->yes() + && $secondType->isArray()->yes() ) { return $firstType; } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 64bb226b0f4..c2f8dbde173 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2363,14 +2363,42 @@ public function dataBinaryOperations(): array 'array', 'max($arrayOfUnknownIntegers, $arrayOfUnknownIntegers)', ], - /*[ - 'array(1, 1, 1, 1)', + [ + 'array{1, 1, 1, 1}', 'max(array(2, 2, 2), 5, array(1, 1, 1, 1))', ], + [ + 'array{int, int, int}', + 'max($arrayOfIntegers, 5)', + ], [ 'array', + 'max($arrayOfUnknownIntegers, 5)', + ], + [ + 'array|int', // could be array 'max($arrayOfUnknownIntegers, $integer, $arrayOfUnknownIntegers)', - ],*/ + ], + [ + 'array', + 'max($arrayOfUnknownIntegers, $conditionalInt)', + ], + [ + '5', + 'min($arrayOfIntegers, 5)', + ], + [ + '5', + 'min($arrayOfUnknownIntegers, 5)', + ], + [ + '1|2', + 'min($arrayOfUnknownIntegers, $conditionalInt)', + ], + [ + '5', + 'min(array(2, 2, 2), 5, array(1, 1, 1, 1))', + ], [ '1.1', 'min(...[1.1, 2.2, 3.3])', From bf923adb994d7b1f18954f2bb8ab3f855485b331 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 19 Jan 2025 14:54:15 +0100 Subject: [PATCH 1020/3097] Revert "Overwrite property expression type only if it's subtype of the native type" This reverts commit eb0e0bcfe2e4947d06c5eb680f5cf568a688ff4a. --- src/Analyser/MutatingScope.php | 15 ++- src/Analyser/NodeScopeResolver.php | 26 +---- .../AnnotationPropertyReflection.php | 21 ---- .../Dummy/ChangedTypePropertyReflection.php | 22 +---- .../Dummy/DummyPropertyReflection.php | 20 ---- src/Reflection/ExtendedPropertyReflection.php | 9 -- src/Reflection/Php/EnumPropertyReflection.php | 21 ---- .../Php/SimpleXMLElementProperty.php | 21 ---- .../Php/UniversalObjectCrateProperty.php | 21 ---- src/Reflection/ResolvedPropertyReflection.php | 20 ---- ...kUnresolvedPropertyPrototypeReflection.php | 4 +- ...eUnresolvedPropertyPrototypeReflection.php | 4 +- .../IntersectionTypePropertyReflection.php | 20 ---- .../Type/UnionTypePropertyReflection.php | 20 ---- .../WrappedExtendedPropertyReflection.php | 21 ---- .../Properties/FoundPropertyReflection.php | 30 ++---- src/Type/ObjectShapePropertyReflection.php | 20 ---- tests/PHPStan/Analyser/nsrt/bug-12393.php | 99 ------------------- 18 files changed, 21 insertions(+), 393 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3bd6db75568..297697ee78d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2119,12 +2119,10 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - - if (!$propertyReflection->hasNativeType()) { - return new MixedType(); - } - $nativeType = $propertyReflection->getNativeType(); + if ($nativeType === null) { + return new ErrorType(); + } return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } @@ -2169,11 +2167,10 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - if (!$propertyReflection->hasNativeType()) { - return new MixedType(); - } - $nativeType = $propertyReflection->getNativeType(); + if ($nativeType === null) { + return new ErrorType(); + } if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $nativeType); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e91ca6748ef..58b6f34df5c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5556,18 +5556,7 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType()) { - $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { - $assignedExprNativeType = $scope->getNativeType($assignedExpr); - if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { - $assignedExprNativeType = $propertyNativeType; - } - $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); - } - } else { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); - } + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } $declaringClass = $propertyReflection->getDeclaringClass(); if ($declaringClass->hasNativeProperty($propertyName)) { @@ -5632,18 +5621,7 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType()) { - $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { - $assignedExprNativeType = $scope->getNativeType($assignedExpr); - if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { - $assignedExprNativeType = $propertyNativeType; - } - $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); - } - } else { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); - } + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } } else { // fallback diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index cc1994bc9aa..9188ef77212 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -7,7 +7,6 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class AnnotationPropertyReflection implements ExtendedPropertyReflection @@ -43,26 +42,6 @@ public function isPublic(): bool return true; } - public function hasPhpDocType(): bool - { - return true; - } - - public function getPhpDocType(): Type - { - return $this->readableType; - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index f235b2e6e3f..07dc20ce683 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -12,7 +12,7 @@ final class ChangedTypePropertyReflection implements WrapperPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType, private Type $phpDocType, private Type $nativeType) + public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType) { } @@ -41,26 +41,6 @@ public function getDocComment(): ?string return $this->reflection->getDocComment(); } - public function hasPhpDocType(): bool - { - return $this->reflection->hasPhpDocType(); - } - - public function getPhpDocType(): Type - { - return $this->phpDocType; - } - - public function hasNativeType(): bool - { - return $this->reflection->hasNativeType(); - } - - public function getNativeType(): Type - { - return $this->nativeType; - } - public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index c2c6d4c7682..40a48911e81 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -37,26 +37,6 @@ public function isPublic(): bool return true; } - public function hasPhpDocType(): bool - { - return false; - } - - public function getPhpDocType(): Type - { - return new MixedType(); - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return new MixedType(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 63b6246dd33..c4a55163bb5 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection; use PHPStan\TrinaryLogic; -use PHPStan\Type\Type; /** * The purpose of this interface is to be able to @@ -26,14 +25,6 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; - public function hasPhpDocType(): bool; - - public function getPhpDocType(): Type; - - public function hasNativeType(): bool; - - public function getNativeType(): Type; - public function isAbstract(): TrinaryLogic; public function isFinal(): TrinaryLogic; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index a74bb419ff4..8a9a4eed281 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -7,7 +7,6 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class EnumPropertyReflection implements ExtendedPropertyReflection @@ -42,26 +41,6 @@ public function getDocComment(): ?string return null; } - public function hasPhpDocType(): bool - { - return false; - } - - public function getPhpDocType(): Type - { - return new MixedType(); - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index d354ae5fe27..a8cfff2cb3d 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -10,7 +10,6 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -45,26 +44,6 @@ public function isPublic(): bool return true; } - public function hasPhpDocType(): bool - { - return false; - } - - public function getPhpDocType(): Type - { - return new MixedType(); - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 0a2f8faf359..3382a493444 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -7,7 +7,6 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class UniversalObjectCrateProperty implements ExtendedPropertyReflection @@ -41,26 +40,6 @@ public function isPublic(): bool return true; } - public function hasPhpDocType(): bool - { - return false; - } - - public function getPhpDocType(): Type - { - return new MixedType(); - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index e964d99b5ed..d5ffc248c66 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -59,26 +59,6 @@ public function isPublic(): bool return $this->reflection->isPublic(); } - public function hasPhpDocType(): bool - { - return $this->reflection->hasPhpDocType(); - } - - public function getPhpDocType(): Type - { - return $this->reflection->getPhpDocType(); - } - - public function hasNativeType(): bool - { - return $this->reflection->hasNativeType(); - } - - public function getNativeType(): Type - { - return $this->reflection->getNativeType(); - } - public function getReadableType(): Type { $type = $this->readableType; diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 06069f8410f..151945e921f 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -79,10 +79,8 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); - $phpDocType = $this->transformStaticType($property->getPhpDocType()); - $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 18beaf3f8e4..4b843829ad0 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -74,10 +74,8 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); - $phpDocType = $this->transformStaticType($property->getPhpDocType()); - $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 9976bab57db..b2d1551e5c3 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -80,26 +80,6 @@ public function getDocComment(): ?string return null; } - public function hasPhpDocType(): bool - { - return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); - } - - public function getPhpDocType(): Type - { - return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); - } - - public function hasNativeType(): bool - { - return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); - } - - public function getNativeType(): Type - { - return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); - } - public function getReadableType(): Type { return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 24e2e911561..bc3c4f4411e 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -80,26 +80,6 @@ public function getDocComment(): ?string return null; } - public function hasPhpDocType(): bool - { - return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); - } - - public function getPhpDocType(): Type - { - return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); - } - - public function hasNativeType(): bool - { - return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); - } - - public function getNativeType(): Type - { - return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); - } - public function getReadableType(): Type { return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index fe64beb0f0b..52e1571309c 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -4,7 +4,6 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class WrappedExtendedPropertyReflection implements ExtendedPropertyReflection @@ -39,26 +38,6 @@ public function getDocComment(): ?string return $this->property->getDocComment(); } - public function hasPhpDocType(): bool - { - return false; - } - - public function getPhpDocType(): Type - { - return new MixedType(); - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->property->getReadableType(); diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 19e77db7e05..36a286a4a02 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -59,26 +59,6 @@ public function getDocComment(): ?string return $this->originalPropertyReflection->getDocComment(); } - public function hasPhpDocType(): bool - { - return $this->originalPropertyReflection->hasPhpDocType(); - } - - public function getPhpDocType(): Type - { - return $this->originalPropertyReflection->getPhpDocType(); - } - - public function hasNativeType(): bool - { - return $this->originalPropertyReflection->hasNativeType(); - } - - public function getNativeType(): Type - { - return $this->originalPropertyReflection->getNativeType(); - } - public function getReadableType(): Type { return $this->readableType; @@ -124,6 +104,16 @@ public function isNative(): bool return $this->getNativeReflection() !== null; } + public function getNativeType(): ?Type + { + $reflection = $this->getNativeReflection(); + if ($reflection === null) { + return null; + } + + return $reflection->getNativeType(); + } + public function getNativeReflection(): ?PhpPropertyReflection { $reflection = $this->originalPropertyReflection; diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index d5fb99f5463..37a98fa9ba9 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -44,26 +44,6 @@ public function getDocComment(): ?string return null; } - public function hasPhpDocType(): bool - { - return true; - } - - public function getPhpDocType(): Type - { - return $this->type; - } - - public function hasNativeType(): bool - { - return false; - } - - public function getNativeType(): Type - { - return new MixedType(); - } - public function getReadableType(): Type { return $this->type; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php deleted file mode 100644 index 5410dc21508..00000000000 --- a/tests/PHPStan/Analyser/nsrt/bug-12393.php +++ /dev/null @@ -1,99 +0,0 @@ -name = $plugin["name"]; - assertType('string', $this->name); - } - - /** - * @param mixed[] $plugin - */ - public function doFoo(array $plugin){ - $this->untypedName = $plugin["name"]; - assertType('mixed', $this->untypedName); - } - - public function doBar(int $i){ - $this->float = $i; - assertType('float', $this->float); - } - - public function doBaz(int $i){ - $this->untypedFloat = $i; - assertType('int', $this->untypedFloat); - } - - public function doLorem(): void - { - $this->a = ['a' => 1]; - assertType('array{a: 1}', $this->a); - } -} - -class HelloWorldStatic -{ - private static string $name; - - /** @var string */ - private static $untypedName; - - private static float $float; - - /** @var float */ - private static $untypedFloat; - - private static array $a; - - /** - * @param mixed[] $plugin - */ - public function __construct(array $plugin){ - self::$name = $plugin["name"]; - assertType('string', self::$name); - } - - /** - * @param mixed[] $plugin - */ - public function doFoo(array $plugin){ - self::$untypedName = $plugin["name"]; - assertType('mixed', self::$untypedName); - } - - public function doBar(int $i){ - self::$float = $i; - assertType('float', self::$float); - } - - public function doBaz(int $i){ - self::$untypedFloat = $i; - assertType('int', self::$untypedFloat); - } - - public function doLorem(): void - { - self::$a = ['a' => 1]; - assertType('array{a: 1}', self::$a); - } -} From e6cd6515a468ad8e8155ee37af98135d89780fe2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 19 Jan 2025 16:05:01 +0100 Subject: [PATCH 1021/3097] Revert "Add non regression tests" This reverts commit abbae6a4ccb528e45338ecebd76cb3f371356393. --- ...mpossibleCheckTypeFunctionCallRuleTest.php | 18 ------------ .../Rules/Comparison/data/bug-8464.php | 18 ------------ .../Rules/Comparison/data/bug-8954.php | 28 ------------------- 3 files changed, 64 deletions(-) delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8464.php delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8954.php diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 896173a6ee0..b15cf528b0a 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1109,22 +1109,4 @@ public function testBug3979(): void $this->analyse([__DIR__ . '/data/bug-3979.php'], []); } - public function testBug8464(): void - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-8464.php'], []); - } - - public function testBug8954(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-8954.php'], []); - } - } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8464.php b/tests/PHPStan/Rules/Comparison/data/bug-8464.php deleted file mode 100644 index 23cd280d7aa..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-8464.php +++ /dev/null @@ -1,18 +0,0 @@ -= 8.0 - -namespace Bug8464; - -final class ObjectUtil -{ - /** - * @param class-string $type - */ - public static function instanceOf(mixed $object, string $type): bool - { - return \is_object($object) - && ( - $object::class === $type || - is_subclass_of($object, $type) - ); - } -} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8954.php b/tests/PHPStan/Rules/Comparison/data/bug-8954.php deleted file mode 100644 index b89b47ba6d5..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-8954.php +++ /dev/null @@ -1,28 +0,0 @@ - $class - * @param class-string $expected - * - * @return ?class-string - */ -function ensureSubclassOf(?string $class, string $expected): ?string { - if ($class === null) { - return $class; - } - - if (!class_exists($class)) { - throw new \Exception("Class “{$class}” does not exist."); - } - - if (!is_subclass_of($class, $expected)) { - throw new \Exception("Class “{$class}” is not a subclass of “{$expected}”."); - } - - return $class; -} From dce063a18a9dddcff98f2345942216205661b230 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 19 Jan 2025 16:05:07 +0100 Subject: [PATCH 1022/3097] Revert "Fix ImpossibleCheckTypeFunctionCallRule for `is_subclass_of` and `is_a`" This reverts commit 0711bec076d632f1011e3535e7dec6b59c04d708. --- .../ImpossibleCheckTypeFunctionCallRule.php | 4 + .../IsAFunctionTypeSpecifyingExtension.php | 16 +-- ...classOfFunctionTypeSpecifyingExtension.php | 16 +-- tests/PHPStan/Analyser/TypeSpecifierTest.php | 4 +- ...mpossibleCheckTypeFunctionCallRuleTest.php | 7 - .../Rules/Comparison/data/bug-3979.php | 130 ------------------ 6 files changed, 9 insertions(+), 168 deletions(-) delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3979.php diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 29b0801eced..9033aa38658 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -8,6 +8,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; +use function strtolower; /** * @implements Rule @@ -37,6 +38,9 @@ public function processNode(Node $node, Scope $scope): array } $functionName = (string) $node->name; + if (strtolower($functionName) === 'is_a') { + return []; + } $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index 4d062efd56a..c4000b9aff0 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -9,12 +9,9 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -50,20 +47,9 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(false); $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); - $superType = $allowString - ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) - : new ObjectWithoutClassType(); - - $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true); - - // prevent false-positives in IsAFunctionTypeSpecifyingHelper - if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { - return new SpecifiedTypes([], []); - } - return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $resultType, + $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true), $context, false, $scope, diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 0ca6cf4e764..2d52ee99e1b 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -9,12 +9,9 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\Generic\GenericClassStringType; -use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -51,20 +48,9 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes([], []); } - $superType = $allowString - ? TypeCombinator::union(new ObjectWithoutClassType(), new ClassStringType()) - : new ObjectWithoutClassType(); - - $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false); - - // prevent false-positives in IsAFunctionTypeSpecifyingHelper - if ($resultType->equals($superType) && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { - return new SpecifiedTypes([], []); - } - return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $resultType, + $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false), $context, false, $scope, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 4ad6a7cec40..999c7169a39 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1133,7 +1133,9 @@ public function dataCondition(): iterable new Arg(new Variable('stringOrNull')), new Arg(new Expr\ConstFetch(new Name('false'))), ]), - [], + [ + '$object' => 'object', + ], [], ], [ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index b15cf528b0a..16529f3a744 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1102,11 +1102,4 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } - public function testBug3979(): void - { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-3979.php'], []); - } - } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3979.php b/tests/PHPStan/Rules/Comparison/data/bug-3979.php deleted file mode 100644 index f0f21220d11..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-3979.php +++ /dev/null @@ -1,130 +0,0 @@ - Date: Wed, 8 Jan 2025 15:20:03 +0100 Subject: [PATCH 1023/3097] Overwrite property expression type only if it's subtype of the native type --- src/Analyser/MutatingScope.php | 15 +-- src/Analyser/NodeScopeResolver.php | 26 ++++- .../AnnotationPropertyReflection.php | 21 ++++ .../Dummy/ChangedTypePropertyReflection.php | 22 ++++- .../Dummy/DummyPropertyReflection.php | 20 ++++ src/Reflection/ExtendedPropertyReflection.php | 9 ++ src/Reflection/Php/EnumPropertyReflection.php | 21 ++++ .../Php/SimpleXMLElementProperty.php | 21 ++++ .../Php/UniversalObjectCrateProperty.php | 21 ++++ src/Reflection/ResolvedPropertyReflection.php | 20 ++++ ...kUnresolvedPropertyPrototypeReflection.php | 4 +- ...eUnresolvedPropertyPrototypeReflection.php | 4 +- .../IntersectionTypePropertyReflection.php | 20 ++++ .../Type/UnionTypePropertyReflection.php | 20 ++++ .../WrappedExtendedPropertyReflection.php | 21 ++++ .../Properties/FoundPropertyReflection.php | 30 ++++-- src/Type/ObjectShapePropertyReflection.php | 20 ++++ tests/PHPStan/Analyser/nsrt/bug-12393.php | 99 +++++++++++++++++++ 18 files changed, 393 insertions(+), 21 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 297697ee78d..3bd6db75568 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2119,11 +2119,13 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } @@ -2167,11 +2169,12 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($propertyReflection === null) { return new ErrorType(); } - $nativeType = $propertyReflection->getNativeType(); - if ($nativeType === null) { - return new ErrorType(); + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); } + $nativeType = $propertyReflection->getNativeType(); + if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $nativeType); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 58b6f34df5c..e91ca6748ef 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5556,7 +5556,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } $declaringClass = $propertyReflection->getDeclaringClass(); if ($declaringClass->hasNativeProperty($propertyName)) { @@ -5621,7 +5632,18 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + if ($propertyReflection->hasNativeType()) { + $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { + $assignedExprNativeType = $scope->getNativeType($assignedExpr); + if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { + $assignedExprNativeType = $propertyNativeType; + } + $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); + } + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } } } else { // fallback diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 9188ef77212..cc1994bc9aa 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class AnnotationPropertyReflection implements ExtendedPropertyReflection @@ -42,6 +43,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->readableType; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 07dc20ce683..f235b2e6e3f 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -12,7 +12,7 @@ final class ChangedTypePropertyReflection implements WrapperPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType) + public function __construct(private ClassReflection $declaringClass, private ExtendedPropertyReflection $reflection, private Type $readableType, private Type $writableType, private Type $phpDocType, private Type $nativeType) { } @@ -41,6 +41,26 @@ public function getDocComment(): ?string return $this->reflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->phpDocType; + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 40a48911e81..c2c6d4c7682 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -37,6 +37,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return new MixedType(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index c4a55163bb5..63b6246dd33 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; /** * The purpose of this interface is to be able to @@ -25,6 +26,14 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; + public function hasPhpDocType(): bool; + + public function getPhpDocType(): Type; + + public function hasNativeType(): bool; + + public function getNativeType(): Type; + public function isAbstract(): TrinaryLogic; public function isFinal(): TrinaryLogic; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index 8a9a4eed281..a74bb419ff4 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class EnumPropertyReflection implements ExtendedPropertyReflection @@ -41,6 +42,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index a8cfff2cb3d..d354ae5fe27 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -10,6 +10,7 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -44,6 +45,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 3382a493444..0a2f8faf359 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class UniversalObjectCrateProperty implements ExtendedPropertyReflection @@ -40,6 +41,26 @@ public function isPublic(): bool return true; } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->readableType; diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index d5ffc248c66..e964d99b5ed 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -59,6 +59,26 @@ public function isPublic(): bool return $this->reflection->isPublic(); } + public function hasPhpDocType(): bool + { + return $this->reflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->reflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->reflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->reflection->getNativeType(); + } + public function getReadableType(): Type { $type = $this->readableType; diff --git a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php index 151945e921f..06069f8410f 100644 --- a/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php @@ -79,8 +79,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php index 4b843829ad0..18beaf3f8e4 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedPropertyPrototypeReflection.php @@ -74,8 +74,10 @@ private function transformPropertyWithStaticType(ClassReflection $declaringClass { $readableType = $this->transformStaticType($property->getReadableType()); $writableType = $this->transformStaticType($property->getWritableType()); + $phpDocType = $this->transformStaticType($property->getPhpDocType()); + $nativeType = $this->transformStaticType($property->getNativeType()); - return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType); + return new ChangedTypePropertyReflection($declaringClass, $property, $readableType, $writableType, $phpDocType, $nativeType); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index b2d1551e5c3..9976bab57db 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::intersect(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index bc3c4f4411e..24e2e911561 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -80,6 +80,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasPhpDocType()); + } + + public function getPhpDocType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getPhpDocType(), $this->properties)); + } + + public function hasNativeType(): bool + { + return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->hasNativeType()); + } + + public function getNativeType(): Type + { + return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getNativeType(), $this->properties)); + } + public function getReadableType(): Type { return TypeCombinator::union(...array_map(static fn (ExtendedPropertyReflection $property): Type => $property->getReadableType(), $this->properties)); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 52e1571309c..fe64beb0f0b 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -4,6 +4,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; final class WrappedExtendedPropertyReflection implements ExtendedPropertyReflection @@ -38,6 +39,26 @@ public function getDocComment(): ?string return $this->property->getDocComment(); } + public function hasPhpDocType(): bool + { + return false; + } + + public function getPhpDocType(): Type + { + return new MixedType(); + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->property->getReadableType(); diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 36a286a4a02..19e77db7e05 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -59,6 +59,26 @@ public function getDocComment(): ?string return $this->originalPropertyReflection->getDocComment(); } + public function hasPhpDocType(): bool + { + return $this->originalPropertyReflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->originalPropertyReflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->originalPropertyReflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->originalPropertyReflection->getNativeType(); + } + public function getReadableType(): Type { return $this->readableType; @@ -104,16 +124,6 @@ public function isNative(): bool return $this->getNativeReflection() !== null; } - public function getNativeType(): ?Type - { - $reflection = $this->getNativeReflection(); - if ($reflection === null) { - return null; - } - - return $reflection->getNativeType(); - } - public function getNativeReflection(): ?PhpPropertyReflection { $reflection = $this->originalPropertyReflection; diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 37a98fa9ba9..d5fb99f5463 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -44,6 +44,26 @@ public function getDocComment(): ?string return null; } + public function hasPhpDocType(): bool + { + return true; + } + + public function getPhpDocType(): Type + { + return $this->type; + } + + public function hasNativeType(): bool + { + return false; + } + + public function getNativeType(): Type + { + return new MixedType(); + } + public function getReadableType(): Type { return $this->type; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php new file mode 100644 index 00000000000..5410dc21508 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393.php @@ -0,0 +1,99 @@ +name = $plugin["name"]; + assertType('string', $this->name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + $this->untypedName = $plugin["name"]; + assertType('mixed', $this->untypedName); + } + + public function doBar(int $i){ + $this->float = $i; + assertType('float', $this->float); + } + + public function doBaz(int $i){ + $this->untypedFloat = $i; + assertType('int', $this->untypedFloat); + } + + public function doLorem(): void + { + $this->a = ['a' => 1]; + assertType('array{a: 1}', $this->a); + } +} + +class HelloWorldStatic +{ + private static string $name; + + /** @var string */ + private static $untypedName; + + private static float $float; + + /** @var float */ + private static $untypedFloat; + + private static array $a; + + /** + * @param mixed[] $plugin + */ + public function __construct(array $plugin){ + self::$name = $plugin["name"]; + assertType('string', self::$name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + self::$untypedName = $plugin["name"]; + assertType('mixed', self::$untypedName); + } + + public function doBar(int $i){ + self::$float = $i; + assertType('float', self::$float); + } + + public function doBaz(int $i){ + self::$untypedFloat = $i; + assertType('int', self::$untypedFloat); + } + + public function doLorem(): void + { + self::$a = ['a' => 1]; + assertType('array{a: 1}', self::$a); + } +} From b6b8ebd0fc5e6787214f31ad94ad2ca06030f26c Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:03:26 +0000 Subject: [PATCH 1024/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 6edf082303d..ed4e90719f8 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", + "jetbrains/phpstorm-stubs": "dev-master#7bcd596c3b30e852a8115a12ae59cb625b3faae0", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 3ffd1f0332f..e6942cdfa89 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bf40a89cec9c4598324b1e8394b7367c", + "content-hash": "967eea3adab8dc888fe0bf6e977f655a", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "62a683f61d9ea11ef8caf8b2ad54e59e92b2c670" + "reference": "7bcd596c3b30e852a8115a12ae59cb625b3faae0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", - "reference": "62a683f61d9ea11ef8caf8b2ad54e59e92b2c670", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7bcd596c3b30e852a8115a12ae59cb625b3faae0", + "reference": "7bcd596c3b30e852a8115a12ae59cb625b3faae0", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-01-04T20:30:22+00:00" + "time": "2025-01-13T11:40:57+00:00" }, { "name": "nette/bootstrap", From bed30a79f4ed17651c48c031b89b60d4ce7453b2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Jan 2025 14:10:16 +0100 Subject: [PATCH 1025/3097] Fix issues about assigning typed properties --- src/Analyser/NodeScopeResolver.php | 22 +++------ src/Type/Accessory/AccessoryArrayListType.php | 5 ++ .../Accessory/AccessoryLiteralStringType.php | 5 ++ .../AccessoryLowercaseStringType.php | 5 ++ .../Accessory/AccessoryNonEmptyStringType.php | 5 ++ .../Accessory/AccessoryNonFalsyStringType.php | 5 ++ .../Accessory/AccessoryNumericStringType.php | 5 ++ .../AccessoryUppercaseStringType.php | 5 ++ src/Type/Accessory/HasOffsetType.php | 5 ++ src/Type/Accessory/HasOffsetValueType.php | 5 ++ src/Type/Accessory/NonEmptyArrayType.php | 5 ++ src/Type/Accessory/OversizedArrayType.php | 5 ++ src/Type/BooleanType.php | 5 ++ src/Type/CallableType.php | 6 +++ src/Type/ClosureType.php | 5 ++ src/Type/Constant/ConstantBooleanType.php | 5 ++ src/Type/FloatType.php | 5 ++ src/Type/Generic/TemplateTypeTrait.php | 5 ++ src/Type/IntegerType.php | 5 ++ src/Type/IntersectionType.php | 5 ++ src/Type/IterableType.php | 5 ++ src/Type/MixedType.php | 5 ++ src/Type/NeverType.php | 5 ++ src/Type/NonexistentParentClassType.php | 5 ++ src/Type/NullType.php | 5 ++ src/Type/ObjectType.php | 5 ++ src/Type/ResourceType.php | 5 ++ src/Type/StaticType.php | 5 ++ src/Type/StrictMixedType.php | 5 ++ src/Type/StringType.php | 5 ++ src/Type/Traits/ArrayTypeTrait.php | 5 ++ src/Type/Traits/LateResolvableTypeTrait.php | 5 ++ src/Type/Traits/ObjectTypeTrait.php | 5 ++ src/Type/Type.php | 11 +++++ src/Type/UnionType.php | 5 ++ src/Type/VoidType.php | 5 ++ tests/PHPStan/Analyser/nsrt/bug-12393.php | 48 ++++++++++++++++++- 37 files changed, 235 insertions(+), 17 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e91ca6748ef..b6c5798b8d8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5556,15 +5556,10 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType()) { + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { - $assignedExprNativeType = $scope->getNativeType($assignedExpr); - if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { - $assignedExprNativeType = $propertyNativeType; - } - $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); - } + + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } @@ -5632,15 +5627,10 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType()) { + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyNativeType->isSuperTypeOf($assignedExprType)->yes()) { - $assignedExprNativeType = $scope->getNativeType($assignedExpr); - if (!$propertyNativeType->isSuperTypeOf($assignedExprNativeType)->yes()) { - $assignedExprNativeType = $propertyNativeType; - } - $scope = $scope->assignExpression($var, $assignedExprType, $assignedExprNativeType); - } + + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 750466a2713..5f60fe8eb7f 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -469,6 +469,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 967c58d6901..9af225a3c10 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -213,6 +213,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 97edeb39ba8..5ea351a924f 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -210,6 +210,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index d630cad147b..961e2cbd953 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -211,6 +211,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index faac90150ec..3befd2d478d 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -212,6 +212,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9429c7ea73f..447bf76ecca 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -213,6 +213,11 @@ public function toArrayKey(): Type return new IntegerType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index a034eea3214..18ee7399bf2 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -210,6 +210,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 492bd1ba27c..455f0de86ea 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -388,6 +388,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function getEnumCases(): array { return []; diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 296c5b73080..ec6e822a310 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -438,6 +438,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function getEnumCases(): array { return []; diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index a46eefc807f..d4726fd5c22 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -445,6 +445,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 70ba480a7e9..c43c86a9038 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -429,6 +429,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 0e26b52a674..679c0b9824c 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -111,6 +111,11 @@ public function toArrayKey(): Type return new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(1)]); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index fe6e412ad28..b47dda2339b 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use Closure; use PHPStan\Analyser\OutOfClassScope; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\Tag\TemplateTag; @@ -321,6 +322,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return TypeCombinator::union($this, new StringType(), new ArrayType(new MixedType(true), new MixedType(true)), new ObjectType(Closure::class)); + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index a76aa905965..55aebcadc89 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -462,6 +462,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return TypeCombinator::union($this, new CallableType()); + } + public function getTemplateTypeMap(): TemplateTypeMap { return $this->templateTypeMap; diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index a01321c1383..ea1c4b09efb 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -107,6 +107,11 @@ public function toArrayKey(): Type return new ConstantIntegerType((int) $this->value); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isTrue(): TrinaryLogic { return TrinaryLogic::createFromBoolean($this->value === true); diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 880006af90d..253af75e4e5 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -143,6 +143,11 @@ public function toArrayKey(): Type return new IntegerType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index c26509b0179..52c13a96805 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -250,6 +250,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ( diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 4eb3bd50fd7..e974888bc71 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -98,6 +98,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return TypeCombinator::union($this, $this->toFloat()); + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index b79f6ed7eaa..ab9f86d0344 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1062,6 +1062,11 @@ public function toArrayKey(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->toArrayKey()); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->toCoercedArgumentType($strictTypes)); + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { $types = TemplateTypeMap::createEmpty(); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 502fb6330ad..e2864494e90 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -234,6 +234,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return TypeCombinator::union($this, new ArrayType(new MixedType(true), new MixedType(true)), new ObjectType(Traversable::class)); + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index c63c78f2147..487a474827a 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -580,6 +580,11 @@ public function toArrayKey(): Type return new BenevolentUnionType([new IntegerType(), new StringType()]); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isIterable(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index eab6009abbf..518ffa8f4a9 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -383,6 +383,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index f52c5ea8deb..0b91e093e66 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -157,6 +157,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return new ErrorType(); + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 7d66fa7e790..fd30ee8bd88 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -166,6 +166,11 @@ public function toArrayKey(): Type return new ConstantStringType(''); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index ac1f1399384..0ad78520f25 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -671,6 +671,11 @@ public function toArrayKey(): Type return $this->toString(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function toBoolean(): BooleanType { if ($this->isInstanceOf('SimpleXMLElement')->yes()) { diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index f62023f2c61..4ed9c79c1c3 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -91,6 +91,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 8885972a667..702f3f751ee 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -646,6 +646,11 @@ public function toArrayKey(): Type return $this->getStaticObjectType()->toArrayKey(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this->getStaticObjectType()->toCoercedArgumentType($strictTypes); + } + public function toBoolean(): BooleanType { return $this->getStaticObjectType()->toBoolean(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 355d186986e..0ff25dc124c 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -395,6 +395,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { return TemplateTypeMap::createEmpty(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index eeedd4bc68a..c0114d84627 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -181,6 +181,11 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isNull(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Traits/ArrayTypeTrait.php b/src/Type/Traits/ArrayTypeTrait.php index ddc501d022b..813b0136d91 100644 --- a/src/Type/Traits/ArrayTypeTrait.php +++ b/src/Type/Traits/ArrayTypeTrait.php @@ -29,6 +29,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 3c5a4e5b5fe..5eb703077ff 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -368,6 +368,11 @@ public function toArrayKey(): Type return $this->resolve()->toArrayKey(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this->resolve()->toCoercedArgumentType($strictTypes); + } + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic { return $this->resolve()->isSmallerThan($otherType, $phpVersion); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 70f7d2c0079..45fc20121f5 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -273,4 +273,9 @@ public function toArrayKey(): Type return new StringType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this; + } + } diff --git a/src/Type/Type.php b/src/Type/Type.php index 0badf2930cf..15886a053cc 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -204,6 +204,17 @@ public function toArray(): Type; public function toArrayKey(): Type; + /** + * Tells how a type might change when passed to an argument + * or assigned to a typed property. + * + * Example: int is accepted by int|float with strict_types = 1 + * Stringable is accepted by string|Stringable even without strict_types. + * + * Note: Logic with $strictTypes=false is mostly not implemented in Type subclasses. + */ + public function toCoercedArgumentType(bool $strictTypes): self; + public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 9156bc09b79..08d678152aa 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -972,6 +972,11 @@ public function toArrayKey(): Type return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey()); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->toCoercedArgumentType($strictTypes)); + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { $types = TemplateTypeMap::createEmpty(); diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index b6bac5908c4..83c5a057263 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -119,6 +119,11 @@ public function toArrayKey(): Type return new ErrorType(); } + public function toCoercedArgumentType(bool $strictTypes): Type + { + return new NullType(); + } + public function isOffsetAccessLegal(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php index 5410dc21508..9445d8632b4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12393.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12393.php @@ -1,7 +1,8 @@ -a = ['a' => 1]; assertType('array{a: 1}', $this->a); } + + public function doFloatTricky(){ + $this->float = 1; + assertType('1.0', $this->float); + } } class HelloWorldStatic @@ -97,3 +103,43 @@ public function doLorem(): void assertType('array{a: 1}', self::$a); } } + +class EntryPointLookup +{ + + /** @var array|null */ + private ?array $entriesData = null; + + /** + * @return array + */ + public function doFoo(): void + { + if ($this->entriesData !== null) { + return; + } + + assertType('null', $this->entriesData); + assertNativeType('null', $this->entriesData); + + $data = $this->getMixed(); + if ($data !== null) { + $this->entriesData = $data; + assertType('array', $this->entriesData); + assertNativeType('array', $this->entriesData); + return; + } + + assertType('null', $this->entriesData); + assertNativeType('null', $this->entriesData); + } + + /** + * @return mixed + */ + public function getMixed() + { + + } + +} From d06f792a9f3630132fec70d7c7322ad7c7898037 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Jan 2025 15:07:45 +0100 Subject: [PATCH 1026/3097] Inferring `new` from parent constructor - reject types that would fail bound check of the child class --- src/Analyser/MutatingScope.php | 13 +++-- tests/PHPStan/Analyser/nsrt/bug-12386.php | 68 +++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12386.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 274d8392e43..b95f28ace15 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5575,7 +5575,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type continue; } - $ancestorMapping[$typeName] = $templateType->getName(); + $ancestorMapping[$typeName] = $templateType; } $resolvedTypeMap = []; @@ -5584,12 +5584,17 @@ private function exactInstantiation(New_ $node, string $className): ?Type continue; } - if (!array_key_exists($ancestorMapping[$typeName], $resolvedTypeMap)) { - $resolvedTypeMap[$ancestorMapping[$typeName]] = $type; + $ancestorType = $ancestorMapping[$typeName]; + if (!$ancestorType->getBound()->isSuperTypeOf($type)->yes()) { continue; } - $resolvedTypeMap[$ancestorMapping[$typeName]] = TypeCombinator::union($resolvedTypeMap[$ancestorMapping[$typeName]], $type); + if (!array_key_exists($ancestorType->getName(), $resolvedTypeMap)) { + $resolvedTypeMap[$ancestorType->getName()] = $type; + continue; + } + + $resolvedTypeMap[$ancestorType->getName()] = TypeCombinator::union($resolvedTypeMap[$ancestorType->getName()], $type); } return new GenericObjectType( diff --git a/tests/PHPStan/Analyser/nsrt/bug-12386.php b/tests/PHPStan/Analyser/nsrt/bug-12386.php new file mode 100644 index 00000000000..59d02b403f3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12386.php @@ -0,0 +1,68 @@ +', $landMapper->fetchAllActivePrependDefault(12)); +} + +/** + * @template T of Clx_Model_Abstract + */ +abstract class Clx_Model_Mapper_Abstract +{ + public function __construct() + { + } +} + +/** + * @template T of Application_Model_Land + * + * @extends Clx_Model_Mapper_Abstract + */ +class ClxProductNet_Model_Mapper_Land extends Clx_Model_Mapper_Abstract +{ + /** + * @param int $defaultLandid + * + * @return Clx_Model_Iterator + */ + public function fetchAllActivePrependDefault($defaultLandid): Clx_Model_Iterator + {} +} + +/** + * @template T of Application_Model_Land + * + * @extends ClxProductNet_Model_Mapper_Land + */ +final class Application_Model_Mapper_Land extends ClxProductNet_Model_Mapper_Land +{ +} + +/** + * @template T of Clx_Model_Abstract + * + * @implements \Iterator + */ +abstract class Clx_Model_Iterator implements \Countable, \Iterator +{} + +abstract class Clx_Model_Abstract implements \Stringable +{} + +abstract class ClxProductNet_Model_Land extends Clx_Model_Abstract +{} + +final class Application_Model_Land extends ClxProductNet_Model_Land +{ + public function __toString() + { + return 'foo'; + } + +} From a54cdb0675e23385adba9d1b2b9e643994fa20d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Jan 2025 14:53:40 +0100 Subject: [PATCH 1027/3097] Fix `samesite` cookie argument precision --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f2b70427a49..df1509bc46e 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10383,7 +10383,7 @@ 'session_destroy' => ['bool'], 'session_encode' => ['string|false'], 'session_gc' => ['int|false'], -'session_get_cookie_params' => ['array{lifetime:0|positive-int,path:non-falsy-string,domain:string,secure:bool,httponly:bool,samesite:string}'], +'session_get_cookie_params' => ['array{lifetime:0|positive-int,path:non-falsy-string,domain:string,secure:bool,httponly:bool,samesite:\'None\'|\'Lax\'|\'Strict\'|\'none\'|\'lax\'|\'strict\'}'], 'session_id' => ['string|false', 'newid='=>'string'], 'session_is_registered' => ['bool', 'name'=>'string'], 'session_module_name' => ['string|false', 'newname='=>'string'], @@ -10400,7 +10400,7 @@ 'session_reset' => ['bool'], 'session_save_path' => ['string|false', 'newname='=>'string'], 'session_set_cookie_params' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'?string', 'secure='=>'bool', 'httponly='=>'bool'], -'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool,samesite?:string}'], +'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool,samesite?:\'None\'|\'Lax\'|\'Strict\'|\'none\'|\'lax\'|\'strict\'}'], 'session_set_save_handler' => ['bool', 'open'=>'callable(string,string):bool', 'close'=>'callable():bool', 'read'=>'callable(string):string', 'write'=>'callable(string,string):bool', 'destroy'=>'callable(string):bool', 'gc'=>'callable(string):bool', 'create_sid='=>'callable():string', 'validate_sid='=>'callable(string):bool', 'update_timestamp='=>'callable(string):bool'], 'session_set_save_handler\'1' => ['bool', 'sessionhandler'=>'SessionHandlerInterface', 'register_shutdown='=>'bool'], 'session_start' => ['bool', 'options='=>'array'], From d2e193ca3cade242cf3fdf605b7f000862248af6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 22 Jan 2025 13:35:31 +0100 Subject: [PATCH 1028/3097] VerbosityLevel - Keep traversing type when we can contain lowercase/upercase strings --- src/Type/VerbosityLevel.php | 10 +++++++- .../WrongVariableNameInVarTagRuleTest.php | 17 +++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-12457.php | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-12457.php diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 988b2a24053..ec733df635e 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -91,10 +91,18 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type { if ($type->isCallable()->yes()) { $moreVerbose = true; - return $type; + + // Keep checking if we need to be very verbose. + return $traverse($type); } if ($type->isConstantValue()->yes() && $type->isNull()->no()) { $moreVerbose = true; + + // For ConstantArrayType we need to keep checking if we need to be very verbose. + if (!$type->isArray()->no()) { + return $traverse($type); + } + return $type; } if ( diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 6083de943d1..e25e8df9246 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -511,4 +511,21 @@ public function testReportWrongType( $this->analyse([__DIR__ . '/data/wrong-var-native-type.php'], $expectedErrors); } + public function testBug12457(): void + { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/bug-12457.php'], [ + [ + 'PHPDoc tag @var with type array{numeric-string} is not subtype of type array{lowercase-string&numeric-string&uppercase-string}.', + 13, + ], + [ + 'PHPDoc tag @var with type callable(): string is not subtype of type callable(): numeric-string&lowercase-string&uppercase-string.', + 22, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-12457.php b/tests/PHPStan/Rules/PhpDoc/data/bug-12457.php new file mode 100644 index 00000000000..2d1564036f6 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-12457.php @@ -0,0 +1,24 @@ + Date: Wed, 22 Jan 2025 13:45:06 +0100 Subject: [PATCH 1029/3097] Fix after merge --- tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 1fc3359bc25..fad4af16629 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -509,7 +509,6 @@ public function testReportWrongType( public function testBug12457(): void { - $this->checkTypeAgainstNativeType = true; $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; $this->analyse([__DIR__ . '/data/bug-12457.php'], [ From f436584a662c5a93cbea3e9c180f20e5cd97c823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondr=CC=8Cej=20Es=CC=8Cler?= Date: Wed, 22 Jan 2025 16:55:36 +0100 Subject: [PATCH 1030/3097] fix ext-amqp signatures --- resources/functionMap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index df1509bc46e..c8f5cf6c802 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -103,10 +103,10 @@ 'AMQPConnection::setLogin' => ['void', 'login'=>'string'], 'AMQPConnection::setPassword' => ['void', 'password'=>'string'], 'AMQPConnection::setPort' => ['void', 'port'=>'int'], -'AMQPConnection::setReadTimeout' => ['void', 'timeout'=>'int'], -'AMQPConnection::setTimeout' => ['void', 'timeout'=>'int'], +'AMQPConnection::setReadTimeout' => ['void', 'timeout'=>'float'], +'AMQPConnection::setTimeout' => ['void', 'timeout'=>'float'], 'AMQPConnection::setVhost' => ['void', 'vhost'=>'string'], -'AMQPConnection::setWriteTimeout' => ['void', 'timeout'=>'int'], +'AMQPConnection::setWriteTimeout' => ['void', 'timeout'=>'float'], 'AMQPEnvelope::getAppId' => ['string|null'], 'AMQPEnvelope::getBody' => ['string'], 'AMQPEnvelope::getContentEncoding' => ['string|null'], From 1cc534759f1cbb5ae05f2e3057d0f487a572aa12 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 14:18:59 +0100 Subject: [PATCH 1031/3097] Do not check abstract properties as uninitialized --- src/Node/ClassPropertiesNode.php | 3 +++ .../Rules/Properties/UninitializedPropertyRuleTest.php | 10 ++++++++++ tests/PHPStan/Rules/Properties/data/bug-12336.php | 7 +++++++ 3 files changed, 20 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12336.php diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index c22ec65c296..e1f299a60e4 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -118,6 +118,9 @@ public function getUninitializedProperties( if ($property->isStatic()) { continue; } + if ($property->isAbstract()) { + continue; + } if ($property->getNativeType() === null) { continue; } diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index 340c1331d99..d434683e6ab 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -7,6 +7,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function strpos; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -216,4 +217,13 @@ public function testRedeclareReadonlyProperties(): void ]); } + public function testBug12336(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/bug-12336.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12336.php b/tests/PHPStan/Rules/Properties/data/bug-12336.php new file mode 100644 index 00000000000..5e7380d9ced --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12336.php @@ -0,0 +1,7 @@ += 8.4 + +namespace Bug12336; + +abstract class ListItem { + abstract public int $item { get; } +} From b3ca610fb4ae14d46da6f14d77499b35195ae40d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 14:38:41 +0100 Subject: [PATCH 1032/3097] Look for overriden property prototype in implemented interfaces --- .../Properties/OverridingPropertyRule.php | 19 ++++++++++++++++--- .../Properties/OverridingPropertyRuleTest.php | 15 +++++++++++++++ .../property-prototype-from-interface.php | 17 +++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 61325ed5081..45fae00fc9e 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -213,19 +213,32 @@ private function findPrototype(ClassReflection $classReflection, string $propert { $parentClass = $classReflection->getParentClass(); if ($parentClass === null) { - return null; + return $this->findPrototypeInInterfaces($classReflection, $propertyName); } if (!$parentClass->hasNativeProperty($propertyName)) { - return null; + return $this->findPrototypeInInterfaces($classReflection, $propertyName); } $property = $parentClass->getNativeProperty($propertyName); if ($property->isPrivate()) { - return null; + return $this->findPrototypeInInterfaces($classReflection, $propertyName); } return $property; } + private function findPrototypeInInterfaces(ClassReflection $classReflection, string $propertyName): ?PhpPropertyReflection + { + foreach ($classReflection->getInterfaces() as $interface) { + if (!$interface->hasNativeProperty($propertyName)) { + continue; + } + + return $interface->getNativeProperty($propertyName); + } + + return null; + } + } diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index 954eaae5115..2a363d7ee08 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -199,4 +199,19 @@ public function testFinal(): void ]); } + public function testPropertyPrototypeFromInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->reportMaybes = true; + $this->analyse([__DIR__ . '/data/property-prototype-from-interface.php'], [ + [ + 'Type string of property Bug12466\Bar::$a is not the same as type int of overridden property Bug12466\Foo::$a.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php b/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php new file mode 100644 index 00000000000..014228effce --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php @@ -0,0 +1,17 @@ += 8.4 + +namespace Bug12466; + +interface Foo +{ + + public int $a { get; set;} + +} + +class Bar implements Foo +{ + + public string $a; + +} From 50f8e491212197ca317b169e5c67978215ef4a0f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 15:13:21 +0100 Subject: [PATCH 1033/3097] Properties might be covariant or contravariant, depending on the allowed operations on the parent --- .../Properties/OverridingPropertyRule.php | 89 +++++++++++++++-- .../Properties/OverridingPropertyRuleTest.php | 41 +++++++- .../Rules/Properties/data/bug-12466.php | 95 +++++++++++++++++++ 3 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12466.php diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 45fae00fc9e..66c0c62e0cf 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\ClassPropertyNode; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Rules\Rule; @@ -21,6 +22,7 @@ final class OverridingPropertyRule implements Rule { public function __construct( + private PhpVersion $phpVersion, private bool $checkPhpDocMethodSignatures, private bool $reportMaybes, ) @@ -129,15 +131,45 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.missingNativeType')->nonIgnorable()->build(); } else { if (!$prototype->getNativeType()->equals($nativeType)) { - $typeErrors[] = RuleErrorBuilder::message(sprintf( - 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', - $nativeType->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(), - $node->getName(), - $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(), - $node->getName(), - ))->identifier('property.nativeType')->nonIgnorable()->build(); + if ( + $this->phpVersion->supportsPropertyHooks() + && ($prototype->isVirtual()->yes() || $prototype->isAbstract()->yes()) + && (!$prototype->isReadable() || !$prototype->isWritable()) + ) { + if (!$prototype->isReadable()) { + if (!$nativeType->isSuperTypeOf($prototype->getNativeType())->yes()) { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of property %s::$%s is not contravariant with type %s of overridden property %s::$%s.', + $nativeType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.nativeType')->nonIgnorable()->build(); + } + } elseif (!$prototype->getNativeType()->isSuperTypeOf($nativeType)->yes()) { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of property %s::$%s is not covariant with type %s of overridden property %s::$%s.', + $nativeType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.nativeType')->nonIgnorable()->build(); + } + } else { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', + $nativeType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.nativeType')->nonIgnorable()->build(); + } } } } elseif ($nativeType !== null) { @@ -167,6 +199,45 @@ public function processNode(Node $node, Scope $scope): array } $verbosity = VerbosityLevel::getRecommendedLevelByType($prototype->getReadableType(), $propertyReflection->getReadableType()); + + if ( + $this->phpVersion->supportsPropertyHooks() + && ($prototype->isVirtual()->yes() || $prototype->isAbstract()->yes()) + && (!$prototype->isReadable() || !$prototype->isWritable()) + ) { + if (!$prototype->isReadable()) { + if (!$propertyReflection->getReadableType()->isSuperTypeOf($prototype->getReadableType())->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc type %s of property %s::$%s is not contravariant with PHPDoc type %s of overridden property %s::$%s.', + $propertyReflection->getReadableType()->describe($verbosity), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getReadableType()->describe($verbosity), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.phpDocType')->tip(sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files', + ))->build(); + } + } elseif (!$prototype->getReadableType()->isSuperTypeOf($propertyReflection->getReadableType())->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc type %s of property %s::$%s is not covariant with PHPDoc type %s of overridden property %s::$%s.', + $propertyReflection->getReadableType()->describe($verbosity), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getReadableType()->describe($verbosity), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.phpDocType')->tip(sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files', + ))->build(); + } + + return $errors; + } + $isSuperType = $prototype->getReadableType()->isSuperTypeOf($propertyReflection->getReadableType()); $canBeTurnedOffError = RuleErrorBuilder::message(sprintf( 'PHPDoc type %s of property %s::$%s is not the same as PHPDoc type %s of overridden property %s::$%s.', diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index 2a363d7ee08..44779f21620 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use function sprintf; @@ -17,7 +18,11 @@ class OverridingPropertyRuleTest extends RuleTestCase protected function getRule(): Rule { - return new OverridingPropertyRule(true, $this->reportMaybes); + return new OverridingPropertyRule( + self::getContainer()->getByType(PhpVersion::class), + true, + $this->reportMaybes, + ); } public function testRule(): void @@ -214,4 +219,38 @@ public function testPropertyPrototypeFromInterface(): void ]); } + public function testBug12466(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $tip = sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files', + ); + + $this->reportMaybes = true; + $this->analyse([__DIR__ . '/data/bug-12466.php'], [ + [ + 'Type int|string|null of property Bug12466OverridenProperty\Baz::$onlyGet is not covariant with type int|string of overridden property Bug12466OverridenProperty\Foo::$onlyGet.', + 34, + ], + [ + 'Type int of property Bug12466OverridenProperty\Baz::$onlySet is not contravariant with type int|string of overridden property Bug12466OverridenProperty\Foo::$onlySet.', + 40, + ], + [ + 'PHPDoc type array of property Bug12466OverridenProperty\BazWithPhpDocs::$onlyGet is not covariant with PHPDoc type array of overridden property Bug12466OverridenProperty\FooWithPhpDocs::$onlyGet.', + 82, + $tip, + ], + [ + 'PHPDoc type array of property Bug12466OverridenProperty\BazWithPhpDocs::$onlySet is not contravariant with PHPDoc type array of overridden property Bug12466OverridenProperty\FooWithPhpDocs::$onlySet.', + 89, + $tip, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12466.php b/tests/PHPStan/Rules/Properties/data/bug-12466.php new file mode 100644 index 00000000000..3722477119e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12466.php @@ -0,0 +1,95 @@ += 8.4 + +namespace Bug12466OverridenProperty; + +interface Foo +{ + + public int|string $onlyGet { get; } + + public int|string $onlySet { set; } + +} + +class Bar implements Foo +{ + + public int $onlyGet { + get { + return 1; + } + } + + public int|string|null $onlySet { + set { + $this->onlySet = $value; + } + } + +} + +class Baz implements Foo +{ + + public int|string|null $onlyGet { + get { + return null; + } + } + + public int $onlySet { + set { + $this->onlySet = $value; + } + } + +} + +interface FooWithPhpDocs +{ + + /** @var array */ + public array $onlyGet { get; } + + /** @var array */ + public array $onlySet { set; } + +} + +class BarWithPhpDocs implements FooWithPhpDocs +{ + + /** @var array */ + public array $onlyGet { + get { + return []; + } + } + + /** @var array */ + public array $onlySet { + set { + $this->onlySet = $value; + } + } + +} + +class BazWithPhpDocs implements FooWithPhpDocs +{ + + /** @var array */ + public array $onlyGet { + get { + return []; + } + } + + /** @var array */ + public array $onlySet { + set { + $this->onlySet = $value; + } + } + +} From 71d032761916517f18932bdee69c9468d8e83c84 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 15:55:22 +0100 Subject: [PATCH 1034/3097] Issue bot - skip phpstanPlayground.configParameter errors --- issue-bot/src/Playground/TabCreator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/issue-bot/src/Playground/TabCreator.php b/issue-bot/src/Playground/TabCreator.php index 544fcb14615..a6254957b25 100644 --- a/issue-bot/src/Playground/TabCreator.php +++ b/issue-bot/src/Playground/TabCreator.php @@ -2,7 +2,9 @@ namespace PHPStan\IssueBot\Playground; +use function array_filter; use function array_map; +use function array_values; use function count; use function floor; use function ksort; @@ -26,6 +28,7 @@ public function create(array $versionedErrors): array $last = null; foreach ($versionedErrors as $phpVersion => $errors) { + $errors = array_values(array_filter($errors, static fn (PlaygroundError $error) => $error->getIdentifier() !== 'phpstanPlayground.configParameter')); $errors = array_map(static function (PlaygroundError $error): PlaygroundError { if ($error->getIdentifier() === null) { return $error; From 9d1239b4884f7eac41c1669962386de1b3e4106c Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Fri, 24 Jan 2025 15:11:47 +0000 Subject: [PATCH 1035/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ed4e90719f8..5b977bf42c2 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.51.0.1", + "ondrejmirtes/better-reflection": "6.53.0.0", "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index e6942cdfa89..1efbc637198 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "967eea3adab8dc888fe0bf6e977f655a", + "content-hash": "5d1bc39ac2e087bb6f4578d58ee655f0", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.51.0.1", + "version": "6.53.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "739c4cc0a01ef79055688606be07cff93551815d" + "reference": "57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/739c4cc0a01ef79055688606be07cff93551815d", - "reference": "739c4cc0a01ef79055688606be07cff93551815d", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9", + "reference": "57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9", "shasum": "" }, "require": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.5.2", + "phpunit/phpunit": "^11.5.3", "rector/rector": "1.2.10" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.51.0.1" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.53.0.0" }, - "time": "2025-01-04T12:23:15+00:00" + "time": "2025-01-24T15:07:34+00:00" }, { "name": "phpstan/php-8-stubs", From 2f74584b83506d430404af1197e872b6318d1433 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 20:29:55 +0100 Subject: [PATCH 1036/3097] FileTypeMapper - fix getting PHPDoc of abstract trait method --- .../Php/PhpClassReflectionExtension.php | 4 +- .../Rules/Methods/MethodSignatureRuleTest.php | 11 +++++ ...overriden-abstract-trait-method-phpdoc.php | 46 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/overriden-abstract-trait-method-phpdoc.php diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 9b9a9132e1a..f08d329483b 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -676,8 +676,8 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( $docComment, - $fileDeclaringClass->getFileName(), - $fileDeclaringClass, + $actualDeclaringClass->getFileName(), + $actualDeclaringClass, $declaringTraitName, $methodReflection->getName(), $positionalParameterNames, diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 93c6a67b9d2..6c07d330edd 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -550,4 +550,15 @@ public function testBug3580(): void $this->analyse([__DIR__ . '/data/bug-3580.php'], []); } + public function testOverridenAbstractTraitMethodPhpDoc(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/overriden-abstract-trait-method-phpdoc.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/overriden-abstract-trait-method-phpdoc.php b/tests/PHPStan/Rules/Methods/data/overriden-abstract-trait-method-phpdoc.php new file mode 100644 index 00000000000..6a89da78629 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/overriden-abstract-trait-method-phpdoc.php @@ -0,0 +1,46 @@ + + */ +trait FooTrait +{ + /** + * Offset checker + * + * @phpstan-param Offset $offset + * @return bool + * @template Offset of key-of + */ + abstract public function offsetExists(mixed $offset): bool; +} + +/** + * @template DataArray of array + * @phpstan-type DataKey key-of + * @phpstan-type DataValue DataArray[DataKey] + */ +class FooClass +{ + + /** @phpstan-use FooTrait */ + use FooTrait; + + /** @phpstan-var DataArray|array{} */ + public array $data = []; + + + /** + * Data checker + * + * @phpstan-param Offset $offset + * @return bool + * @template Offset of key-of + */ + public function offsetExists(mixed $offset): bool + { + return array_key_exists($offset, $this->data); + } +} From ebcb5dabec2ff9b8bf54dffcfd63c95b4aeb8526 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 21:53:36 +0100 Subject: [PATCH 1037/3097] Refactoring of PhpDocBlock that will enable a bugfix down the road --- src/PhpDoc/PhpDocBlock.php | 85 ++++++++++++++------------------------ 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index fd75fa53067..f8030003fee 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -128,18 +128,24 @@ public static function resolvePhpDocBlockForProperty( array $newPositionalParameterNames, // unused ): self { - return self::resolvePhpDocBlockTree( - $docComment, - $classReflection, - $trait, + $docBlocksFromParents = self::resolveParentPhpDocBlocks( + self::getParentReflections($classReflection), $propertyName, - $file, 'hasNativeProperty', 'getNativeProperty', __FUNCTION__, - $explicit, - [], - [], + $explicit ?? $docComment !== null, + $newPositionalParameterNames, + ); + + return new self( + $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $file, + $classReflection, + $trait, + $explicit ?? true, + self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + $docBlocksFromParents, ); } @@ -158,18 +164,24 @@ public static function resolvePhpDocBlockForConstant( array $newPositionalParameterNames, // unused ): self { - return self::resolvePhpDocBlockTree( - $docComment, - $classReflection, - null, + $docBlocksFromParents = self::resolveParentPhpDocBlocks( + self::getParentReflections($classReflection), $constantName, - $file, 'hasConstant', 'getConstant', __FUNCTION__, - $explicit, - [], - [], + $explicit ?? $docComment !== null, + $newPositionalParameterNames, + ); + + return new self( + $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $file, + $classReflection, + $trait, + $explicit ?? true, + self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + $docBlocksFromParents, ); } @@ -188,45 +200,12 @@ public static function resolvePhpDocBlockForMethod( array $newPositionalParameterNames, ): self { - return self::resolvePhpDocBlockTree( - $docComment, - $classReflection, - $trait, + $docBlocksFromParents = self::resolveParentPhpDocBlocks( + self::getParentReflections($classReflection), $methodName, - $file, 'hasNativeMethod', 'getNativeMethod', __FUNCTION__, - $explicit, - $originalPositionalParameterNames, - $newPositionalParameterNames, - ); - } - - /** - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - */ - private static function resolvePhpDocBlockTree( - ?string $docComment, - ClassReflection $classReflection, - ?string $trait, - string $name, - ?string $file, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, - ?bool $explicit, - array $originalPositionalParameterNames, - array $newPositionalParameterNames, - ): self - { - $docBlocksFromParents = self::resolveParentPhpDocBlocks( - $classReflection, - $name, - $hasMethodName, - $getMethodName, - $resolveMethodName, $explicit ?? $docComment !== null, $newPositionalParameterNames, ); @@ -264,11 +243,12 @@ private static function remapParameterNames( } /** + * @param array $parentReflections * @param array $positionalParameterNames * @return array */ private static function resolveParentPhpDocBlocks( - ClassReflection $classReflection, + array $parentReflections, string $name, string $hasMethodName, string $getMethodName, @@ -278,7 +258,6 @@ private static function resolveParentPhpDocBlocks( ): array { $result = []; - $parentReflections = self::getParentReflections($classReflection); foreach ($parentReflections as $parentReflection) { $oneResult = self::resolvePhpDocBlockFromClass( From b57bcadc279b9845c33d5a52e00d330b0f48aac4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Jan 2025 21:43:57 +0100 Subject: [PATCH 1038/3097] Inherit PHPDoc implicitly from abstract trait method --- src/PhpDoc/PhpDocBlock.php | 21 +++++++++- .../inherit-abstract-trait-method-phpdoc.php | 41 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/inherit-abstract-trait-method-phpdoc.php diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index f8030003fee..dd3ebd84b36 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -16,6 +16,7 @@ use PHPStan\Type\TypeTraverser; use function array_key_exists; use function count; +use function is_bool; use function strtolower; use function substr; @@ -200,8 +201,26 @@ public static function resolvePhpDocBlockForMethod( array $newPositionalParameterNames, ): self { + $parentReflections = self::getParentReflections($classReflection); + foreach ($classReflection->getTraits(true) as $traitReflection) { + if (!$traitReflection->hasNativeMethod($methodName)) { + continue; + } + $traitMethod = $traitReflection->getNativeMethod($methodName); + $abstract = $traitMethod->isAbstract(); + if (is_bool($abstract)) { + if (!$abstract) { + continue; + } + } elseif (!$abstract->yes()) { + continue; + } + + $parentReflections[] = $traitReflection; + } + $docBlocksFromParents = self::resolveParentPhpDocBlocks( - self::getParentReflections($classReflection), + $parentReflections, $methodName, 'hasNativeMethod', 'getNativeMethod', diff --git a/tests/PHPStan/Analyser/nsrt/inherit-abstract-trait-method-phpdoc.php b/tests/PHPStan/Analyser/nsrt/inherit-abstract-trait-method-phpdoc.php new file mode 100644 index 00000000000..8bddb79ecac --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/inherit-abstract-trait-method-phpdoc.php @@ -0,0 +1,41 @@ +doFoo()); + assertType('mixed', $foo->doBar()); +}; From 0a4e892ca9b087c30e777b4d8efded06fef6b8b7 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:03:17 +0000 Subject: [PATCH 1039/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 5b977bf42c2..ba01942197a 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#7bcd596c3b30e852a8115a12ae59cb625b3faae0", + "jetbrains/phpstorm-stubs": "dev-master#6c6bf204cbdf39006f12a6c923b8217444acd67f", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 1efbc637198..8d648923dba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5d1bc39ac2e087bb6f4578d58ee655f0", + "content-hash": "26e17239736b4c0401181c73d03bd60d", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "7bcd596c3b30e852a8115a12ae59cb625b3faae0" + "reference": "6c6bf204cbdf39006f12a6c923b8217444acd67f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7bcd596c3b30e852a8115a12ae59cb625b3faae0", - "reference": "7bcd596c3b30e852a8115a12ae59cb625b3faae0", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/6c6bf204cbdf39006f12a6c923b8217444acd67f", + "reference": "6c6bf204cbdf39006f12a6c923b8217444acd67f", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-01-13T11:40:57+00:00" + "time": "2025-01-27T10:32:46+00:00" }, { "name": "nette/bootstrap", From 4fc14ac2942148633afbe37b536038740c417065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Tue, 28 Jan 2025 10:23:06 +0100 Subject: [PATCH 1040/3097] Update LICENSE --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 7c0f2b7b69a..e5f34e607a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 60f9b8b8c3a190fcc1a70b1511dedd17a853d942 Mon Sep 17 00:00:00 2001 From: Sebastian Blank Date: Thu, 30 Jan 2025 11:16:58 +0100 Subject: [PATCH 1041/3097] `Imagick::getConfigureOptions()` returns array instead of string --- resources/functionMap.php | 2 +- tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 9 +++++++++ tests/PHPStan/Rules/Methods/data/imagick.php | 13 +++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/imagick.php diff --git a/resources/functionMap.php b/resources/functionMap.php index c8f5cf6c802..0b93513557f 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -4729,7 +4729,7 @@ 'Imagick::getColorspace' => ['Imagick::COLORSPACE_*'], 'Imagick::getCompression' => ['Imagick::COMPRESSION_*'], 'Imagick::getCompressionQuality' => ['int'], -'Imagick::getConfigureOptions' => ['string'], +'Imagick::getConfigureOptions' => ['array'], 'Imagick::getCopyright' => ['string'], 'Imagick::getFeatures' => ['string'], 'Imagick::getFilename' => ['string'], diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 0da6911a43e..8b08b658f56 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2829,6 +2829,15 @@ public function testBug5623(): void $this->analyse([__DIR__ . '/data/bug-5623.php'], []); } + public function testImagick(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/imagick.php'], []); + } + public function testImagickPixel(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/imagick.php b/tests/PHPStan/Rules/Methods/data/imagick.php new file mode 100644 index 00000000000..572a3e8f4f1 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/imagick.php @@ -0,0 +1,13 @@ + Date: Wed, 29 Jan 2025 14:18:48 +0100 Subject: [PATCH 1042/3097] GenericStaticType - support for `static<...>` --- phpstan-baseline.neon | 10 + src/Analyser/MutatingScope.php | 89 +++++- src/PhpDoc/TypeNodeResolver.php | 9 + .../ResolvedFunctionVariantWithOriginal.php | 3 +- ...ypeUnresolvedMethodPrototypeReflection.php | 10 + src/Rules/Generics/GenericObjectTypeCheck.php | 7 +- src/Rules/Methods/MethodSignatureRule.php | 10 + src/Rules/MissingTypehintCheck.php | 3 +- src/Type/Generic/GenericStaticType.php | 298 ++++++++++++++++++ src/Type/VerbosityLevel.php | 3 +- .../PHPStan/Analyser/nsrt/generic-static.php | 120 +++++++ .../Rules/Methods/MethodSignatureRuleTest.php | 7 + .../MissingMethodReturnTypehintRuleTest.php | 11 + .../Rules/Methods/ReturnTypeRuleTest.php | 11 +- tests/PHPStan/Rules/Methods/data/bug-4590.php | 8 + .../method-signature-generic-static-type.php | 53 ++++ .../missing-return-type-generic-static.php | 17 + .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 10 + .../incompatible-phpdoc-generic-static.php | 19 ++ tests/PHPStan/Type/StaticTypeTest.php | 145 +++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 120 +++++++ 21 files changed, 950 insertions(+), 13 deletions(-) create mode 100644 src/Type/Generic/GenericStaticType.php create mode 100644 tests/PHPStan/Analyser/nsrt/generic-static.php create mode 100644 tests/PHPStan/Rules/Methods/data/method-signature-generic-static-type.php create mode 100644 tests/PHPStan/Rules/Methods/data/missing-return-type-generic-static.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/incompatible-phpdoc-generic-static.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index be237d9d794..64c868244da 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -985,6 +985,16 @@ parameters: count: 2 path: src/Type/Generic/GenericObjectType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) or Type\\:\\:getObjectClassNames\\(\\) instead\\.$#" + count: 1 + path: src/Type/Generic/GenericStaticType.php + + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\TypeWithClassName is error\\-prone and deprecated\\. Use Type\\:\\:getObjectClassNames\\(\\) or Type\\:\\:getObjectClassReflections\\(\\) instead\\.$#" + count: 1 + path: src/Type/Generic/GenericStaticType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b95f28ace15..0e7b7c328da 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -102,6 +102,7 @@ use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -5437,8 +5438,17 @@ public function debug(): array private function exactInstantiation(New_ $node, string $className): ?Type { $resolvedClassName = $this->resolveExactName(new Name($className)); + $isStatic = false; if ($resolvedClassName === null) { - return null; + if (strtolower($className) !== 'static') { + return null; + } + + if (!$this->isInClass()) { + return null; + } + $resolvedClassName = $this->getClassReflection()->getName(); + $isStatic = true; } if (!$this->reflectionProvider->hasClass($resolvedClassName)) { @@ -5495,7 +5505,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return $methodResult; } - $objectType = new ObjectType($resolvedClassName); + $objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName); if (!$classReflection->isGeneric()) { return $objectType; } @@ -5528,6 +5538,14 @@ private function exactInstantiation(New_ $node, string $className): ?Type } if ($constructorMethod instanceof DummyConstructorReflection) { + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + null, + [], + ); + } return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), @@ -5536,6 +5554,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type if ($constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) { if (!$constructorMethod->getDeclaringClass()->isGeneric()) { + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), @@ -5544,6 +5571,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type $newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); $ancestorType = $newType->getAncestorWithClassName($constructorMethod->getDeclaringClass()->getName()); if ($ancestorType === null) { + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), @@ -5551,6 +5587,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type } $ancestorClassReflections = $ancestorType->getObjectClassReflections(); if (count($ancestorClassReflections) !== 1) { + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), @@ -5561,6 +5606,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type $newParentType = $this->getType($newParentNode); $newParentTypeClassReflections = $newParentType->getObjectClassReflections(); if (count($newParentTypeClassReflections) !== 1) { + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), @@ -5597,6 +5651,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type $resolvedTypeMap[$ancestorType->getName()] = TypeCombinator::union($resolvedTypeMap[$ancestorType->getName()], $type); } + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), @@ -5612,10 +5675,19 @@ private function exactInstantiation(New_ $node, string $className): ?Type if ($this->explicitMixedInUnknownGenericNew) { $resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); - return TypeTraverser::map(new GenericObjectType( + $newGenericType = new GenericObjectType( $resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), - ), static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type { + ); + if ($isStatic) { + $newGenericType = new GenericStaticType( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), + null, + [], + ); + } + return TypeTraverser::map($newGenericType, static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type { if ($type instanceof TemplateType && !$type->isArgument()) { $newType = $resolvedTemplateTypeMap->getType($type->getName()); if ($newType === null || $newType instanceof ErrorType) { @@ -5654,6 +5726,15 @@ private function exactInstantiation(New_ $node, string $className): ?Type $list[] = $bound; } + if ($isStatic) { + return new GenericStaticType( + $classReflection, + $list, + null, + [], + ); + } + return new GenericObjectType( $resolvedClassName, $list, diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 744265adf67..af83130ac1a 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -70,6 +70,7 @@ use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; @@ -784,6 +785,14 @@ static function (string $variance): TemplateTypeVariance { return $type->isResolvable() ? $type->resolve() : $type; } + return new ErrorType(); + } elseif ($mainTypeName === 'static') { + if ($nameScope->getClassName() !== null && $this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + + return new GenericStaticType($classReflection, $genericTypes, null, $variances); + } + return new ErrorType(); } diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index c9f0d94ac6a..37cd59b2dc0 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -7,6 +7,7 @@ use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -245,7 +246,7 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance }; return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references, $objectCb): Type { - if (BleedingEdgeToggle::isBleedingEdge() && $type instanceof GenericObjectType) { + if (BleedingEdgeToggle::isBleedingEdge() && ($type instanceof GenericObjectType || $type instanceof GenericStaticType)) { return TypeTraverser::map($type, $objectCb); } diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 607a08a37f4..18e9ad6b08e 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -10,6 +10,9 @@ use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; use PHPStan\Reflection\ResolvedMethodReflection; +use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; +use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -114,6 +117,13 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, private function transformStaticType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { + if ($type instanceof GenericStaticType) { + if ($this->calledOnType instanceof ObjectType) { + return new GenericObjectType($this->calledOnType->getClassName(), $type->getTypes()); + } + + return $this->calledOnType; + } if ($type instanceof StaticType) { return $this->calledOnType; } diff --git a/src/Rules/Generics/GenericObjectTypeCheck.php b/src/Rules/Generics/GenericObjectTypeCheck.php index 3f437a0b91e..c0a4936bdc8 100644 --- a/src/Rules/Generics/GenericObjectTypeCheck.php +++ b/src/Rules/Generics/GenericObjectTypeCheck.php @@ -6,6 +6,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -166,15 +167,15 @@ public function check( } /** - * @return GenericObjectType[] + * @return list */ private function getGenericTypes(Type $phpDocType): array { $genericObjectTypes = []; TypeTraverser::map($phpDocType, static function (Type $type, callable $traverse) use (&$genericObjectTypes): Type { - if ($type instanceof GenericObjectType) { + if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (!$resolvedType instanceof GenericObjectType) { + if (!$resolvedType instanceof GenericObjectType && !$resolvedType instanceof GenericStaticType) { throw new ShouldNotHappenException(); } $genericObjectTypes[] = $resolvedType; diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 4cd7fd8277f..0a171835e14 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -18,6 +18,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; @@ -269,6 +270,15 @@ private function checkParameterTypeCompatibility( private function transformStaticType(ClassReflection $declaringClass, Type $type): Type { return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($declaringClass): Type { + if ($type instanceof GenericStaticType) { + if ($declaringClass->isFinal()) { + $changedType = $type->changeBaseClass($declaringClass)->getStaticObjectType(); + } else { + $changedType = $type->changeBaseClass($declaringClass); + } + return $traverse($changedType); + } + if ($type instanceof StaticType) { if ($declaringClass->isFinal()) { $changedType = new ObjectType($declaringClass->getName()); diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 34677039213..2f1317e9209 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -13,6 +13,7 @@ use PHPStan\Type\ConditionalType; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\IntersectionType; @@ -113,7 +114,7 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array $objectTypes = []; TypeTraverser::map($type, function (Type $type, callable $traverse) use (&$objectTypes): Type { - if ($type instanceof GenericObjectType) { + if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { $traverse($type); return $type; } diff --git a/src/Type/Generic/GenericStaticType.php b/src/Type/Generic/GenericStaticType.php new file mode 100644 index 00000000000..076717de9e9 --- /dev/null +++ b/src/Type/Generic/GenericStaticType.php @@ -0,0 +1,298 @@ + $types + * @param array $variances + */ + public function __construct( + private ClassReflection $classReflection, + private array $types, + private ?Type $subtractedType, + private array $variances, + ) + { + parent::__construct($classReflection, $subtractedType); + $this->baseClass = $classReflection->getName(); + } + + public function getClassName(): string + { + return $this->baseClass; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** @return array */ + public function getVariances(): array + { + return $this->variances; + } + + public function getStaticObjectType(): ObjectType + { + if ($this->staticObjectType === null) { + if ($this->classReflection->isGeneric()) { + return $this->staticObjectType = new GenericObjectType( + $this->classReflection->getName(), + $this->types, + $this->subtractedType, + $this->classReflection, + $this->variances, + ); + } + + return $this->staticObjectType = parent::getStaticObjectType(); + } + + return $this->staticObjectType; + } + + public function changeBaseClass(ClassReflection $classReflection): self + { + if ($classReflection->getName() === $this->getClassName()) { + return $this; + } + + // this template type mapping logic is very similar to mapping logic in MutatingScope::exactInstantiation() + // where inferring "new Foo" but with the constructor being only in Foo parent class + + $newType = new GenericObjectType($classReflection->getName(), $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); + $ancestorType = $newType->getAncestorWithClassName($this->getClassName()); + if ($ancestorType === null) { + return new self($classReflection, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), $this->subtractedType, $this->variances); + } + + $ancestorClassReflections = $ancestorType->getObjectClassReflections(); + if (count($ancestorClassReflections) !== 1) { + return new self($classReflection, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), $this->subtractedType, $this->variances); + } + + $ancestorClassReflection = $ancestorClassReflections[0]; + $ancestorMapping = []; + foreach ($ancestorClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $templateType) { + if (!$templateType instanceof TemplateType) { + continue; + } + + $ancestorMapping[$typeName] = $templateType; + } + + $resolvedTypeMap = []; + foreach ($ancestorClassReflection->typeMapFromList($this->types)->getTypes() as $typeName => $type) { + if (!array_key_exists($typeName, $ancestorMapping)) { + continue; + } + + $ancestorType = $ancestorMapping[$typeName]; + if (!$ancestorType->getBound()->isSuperTypeOf($type)->yes()) { + continue; + } + + if (!array_key_exists($ancestorType->getName(), $resolvedTypeMap)) { + $resolvedTypeMap[$ancestorType->getName()] = $type; + continue; + } + + $resolvedTypeMap[$ancestorType->getName()] = TypeCombinator::union($resolvedTypeMap[$ancestorType->getName()], $type); + } + + $resolvedVariances = []; + foreach ($ancestorClassReflection->varianceMapFromList($this->variances)->getVariances() as $typeName => $variance) { + if (!array_key_exists($typeName, $ancestorMapping)) { + continue; + } + + $ancestorType = $ancestorMapping[$typeName]; + if (!array_key_exists($ancestorType->getName(), $resolvedVariances)) { + $resolvedVariances[$ancestorType->getName()] = $variance; + continue; + } + + $resolvedVariances[$ancestorType->getName()] = $resolvedVariances[$ancestorType->getName()]->compose($variance); + } + + return new self($classReflection, $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), $this->subtractedType, $classReflection->varianceMapToList(new TemplateTypeVarianceMap($resolvedVariances))); + } + + public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOfWithReason($this); + } + + if ($type instanceof self) { + return $this->getStaticObjectType()->isSuperTypeOfWithReason($type->getStaticObjectType()); + } + + return parent::isSuperTypeOfWithReason($type)->and(IsSuperTypeOfResult::createMaybe()); + } + + public function traverse(callable $cb): Type + { + $subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null; + + $typesChanged = false; + $types = []; + foreach ($this->types as $type) { + $newType = $cb($type); + $types[] = $newType; + if ($newType === $type) { + continue; + } + + $typesChanged = true; + } + + if ($subtractedType !== $this->getSubtractedType() || $typesChanged) { + return new self( + $this->classReflection, + $types, + $subtractedType, + $this->variances, + ); + } + + return $this; + } + + public function traverseSimultaneously(Type $right, callable $cb): Type + { + if (!$right instanceof TypeWithClassName) { + return $this; + } + + $ancestor = $right->getAncestorWithClassName($this->getClassName()); + if (!$ancestor instanceof self) { + return $this; + } + + if (count($this->types) !== count($ancestor->types)) { + return $this; + } + + $typesChanged = false; + $types = []; + foreach ($this->types as $i => $leftType) { + $rightType = $ancestor->types[$i]; + $newType = $cb($leftType, $rightType); + $types[] = $newType; + if ($newType === $leftType) { + continue; + } + + $typesChanged = true; + } + + if ($typesChanged) { + return new self( + $this->classReflection, + $types, + null, + $this->variances, + ); + } + + return $this; + } + + public function changeSubtractedType(?Type $subtractedType): Type + { + if ($subtractedType !== null) { + $classReflection = $this->getClassReflection(); + if ($classReflection->getAllowedSubTypes() !== null) { + $objectType = $this->getStaticObjectType()->changeSubtractedType($subtractedType); + if ($objectType instanceof NeverType) { + return $objectType; + } + + if ($objectType instanceof ObjectType && $objectType->getSubtractedType() !== null) { + return new self($classReflection, $this->types, $objectType->getSubtractedType(), $this->variances); + } + + return TypeCombinator::intersect($this, $objectType); + } + } + + return new self( + $this->classReflection, + $this->types, + $subtractedType, + $this->variances, + ); + } + + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap + { + return $this->getStaticObjectType()->inferTemplateTypes($receivedType); + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + return $this->getStaticObjectType()->getReferencedTemplateTypes($positionVariance); + } + + public function toPhpDocNode(): TypeNode + { + /** @var IdentifierTypeNode $parent */ + $parent = parent::toPhpDocNode(); + return new GenericTypeNode( + $parent, + array_map(static fn (Type $type) => $type->toPhpDocNode(), $this->types), + array_map(static fn (TemplateTypeVariance $variance) => $variance->toPhpDocNodeVariance(), $this->variances), + ); + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): Type + { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($reflectionProvider->hasClass($properties['baseClass'])) { + return new self( + $reflectionProvider->getClass($properties['baseClass']), + $properties['types'], + $properties['subtractedType'], + $properties['variances'], + ); + } + + return new ErrorType(); + } + +} diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index ec733df635e..12b5ecbd154 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -11,6 +11,7 @@ use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; final class VerbosityLevel @@ -152,7 +153,7 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $containsInvariantTemplateType = false; TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type { - if ($type instanceof GenericObjectType) { + if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { $reflection = $type->getClassReflection(); if ($reflection !== null) { $templateTypeMap = $reflection->getTemplateTypeMap(); diff --git a/tests/PHPStan/Analyser/nsrt/generic-static.php b/tests/PHPStan/Analyser/nsrt/generic-static.php new file mode 100644 index 00000000000..81096260d3a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/generic-static.php @@ -0,0 +1,120 @@ + + */ + public function map(callable $cb); + + /** @return static */ + public function flip(); + + /** @return static */ + public function fluent(); + +} + +/** + * @template T + * @template U + * @implements Foo + */ +class FooImpl implements Foo +{ + + public function map(callable $cb) + { + + } + + public function flip() + { + + } + + public function fluent() + { + + } + + public function doFoo(): void + { + assertType('static(GenericStatic\FooImpl)', $this->map(function () { + return 1; + })); + + assertType('static(GenericStatic\FooImpl)', $this->flip()); + assertType('static(GenericStatic\FooImpl)', $this->fluent()); + } + + /** + * @param FooImpl $s + */ + public function doBar(self $s): void + { + assertType('GenericStatic\\FooImpl', $s->map(function () { + return 1; + })); + + assertType('GenericStatic\\FooImpl', $s->flip()); + assertType('GenericStatic\\FooImpl', $s->fluent()); + } + +} + +/** + * @template T + * @template U + * @implements Foo + */ +abstract class Inconsistent implements Foo +{ + + public function fluent() + { + + } + + /** + * @param Inconsistent $s + */ + public function test(self $s): void + { + assertType('GenericStatic\\Inconsistent', $s->fluent()); + } + +} + +/** + * @template T + * @implements Foo + */ +abstract class Inconsistent2 implements Foo +{ + + public function fluent() + { + + } + + /** + * @param Inconsistent2 $s + */ + public function test(self $s): void + { + assertType('GenericStatic\\Inconsistent2', $s->fluent()); + } + +} diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 6c07d330edd..36fa7499877 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -561,4 +561,11 @@ public function testOverridenAbstractTraitMethodPhpDoc(): void $this->analyse([__DIR__ . '/data/overriden-abstract-trait-method-phpdoc.php'], []); } + public function testGenericStaticType(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/method-signature-generic-static-type.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 49fc0b97d17..dbaf8f68e4d 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -100,4 +100,15 @@ public function testBug9571PhpDocs(): void $this->analyse([__DIR__ . '/data/bug-9571-phpdocs.php'], []); } + public function testGenericStatic(): void + { + $this->analyse([__DIR__ . '/data/missing-return-type-generic-static.php'], [ + [ + 'Method MissingReturnTypeGenericStatic\Foo::doFoo() return type has no value type specified in iterable type array.', + 12, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 98e54bce0e8..a7981429416 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -437,19 +437,24 @@ public function testInferArrayKey(): void public function testBug4590(): void { $this->analyse([__DIR__ . '/data/bug-4590.php'], [ + [ + 'Method Bug4590\OkResponse::testGenericStatic() should return static(Bug4590\OkResponse>) but returns static(Bug4590\OkResponse).', + 36, + 'Template type T on class Bug4590\OkResponse is not covariant. Learn more: https://phpstan.org/blog/whats-up-with-template-covariant', + ], [ 'Method Bug4590\\Controller::test1() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', - 39, + 47, 'Template type T on class Bug4590\OkResponse is not covariant. Learn more: https://phpstan.org/blog/whats-up-with-template-covariant', ], [ 'Method Bug4590\\Controller::test2() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', - 47, + 55, 'Template type T on class Bug4590\OkResponse is not covariant. Learn more: https://phpstan.org/blog/whats-up-with-template-covariant', ], [ 'Method Bug4590\\Controller::test3() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', - 55, + 63, 'Template type T on class Bug4590\OkResponse is not covariant. Learn more: https://phpstan.org/blog/whats-up-with-template-covariant', ], ]); diff --git a/tests/PHPStan/Rules/Methods/data/bug-4590.php b/tests/PHPStan/Rules/Methods/data/bug-4590.php index a3db4a34452..ed2d79d790c 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4590.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4590.php @@ -27,6 +27,14 @@ public function getBody() { return $this->body; } + + /** + * @return static> + */ + public static function testGenericStatic() + { + return new static(["ok" => "hello"]); + } } class Controller diff --git a/tests/PHPStan/Rules/Methods/data/method-signature-generic-static-type.php b/tests/PHPStan/Rules/Methods/data/method-signature-generic-static-type.php new file mode 100644 index 00000000000..cd54f09f16c --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-signature-generic-static-type.php @@ -0,0 +1,53 @@ + + */ + public function doFoo() + { + + } + +} + +/** + * @template T + * @extends Foo + */ +class Bar extends Foo +{ + + /** + * @return static + */ + public function doFoo() + { + + } + +} + +/** + * @template T + * @extends Foo + */ +final class FinalBar extends Foo +{ + + /** + * @return static + */ + public function doFoo() + { + + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/missing-return-type-generic-static.php b/tests/PHPStan/Rules/Methods/data/missing-return-type-generic-static.php new file mode 100644 index 00000000000..688556c99b1 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/missing-return-type-generic-static.php @@ -0,0 +1,17 @@ + */ + public function doFoo() + { + + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index ace29c0b955..d8a6576b1a6 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -480,4 +480,14 @@ public function testParamClosureThis(): void ]); } + public function testGenericStatic(): void + { + $this->analyse([__DIR__ . '/data/incompatible-phpdoc-generic-static.php'], [ + [ + 'Generic type static(IncompatiblePhpDocGenericStatic\Foo) in PHPDoc tag @return specifies 2 template types, but class IncompatiblePhpDocGenericStatic\Foo supports only 1: T', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-phpdoc-generic-static.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-phpdoc-generic-static.php new file mode 100644 index 00000000000..b73a87b955d --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-phpdoc-generic-static.php @@ -0,0 +1,19 @@ + + */ + public function doFoo() + { + + } + +} diff --git a/tests/PHPStan/Type/StaticTypeTest.php b/tests/PHPStan/Type/StaticTypeTest.php index a462bda476d..119ce10fb93 100644 --- a/tests/PHPStan/Type/StaticTypeTest.php +++ b/tests/PHPStan/Type/StaticTypeTest.php @@ -10,8 +10,14 @@ use InvalidArgumentException; use Iterator; use LogicException; +use PHPStan\Generics\FunctionsAssertType\C; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; +use PHPStan\Type\Generic\TemplateTypeFactory; +use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; use StaticTypeTest\Base; use StaticTypeTest\Child; use StaticTypeTest\FinalChild; @@ -270,6 +276,18 @@ public function dataIsSuperTypeOf(): array ]), TrinaryLogic::createNo(), ], + [ + new GenericStaticType( + $reflectionProvider->getClass(\MethodSignatureGenericStaticType\Foo::class), // phpcs:ignore + [new StringType()], + null, + [], + ), + new GenericObjectType(\MethodSignatureGenericStaticType\FinalBar::class, [ // phpcs:ignore + new IntegerType(), + ]), + TrinaryLogic::createNo(), + ], ]; } @@ -323,4 +341,131 @@ public function testEquals(StaticType $type, StaticType $otherType, bool $expect $this->assertSame($expected, $otherType->equals($type)); } + public function dataAccepts(): iterable + { + $reflectionProvider = $this->createReflectionProvider(); + $c = $reflectionProvider->getClass(C::class); + + yield [ + new StaticType($c), + new StaticType($c), + TrinaryLogic::createYes(), + ]; + + yield [ + // static !== static + new StaticType($c), + new GenericStaticType($c, [new IntegerType()], null, []), + TrinaryLogic::createNo(), + ]; + + yield [ + // static !== static + new StaticType($c), + new GenericStaticType($c, [new IntegerType()], null, [ + TemplateTypeVariance::createCovariant(), + ]), + TrinaryLogic::createNo(), + ]; + + yield [ + // static === static + new StaticType($c), + new GenericStaticType($c, [ + TemplateTypeFactory::create(TemplateTypeScope::createWithClass($c->getName()), 'T', null, TemplateTypeVariance::createInvariant()), + ], null, []), + TrinaryLogic::createYes(), + ]; + + yield [ + // static !== static + new GenericStaticType($c, [ + TemplateTypeFactory::create(TemplateTypeScope::createWithClass($c->getName()), 'T', null, TemplateTypeVariance::createInvariant()), + ], null, []), + new StaticType($c), + TrinaryLogic::createNo(), // could be Yes + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new GenericStaticType($c, [new IntegerType()], null, []), + TrinaryLogic::createYes(), + ]; + + yield [ + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, []), + new GenericStaticType($c, [new IntegerType()], null, []), + TrinaryLogic::createNo(), + ]; + + yield [ + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new IntegerType()], null, []), + TrinaryLogic::createYes(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, []), + TrinaryLogic::createNo(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, [ + TemplateTypeVariance::createContravariant(), + ]), + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, []), + TrinaryLogic::createYes(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new ObjectType($c->getName()), + TrinaryLogic::createNo(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new GenericObjectType($c->getName(), [new IntegerType()], null), + TrinaryLogic::createNo(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new ObjectWithoutClassType(), + TrinaryLogic::createNo(), + ]; + + yield [ + new GenericStaticType($c, [new IntegerType()], null, []), + new ObjectWithoutClassType(), + TrinaryLogic::createNo(), + ]; + } + + /** + * @dataProvider dataAccepts + */ + public function testAccepts(StaticType $type, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $type->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $type->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())), + ); + } + } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 0e125a4a81e..d81d4cadc34 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -15,6 +15,7 @@ use Iterator; use ObjectShapesAcceptance\ClassWithFooIntProperty; use PHPStan\Fixture\FinalClass; +use PHPStan\Generics\FunctionsAssertType\C; use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; @@ -38,6 +39,7 @@ use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateBenevolentUnionType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateObjectType; @@ -2681,6 +2683,65 @@ public function dataUnion(): iterable IntersectionType::class, 'array&hasOffsetValue(\'thing\', mixed)', ]; + + $c = $reflectionProvider->getClass(C::class); + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new StringType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + UnionType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)|static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, [TemplateTypeVariance::createCovariant()]), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new StaticType($c), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + StaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new ObjectWithoutClassType(), + ], + ObjectWithoutClassType::class, + 'object', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new ObjectType($c->getName()), + ], + ObjectType::class, + $c->getName(), + ]; } /** @@ -4537,6 +4598,65 @@ public function dataIntersect(): iterable ConstantStringType::class, '\'FOO\'', ]; + + $c = $reflectionProvider->getClass(C::class); + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new StringType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new GenericStaticType($c, [new UnionType([ + new IntegerType(), + new StringType(), + ])], null, [TemplateTypeVariance::createCovariant()]), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new StaticType($c), + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new ObjectWithoutClassType(), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; + + yield [ + [ + new GenericStaticType($c, [new IntegerType()], null, [TemplateTypeVariance::createCovariant()]), + new ObjectType($c->getName()), + ], + GenericStaticType::class, + 'static(PHPStan\Generics\FunctionsAssertType\C)', + ]; } /** From c6c5bf658f4a611035c15838267d974911abfcdc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 15:58:16 +0100 Subject: [PATCH 1043/3097] Fix --- src/Type/Generic/GenericStaticType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Generic/GenericStaticType.php b/src/Type/Generic/GenericStaticType.php index 076717de9e9..8b1cd3d057e 100644 --- a/src/Type/Generic/GenericStaticType.php +++ b/src/Type/Generic/GenericStaticType.php @@ -82,7 +82,7 @@ public function getStaticObjectType(): ObjectType return $this->staticObjectType; } - public function changeBaseClass(ClassReflection $classReflection): self + public function changeBaseClass(ClassReflection $classReflection): StaticType { if ($classReflection->getName() === $this->getClassName()) { return $this; From e2e2e770174a0e5029d2950314710d6bd82a2687 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 15:58:47 +0100 Subject: [PATCH 1044/3097] PHP 7.4+ allows return type covariance --- src/Type/Generic/GenericStaticType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Generic/GenericStaticType.php b/src/Type/Generic/GenericStaticType.php index 0e72cc9a7c0..54f9e5a8092 100644 --- a/src/Type/Generic/GenericStaticType.php +++ b/src/Type/Generic/GenericStaticType.php @@ -72,7 +72,7 @@ public function getStaticObjectType(): ObjectType return $this->staticObjectType; } - public function changeBaseClass(ClassReflection $classReflection): StaticType + public function changeBaseClass(ClassReflection $classReflection): self { if ($classReflection->getName() === $this->getClassName()) { return $this; From 692b0182ed77ed735ea411357157c331ca9b7c2e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 16:17:14 +0100 Subject: [PATCH 1045/3097] Fix nested generic static --- .../Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php | 2 +- src/Type/StaticType.php | 4 ++-- tests/PHPStan/Analyser/nsrt/generic-static.php | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 18e9ad6b08e..18d8ee02579 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -119,7 +119,7 @@ private function transformStaticType(Type $type): Type return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { if ($type instanceof GenericStaticType) { if ($this->calledOnType instanceof ObjectType) { - return new GenericObjectType($this->calledOnType->getClassName(), $type->getTypes()); + return $traverse(new GenericObjectType($this->calledOnType->getClassName(), $type->getTypes())); } return $this->calledOnType; diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 7056fc5901c..a28eacfab6c 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -300,10 +300,10 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop } $type = $type->changeBaseClass($classReflection); if (!$isFinal || $type instanceof ThisType) { - return $type; + return $traverse($type); } - return $type->getStaticObjectType(); + return $traverse($type->getStaticObjectType()); } return $traverse($type); diff --git a/tests/PHPStan/Analyser/nsrt/generic-static.php b/tests/PHPStan/Analyser/nsrt/generic-static.php index 81096260d3a..49f8df800ca 100644 --- a/tests/PHPStan/Analyser/nsrt/generic-static.php +++ b/tests/PHPStan/Analyser/nsrt/generic-static.php @@ -24,6 +24,9 @@ public function flip(); /** @return static */ public function fluent(); + /** @return static> */ + public function nested(); + } /** @@ -57,6 +60,7 @@ public function doFoo(): void assertType('static(GenericStatic\FooImpl)', $this->flip()); assertType('static(GenericStatic\FooImpl)', $this->fluent()); + assertType('static(GenericStatic\FooImpl)>)', $this->nested()); } /** @@ -70,6 +74,7 @@ public function doBar(self $s): void assertType('GenericStatic\\FooImpl', $s->flip()); assertType('GenericStatic\\FooImpl', $s->fluent()); + assertType('GenericStatic\FooImpl>', $s->nested()); } } From dcbf321941c64c41e06065a20c4875629aa54270 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 19:42:57 +0100 Subject: [PATCH 1046/3097] Fix generic `static<...>` when the child class is not generic --- ...ypeUnresolvedMethodPrototypeReflection.php | 9 ++++--- src/Type/Generic/GenericStaticType.php | 8 ++++++ .../PHPStan/Analyser/nsrt/generic-static.php | 27 ++++++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 18d8ee02579..44ee69782d8 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -10,9 +10,7 @@ use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; use PHPStan\Reflection\ResolvedMethodReflection; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\GenericStaticType; -use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -118,8 +116,11 @@ private function transformStaticType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { if ($type instanceof GenericStaticType) { - if ($this->calledOnType instanceof ObjectType) { - return $traverse(new GenericObjectType($this->calledOnType->getClassName(), $type->getTypes())); + $calledOnTypeReflections = $this->calledOnType->getObjectClassReflections(); + if (count($calledOnTypeReflections) === 1) { + $calledOnTypeReflection = $calledOnTypeReflections[0]; + + return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType()); } return $this->calledOnType; diff --git a/src/Type/Generic/GenericStaticType.php b/src/Type/Generic/GenericStaticType.php index 8b1cd3d057e..0af291f50ed 100644 --- a/src/Type/Generic/GenericStaticType.php +++ b/src/Type/Generic/GenericStaticType.php @@ -7,6 +7,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; +use PHPStan\ShouldNotHappenException; use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IsSuperTypeOfResult; @@ -40,6 +41,9 @@ public function __construct( private array $variances, ) { + if (count($this->types) === 0) { + throw new ShouldNotHappenException('Cannot create GenericStaticType with zero types.'); + } parent::__construct($classReflection, $subtractedType); $this->baseClass = $classReflection->getName(); } @@ -88,6 +92,10 @@ public function changeBaseClass(ClassReflection $classReflection): StaticType return $this; } + if (!$classReflection->isGeneric()) { + return new StaticType($classReflection); + } + // this template type mapping logic is very similar to mapping logic in MutatingScope::exactInstantiation() // where inferring "new Foo" but with the constructor being only in Foo parent class diff --git a/tests/PHPStan/Analyser/nsrt/generic-static.php b/tests/PHPStan/Analyser/nsrt/generic-static.php index 49f8df800ca..129e72147a5 100644 --- a/tests/PHPStan/Analyser/nsrt/generic-static.php +++ b/tests/PHPStan/Analyser/nsrt/generic-static.php @@ -74,7 +74,7 @@ public function doBar(self $s): void assertType('GenericStatic\\FooImpl', $s->flip()); assertType('GenericStatic\\FooImpl', $s->fluent()); - assertType('GenericStatic\FooImpl>', $s->nested()); + assertType('GenericStatic\FooImpl>', $s->nested()); } } @@ -97,6 +97,7 @@ public function fluent() */ public function test(self $s): void { + assertType('static(GenericStatic\Inconsistent)', $this->fluent()); assertType('GenericStatic\\Inconsistent', $s->fluent()); } @@ -119,7 +120,31 @@ public function fluent() */ public function test(self $s): void { + assertType('static(GenericStatic\Inconsistent2)', $this->fluent()); assertType('GenericStatic\\Inconsistent2', $s->fluent()); } } + +/** + * @template T + * @template K + */ +class A { + /** @return static */ + public function doFoo() {} + +} + +/** @extends A */ +class B extends A { + public function doBar(): void + { + $f = $this->doFoo(); + assertType('static(GenericStatic\B)', $f); + } +} + +function (): void { + assertType(B::class, (new B)->doFoo()); +}; From 3f83b463e9fce993da1d5c2391f4ee960d85a315 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 19:54:31 +0100 Subject: [PATCH 1047/3097] Fix CS --- .../Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 44ee69782d8..cc8c7121eee 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -15,6 +15,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use function array_map; +use function count; final class CalledOnTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { From 196e0e3b56e60f68b248b70b069847e436b8f419 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 21:07:33 +0100 Subject: [PATCH 1048/3097] Try fixing generic static type issue --- ...lledOnTypeUnresolvedMethodPrototypeReflection.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index cc8c7121eee..2e57c4ef07e 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -11,11 +11,11 @@ use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\Generic\GenericStaticType; +use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use function array_map; -use function count; final class CalledOnTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { @@ -117,11 +117,11 @@ private function transformStaticType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { if ($type instanceof GenericStaticType) { - $calledOnTypeReflections = $this->calledOnType->getObjectClassReflections(); - if (count($calledOnTypeReflections) === 1) { - $calledOnTypeReflection = $calledOnTypeReflections[0]; - - return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType()); + if ($this->calledOnType instanceof ObjectType) { + $calledOnTypeReflection = $this->calledOnType->getClassReflection(); + if ($calledOnTypeReflection !== null) { + return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType()); + } } return $this->calledOnType; From 703ec3179658ae668b1bbe311aa828be66cc171a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 21:15:54 +0100 Subject: [PATCH 1049/3097] Revert "Try fixing generic static type issue" This reverts commit 196e0e3b56e60f68b248b70b069847e436b8f419. --- ...lledOnTypeUnresolvedMethodPrototypeReflection.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 2e57c4ef07e..cc8c7121eee 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -11,11 +11,11 @@ use PHPStan\Reflection\Php\DummyParameterWithPhpDocs; use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\Generic\GenericStaticType; -use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use function array_map; +use function count; final class CalledOnTypeUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection { @@ -117,11 +117,11 @@ private function transformStaticType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { if ($type instanceof GenericStaticType) { - if ($this->calledOnType instanceof ObjectType) { - $calledOnTypeReflection = $this->calledOnType->getClassReflection(); - if ($calledOnTypeReflection !== null) { - return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType()); - } + $calledOnTypeReflections = $this->calledOnType->getObjectClassReflections(); + if (count($calledOnTypeReflections) === 1) { + $calledOnTypeReflection = $calledOnTypeReflections[0]; + + return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType()); } return $this->calledOnType; From 48055331343c8162774a74d69961412b624b298f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 21:32:59 +0100 Subject: [PATCH 1050/3097] Fix tests --- tests/PHPStan/Generics/data/variance-2.json | 8 ++++---- tests/PHPStan/Generics/data/variance-4.json | 2 +- tests/PHPStan/Generics/data/variance-5.json | 2 +- tests/PHPStan/Generics/data/variance.php | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/PHPStan/Generics/data/variance-2.json b/tests/PHPStan/Generics/data/variance-2.json index e0db4f89f62..3c7d9da4d4a 100644 --- a/tests/PHPStan/Generics/data/variance-2.json +++ b/tests/PHPStan/Generics/data/variance-2.json @@ -61,17 +61,17 @@ }, { "message": "Template type T is declared as covariant, but occurs in contravariant position in parameter t of method PHPStan\\Generics\\Variance\\ConstructorAndStatic::create().", - "line": 153, + "line": 154, "ignorable": true }, { "message": "Template type T is declared as covariant, but occurs in contravariant position in parameter w of method PHPStan\\Generics\\Variance\\ConstructorAndStatic::create().", - "line": 153, + "line": 154, "ignorable": true }, { "message": "Template type T is declared as covariant, but occurs in invariant position in parameter v of method PHPStan\\Generics\\Variance\\ConstructorAndStatic::create().", - "line": 153, + "line": 154, "ignorable": true } -] +] \ No newline at end of file diff --git a/tests/PHPStan/Generics/data/variance-4.json b/tests/PHPStan/Generics/data/variance-4.json index c6fa3681038..7757cc3dead 100644 --- a/tests/PHPStan/Generics/data/variance-4.json +++ b/tests/PHPStan/Generics/data/variance-4.json @@ -1,7 +1,7 @@ [ { "message": "Property PHPStan\\Generics\\Variance\\ConstructorAndStatic::$data is never read, only written.", - "line": 134, + "line": 135, "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Generics/data/variance-5.json b/tests/PHPStan/Generics/data/variance-5.json index 016235e9009..a16b075630e 100644 --- a/tests/PHPStan/Generics/data/variance-5.json +++ b/tests/PHPStan/Generics/data/variance-5.json @@ -1,7 +1,7 @@ [ { "message": "Parameter #1 $it of function PHPStan\\Generics\\Variance\\acceptInvariantIterOfDateTimeInterface expects PHPStan\\Generics\\Variance\\InvariantIter, PHPStan\\Generics\\Variance\\InvariantIter given.", - "line": 164, + "line": 165, "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Generics/data/variance.php b/tests/PHPStan/Generics/data/variance.php index defa4910d3a..8b68847f77d 100644 --- a/tests/PHPStan/Generics/data/variance.php +++ b/tests/PHPStan/Generics/data/variance.php @@ -127,6 +127,7 @@ function set($v): void; /** * @template-covariant T * @template U + * @phpstan-consistent-constructor */ class ConstructorAndStatic { @@ -151,7 +152,7 @@ public function __construct($t, $u, $v, $w) { * @return Static */ public static function create($t, $u, $v, $w) { - return new self($t, $u, $v, $w); + return new static($t, $u, $v, $w); } } From c126d48c930fc45d0d787461a1f8f962177cbbf9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 22:12:28 +0100 Subject: [PATCH 1051/3097] Generic static type fix --- src/Type/Generic/GenericStaticType.php | 76 +++++++++---------- .../PHPStan/Analyser/nsrt/generic-static.php | 23 ++++++ 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/Type/Generic/GenericStaticType.php b/src/Type/Generic/GenericStaticType.php index 0af291f50ed..7d5ef16d6ad 100644 --- a/src/Type/Generic/GenericStaticType.php +++ b/src/Type/Generic/GenericStaticType.php @@ -96,65 +96,59 @@ public function changeBaseClass(ClassReflection $classReflection): StaticType return new StaticType($classReflection); } - // this template type mapping logic is very similar to mapping logic in MutatingScope::exactInstantiation() - // where inferring "new Foo" but with the constructor being only in Foo parent class + $templateTags = $this->getClassReflection()->getTemplateTags(); + $i = 0; + $indexedTypes = []; + $indexedVariances = []; + foreach ($templateTags as $typeName => $tag) { + if (!array_key_exists($i, $this->types)) { + break; + } + if (!array_key_exists($i, $this->variances)) { + break; + } + $indexedTypes[$typeName] = $this->types[$i]; + $indexedVariances[$typeName] = $this->variances[$i]; + $i++; + } $newType = new GenericObjectType($classReflection->getName(), $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); $ancestorType = $newType->getAncestorWithClassName($this->getClassName()); if ($ancestorType === null) { - return new self($classReflection, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), $this->subtractedType, $this->variances); + return new self( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $this->subtractedType, + $classReflection->varianceMapToList($classReflection->getCallSiteVarianceMap()), + ); } - $ancestorClassReflections = $ancestorType->getObjectClassReflections(); - if (count($ancestorClassReflections) !== 1) { - return new self($classReflection, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), $this->subtractedType, $this->variances); + $ancestorClassReflection = $ancestorType->getClassReflection(); + if ($ancestorClassReflection === null) { + return new self( + $classReflection, + $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $this->subtractedType, + $classReflection->varianceMapToList($classReflection->getCallSiteVarianceMap()), + ); } - $ancestorClassReflection = $ancestorClassReflections[0]; - $ancestorMapping = []; + $newClassTypes = []; + $newClassVariances = []; foreach ($ancestorClassReflection->getActiveTemplateTypeMap()->getTypes() as $typeName => $templateType) { if (!$templateType instanceof TemplateType) { continue; } - $ancestorMapping[$typeName] = $templateType; - } - - $resolvedTypeMap = []; - foreach ($ancestorClassReflection->typeMapFromList($this->types)->getTypes() as $typeName => $type) { - if (!array_key_exists($typeName, $ancestorMapping)) { - continue; - } - - $ancestorType = $ancestorMapping[$typeName]; - if (!$ancestorType->getBound()->isSuperTypeOf($type)->yes()) { - continue; - } - - if (!array_key_exists($ancestorType->getName(), $resolvedTypeMap)) { - $resolvedTypeMap[$ancestorType->getName()] = $type; - continue; - } - - $resolvedTypeMap[$ancestorType->getName()] = TypeCombinator::union($resolvedTypeMap[$ancestorType->getName()], $type); - } - - $resolvedVariances = []; - foreach ($ancestorClassReflection->varianceMapFromList($this->variances)->getVariances() as $typeName => $variance) { - if (!array_key_exists($typeName, $ancestorMapping)) { - continue; - } - - $ancestorType = $ancestorMapping[$typeName]; - if (!array_key_exists($ancestorType->getName(), $resolvedVariances)) { - $resolvedVariances[$ancestorType->getName()] = $variance; + if (!array_key_exists($typeName, $indexedTypes)) { continue; } - $resolvedVariances[$ancestorType->getName()] = $resolvedVariances[$ancestorType->getName()]->compose($variance); + $newClassTypes[$templateType->getName()] = $indexedTypes[$typeName]; + $newClassVariances[$templateType->getName()] = $indexedVariances[$typeName]; } - return new self($classReflection, $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), $this->subtractedType, $classReflection->varianceMapToList(new TemplateTypeVarianceMap($resolvedVariances))); + return new self($classReflection, $classReflection->typeMapToList(new TemplateTypeMap($newClassTypes)), $this->subtractedType, $classReflection->varianceMapToList(new TemplateTypeVarianceMap($newClassVariances))); } public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult diff --git a/tests/PHPStan/Analyser/nsrt/generic-static.php b/tests/PHPStan/Analyser/nsrt/generic-static.php index 129e72147a5..c7163151f72 100644 --- a/tests/PHPStan/Analyser/nsrt/generic-static.php +++ b/tests/PHPStan/Analyser/nsrt/generic-static.php @@ -126,6 +126,29 @@ public function test(self $s): void } +/** + * @template T + * @implements Foo + */ +abstract class Inconsistent3 implements Foo +{ + + public function fluent() + { + + } + + /** + * @param Inconsistent3 $s + */ + public function test(self $s): void + { + assertType('static(GenericStatic\Inconsistent3)', $this->fluent()); + assertType('GenericStatic\\Inconsistent3', $s->fluent()); + } + +} + /** * @template T * @template K From 0b28f6001b4d308e9fbfe3cb7feff6c259f47cc2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 2 Feb 2025 16:07:50 +0100 Subject: [PATCH 1052/3097] Cleanup --- .../Php/PhpClassReflectionExtension.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index faa09ee01d8..413f5bca736 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -528,16 +528,12 @@ private function createMethod( if ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName())) { $variantsByType = ['positional' => []]; - $reflectionMethod = null; $throwType = null; $asserts = Assertions::createEmpty(); $acceptsNamedArguments = true; $selfOutType = null; $phpDocComment = null; - if ($classReflection->getNativeReflection()->hasMethod($methodReflection->getName())) { - $reflectionMethod = $classReflection->getNativeReflection()->getMethod($methodReflection->getName()); - } - $methodSignaturesResult = $this->signatureMapProvider->getMethodSignatures($declaringClassName, $methodReflection->getName(), $reflectionMethod); + $methodSignaturesResult = $this->signatureMapProvider->getMethodSignatures($declaringClassName, $methodReflection->getName(), $methodReflection); foreach ($methodSignaturesResult as $signatureType => $methodSignatures) { if ($methodSignatures === null) { continue; @@ -615,15 +611,15 @@ private function createMethod( } } } - if ($stubPhpDocPair === null && $reflectionMethod !== null && $reflectionMethod->getDocComment() !== false) { - $filename = $reflectionMethod->getFileName(); + if ($stubPhpDocPair === null && $methodReflection->getDocComment() !== false) { + $filename = $methodReflection->getFileName(); if ($filename !== false) { $phpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( $filename, $declaringClassName, null, - $reflectionMethod->getName(), - $reflectionMethod->getDocComment(), + $methodReflection->getName(), + $methodReflection->getDocComment(), ); $throwsTag = $phpDocBlock->getThrowsTag(); if ($throwsTag !== null) { @@ -655,7 +651,7 @@ private function createMethod( } $signatureParameters = $methodSignature->getParameters(); - foreach ($reflectionMethod->getParameters() as $paramI => $reflectionParameter) { + foreach ($methodReflection->getParameters() as $paramI => $reflectionParameter) { if (!array_key_exists($paramI, $signatureParameters)) { continue; } From a387fa32788fd38bc35313558f6e6a46fc2c451c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 20:14:50 +0100 Subject: [PATCH 1053/3097] AttributeReflection for easy attributes reading --- conf/config.neon | 3 + src/Analyser/DirectInternalScopeFactory.php | 3 + src/Analyser/LazyInternalScopeFactory.php | 2 + src/Analyser/MutatingScope.php | 33 ++++ src/Analyser/NodeScopeResolver.php | 3 + .../AnnotationMethodReflection.php | 5 + .../AnnotationPropertyReflection.php | 5 + .../AnnotationsMethodParameterReflection.php | 5 + src/Reflection/AttributeReflection.php | 33 ++++ src/Reflection/AttributeReflectionFactory.php | 134 +++++++++++++ .../BetterReflectionProvider.php | 5 + src/Reflection/ClassConstantReflection.php | 5 + src/Reflection/ClassReflection.php | 16 +- .../Dummy/ChangedTypeMethodReflection.php | 5 + .../Dummy/ChangedTypePropertyReflection.php | 5 + .../Dummy/DummyClassConstantReflection.php | 5 + .../Dummy/DummyConstructorReflection.php | 5 + .../Dummy/DummyMethodReflection.php | 5 + .../Dummy/DummyPropertyReflection.php | 5 + src/Reflection/EnumCaseReflection.php | 18 +- src/Reflection/ExtendedMethodReflection.php | 5 + .../ExtendedParameterReflection.php | 5 + src/Reflection/ExtendedPropertyReflection.php | 5 + src/Reflection/FunctionReflection.php | 5 + src/Reflection/FunctionReflectionFactory.php | 2 + .../GenericParametersAcceptorResolver.php | 1 + src/Reflection/InitializerExprContext.php | 26 +++ .../ExtendedNativeParameterReflection.php | 10 + .../Native/NativeFunctionReflection.php | 8 + .../Native/NativeMethodReflection.php | 8 + src/Reflection/ParametersAcceptorSelector.php | 5 + .../Php/ClosureCallMethodReflection.php | 6 + .../Php/EnumCasesMethodReflection.php | 5 + src/Reflection/Php/EnumPropertyReflection.php | 5 + src/Reflection/Php/ExitFunctionReflection.php | 6 + src/Reflection/Php/ExtendedDummyParameter.php | 10 + .../Php/PhpClassReflectionExtension.php | 9 +- .../PhpFunctionFromParserNodeReflection.php | 11 ++ src/Reflection/Php/PhpFunctionReflection.php | 12 ++ .../Php/PhpMethodFromParserNodeReflection.php | 7 + src/Reflection/Php/PhpMethodReflection.php | 16 ++ .../Php/PhpMethodReflectionFactory.php | 3 + .../PhpParameterFromParserNodeReflection.php | 10 + src/Reflection/Php/PhpParameterReflection.php | 10 + src/Reflection/Php/PhpPropertyReflection.php | 10 + .../Php/SimpleXMLElementProperty.php | 5 + .../Php/UniversalObjectCrateProperty.php | 5 + .../RealClassClassConstantReflection.php | 9 + .../ResolvedFunctionVariantWithOriginal.php | 1 + src/Reflection/ResolvedMethodReflection.php | 5 + src/Reflection/ResolvedPropertyReflection.php | 5 + .../NativeFunctionReflectionProvider.php | 15 +- ...ackUnresolvedMethodPrototypeReflection.php | 1 + ...ypeUnresolvedMethodPrototypeReflection.php | 1 + .../Type/IntersectionTypeMethodReflection.php | 5 + .../IntersectionTypePropertyReflection.php | 5 + .../Type/UnionTypeMethodReflection.php | 5 + .../Type/UnionTypePropertyReflection.php | 5 + .../WrappedExtendedMethodReflection.php | 6 + .../WrappedExtendedPropertyReflection.php | 5 + .../Properties/FoundPropertyReflection.php | 5 + src/Testing/PHPStanTestCase.php | 2 + src/Testing/RuleTestCase.php | 2 + src/Testing/TypeInferenceTestCase.php | 2 + src/Type/ObjectShapePropertyReflection.php | 5 + tests/PHPStan/Analyser/AnalyserTest.php | 2 + .../AttributeReflectionFromNodeRuleTest.php | 96 ++++++++++ .../Reflection/AttributeReflectionTest.php | 179 ++++++++++++++++++ .../data/attribute-reflection-enum.php | 11 ++ .../Reflection/data/attribute-reflection.php | 62 ++++++ 70 files changed, 939 insertions(+), 5 deletions(-) create mode 100644 src/Reflection/AttributeReflection.php create mode 100644 src/Reflection/AttributeReflectionFactory.php create mode 100644 tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php create mode 100644 tests/PHPStan/Reflection/AttributeReflectionTest.php create mode 100644 tests/PHPStan/Reflection/data/attribute-reflection-enum.php create mode 100644 tests/PHPStan/Reflection/data/attribute-reflection.php diff --git a/conf/config.neon b/conf/config.neon index bd13fe06122..b9f6445b081 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -677,6 +677,9 @@ services: - class: PHPStan\Process\CpuCoreCounter + - + class: PHPStan\Reflection\AttributeReflectionFactory + - implement: PHPStan\Reflection\FunctionReflectionFactory arguments: diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 22f709cbc96..f65a599113f 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -7,6 +7,7 @@ use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; @@ -31,6 +32,7 @@ public function __construct( private NodeScopeResolver $nodeScopeResolver, private RicherScopeGetTypeHelper $richerScopeGetTypeHelper, private PhpVersion $phpVersion, + private AttributeReflectionFactory $attributeReflectionFactory, private int|array|null $configPhpVersion, private ConstantResolver $constantResolver, ) @@ -71,6 +73,7 @@ public function create( $this->constantResolver, $context, $this->phpVersion, + $this->attributeReflectionFactory, $this->configPhpVersion, $declareStrictTypes, $function, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 657cb8c8656..1d4154261d1 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -7,6 +7,7 @@ use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; @@ -56,6 +57,7 @@ public function create( $this->container->getByType(ConstantResolver::class), $context, $this->container->getByType(PhpVersion::class), + $this->container->getByType(AttributeReflectionFactory::class), $this->container->getParameter('phpVersion'), $declareStrictTypes, $function, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 27b2f00f454..073a08368ca 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -26,7 +26,10 @@ use PhpParser\Node\InterpolatedStringPart; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\PropertyHook; use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PhpParser\NodeFinder; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\Expr\AlwaysRememberedExpr; @@ -52,6 +55,8 @@ use PHPStan\Php\PhpVersions; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\Callables\SimpleThrowPoint; @@ -212,6 +217,7 @@ public function __construct( private ConstantResolver $constantResolver, private ScopeContext $context, private PhpVersion $phpVersion, + private AttributeReflectionFactory $attributeReflectionFactory, private int|array|null $configPhpVersion, private bool $declareStrictTypes = false, private PhpFunctionFromParserNodeReflection|null $function = null, @@ -2974,6 +2980,7 @@ public function enterClassMethod( $this->getRealParameterTypes($classMethod), array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes), $this->getRealParameterDefaultValues($classMethod), + $this->getParameterAttributes($classMethod), $this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)), $phpDocReturnType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocReturnType)) : null, $throwType, @@ -2990,6 +2997,7 @@ public function enterClassMethod( $immediatelyInvokedCallableParameters, array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters), $isConstructor, + $this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)), ), !$classMethod->isStatic(), ); @@ -3059,6 +3067,7 @@ public function enterPropertyHook( $realParameterTypes, $phpDocParameterTypes, [], + $this->getParameterAttributes($hook), $realReturnType, $phpDocReturnType, $throwType, @@ -3075,6 +3084,7 @@ public function enterPropertyHook( [], [], false, + $this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)), ), true, ); @@ -3138,6 +3148,27 @@ private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): return $realParameterDefaultValues; } + /** + * @return array> + */ + private function getParameterAttributes(ClassMethod|Function_|PropertyHook $functionLike): array + { + $parameterAttributes = []; + $className = null; + if ($this->isInClass()) { + $className = $this->getClassReflection()->getName(); + } + foreach ($functionLike->getParams() as $parameter) { + if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { + throw new ShouldNotHappenException(); + } + + $parameterAttributes[$parameter->var->name] = $this->attributeReflectionFactory->fromAttrGroups($parameter->attrGroups, InitializerExprContext::fromStubParameter($className, $this->getFile(), $functionLike)); + } + + return $parameterAttributes; + } + /** * @api * @param Type[] $phpDocParameterTypes @@ -3171,6 +3202,7 @@ public function enterFunction( $this->getRealParameterTypes($function), array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes), $this->getRealParameterDefaultValues($function), + $this->getParameterAttributes($function), $this->getFunctionType($function->returnType, $function->returnType === null, false), $phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null, $throwType, @@ -3184,6 +3216,7 @@ public function enterFunction( array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes), $immediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, + $this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)), ), false, ); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b6c5798b8d8..d5fde308135 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -132,6 +132,7 @@ use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\PhpDoc\Tag\VarTag; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\Callables\CallableParametersAcceptor; use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\Callables\SimpleThrowPoint; @@ -254,6 +255,7 @@ public function __construct( private readonly StubPhpDocProvider $stubPhpDocProvider, private readonly PhpVersion $phpVersion, private readonly SignatureMapProvider $signatureMapProvider, + private readonly AttributeReflectionFactory $attributeReflectionFactory, private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver, private readonly FileHelper $fileHelper, private readonly TypeSpecifier $typeSpecifier, @@ -2134,6 +2136,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 865abdbe044..b7aab264ff5 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -176,4 +176,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index cc1994bc9aa..ea0d7e24189 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -143,4 +143,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index 4f6b6407859..b01a6db6ff8 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -75,4 +75,9 @@ public function getDefaultValue(): ?Type return $this->defaultValue; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/AttributeReflection.php b/src/Reflection/AttributeReflection.php new file mode 100644 index 00000000000..74d68742238 --- /dev/null +++ b/src/Reflection/AttributeReflection.php @@ -0,0 +1,33 @@ + $argumentTypes + */ + public function __construct(private string $name, private array $argumentTypes) + { + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getArgumentTypes(): array + { + return $this->argumentTypes; + } + +} diff --git a/src/Reflection/AttributeReflectionFactory.php b/src/Reflection/AttributeReflectionFactory.php new file mode 100644 index 00000000000..74a7d0efaa6 --- /dev/null +++ b/src/Reflection/AttributeReflectionFactory.php @@ -0,0 +1,134 @@ + $reflections + * @return list + */ + public function fromNativeReflection(array $reflections, InitializerExprContext $context): array + { + $attributes = []; + foreach ($reflections as $reflection) { + $attribute = $this->fromNameAndArgumentExpressions($reflection->getName(), $reflection->getArgumentsExpressions(), $context); + if ($attribute === null) { + continue; + } + + $attributes[] = $attribute; + } + + return $attributes; + } + + /** + * @param AttributeGroup[] $attrGroups + * @return list + */ + public function fromAttrGroups(array $attrGroups, InitializerExprContext $context): array + { + $attributes = []; + foreach ($attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $arguments = []; + foreach ($attr->args as $i => $arg) { + if ($arg->name === null) { + $argName = $i; + } else { + $argName = $arg->name->toString(); + } + + $arguments[$argName] = $arg->value; + } + $attributeReflection = $this->fromNameAndArgumentExpressions($attr->name->toString(), $arguments, $context); + if ($attributeReflection === null) { + continue; + } + + $attributes[] = $attributeReflection; + } + } + + return $attributes; + } + + /** + * @param array $arguments + */ + private function fromNameAndArgumentExpressions(string $name, array $arguments, InitializerExprContext $context): ?AttributeReflection + { + if (count($arguments) === 0) { + return new AttributeReflection($name, []); + } + + $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); + if (!$reflectionProvider->hasClass($name)) { + return null; + } + + $classReflection = $reflectionProvider->getClass($name); + if (!$classReflection->hasConstructor()) { + return null; + } + + if (!$classReflection->isAttributeClass()) { + return null; + } + + $constructor = $classReflection->getConstructor(); + $parameters = $constructor->getOnlyVariant()->getParameters(); + $namedArgTypes = []; + foreach ($arguments as $i => $argExpr) { + if (is_int($i)) { + if (isset($parameters[$i])) { + $namedArgTypes[$parameters[$i]->getName()] = $this->initializerExprTypeResolver->getType($argExpr, $context); + continue; + } + if (count($parameters) > 0) { + $lastParameter = $parameters[count($parameters) - 1]; + if ($lastParameter->isVariadic()) { + $parameterName = $lastParameter->getName(); + if (array_key_exists($parameterName, $namedArgTypes)) { + $namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $this->initializerExprTypeResolver->getType($argExpr, $context)); + continue; + } + $namedArgTypes[$parameterName] = $this->initializerExprTypeResolver->getType($argExpr, $context); + } + } + continue; + } + + foreach ($parameters as $parameter) { + if ($parameter->getName() !== $i) { + continue; + } + + $namedArgTypes[$i] = $this->initializerExprTypeResolver->getType($argExpr, $context); + break; + } + } + + return new AttributeReflection($classReflection->getName(), $namedArgTypes); + } + +} diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 72f918f62bc..4029d265546 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -31,6 +31,7 @@ use PHPStan\PhpDoc\Tag\ParamClosureThisTag; use PHPStan\PhpDoc\Tag\ParamOutTag; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassNameHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Constant\RuntimeConstantReflection; @@ -93,6 +94,7 @@ public function __construct( private FileHelper $fileHelper, private PhpStormStubsSourceStubber $phpstormStubsSourceStubber, private SignatureMapProvider $signatureMapProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private array $universalObjectCratesClasses, ) { @@ -146,6 +148,7 @@ public function getClass(string $className): ClassReflection $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), @@ -240,6 +243,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), @@ -354,6 +358,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection array_map(static fn (ParamOutTag $paramOutTag): Type => $paramOutTag->getType(), $phpDocParameterOutTags), $phpDocParameterImmediatelyInvokedCallable, array_map(static fn (ParamClosureThisTag $tag): Type => $tag->getType(), $phpDocParameterClosureThisTypeTags), + $this->attributeReflectionFactory->fromNativeReflection($reflectionFunction->getAttributes(), InitializerExprContext::fromFunction($reflectionFunction->getName(), $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null)), ); } diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index cafc2013416..6d0452caff5 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -21,4 +21,9 @@ public function hasNativeType(): bool; public function getNativeType(): ?Type; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 909a6b50b91..334311302ad 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -155,6 +155,7 @@ public function __construct( private PhpDocInheritanceResolver $phpDocInheritanceResolver, private PhpVersion $phpVersion, private SignatureMapProvider $signatureMapProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, private array $allowedSubTypesClassReflectionExtensions, @@ -773,7 +774,7 @@ public function getEnumCases(): array $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext); } $caseName = $case->getName(); - $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType); + $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); } return $this->enumCases = $cases; @@ -799,7 +800,7 @@ public function getEnumCase(string $name): EnumCaseReflection $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this)); } - return new EnumCaseReflection($this, $case, $valueType); + return new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); } public function isClass(): bool @@ -1092,6 +1093,7 @@ public function getConstant(string $name): ClassConstantReflection $isDeprecated, $isInternal, $isFinal, + $this->attributeReflectionFactory->fromNativeReflection($reflectionConstant->getAttributes(), InitializerExprContext::fromClass($declaringClass->getName(), $fileName)), ); } return $this->constants[$name]; @@ -1322,6 +1324,14 @@ private function findAttributeFlags(): ?int return null; } + /** + * @return list + */ + public function getAttributes(): array + { + return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())); + } + public function getAttributeClassFlags(): int { $flags = $this->findAttributeFlags(); @@ -1505,6 +1515,7 @@ public function withTypes(array $types): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, $this->allowedSubTypesClassReflectionExtensions, @@ -1534,6 +1545,7 @@ public function withVariances(array $variances): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, $this->allowedSubTypesClassReflectionExtensions, diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 3b3279596aa..932d100db21 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -149,4 +149,9 @@ public function isPure(): TrinaryLogic return $this->reflection->isPure(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index f235b2e6e3f..0421bb3ff9d 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -141,4 +141,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/Dummy/DummyClassConstantReflection.php b/src/Reflection/Dummy/DummyClassConstantReflection.php index e38a8740dc5..ffc6afe7c9d 100644 --- a/src/Reflection/Dummy/DummyClassConstantReflection.php +++ b/src/Reflection/Dummy/DummyClassConstantReflection.php @@ -106,4 +106,9 @@ public function getNativeType(): ?Type return null; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index e3dff92c38b..4c2efda773e 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -147,4 +147,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index c02b5ea3705..a67f79e00b0 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -139,4 +139,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index c2c6d4c7682..133629a45c5 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -137,4 +137,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/EnumCaseReflection.php b/src/Reflection/EnumCaseReflection.php index 7c250f0cf59..a84fd205efd 100644 --- a/src/Reflection/EnumCaseReflection.php +++ b/src/Reflection/EnumCaseReflection.php @@ -14,7 +14,15 @@ final class EnumCaseReflection { - public function __construct(private ClassReflection $declaringEnum, private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, private ?Type $backingValueType) + /** + * @param list $attributes + */ + public function __construct( + private ClassReflection $declaringEnum, + private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, + private ?Type $backingValueType, + private array $attributes, + ) { } @@ -48,4 +56,12 @@ public function getDeprecatedDescription(): ?string return null; } + /** + * @return list + */ + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index b49a71bb1a3..03a9a2b2eab 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -58,4 +58,9 @@ public function isAbstract(): TrinaryLogic|bool; */ public function isPure(): TrinaryLogic; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ExtendedParameterReflection.php b/src/Reflection/ExtendedParameterReflection.php index aff5f65822b..ab50b76bd8f 100644 --- a/src/Reflection/ExtendedParameterReflection.php +++ b/src/Reflection/ExtendedParameterReflection.php @@ -21,4 +21,9 @@ public function isImmediatelyInvokedCallable(): TrinaryLogic; public function getClosureThisType(): ?Type; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 63b6246dd33..25f79396a16 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -54,4 +54,9 @@ public function isProtectedSet(): bool; public function isPrivateSet(): bool; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 33b355b8446..297e4dd7d30 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -57,4 +57,9 @@ public function returnsByReference(): TrinaryLogic; */ public function isPure(): TrinaryLogic; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/FunctionReflectionFactory.php b/src/Reflection/FunctionReflectionFactory.php index 405eea46b11..993bf34b3b4 100644 --- a/src/Reflection/FunctionReflectionFactory.php +++ b/src/Reflection/FunctionReflectionFactory.php @@ -15,6 +15,7 @@ interface FunctionReflectionFactory * @param array $phpDocParameterOutTypes * @param array $phpDocParameterImmediatelyInvokedCallable * @param array $phpDocParameterClosureThisTypes + * @param list $attributes */ public function create( ReflectionFunction $reflection, @@ -33,6 +34,7 @@ public function create( array $phpDocParameterOutTypes, array $phpDocParameterImmediatelyInvokedCallable, array $phpDocParameterClosureThisTypes, + array $attributes, ): PhpFunctionReflection; } diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index e680908c32f..35f70bf37dc 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -103,6 +103,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc null, TrinaryLogic::createMaybe(), null, + [], ), $parametersAcceptor->getParameters()), $parametersAcceptor->isVariadic(), $parametersAcceptor->getReturnType(), diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index 650408f3498..e6fb8ab6e5b 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -90,6 +90,32 @@ public static function fromClass(string $className, ?string $fileName): self ); } + public static function fromFunction(string $functionName, ?string $fileName): self + { + return new self( + $fileName, + self::parseNamespace($functionName), + null, + null, + $functionName, + $functionName, + null, + ); + } + + public static function fromClassMethod(string $className, ?string $traitName, string $methodName, ?string $fileName): self + { + return new self( + $fileName, + self::parseNamespace($className), + $className, + $traitName, + $methodName, + sprintf('%s::%s', $className, $methodName), + null, + ); + } + public static function fromReflectionParameter(ReflectionParameter $parameter): self { $declaringFunction = $parameter->getDeclaringFunction(); diff --git a/src/Reflection/Native/ExtendedNativeParameterReflection.php b/src/Reflection/Native/ExtendedNativeParameterReflection.php index 90c653484bb..00e2ea1a99e 100644 --- a/src/Reflection/Native/ExtendedNativeParameterReflection.php +++ b/src/Reflection/Native/ExtendedNativeParameterReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Native; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -11,6 +12,9 @@ final class ExtendedNativeParameterReflection implements ExtendedParameterReflection { + /** + * @param list $attributes + */ public function __construct( private string $name, private bool $optional, @@ -23,6 +27,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -87,4 +92,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 730f8c61e91..7668d51f9e6 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Native; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; @@ -20,6 +21,7 @@ final class NativeFunctionReflection implements FunctionReflection /** * @param list $variants * @param list|null $namedArgumentsVariants + * @param list $attributes */ public function __construct( private string $name, @@ -32,6 +34,7 @@ public function __construct( private ?string $phpDocComment, ?TrinaryLogic $returnsByReference, private bool $acceptsNamedArguments, + private array $attributes, ) { $this->assertions = $assertions ?? Assertions::createEmpty(); @@ -142,4 +145,9 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 731d4973fe6..a9ec6063d8e 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -24,6 +25,7 @@ final class NativeMethodReflection implements ExtendedMethodReflection /** * @param list $variants * @param list|null $namedArgumentsVariants + * @param list $attributes */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -37,6 +39,7 @@ public function __construct( private bool $acceptsNamedArguments, private ?Type $selfOutType, private ?string $phpDocComment, + private array $attributes, ) { } @@ -215,4 +218,9 @@ public function returnsByReference(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 703d3458065..81bc3a2a997 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -648,6 +648,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null, $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(), $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null, + $parameter instanceof ExtendedParameterReflection ? $parameter->getAttributes() : [], ); continue; } @@ -667,6 +668,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $outType = $parameters[$i]->getOutType(); $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable(); $closureThisType = $parameters[$i]->getClosureThisType(); + $attributes = $parameters[$i]->getAttributes(); if ($parameter instanceof ExtendedParameterReflection) { $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType()); $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType()); @@ -684,6 +686,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc } $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable); + $attributes = array_merge($attributes, $parameter->getAttributes()); } else { $nativeType = new MixedType(); $phpDocType = $type; @@ -704,6 +707,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $outType, $immediatelyInvokedCallable, $closureThisType, + $attributes, ); if ($isVariadic) { @@ -798,6 +802,7 @@ private static function wrapParameter(ParameterReflection $parameter): ExtendedP null, TrinaryLogic::createMaybe(), null, + [], ); } diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index e28f4cd2594..ae8292e32df 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -96,6 +96,7 @@ public function getVariants(): array null, TrinaryLogic::createMaybe(), null, + [], ), $parameters), $this->closureType->isVariadic(), $this->closureType->getReturnType(), @@ -186,4 +187,9 @@ public function isPure(): TrinaryLogic return $this->nativeMethodReflection->isPure(); } + public function getAttributes(): array + { + return $this->nativeMethodReflection->getAttributes(); + } + } diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index 2758c04c27f..61ee5d767b6 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -151,4 +151,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index a74bb419ff4..ca92d9258c6 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -137,4 +137,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index 76c8e7cf7a2..4020bbdc090 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -58,6 +58,7 @@ public function getVariants(): array null, TrinaryLogic::createNo(), null, + [], ), ], false, @@ -137,4 +138,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createNo(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/ExtendedDummyParameter.php b/src/Reflection/Php/ExtendedDummyParameter.php index 43151a7a7f4..19a917e0a17 100644 --- a/src/Reflection/Php/ExtendedDummyParameter.php +++ b/src/Reflection/Php/ExtendedDummyParameter.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -11,6 +12,9 @@ final class ExtendedDummyParameter extends DummyParameter implements ExtendedParameterReflection { + /** + * @param list $attributes + */ public function __construct( string $name, Type $type, @@ -23,6 +27,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { parent::__construct($name, $type, $optional, $passedByReference, $variadic, $defaultValue); @@ -58,4 +63,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 413f5bca736..a0d4d17471f 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -20,10 +20,12 @@ use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension; use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; @@ -96,6 +98,7 @@ public function __construct( private StubPhpDocProvider $stubPhpDocProvider, private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private FileTypeMapper $fileTypeMapper, + private AttributeReflectionFactory $attributeReflectionFactory, private bool $inferPrivatePropertyTypeFromConstructor, ) { @@ -213,7 +216,7 @@ private function createProperty( $types[] = $value; } - return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false); + return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false, []); } } @@ -425,6 +428,7 @@ private function createProperty( $isInternal, $isReadOnlyByPhpDoc, $isAllowedPrivateMutation, + $this->attributeReflectionFactory->fromNativeReflection($propertyReflection->getAttributes(), InitializerExprContext::fromClass($declaringClassReflection->getName(), $declaringClassReflection->getFileName())), ); } @@ -681,6 +685,7 @@ private function createMethod( $acceptsNamedArguments, $selfOutType, $phpDocComment, + $this->attributeReflectionFactory->fromNativeReflection($methodReflection->getAttributes(), InitializerExprContext::fromClassMethod($declaringClassName, null, $methodReflection->getName(), null)), ); } @@ -849,6 +854,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $immediatelyInvokedCallableParameters, $closureThisParameters, $acceptsNamedArguments, + $this->attributeReflectionFactory->fromNativeReflection($methodReflection->getAttributes(), InitializerExprContext::fromClassMethod($actualDeclaringClass->getName(), $declaringTraitName, $methodReflection->getName(), $actualDeclaringClass->getFileName())), ); } @@ -931,6 +937,7 @@ private function createNativeMethodVariant( $parameterOutType ?? $parameterSignature->getOutType(), $immediatelyInvoked, $closureThisType, + [], ); } diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 809e32f8881..02a5b642afb 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; @@ -41,9 +42,11 @@ class PhpFunctionFromParserNodeReflection implements FunctionReflection, Extende * @param Type[] $realParameterTypes * @param Type[] $phpDocParameterTypes * @param Type[] $realParameterDefaultValues + * @param array> $parameterAttributes * @param Type[] $parameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( FunctionLike $functionLike, @@ -52,6 +55,7 @@ public function __construct( private array $realParameterTypes, private array $phpDocParameterTypes, private array $realParameterDefaultValues, + private array $parameterAttributes, private Type $realReturnType, private ?Type $phpDocReturnType, private ?Type $throwType, @@ -65,6 +69,7 @@ public function __construct( private array $parameterOutTypes, private array $immediatelyInvokedCallableParameters, private array $phpDocClosureThisTypeParameters, + private array $attributes, ) { $this->functionLike = $functionLike; @@ -180,6 +185,7 @@ public function getParameters(): array $this->parameterOutTypes[$parameter->var->name] ?? null, $immediatelyInvokedCallable, $closureThisType, + $this->parameterAttributes[$parameter->var->name] ?? [], ); } @@ -323,4 +329,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->isPure); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index ea6764ec2ec..368ac3f4710 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -8,10 +8,13 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicFunctionsVisitor; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -37,11 +40,13 @@ final class PhpFunctionReflection implements FunctionReflection * @param array $phpDocParameterOutTypes * @param array $phpDocParameterImmediatelyInvokedCallable * @param array $phpDocParameterClosureThisTypes + * @param list $attributes */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionFunction $reflection, private Parser $parser, + private AttributeReflectionFactory $attributeReflectionFactory, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, private ?Type $phpDocReturnType, @@ -57,6 +62,7 @@ public function __construct( private array $phpDocParameterOutTypes, private array $phpDocParameterImmediatelyInvokedCallable, private array $phpDocParameterClosureThisTypes, + private array $attributes, ) { } @@ -127,6 +133,7 @@ private function getParameters(): array $this->phpDocParameterOutTypes[$reflection->getName()] ?? null, $immediatelyInvokedCallable, $this->phpDocParameterClosureThisTypes[$reflection->getName()] ?? null, + $this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)), ); }, $this->reflection->getParameters()); } @@ -262,4 +269,9 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 66dfbd59ed0..cd0e6fcbf68 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -35,8 +36,10 @@ final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeR * @param Type[] $realParameterTypes * @param Type[] $phpDocParameterTypes * @param Type[] $realParameterDefaultValues + * @param array> $parameterAttributes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( private ClassReflection $declaringClass, @@ -47,6 +50,7 @@ public function __construct( array $realParameterTypes, array $phpDocParameterTypes, array $realParameterDefaultValues, + array $parameterAttributes, Type $realReturnType, ?Type $phpDocReturnType, ?Type $throwType, @@ -63,6 +67,7 @@ public function __construct( array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, private bool $isConstructor, + array $attributes, ) { if ($this->classMethod instanceof Node\PropertyHook) { @@ -116,6 +121,7 @@ public function __construct( $realParameterTypes, $phpDocParameterTypes, $realParameterDefaultValues, + $parameterAttributes, $realReturnType, $phpDocReturnType, $throwType, @@ -129,6 +135,7 @@ public function __construct( $parameterOutTypes, $immediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, + $attributes, ); } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 6209a57bc8d..b2b45932a39 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -8,12 +8,15 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicMethodsVisitor; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\ReflectionProvider; @@ -63,6 +66,7 @@ final class PhpMethodReflection implements ExtendedMethodReflection * @param Type[] $phpDocParameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, @@ -70,6 +74,7 @@ public function __construct( private ?ClassReflection $declaringTrait, private ReflectionMethod $reflection, private ReflectionProvider $reflectionProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private Parser $parser, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, @@ -87,6 +92,7 @@ public function __construct( private array $phpDocParameterOutTypes, private array $immediatelyInvokedCallableParameters, private array $phpDocClosureThisTypeParameters, + private array $attributes, ) { } @@ -230,6 +236,7 @@ private function getParameters(): array $this->phpDocParameterOutTypes[$reflection->getName()] ?? null, $this->immediatelyInvokedCallableParameters[$reflection->getName()] ?? TrinaryLogic::createMaybe(), $this->phpDocClosureThisTypeParameters[$reflection->getName()] ?? null, + $this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)), ), $this->reflection->getParameters()); } @@ -458,6 +465,7 @@ public function changePropertyGetHookPhpDocType(Type $phpDocType): self $this->declaringTrait, $this->reflection, $this->reflectionProvider, + $this->attributeReflectionFactory, $this->parser, $this->templateTypeMap, $this->phpDocParameterTypes, @@ -475,6 +483,7 @@ public function changePropertyGetHookPhpDocType(Type $phpDocType): self $this->phpDocParameterOutTypes, $this->immediatelyInvokedCallableParameters, $this->phpDocClosureThisTypeParameters, + $this->attributes, ); } @@ -489,6 +498,7 @@ public function changePropertySetHookPhpDocType(string $parameterName, Type $php $this->declaringTrait, $this->reflection, $this->reflectionProvider, + $this->attributeReflectionFactory, $this->parser, $this->templateTypeMap, $phpDocParameterTypes, @@ -506,7 +516,13 @@ public function changePropertySetHookPhpDocType(string $parameterName, Type $php $this->phpDocParameterOutTypes, $this->immediatelyInvokedCallableParameters, $this->phpDocClosureThisTypeParameters, + $this->attributes, ); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 22028286d3d..ec95a2de818 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -17,6 +18,7 @@ interface PhpMethodReflectionFactory * @param Type[] $phpDocParameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function create( ClassReflection $declaringClass, @@ -38,6 +40,7 @@ public function create( array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, bool $acceptsNamedArguments, + array $attributes, ): PhpMethodReflection; } diff --git a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php index f9bdddc13e0..f048ea71006 100644 --- a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -15,6 +16,9 @@ final class PhpParameterFromParserNodeReflection implements ExtendedParameterRef private ?Type $type = null; + /** + * @param list $attributes + */ public function __construct( private string $name, private bool $optional, @@ -26,6 +30,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -103,4 +108,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index c4c2713c4ba..01f69e3ccb3 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\InitializerExprContext; @@ -21,6 +22,9 @@ final class PhpParameterReflection implements ExtendedParameterReflection private ?Type $nativeType = null; + /** + * @param list $attributes + */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionParameter $reflection, @@ -29,6 +33,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -138,4 +143,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 1284d4a699e..c667c6e5e38 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -6,6 +6,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; @@ -26,6 +27,9 @@ final class PhpPropertyReflection implements ExtendedPropertyReflection private ?Type $type = null; + /** + * @param list $attributes + */ public function __construct( private ClassReflection $declaringClass, private ?ClassReflection $declaringTrait, @@ -39,6 +43,7 @@ public function __construct( private bool $isInternal, private bool $isReadOnlyByPhpDoc, private bool $isAllowedPrivateMutation, + private array $attributes, ) { } @@ -278,4 +283,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index d354ae5fe27..45438eb3c2e 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -151,4 +151,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 0a2f8faf359..0b478d51c5e 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -141,4 +141,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/RealClassClassConstantReflection.php b/src/Reflection/RealClassClassConstantReflection.php index f9194090d3c..96795f64278 100644 --- a/src/Reflection/RealClassClassConstantReflection.php +++ b/src/Reflection/RealClassClassConstantReflection.php @@ -14,6 +14,9 @@ final class RealClassClassConstantReflection implements ClassConstantReflection private ?Type $valueType = null; + /** + * @param list $attributes + */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ClassReflection $declaringClass, @@ -24,6 +27,7 @@ public function __construct( private bool $isDeprecated, private bool $isInternal, private bool $isFinal, + private array $attributes, ) { } @@ -144,4 +148,9 @@ public function getDocComment(): ?string return $docComment; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index d3dd2aa2f1e..d7d2f09acc7 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -120,6 +120,7 @@ function (ExtendedParameterReflection $param): ExtendedParameterReflection { $paramOutType, $param->isImmediatelyInvokedCallable(), $closureThisType, + $param->getAttributes(), ); }, $this->parametersAcceptor->getParameters(), diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 33b70bafe48..bd7ef46f2bc 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -214,4 +214,9 @@ public function isAbstract(): TrinaryLogic return $abstract; } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index e964d99b5ed..797b1ede604 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -203,4 +203,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 6332238c330..766e665115a 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -9,7 +9,9 @@ use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\TrinaryLogic; @@ -28,7 +30,13 @@ final class NativeFunctionReflectionProvider /** @var NativeFunctionReflection[] */ private array $functionMap = []; - public function __construct(private SignatureMapProvider $signatureMapProvider, private Reflector $reflector, private FileTypeMapper $fileTypeMapper, private StubPhpDocProvider $stubPhpDocProvider) + public function __construct( + private SignatureMapProvider $signatureMapProvider, + private Reflector $reflector, + private FileTypeMapper $fileTypeMapper, + private StubPhpDocProvider $stubPhpDocProvider, + private AttributeReflectionFactory $attributeReflectionFactory, + ) { } @@ -52,9 +60,12 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $docComment = null; $returnsByReference = TrinaryLogic::createMaybe(); $acceptsNamedArguments = true; + $fileName = null; + $attributes = []; try { $reflectionFunction = $this->reflector->reflectFunction($functionName); $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); + $attributes = $reflectionFunctionAdapter->getAttributes(); $returnsByReference = TrinaryLogic::createFromBoolean($reflectionFunctionAdapter->returnsReference()); $realFunctionName = $reflectionFunction->getName(); $isDeprecated = $reflectionFunction->isDeprecated(); @@ -124,6 +135,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $phpDoc !== null ? NativeFunctionReflectionProvider::getParamOutTypeFromPhpDoc($parameterSignature->getName(), $phpDoc) : null, $immediatelyInvokedCallable, $closureThisType, + [], ); }, $functionSignature->getParameters()), $functionSignature->isVariadic(), @@ -151,6 +163,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $docComment, $returnsByReference, $acceptsNamedArguments, + $this->attributeReflectionFactory->fromNativeReflection($attributes, InitializerExprContext::fromFunction($realFunctionName, $fileName)), ); $this->functionMap[$lowerCasedFunctionName] = $functionReflection; diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index eaca01ec4ca..3b9572b61ea 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -98,6 +98,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, $parameter->isImmediatelyInvokedCallable(), $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), ), $acceptor->getParameters(), ), diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 1e254b94b7d..6fce0b017fa 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -95,6 +95,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, $parameter->isImmediatelyInvokedCallable(), $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), ), $acceptor->getParameters(), ), diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index c19986d71cd..f0ce213ad79 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -218,4 +218,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract()); } + public function getAttributes(): array + { + return $this->methods[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 9976bab57db..d0729a22604 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -190,4 +190,9 @@ public function isPrivateSet(): bool return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); } + public function getAttributes(): array + { + return $this->properties[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 167493c0b88..3d8015ddaf6 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -195,4 +195,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract()); } + public function getAttributes(): array + { + return $this->methods[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 24e2e911561..eb2d00aed55 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -190,4 +190,9 @@ public function isPrivateSet(): bool return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); } + public function getAttributes(): array + { + return $this->properties[0]->getAttributes(); + } + } diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index c14e51656ee..42bc13b430c 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -75,6 +75,7 @@ public function getVariants(): array null, TrinaryLogic::createMaybe(), null, + [], ), $variant->getParameters()), $variant->isVariadic(), $variant->getReturnType(), @@ -162,4 +163,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index fe64beb0f0b..9f4fb2d9045 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -134,4 +134,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 19e77db7e05..1b14b785aa4 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -173,4 +173,9 @@ public function isPrivateSet(): bool return $this->originalPropertyReflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->originalPropertyReflection->getAttributes(); + } + } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 849fbfac11b..7587ac8d054 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -25,6 +25,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; @@ -163,6 +164,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $container->getByType(NodeScopeResolver::class), new RicherScopeGetTypeHelper($initializerExprTypeResolver), $container->getByType(PhpVersion::class), + $container->getByType(AttributeReflectionFactory::class), $container->getParameter('phpVersion'), $constantResolver, ), diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 8018b441816..b40a8ebca05 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -22,6 +22,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; @@ -90,6 +91,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), $typeSpecifier, diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 614db4f0c5f..da7cbe82c2a 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -16,6 +16,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; @@ -70,6 +71,7 @@ public static function processFile( self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), $typeSpecifier, diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index d5fb99f5463..971a96b2f6c 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -139,4 +139,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 0a27ee28896..6162106ac58 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -19,6 +19,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\AlwaysFailRule; @@ -713,6 +714,7 @@ private function createAnalyser(): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), $phpDocInheritanceResolver, $fileHelper, $typeSpecifier, diff --git a/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php new file mode 100644 index 00000000000..99ef6d9495c --- /dev/null +++ b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php @@ -0,0 +1,96 @@ +> + */ +class AttributeReflectionFromNodeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new /** @implements Rule */ class implements Rule { + + public function getNodeType(): string + { + return NodeAbstract::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof InClassMethodNode) { + $reflection = $node->getMethodReflection(); + } elseif ($node instanceof InFunctionNode) { + $reflection = $node->getFunctionReflection(); + } else { + return []; + } + + $parts = []; + foreach ($reflection->getAttributes() as $attribute) { + $args = []; + foreach ($attribute->getArgumentTypes() as $argName => $argType) { + $args[] = sprintf('%s: %s', $argName, $argType->describe(VerbosityLevel::precise())); + } + + $parts[] = sprintf('#[%s(%s)]', $attribute->getName(), implode(', ', $args)); + } + + if (count($parts) === 0) { + return []; + } + + return [ + RuleErrorBuilder::message(implode(', ', $parts))->identifier('test.attributes')->build(), + ]; + } + + }; + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/attribute-reflection.php'], [ + [ + '#[AttributeReflectionTest\MyAttr(one: 7, two: 8)]', + 28, + ], + [ + '#[AttributeReflectionTest\MyAttr()]', + 39, + ], + [ + '#[AttributeReflectionTest\Nonexistent()]', + 44, + ], + [ + '#[AttributeReflectionTest\MyAttr(one: 11, two: 12)]', + 54, + ], + [ + '#[AttributeReflectionTest\MyAttr(one: 28, two: 29)]', + 59, + ], + ]); + } + +} diff --git a/tests/PHPStan/Reflection/AttributeReflectionTest.php b/tests/PHPStan/Reflection/AttributeReflectionTest.php new file mode 100644 index 00000000000..f0c22f45ecb --- /dev/null +++ b/tests/PHPStan/Reflection/AttributeReflectionTest.php @@ -0,0 +1,179 @@ +createReflectionProvider(); + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction'), null)->getAttributes(), + [ + [MyAttr::class, []], + ], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction2'), null)->getAttributes(), + [ + ['AttributeReflectionTest\\Nonexistent', []], + ], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction3'), null)->getAttributes(), + [], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction4'), null)->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '11', + 'two' => '12', + ], + ], + ], + ]; + + $foo = $reflectionProvider->getClass(Foo::class); + + yield [ + $foo->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '1', + 'two' => '2', + ], + ], + ], + ]; + + yield [ + $foo->getConstant('MY_CONST')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '3', + 'two' => '4', + ], + ], + ], + ]; + + yield [ + $foo->getNativeProperty('prop')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '5', + 'two' => '6', + ], + ], + ], + ]; + + yield [ + $foo->getConstructor()->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '7', + 'two' => '8', + ], + ], + ], + ]; + + if (PHP_VERSION_ID >= 80100) { + $enum = $reflectionProvider->getClass('AttributeReflectionTest\\FooEnum'); + + yield [ + $enum->getEnumCase('TEST')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '15', + 'two' => '16', + ], + ], + ], + ]; + + yield [ + $enum->getEnumCases()['TEST']->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '15', + 'two' => '16', + ], + ], + ], + ]; + } + + yield [ + $foo->getConstructor()->getOnlyVariant()->getParameters()[0]->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '9', + 'two' => '10', + ], + ], + ], + ]; + } + + /** + * @dataProvider dataAttributeReflections + * @param list $attributeReflections + * @param list}> $expectations + */ + public function testAttributeReflections( + array $attributeReflections, + array $expectations, + ): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->assertCount(count($expectations), $attributeReflections); + foreach ($expectations as $i => [$name, $argumentTypes]) { + $attribute = $attributeReflections[$i]; + $this->assertSame($name, $attribute->getName()); + + $attributeArgumentTypes = $attribute->getArgumentTypes(); + $this->assertCount(count($argumentTypes), $attributeArgumentTypes); + + foreach ($argumentTypes as $argumentName => $argumentType) { + $this->assertArrayHasKey($argumentName, $attributeArgumentTypes); + $this->assertSame($argumentType, $attributeArgumentTypes[$argumentName]->describe(VerbosityLevel::precise())); + } + } + } + +} diff --git a/tests/PHPStan/Reflection/data/attribute-reflection-enum.php b/tests/PHPStan/Reflection/data/attribute-reflection-enum.php new file mode 100644 index 00000000000..fd028126794 --- /dev/null +++ b/tests/PHPStan/Reflection/data/attribute-reflection-enum.php @@ -0,0 +1,11 @@ += 8.1 + +namespace AttributeReflectionTest; + +enum FooEnum +{ + + #[MyAttr(one: 15, two: 16)] + case TEST; + +} diff --git a/tests/PHPStan/Reflection/data/attribute-reflection.php b/tests/PHPStan/Reflection/data/attribute-reflection.php new file mode 100644 index 00000000000..34ec36599f5 --- /dev/null +++ b/tests/PHPStan/Reflection/data/attribute-reflection.php @@ -0,0 +1,62 @@ += 8.0 + +namespace AttributeReflectionTest; + +use Attribute; + +#[Attribute] +class MyAttr +{ + + public function __construct($one, $two) + { + + } + +} + +#[MyAttr(1, 2)] +class Foo +{ + + #[MyAttr(one: 3, two: 4)] + public const MY_CONST = 1; + + #[MyAttr(two: 6, one: 5)] + private $prop; + + #[MyAttr(7, 8)] + public function __construct( + #[MyAttr(9, 10)] + int $test + ) + { + + } + +} + +#[MyAttr()] +function myFunction() { + +} + +#[Nonexistent()] +function myFunction2() { + +} + +#[Nonexistent(1, 2)] +function myFunction3() { + +} + +#[MyAttr(11, 12)] +function myFunction4() { + +} + +#[MyAttr(28, two: 29)] +function myFunction5() { + +} From 5cfe0751c77ec6664b0de583d100ef2708e38a12 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 3 Feb 2025 11:07:50 +0100 Subject: [PATCH 1054/3097] AttributeReflectionFromNodeRuleTest - test parameter attributes --- .../AttributeReflectionFromNodeRuleTest.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php index 99ef6d9495c..756ff9e1b12 100644 --- a/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php +++ b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php @@ -51,6 +51,23 @@ public function processNode(Node $node, Scope $scope): array $parts[] = sprintf('#[%s(%s)]', $attribute->getName(), implode(', ', $args)); } + foreach ($reflection->getParameters() as $parameter) { + $parameterAttributes = []; + foreach ($parameter->getAttributes() as $parameterAttribute) { + $parameterArgs = []; + foreach ($parameterAttribute->getArgumentTypes() as $argName => $argType) { + $parameterArgs[] = sprintf('%s: %s', $argName, $argType->describe(VerbosityLevel::precise())); + } + $parameterAttributes[] = sprintf('#[%s(%s)]', $parameterAttribute->getName(), implode(', ', $parameterArgs)); + } + + if (count($parameterAttributes) === 0) { + continue; + } + + $parts[] = sprintf('$%s: %s', $parameter->getName(), implode(', ', $parameterAttributes)); + } + if (count($parts) === 0) { return []; } @@ -71,7 +88,7 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/attribute-reflection.php'], [ [ - '#[AttributeReflectionTest\MyAttr(one: 7, two: 8)]', + '#[AttributeReflectionTest\MyAttr(one: 7, two: 8)], $test: #[AttributeReflectionTest\MyAttr(one: 9, two: 10)]', 28, ], [ From eded2c3a3b8c34232f9eb548f5ab9b0b9a138fac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 3 Feb 2025 11:27:15 +0100 Subject: [PATCH 1055/3097] Standalone null with default value null does not make parameter implicitly nullable --- src/Rules/FunctionDefinitionCheck.php | 7 +++++++ .../Methods/ExistingClassesInTypehintsRuleTest.php | 8 ++++++++ tests/PHPStan/Rules/Methods/data/bug-12501.php | 11 +++++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12501.php diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 327c0a7be43..87b97215de2 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -710,6 +710,13 @@ private function checkImplicitlyNullableType( return null; } + if ($type instanceof Identifier && strtolower($type->name) === 'null') { + return null; + } + if ($type instanceof Name && $type->toLowerString() === 'null') { + return null; + } + if ($type instanceof UnionType) { foreach ($type->types as $innerType) { if ($innerType instanceof Identifier && strtolower($innerType->name) === 'null') { diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index f95708eda30..d52b95e8e8a 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -548,4 +548,12 @@ public function testDeprecatedImplicitlyNullableParameterType(): void ]); } + public function testBug12501(): void + { + if (PHP_VERSION_ID < 80400) { + self::markTestSkipped('This test needs PHP 8.4.'); + } + $this->analyse([__DIR__ . '/data/bug-12501.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12501.php b/tests/PHPStan/Rules/Methods/data/bug-12501.php new file mode 100644 index 00000000000..583a3a9f1b7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12501.php @@ -0,0 +1,11 @@ += 8.4 + +declare(strict_types = 1); + +namespace Bug12501; + +final readonly class EmptyObject { + public function __construct( + public null $value1 = null, + ) {} +} From 166dcbee6c37daf98b06f4aecc0bf7fca4d5244a Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz <80641364+jakubtobiasz@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:32:58 +0100 Subject: [PATCH 1056/3097] Prevent declaring hooked properties as readonly Co-authored-by: Ondrej Mirtes --- Makefile | 2 ++ .../Properties/PropertiesInInterfaceRule.php | 9 +++++++ src/Rules/Properties/PropertyInClassRule.php | 17 ++++++++---- .../PropertiesInInterfaceRuleTest.php | 22 ++++++++++++++++ .../Properties/PropertyInClassRuleTest.php | 26 +++++++++++++++++++ ...ked-properties-without-bodies-in-class.php | 2 +- .../readonly-property-hooks-in-interface.php | 12 +++++++++ .../data/readonly-property-hooks.php | 20 ++++++++++++++ 8 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php create mode 100644 tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php diff --git a/Makefile b/Makefile index b0379d18e72..ab0a439c3b5 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,8 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/non-abstract-hooked-properties-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ + --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php \ + --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ diff --git a/src/Rules/Properties/PropertiesInInterfaceRule.php b/src/Rules/Properties/PropertiesInInterfaceRule.php index df5354adb37..b6a3c7d4933 100644 --- a/src/Rules/Properties/PropertiesInInterfaceRule.php +++ b/src/Rules/Properties/PropertiesInInterfaceRule.php @@ -57,6 +57,15 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($node->isReadOnly()) { + return [ + RuleErrorBuilder::message('Interfaces cannot include readonly hooked properties.') + ->nonIgnorable() + ->identifier('property.readOnlyInInterface') + ->build(), + ]; + } + if ($this->hasAnyHookBody($node)) { return [ RuleErrorBuilder::message('Interfaces cannot include property hooks with bodies.') diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index 3500959541e..50e3880013d 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -72,11 +72,7 @@ public function processNode(Node $node, Scope $scope): array ->build(), ]; } - - return []; - } - - if (!$this->doAllHooksHaveBody($node)) { + } elseif (!$this->doAllHooksHaveBody($node)) { return [ RuleErrorBuilder::message('Non-abstract properties cannot include hooks without bodies.') ->nonIgnorable() @@ -85,6 +81,17 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($node->isReadOnly()) { + if ($node->hasHooks()) { + return [ + RuleErrorBuilder::message('Hooked properties cannot be readonly.') + ->nonIgnorable() + ->identifier('property.hookReadOnly') + ->build(), + ]; + } + } + return []; } diff --git a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php index de425931dab..dc011f8a825 100644 --- a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php @@ -118,4 +118,26 @@ public function testPhp84AndPropertyHooksWithBodiesInInterface(): void ]); } + public function testPhp84AndReadonlyPropertyHooksInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/readonly-property-hooks-in-interface.php'], [ + [ + 'Interfaces cannot include readonly hooked properties.', + 7, + ], + [ + 'Interfaces cannot include readonly hooked properties.', + 9, + ], + [ + 'Interfaces cannot include readonly hooked properties.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index f6fde1e51c9..6d61e5c7cc1 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -151,4 +151,30 @@ public function testPhp84AndAbstractHookedPropertiesWithBodies(): void ]); } + public function testPhp84AndReadonlyHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/readonly-property-hooks.php'], [ + [ + 'Hooked properties cannot be readonly.', + 7, + ], + [ + 'Hooked properties cannot be readonly.', + 12, + ], + [ + 'Hooked properties cannot be readonly.', + 14, + ], + [ + 'Hooked properties cannot be readonly.', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php index fb0edcc3ce4..24238e5c14f 100644 --- a/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php +++ b/tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php @@ -12,7 +12,7 @@ class AbstractPerson class PromotedHookedPropertyWithoutVisibility { - public function __construct(mixed $test { get; }) + public function __construct(public mixed $test { get; }) { } diff --git a/tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php b/tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php new file mode 100644 index 00000000000..53aa244fa9f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php @@ -0,0 +1,12 @@ + $this->firstName; + set => $this->firstName; + } + + public readonly string $middleName { get => $this->middleName; } + + public readonly string $lastName { set => $this->lastName; } +} + +abstract class HiWorld +{ + public abstract readonly string $firstName { get { return 'jake'; } set; } +} From 471b818a54a4290c8ea86876f04bde53b4c94c2f Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 5 Feb 2025 19:22:37 +0900 Subject: [PATCH 1057/3097] Improved support for enum-string types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- src/PhpDoc/TypeNodeResolver.php | 11 ++++- .../Analyser/nsrt/more-type-strings-php8.php | 48 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/more-types.php | 2 +- 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/more-type-strings-php8.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index aa159d23a4f..1b9403b7c5d 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -235,9 +235,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco case 'class-string': case 'interface-string': case 'trait-string': - case 'enum-string': return new ClassStringType(); + case 'enum-string': + return new GenericClassStringType(new ObjectType('UnitEnum')); + case 'callable-string': return new IntersectionType([new StringType(), new CallableType()]); @@ -704,6 +706,13 @@ static function (string $variance): TemplateTypeVariance { } } + return new ErrorType(); + } elseif ($mainTypeName === 'enum-string') { + if (count($genericTypes) === 1) { + $genericType = $genericTypes[0]; + return new GenericClassStringType(TypeCombinator::intersect($genericType, new ObjectType('UnitEnum'))); + } + return new ErrorType(); } elseif ($mainTypeName === 'int') { if (count($genericTypes) === 2) { // int, int<1, 3> diff --git a/tests/PHPStan/Analyser/nsrt/more-type-strings-php8.php b/tests/PHPStan/Analyser/nsrt/more-type-strings-php8.php new file mode 100644 index 00000000000..d81c6e99071 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/more-type-strings-php8.php @@ -0,0 +1,48 @@ += 8.1 + +namespace MoreTypeStringsPhp8; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @param interface-string $interfaceString + * @param trait-string $traitString + * @param interface-string $genericInterfaceString + * @param trait-string $genericTraitString + * @param enum-string $genericEnumString + * @param enum-string $genericInterfaceEnumString + */ + public function doFoo( + string $interfaceString, + string $traitString, + string $genericInterfaceString, + string $genericTraitString, + string $genericEnumString, + string $genericInterfaceEnumString, + ): void + { + assertType('class-string', $interfaceString); + assertType('class-string', $traitString); + assertType('class-string', $genericInterfaceString); + assertType('string', $genericTraitString); + assertType('class-string', $genericEnumString); + assertType('class-string', $genericInterfaceEnumString); + } + +} + +enum Bar +{ + + case A; + case B; + +} + +interface BuzInterface +{ + +} diff --git a/tests/PHPStan/Analyser/nsrt/more-types.php b/tests/PHPStan/Analyser/nsrt/more-types.php index e98bc06a762..c8ae927b2d8 100644 --- a/tests/PHPStan/Analyser/nsrt/more-types.php +++ b/tests/PHPStan/Analyser/nsrt/more-types.php @@ -42,7 +42,7 @@ public function doFoo( assertType('array&callable(): mixed', $callableArray); assertType('resource', $closedResource); assertType('resource', $openResource); - assertType('class-string', $enumString); + assertType('class-string', $enumString); assertType('literal-string&non-empty-string', $nonEmptyLiteralString); assertType('float|int|int<1, max>|non-falsy-string|true', $nonEmptyScalar); assertType("0|0.0|''|'0'|false", $emptyScalar); From fb02c76bc94570cf68d468bfc909d6992f64193f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 11:27:38 +0100 Subject: [PATCH 1058/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/6947 --- .../ElseIfConstantConditionRuleTest.php | 16 ++++++++++++++++ .../PHPStan/Rules/Comparison/data/bug-6947.php | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-6947.php diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index 8a994666bee..df44e547155 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -140,4 +140,20 @@ public function testBug11674(): void ]); } + public function testBug6947(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-6947.php'], [ + [ + 'Elseif condition is always false.', + 11, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-6947.php b/tests/PHPStan/Rules/Comparison/data/bug-6947.php new file mode 100644 index 00000000000..99223a770ee --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-6947.php @@ -0,0 +1,17 @@ +getValue())) { + + } elseif (is_array($this->getValue())) { + + } + } + + abstract public function getValue():int|float|string|null; +} From 2539ab7dc145b90875c4e83ccd1dbc3fb4f77dc1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 11:31:06 +0100 Subject: [PATCH 1059/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/10240 Closes https://github.com/phpstan/phpstan/issues/10488 Closes https://github.com/phpstan/phpstan/issues/12073 --- .../Rules/Methods/MethodSignatureRuleTest.php | 33 +++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-10240.php | 35 ++++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-10488.php | 17 +++++++++ .../PHPStan/Rules/Methods/data/bug-12073.php | 37 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10240.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-10488.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12073.php diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 36fa7499877..54c9f5c4d17 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -568,4 +568,37 @@ public function testGenericStaticType(): void $this->analyse([__DIR__ . '/data/method-signature-generic-static-type.php'], []); } + public function testBug10240(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-10240.php'], []); + } + + public function testBug10488(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-10488.php'], []); + } + + public function testBug12073(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-12073.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10240.php b/tests/PHPStan/Rules/Methods/data/bug-10240.php new file mode 100644 index 00000000000..60047d13a64 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10240.php @@ -0,0 +1,35 @@ += 8.0 + +namespace Bug10240; + +interface MyInterface +{ + /** + * @phpstan-param truthy-string $truthyStrParam + */ + public function doStuff( + string $truthyStrParam, + ): void; +} + +trait MyTrait +{ + /** + * @phpstan-param truthy-string $truthyStrParam + */ + abstract public function doStuff( + string $truthyStrParam, + ): void; +} + +class MyClass implements MyInterface +{ + use MyTrait; + + public function doStuff( + string $truthyStrParam, + ): void + { + // ... + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-10488.php b/tests/PHPStan/Rules/Methods/data/bug-10488.php new file mode 100644 index 00000000000..fec3d30001e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10488.php @@ -0,0 +1,17 @@ += 8.0 + +namespace Bug10488; + +trait Bar +{ + /** + * @param array $data + */ + + abstract protected function test(array $data): void; +} + +abstract class Foo +{ + use Bar; +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-12073.php b/tests/PHPStan/Rules/Methods/data/bug-12073.php new file mode 100644 index 00000000000..1c41c97c37a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12073.php @@ -0,0 +1,37 @@ + $field + */ + abstract public function field(array $field): static; +} + +class GroupBuilder +{ + use HasFieldBuildersTrait; + + /** @var array */ + private array $group = []; + + private function __construct() + { + } + + /** + * @param array $field + */ + public function field(array $field): static + { + if (! is_array($this->group['fields'] ?? null)) { + $this->group['fields'] = []; + } + + $this->group['fields'][] = $field; + + return $this; + } +} From da7141a07bb4121b5f02b3a50ba6c5c097f6a069 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 11:37:52 +0100 Subject: [PATCH 1060/3097] Fix build --- .../Rules/Comparison/ElseIfConstantConditionRuleTest.php | 2 +- tests/PHPStan/Rules/Comparison/data/bug-6947.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php index df44e547155..f337950a0f0 100644 --- a/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ElseIfConstantConditionRuleTest.php @@ -150,7 +150,7 @@ public function testBug6947(): void $this->analyse([__DIR__ . '/data/bug-6947.php'], [ [ 'Elseif condition is always false.', - 11, + 13, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ]); diff --git a/tests/PHPStan/Rules/Comparison/data/bug-6947.php b/tests/PHPStan/Rules/Comparison/data/bug-6947.php index 99223a770ee..c48ee436974 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-6947.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-6947.php @@ -1,4 +1,6 @@ -= 8.0 + +declare(strict_types = 1); namespace Bug6947; From 10388a9b6c403da3ac8d59b92fed9af52a3ca12d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 11:42:03 +0100 Subject: [PATCH 1061/3097] Continue refactoring (unDRYing) PhpDocBlock --- phpstan-baseline.neon | 10 - src/PhpDoc/PhpDocBlock.php | 239 ++++++++++++----------- src/PhpDoc/PhpDocInheritanceResolver.php | 5 - 3 files changed, 122 insertions(+), 132 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 64c868244da..d9980551b24 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -171,16 +171,6 @@ parameters: count: 1 path: src/Internal/ContainerDynamicReturnTypeExtension.php - - - message: "#^Variable method call on PHPStan\\\\Reflection\\\\ClassReflection\\.$#" - count: 2 - path: src/PhpDoc/PhpDocBlock.php - - - - message: "#^Variable static method call on PHPStan\\\\PhpDoc\\\\PhpDocBlock\\.$#" - count: 1 - path: src/PhpDoc/PhpDocBlock.php - - message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV…' will always evaluate to true\\.$#" count: 1 diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index dd3ebd84b36..786a8e21196 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -4,13 +4,8 @@ use PHPStan\PhpDoc\Tag\AssertTagParameter; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ConstantReflection; -use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Php\PhpMethodReflection; -use PHPStan\Reflection\Php\PhpPropertyReflection; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ResolvedMethodReflection; -use PHPStan\Reflection\ResolvedPropertyReflection; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -114,10 +109,6 @@ public function transformAssertTagParameterWithParameterNameMapping(AssertTagPar return $parameter; } - /** - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - */ public static function resolvePhpDocBlockForProperty( ?string $docComment, ClassReflection $classReflection, @@ -125,19 +116,22 @@ public static function resolvePhpDocBlockForProperty( string $propertyName, ?string $file, ?bool $explicit, - array $originalPositionalParameterNames, // unused - array $newPositionalParameterNames, // unused ): self { - $docBlocksFromParents = self::resolveParentPhpDocBlocks( - self::getParentReflections($classReflection), - $propertyName, - 'hasNativeProperty', - 'getNativeProperty', - __FUNCTION__, - $explicit ?? $docComment !== null, - $newPositionalParameterNames, - ); + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolvePropertyPhpDocBlockFromClass( + $parentReflection, + $propertyName, + $explicit ?? $docComment !== null, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } return new self( $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, @@ -145,43 +139,41 @@ public static function resolvePhpDocBlockForProperty( $classReflection, $trait, $explicit ?? true, - self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + [], $docBlocksFromParents, ); } - /** - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - */ public static function resolvePhpDocBlockForConstant( ?string $docComment, ClassReflection $classReflection, - ?string $trait, // unused string $constantName, ?string $file, ?bool $explicit, - array $originalPositionalParameterNames, // unused - array $newPositionalParameterNames, // unused ): self { - $docBlocksFromParents = self::resolveParentPhpDocBlocks( - self::getParentReflections($classReflection), - $constantName, - 'hasConstant', - 'getConstant', - __FUNCTION__, - $explicit ?? $docComment !== null, - $newPositionalParameterNames, - ); + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolveConstantPhpDocBlockFromClass( + $parentReflection, + $constantName, + $explicit ?? $docComment !== null, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } return new self( $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, $file, $classReflection, - $trait, + null, $explicit ?? true, - self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + [], $docBlocksFromParents, ); } @@ -219,15 +211,21 @@ public static function resolvePhpDocBlockForMethod( $parentReflections[] = $traitReflection; } - $docBlocksFromParents = self::resolveParentPhpDocBlocks( - $parentReflections, - $methodName, - 'hasNativeMethod', - 'getNativeMethod', - __FUNCTION__, - $explicit ?? $docComment !== null, - $newPositionalParameterNames, - ); + $docBlocksFromParents = []; + foreach ($parentReflections as $parentReflection) { + $oneResult = self::resolveMethodPhpDocBlockFromClass( + $parentReflection, + $methodName, + $explicit ?? $docComment !== null, + $newPositionalParameterNames, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } return new self( $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, @@ -262,117 +260,124 @@ private static function remapParameterNames( } /** - * @param array $parentReflections - * @param array $positionalParameterNames - * @return array + * @return array */ - private static function resolveParentPhpDocBlocks( - array $parentReflections, + private static function getParentReflections(ClassReflection $classReflection): array + { + $result = []; + + $parent = $classReflection->getParentClass(); + if ($parent !== null) { + $result[] = $parent; + } + + foreach ($classReflection->getInterfaces() as $interface) { + $result[] = $interface; + } + + return $result; + } + + private static function resolveConstantPhpDocBlockFromClass( + ClassReflection $classReflection, string $name, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, bool $explicit, - array $positionalParameterNames, - ): array + ): ?self { - $result = []; + if ($classReflection->hasConstant($name)) { + $parentReflection = $classReflection->getConstant($name); + if ($parentReflection->isPrivate()) { + return null; + } - foreach ($parentReflections as $parentReflection) { - $oneResult = self::resolvePhpDocBlockFromClass( - $parentReflection, + $classReflection = $parentReflection->getDeclaringClass(); + + return self::resolvePhpDocBlockForConstant( + $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection, $name, - $hasMethodName, - $getMethodName, - $resolveMethodName, + $classReflection->getFileName(), $explicit, - $positionalParameterNames, ); - - if ($oneResult === null) { // Null if it is private or from a wrong trait. - continue; - } - - $result[] = $oneResult; } - return $result; + return null; } - /** - * @return array - */ - private static function getParentReflections(ClassReflection $classReflection): array + private static function resolvePropertyPhpDocBlockFromClass( + ClassReflection $classReflection, + string $name, + bool $explicit, + ): ?self { - $result = []; + if ($classReflection->hasNativeProperty($name)) { + $parentReflection = $classReflection->getNativeProperty($name); + if ($parentReflection->isPrivate()) { + return null; + } - $parent = $classReflection->getParentClass(); - if ($parent !== null) { - $result[] = $parent; - } + $classReflection = $parentReflection->getDeclaringClass(); + $traitReflection = $parentReflection->getDeclaringTrait(); - foreach ($classReflection->getInterfaces() as $interface) { - $result[] = $interface; + $trait = $traitReflection !== null + ? $traitReflection->getName() + : null; + + return self::resolvePhpDocBlockForProperty( + $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection, + $trait, + $name, + $classReflection->getFileName(), + $explicit, + ); } - return $result; + return null; } /** * @param array $positionalParameterNames */ - private static function resolvePhpDocBlockFromClass( + private static function resolveMethodPhpDocBlockFromClass( ClassReflection $classReflection, string $name, - string $hasMethodName, - string $getMethodName, - string $resolveMethodName, bool $explicit, array $positionalParameterNames, ): ?self { - if ($classReflection->$hasMethodName($name)) { - /** @var PropertyReflection|MethodReflection|ConstantReflection $parentReflection */ - $parentReflection = $classReflection->$getMethodName($name); + if ($classReflection->hasNativeMethod($name)) { + $parentReflection = $classReflection->getNativeMethod($name); if ($parentReflection->isPrivate()) { return null; } $classReflection = $parentReflection->getDeclaringClass(); - - if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection) { + $traitReflection = null; + if ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) { $traitReflection = $parentReflection->getDeclaringTrait(); - $positionalMethodParameterNames = []; - } elseif ($parentReflection instanceof MethodReflection) { - $traitReflection = null; - if ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) { - $traitReflection = $parentReflection->getDeclaringTrait(); - } - $methodVariants = $parentReflection->getVariants(); - $positionalMethodParameterNames = []; - $lowercaseMethodName = strtolower($parentReflection->getName()); - if ( - count($methodVariants) === 1 - && $lowercaseMethodName !== '__construct' - && $lowercaseMethodName !== strtolower($parentReflection->getDeclaringClass()->getName()) - ) { - $methodParameters = $methodVariants[0]->getParameters(); - foreach ($methodParameters as $methodParameter) { - $positionalMethodParameterNames[] = $methodParameter->getName(); - } - } else { - $positionalMethodParameterNames = $positionalParameterNames; + } + $methodVariants = $parentReflection->getVariants(); + $positionalMethodParameterNames = []; + $lowercaseMethodName = strtolower($parentReflection->getName()); + if ( + count($methodVariants) === 1 + && $lowercaseMethodName !== '__construct' + && $lowercaseMethodName !== strtolower($parentReflection->getDeclaringClass()->getName()) + ) { + $methodParameters = $methodVariants[0]->getParameters(); + foreach ($methodParameters as $methodParameter) { + $positionalMethodParameterNames[] = $methodParameter->getName(); } } else { - $traitReflection = null; - $positionalMethodParameterNames = []; + $positionalMethodParameterNames = $positionalParameterNames; } $trait = $traitReflection !== null ? $traitReflection->getName() : null; - return self::$resolveMethodName( + return self::resolvePhpDocBlockForMethod( $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, $classReflection, $trait, diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 98366e15eab..5b6aaacc3bd 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -33,8 +33,6 @@ public function resolvePhpDocForProperty( $propertyName, $classReflectionFileName, null, - [], - [], ); return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null, $propertyName, null); @@ -50,12 +48,9 @@ public function resolvePhpDocForConstant( $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForConstant( $docComment, $classReflection, - null, $constantName, $classReflectionFileName, null, - [], - [], ); return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, null, null, null, $constantName); From 49c631a5a6cab485dc87516071fc94c429eb5a6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 14:19:26 +0100 Subject: [PATCH 1062/3097] Fix PHPDoc inheritance from generic trait --- src/PhpDoc/PhpDocBlock.php | 45 ++++++++++++------- ...ait-method-implicit-phpdoc-inheritance.php | 32 +++++++++++++ 2 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/abstract-generic-trait-method-implicit-phpdoc-inheritance.php diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 786a8e21196..8036cd78ee7 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -193,7 +193,22 @@ public static function resolvePhpDocBlockForMethod( array $newPositionalParameterNames, ): self { - $parentReflections = self::getParentReflections($classReflection); + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolveMethodPhpDocBlockFromClass( + $parentReflection, + $methodName, + $explicit ?? $docComment !== null, + $newPositionalParameterNames, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } + foreach ($classReflection->getTraits(true) as $traitReflection) { if (!$traitReflection->hasNativeMethod($methodName)) { continue; @@ -208,23 +223,21 @@ public static function resolvePhpDocBlockForMethod( continue; } - $parentReflections[] = $traitReflection; - } - - $docBlocksFromParents = []; - foreach ($parentReflections as $parentReflection) { - $oneResult = self::resolveMethodPhpDocBlockFromClass( - $parentReflection, - $methodName, - $explicit ?? $docComment !== null, - $newPositionalParameterNames, - ); - - if ($oneResult === null) { // Null if it is private or from a wrong trait. - continue; + $methodVariant = $traitMethod->getOnlyVariant(); + $positionalMethodParameterNames = []; + foreach ($methodVariant->getParameters() as $methodParameter) { + $positionalMethodParameterNames[] = $methodParameter->getName(); } - $docBlocksFromParents[] = $oneResult; + $docBlocksFromParents[] = new self( + $traitMethod->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection->getFileName(), + $classReflection, + $traitReflection->getName(), + $explicit ?? $traitMethod->getDocComment() !== null, + self::remapParameterNames($newPositionalParameterNames, $positionalMethodParameterNames), + [], + ); } return new self( diff --git a/tests/PHPStan/Analyser/nsrt/abstract-generic-trait-method-implicit-phpdoc-inheritance.php b/tests/PHPStan/Analyser/nsrt/abstract-generic-trait-method-implicit-phpdoc-inheritance.php new file mode 100644 index 00000000000..1010ae098b7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/abstract-generic-trait-method-implicit-phpdoc-inheritance.php @@ -0,0 +1,32 @@ + */ + use Foo; + + public function doFoo() + { + return 1; + } + +} + +function (UseFoo $f): void { + assertType('int', $f->doFoo()); +}; From bdf41ae8e207274bf20071ef8fd7961841fc3d02 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 15:50:30 +0100 Subject: [PATCH 1063/3097] Fix CS --- src/PhpDoc/PhpDocBlock.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 376a4bc130e..8036cd78ee7 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -3,9 +3,7 @@ namespace PHPStan\PhpDoc; use PHPStan\PhpDoc\Tag\AssertTagParameter; -use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\ConditionalTypeForParameter; From d5312c05b80f5a869f970ef44bc9a9ad2e3dc55c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Feb 2025 16:00:35 +0100 Subject: [PATCH 1064/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/9657 --- .../MissingMethodReturnTypehintRuleTest.php | 10 ++++++++ tests/PHPStan/Rules/Methods/data/bug-9657.php | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-9657.php diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index dbaf8f68e4d..1e6befc0084 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -111,4 +112,13 @@ public function testGenericStatic(): void ]); } + public function testBug9657(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/bug-9657.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-9657.php b/tests/PHPStan/Rules/Methods/data/bug-9657.php new file mode 100644 index 00000000000..e7a6ac9ba99 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-9657.php @@ -0,0 +1,25 @@ += 8.0 + +namespace Bug9657; + +/** + * @template T + */ +trait Convertable +{ + /** + * @return T + */ + abstract public function toOther(): mixed; +} + +final class Thing +{ + /** @use Convertable> */ + use Convertable; + + public function toOther(): array + { + return []; + } +} From d0b4a27fcf1aa3ff2c77c0a1ca1c980f4fa94823 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Wed, 5 Feb 2025 20:14:02 +0100 Subject: [PATCH 1065/3097] Add a test covering a hooked property in a readonly class --- .../PHPStan/Rules/Properties/PropertyInClassRuleTest.php | 4 ++++ .../Rules/Properties/data/readonly-property-hooks.php | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 6d61e5c7cc1..20541e8f19d 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -174,6 +174,10 @@ public function testPhp84AndReadonlyHookedProperties(): void 'Hooked properties cannot be readonly.', 19, ], + [ + 'Hooked properties cannot be readonly.', + 24, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php b/tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php index f727d148b33..f97afbb5429 100644 --- a/tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php +++ b/tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php @@ -18,3 +18,11 @@ abstract class HiWorld { public abstract readonly string $firstName { get { return 'jake'; } set; } } + +readonly class GoodMorningWorld +{ + public string $firstName { + get => $this->firstName; + set => $this->firstName; + } +} From a4a008e808975aa6a2862006775a2cb7a3c98314 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Fri, 7 Feb 2025 14:04:13 +0000 Subject: [PATCH 1066/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ba01942197a..d18d6cb12d1 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.53.0.0", + "ondrejmirtes/better-reflection": "6.55.0.0", "phpstan/php-8-stubs": "0.4.9", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 8d648923dba..f7395599ee8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "26e17239736b4c0401181c73d03bd60d", + "content-hash": "b9e114c5e98d4e07f318d6455cf53e54", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.53.0.0", + "version": "6.55.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9" + "reference": "ad41e14a5a95478b7e43035b0c1d31625973f0f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9", - "reference": "57ede1054d8e3cfa6341c4d7c49a3c92ccec53b9", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/ad41e14a5a95478b7e43035b0c1d31625973f0f2", + "reference": "ad41e14a5a95478b7e43035b0c1d31625973f0f2", "shasum": "" }, "require": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.5.3", + "phpunit/phpunit": "^11.5.6", "rector/rector": "1.2.10" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.53.0.0" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.55.0.0" }, - "time": "2025-01-24T15:07:34+00:00" + "time": "2025-02-07T13:59:27+00:00" }, { "name": "phpstan/php-8-stubs", From 54a513633d1c2553ff58a2995d7dc0f916a01670 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 7 Feb 2025 15:05:21 +0100 Subject: [PATCH 1067/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/12327 --- .../Analyser/AnalyserIntegrationTest.php | 9 +++++++++ tests/PHPStan/Analyser/data/bug-12327.php | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12327.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index df2d5e5ada4..7028593feec 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -801,6 +801,15 @@ public function testBug7214(): void $this->assertSame(6, $errors[0]->getLine()); } + public function testBug12327(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12327.php'); + $this->assertCount(1, $errors); + + $this->assertSame('Class Bug12327\DoesNotMatter uses unknown trait Bug12327\ThisTriggersTheIssue.', $errors[0]->getMessage()); + $this->assertSame(15, $errors[0]->getLine()); + } + public function testBug7215(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7215.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12327.php b/tests/PHPStan/Analyser/data/bug-12327.php new file mode 100644 index 00000000000..7a61985ff65 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12327.php @@ -0,0 +1,18 @@ + Date: Fri, 7 Feb 2025 15:10:59 +0100 Subject: [PATCH 1068/3097] Enable usage of `ReflectionClass::isSubclassOf()` with invariant `@template T` --- phpstan-baseline.neon | 6 -- ...assIsSubclassOfTypeSpecifyingExtension.php | 40 +++++++++--- .../PHPStan/Analyser/nsrt/bug-12473-types.php | 48 ++++++++++++++ .../ImpossibleCheckTypeMethodCallRuleTest.php | 23 +++++++ .../Rules/Comparison/data/bug-12473.php | 62 +++++++++++++++++++ 5 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12473-types.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-12473.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9ca87d4dcd2..3c27adf2bf2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1593,12 +1593,6 @@ parameters: count: 2 path: src/Type/Php/RangeFunctionReturnTypeExtension.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index df49f7cb169..65f2b787bd3 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -9,10 +9,9 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\MethodReflection; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MethodTypeSpecifyingExtension; -use PHPStan\Type\ObjectType; +use PHPStan\Type\ObjectWithoutClassType; use ReflectionClass; final class ReflectionClassIsSubclassOfTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension @@ -34,24 +33,47 @@ public function isMethodSupported(MethodReflection $methodReflection, MethodCall { return $methodReflection->getName() === 'isSubclassOf' && isset($node->getArgs()[0]) - && $context->true(); + && !$context->null(); } public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { + $calledOnType = $scope->getType($node->var); + $reflectionType = $calledOnType->getTemplateType(ReflectionClass::class, 'T'); + if (!(new ObjectWithoutClassType())->isSuperTypeOf($reflectionType)->yes()) { + return new SpecifiedTypes(); + } + $valueType = $scope->getType($node->getArgs()[0]->value); - if (!$valueType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); + $objectType = $valueType->getClassStringObjectType(); + $narrowingType = new GenericObjectType(ReflectionClass::class, [$objectType]); + + if (!$reflectionType->isSuperTypeOf($objectType)->yes()) { + // cause "always false" error + return $this->typeSpecifier->create( + $node->var, + $narrowingType, + $context, + $scope, + ); + } + + if ($objectType->isSuperTypeOf($reflectionType)->yes()) { + // cause "always true" error + return $this->typeSpecifier->create( + $node->var, + $narrowingType, + $context, + $scope, + ); } return $this->typeSpecifier->create( $node->var, - new GenericObjectType(ReflectionClass::class, [ - new ObjectType($valueType->getValue()), - ]), + $narrowingType, $context, $scope, - ); + )->setAlwaysOverwriteTypes(); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12473-types.php b/tests/PHPStan/Analyser/nsrt/bug-12473-types.php new file mode 100644 index 00000000000..d1f7a0a9cc5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12473-types.php @@ -0,0 +1,48 @@ += 8.4 + +namespace Bug12473Types; + +use ReflectionClass; +use function PHPStan\Testing\assertType; + +class Picture +{ +} + +class PictureUser extends Picture +{ +} + +class PictureProduct extends Picture +{ +} + +/** + * @param class-string $a + */ +function doFoo(string $a): void +{ + $r = new ReflectionClass($a); + assertType('ReflectionClass', $r); + if ($r->isSubclassOf(Picture::class)) { + assertType('ReflectionClass', $r); + } else { + assertType('ReflectionClass', $r); + } + assertType('ReflectionClass|ReflectionClass', $r); +} + +/** + * @param class-string $a + */ +function doFoo2(string $a): void +{ + $r = new ReflectionClass($a); + assertType('ReflectionClass', $r); + if ($r->isSubclassOf(Picture::class)) { + assertType('ReflectionClass', $r); + } else { + assertType('*NEVER*', $r); + } + assertType('ReflectionClass', $r); +} diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index eceaff15f9b..f45e84e1456 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -254,6 +254,29 @@ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLast $this->analyse([__DIR__ . '/data/impossible-method-report-always-true-last-condition.php'], $expectedErrors); } + public function testBug12473(): void + { + $this->treatPhpDocTypesAsCertain = true; + $tip = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + $this->analyse([__DIR__ . '/data/bug-12473.php'], [ + [ + 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\Picture\' will always evaluate to true.', + 39, + $tip, + ], + [ + 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\PictureProduct\' will always evaluate to false.', + 49, + $tip, + ], + [ + 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\PictureUser\' will always evaluate to true.', + 59, + $tip, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12473.php b/tests/PHPStan/Rules/Comparison/data/bug-12473.php new file mode 100644 index 00000000000..44ecbb873f5 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12473.php @@ -0,0 +1,62 @@ + $fqn */ + $fqn = $pictureType; + if ($fqn === Picture::class) { + return Picture::class; + } + $refl = new \ReflectionClass($fqn); + if (!$refl->isSubclassOf(Picture::class)) { + return null; + } + + return $fqn; +} + +/** + * @param class-string $a + */ +function doFoo(string $a): void { + $r = new ReflectionClass($a); + if ($r->isSubclassOf(Picture::class)) { + + } +} + +/** + * @param class-string $a + */ +function doFoo2(string $a): void { + $r = new ReflectionClass($a); + if ($r->isSubclassOf(PictureProduct::class)) { + + } +} + +/** + * @param class-string $a + */ +function doFoo3(string $a): void { + $r = new ReflectionClass($a); + if ($r->isSubclassOf(PictureUser::class)) { + + } +} From d5447821013ea2c0f9bde91f5f2fbd7f7a26c8a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 7 Feb 2025 15:52:59 +0100 Subject: [PATCH 1069/3097] Fix ReflectionClassIsSubclassOfTypeSpecifyingExtension --- ...nClassIsSubclassOfTypeSpecifyingExtension.php | 16 ++++------------ .../ImpossibleCheckTypeMethodCallRuleTest.php | 8 ++++---- .../PHPStan/Rules/Comparison/data/bug-12473.php | 11 +++++++++++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index 65f2b787bd3..f472eab562f 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -12,6 +12,7 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MethodTypeSpecifyingExtension; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\TypeCombinator; use ReflectionClass; final class ReflectionClassIsSubclassOfTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension @@ -46,20 +47,11 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod $valueType = $scope->getType($node->getArgs()[0]->value); $objectType = $valueType->getClassStringObjectType(); - $narrowingType = new GenericObjectType(ReflectionClass::class, [$objectType]); - if (!$reflectionType->isSuperTypeOf($objectType)->yes()) { - // cause "always false" error - return $this->typeSpecifier->create( - $node->var, - $narrowingType, - $context, - $scope, - ); - } + $intersected = TypeCombinator::intersect($reflectionType, $objectType); + $narrowingType = new GenericObjectType(ReflectionClass::class, [$intersected]); - if ($objectType->isSuperTypeOf($reflectionType)->yes()) { - // cause "always true" error + if ($reflectionType->isSuperTypeOf($objectType)->no()) { return $this->typeSpecifier->create( $node->var, $narrowingType, diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index f45e84e1456..6bc2d8e2d5d 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -259,21 +259,21 @@ public function testBug12473(): void $this->treatPhpDocTypesAsCertain = true; $tip = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/data/bug-12473.php'], [ - [ + /*[ 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\Picture\' will always evaluate to true.', 39, $tip, - ], + ],*/ [ 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\PictureProduct\' will always evaluate to false.', 49, $tip, ], - [ + /*[ 'Call to method ReflectionClass::isSubclassOf() with \'Bug12473\\\\PictureUser\' will always evaluate to true.', 59, $tip, - ], + ],*/ ]); } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12473.php b/tests/PHPStan/Rules/Comparison/data/bug-12473.php index 44ecbb873f5..250b7c83a78 100644 --- a/tests/PHPStan/Rules/Comparison/data/bug-12473.php +++ b/tests/PHPStan/Rules/Comparison/data/bug-12473.php @@ -60,3 +60,14 @@ function doFoo3(string $a): void { } } + +/** + * @param ReflectionClass $a + * @param class-string $b + * @return void + */ +function doFoo4(ReflectionClass $a, string $b): void { + if ($a->isSubclassOf($b)) { + + } +}; From 85a895ebd18235032cfbe19894f89b4e0e5b2f0b Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Sun, 9 Feb 2025 09:40:06 +0000 Subject: [PATCH 1070/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index d18d6cb12d1..b35fcf8df87 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.55.0.0", - "phpstan/php-8-stubs": "0.4.9", + "phpstan/php-8-stubs": "0.4.10", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index f7395599ee8..12057cf7b44 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b9e114c5e98d4e07f318d6455cf53e54", + "content-hash": "89a9535ab6639eb29e06b8e07f46c11d", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.9", + "version": "0.4.10", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "1857c330fea6e795af1f7435ed02a18652e7dd8c" + "reference": "97d994e9f3bc539ccabf2392a6e478cdf25a7940" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1857c330fea6e795af1f7435ed02a18652e7dd8c", - "reference": "1857c330fea6e795af1f7435ed02a18652e7dd8c", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/97d994e9f3bc539ccabf2392a6e478cdf25a7940", + "reference": "97d994e9f3bc539ccabf2392a6e478cdf25a7940", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.9" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.10" }, - "time": "2024-12-02T00:21:59+00:00" + "time": "2025-02-09T09:39:32+00:00" }, { "name": "phpstan/phpdoc-parser", From ad610cf827fa1025615ab8870dfb61b625ffbcf2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Feb 2025 10:33:09 +0100 Subject: [PATCH 1071/3097] Test for crash (php-8-stubs mentioning enum in functions map) --- .../Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-12549.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12549.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 7028593feec..e1e77ddd1a9 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1495,6 +1495,12 @@ public function testBug11913(): void $this->assertNoErrors($errors); } + public function testBug12549(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12549.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12549.php b/tests/PHPStan/Analyser/data/bug-12549.php new file mode 100644 index 00000000000..e1bd8c5f0c5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12549.php @@ -0,0 +1,17 @@ +bar(self::OPTION_ROUNDING_MODE); + } + + private function bar(string $v): void + { + } +} From 743f1ab9175a197cb29e067b94b095fa129bb56c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Feb 2025 10:54:36 +0100 Subject: [PATCH 1072/3097] Fix build --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e1e77ddd1a9..76c62869a04 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1497,6 +1497,10 @@ public function testBug11913(): void public function testBug12549(): void { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12549.php'); $this->assertNoErrors($errors); } From b82230a48267ef3d55c568a707a5560b30ccea20 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Feb 2025 10:44:17 +0100 Subject: [PATCH 1073/3097] Virtual property cannot be uninitialized --- src/Analyser/MutatingScope.php | 2 +- src/Node/ClassPropertiesNode.php | 3 +++ src/Rules/IssetCheck.php | 4 +--- ...aultValueTypesAssignedToPropertiesRule.php | 3 +-- .../UninitializedPropertyRuleTest.php | 9 ++++++++ .../Rules/Properties/data/bug-12547.php | 11 +++++++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 15 ++++++++++++ .../Variables/data/isset-virtual-property.php | 23 +++++++++++++++++++ 8 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12547.php create mode 100644 tests/PHPStan/Rules/Variables/data/isset-virtual-property.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 073a08368ca..0c0af430e8a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2633,7 +2633,7 @@ public function hasPropertyNativeType($propertyFetch): bool return false; } - return !$propertyReflection->getNativeType() instanceof MixedType; + return $propertyReflection->hasNativeType(); } private function getTypeFromArrayDimFetch( diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index e1f299a60e4..ec4dcba592f 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -131,6 +131,9 @@ public function getUninitializedProperties( $is = TrinaryLogic::createFromBoolean($property->isPromoted() && !$property->isPromotedFromTrait()); if (!$is->yes() && $classReflection->hasNativeProperty($property->getName())) { $propertyReflection = $classReflection->getNativeProperty($property->getName()); + if ($propertyReflection->isVirtual()->yes()) { + continue; + } foreach ($extensions as $extension) { if (!$extension->isInitialized($propertyReflection, $property->getName())) { diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 89433d32cae..528f037f8b0 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -7,7 +7,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Properties\PropertyDescriptor; use PHPStan\Rules\Properties\PropertyReflectionFinder; -use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -143,8 +142,7 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str return null; } - $nativeType = $propertyReflection->getNativeType(); - if (!$nativeType instanceof MixedType) { + if ($propertyReflection->hasNativeType() && !$propertyReflection->isVirtual()->yes()) { if (!$scope->hasExpressionType($expr)->yes()) { if ($expr instanceof Node\Expr\PropertyFetch) { return $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier); diff --git a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php index 63cd185b7ad..cb68412aa4e 100644 --- a/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/DefaultValueTypesAssignedToPropertiesRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; -use PHPStan\Type\MixedType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -38,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array $propertyReflection = $classReflection->getNativeProperty($node->getName()); $propertyType = $propertyReflection->getWritableType(); - if ($propertyReflection->getNativeType() instanceof MixedType) { + if (!$propertyReflection->hasNativeType()) { if ($default instanceof Node\Expr\ConstFetch && $default->name->toLowerString() === 'null') { return []; } diff --git a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php index d434683e6ab..22c15b707ba 100644 --- a/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php @@ -226,4 +226,13 @@ public function testBug12336(): void $this->analyse([__DIR__ . '/data/bug-12336.php'], []); } + public function testBug12547(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/bug-12547.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12547.php b/tests/PHPStan/Rules/Properties/data/bug-12547.php new file mode 100644 index 00000000000..d4f0950960d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12547.php @@ -0,0 +1,11 @@ += 8.4 + +declare(strict_types = 1); + +namespace Bug12547; + +class Example { + public \DateTimeImmutable $noon { + get => new \DateTimeImmutable('12:00'); + } +} diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index a3e1ed68a2c..a157df7e80b 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -449,4 +449,19 @@ public function testBug10064(): void $this->analyse([__DIR__ . '/data/bug-10064.php'], []); } + public function testVirtualProperty(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/isset-virtual-property.php'], [ + [ + 'Property IssetVirtualProperty\Example::$noon (DateTimeImmutable) in isset() is not nullable.', + 16, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/isset-virtual-property.php b/tests/PHPStan/Rules/Variables/data/isset-virtual-property.php new file mode 100644 index 00000000000..ea9488ba44d --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/isset-virtual-property.php @@ -0,0 +1,23 @@ += 8.4 + +namespace IssetVirtualProperty; + +class Example { + public \DateTimeImmutable $noon { + get => new \DateTimeImmutable('12:00'); + } + + public ?\DateTimeImmutable $nullableNoon { + get => new \DateTimeImmutable('12:00'); + } + + public function doFoo(): void + { + if (isset($this->noon)) { + + } + if (isset($this->nullableNoon)) { + + } + } +} From bb9888a63b7645e1d71bf5bf019441640e7d0d2d Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Fri, 7 Feb 2025 09:42:16 +0100 Subject: [PATCH 1074/3097] Prevent setting a default value for a virtual property --- Makefile | 1 + src/Node/ClassPropertyNode.php | 5 +++++ src/Rules/Properties/PropertyInClassRule.php | 11 ++++++++++ .../Properties/PropertyInClassRuleTest.php | 14 ++++++++++++ .../data/virtual-hooked-properties.php | 22 +++++++++++++++++++ 5 files changed, 53 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php diff --git a/Makefile b/Makefile index ab0a439c3b5..d1fd73e1ddd 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index c8602aef794..b567aa7f7b6 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -160,4 +160,9 @@ public function getHooks(): array return $this->originalNode->hooks; } + public function isVirtual(): bool + { + return $this->classReflection->getNativeProperty($this->name)->isVirtual()->yes(); + } + } diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index 50e3880013d..661fb52c4cc 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -92,6 +92,17 @@ public function processNode(Node $node, Scope $scope): array } } + if ($node->isVirtual()) { + if ($node->getDefault() !== null) { + return [ + RuleErrorBuilder::message('Virtual hooked properties cannot have a default value.') + ->nonIgnorable() + ->identifier('property.virtualDefault') + ->build(), + ]; + } + } + return []; } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 20541e8f19d..6918499f755 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -181,4 +181,18 @@ public function testPhp84AndReadonlyHookedProperties(): void ]); } + public function testPhp84AndVirtualHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/virtual-hooked-properties.php'], [ + [ + 'Virtual hooked properties cannot have a default value.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php new file mode 100644 index 00000000000..4c69f70d60e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php @@ -0,0 +1,22 @@ + $this->firstName; + set => $this->firstName = $value; + } + + public string $middleName { + get => $this->middleName; + set => $this->middleName = $value; + } + + public string $lastName = 'Doe' { + get => 'Smith'; + } + + public string $maidenName = 'Brown'; +} From 39bfb8c6f5722ad6c6fafdad5d9235c6c0389b85 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Sun, 9 Feb 2025 11:45:17 +0000 Subject: [PATCH 1075/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index b35fcf8df87..8b85b7772e3 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.55.0.0", + "ondrejmirtes/better-reflection": "6.56.0.0", "phpstan/php-8-stubs": "0.4.10", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 12057cf7b44..21017bc41a9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "89a9535ab6639eb29e06b8e07f46c11d", + "content-hash": "ed330370de707366a77276c7da618015", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.55.0.0", + "version": "6.56.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "ad41e14a5a95478b7e43035b0c1d31625973f0f2" + "reference": "61b25baaca1ea904447f46d975a4ae7c99b722e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/ad41e14a5a95478b7e43035b0c1d31625973f0f2", - "reference": "ad41e14a5a95478b7e43035b0c1d31625973f0f2", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/61b25baaca1ea904447f46d975a4ae7c99b722e6", + "reference": "61b25baaca1ea904447f46d975a4ae7c99b722e6", "shasum": "" }, "require": { @@ -2212,7 +2212,7 @@ "doctrine/coding-standard": "^12.0.0", "phpstan/phpstan": "^1.10.60", "phpstan/phpstan-phpunit": "^1.3.16", - "phpunit/phpunit": "^11.5.6", + "phpunit/phpunit": "^11.5.7", "rector/rector": "1.2.10" }, "suggest": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.55.0.0" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.56.0.0" }, - "time": "2025-02-07T13:59:27+00:00" + "time": "2025-02-09T11:42:32+00:00" }, { "name": "phpstan/php-8-stubs", From e664bed7b62e2a58d571fb631ddf47030914a2b5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Feb 2025 12:47:46 +0100 Subject: [PATCH 1076/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/12544 --- .../Rules/Methods/CallMethodsRuleTest.php | 14 +++++++++++++ .../PHPStan/Rules/Methods/data/bug-12544.php | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12544.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 8b08b658f56..48caf8f4f1a 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3500,4 +3500,18 @@ public function testBug4801(): void $this->analyse([__DIR__ . '/data/bug-4801.php'], []); } + public function testBug12544(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12544.php'], [ + [ + 'Call to private method somethingElse() of class Bug12544\Bar.', + 20, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12544.php b/tests/PHPStan/Rules/Methods/data/bug-12544.php new file mode 100644 index 00000000000..56860b669db --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12544.php @@ -0,0 +1,21 @@ +hello(); + $bar->somethingElse(); +}; From e140197a0ab83abf36af13760b75374485ae1e2a Mon Sep 17 00:00:00 2001 From: Steen Rabol Date: Sun, 9 Feb 2025 13:49:29 +0100 Subject: [PATCH 1077/3097] Update functionMap.php All trader functions return array|false, not only array --- resources/functionMap.php | 316 +++++++++++++++++++------------------- 1 file changed, 158 insertions(+), 158 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 0b93513557f..c840903d5e7 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -12661,169 +12661,169 @@ 'TokyoTyrantTable::putShl' => ['void', 'key'=>'string', 'value'=>'string', 'width'=>'int'], 'TokyoTyrantTable::setIndex' => ['mixed', 'column'=>'string', 'type'=>'int'], 'touch' => ['bool', 'filename'=>'string', 'time='=>'int', 'atime='=>'int'], -'trader_acos' => ['array', 'real'=>'array'], -'trader_ad' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array'], -'trader_add' => ['array', 'real0'=>'array', 'real1'=>'array'], -'trader_adosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int'], -'trader_adx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_adxr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_apo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], -'trader_aroon' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], -'trader_aroonosc' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], -'trader_asin' => ['array', 'real'=>'array'], -'trader_atan' => ['array', 'real'=>'array'], -'trader_atr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_avgprice' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_bbands' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDevUp='=>'float', 'nbDevDn='=>'float', 'mAType='=>'int'], -'trader_beta' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], -'trader_bop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cci' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_cdl2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3blackcrows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3inside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3linestrike' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3outside' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3starsinsouth' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdl3whitesoldiers' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlabandonedbaby' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdladvanceblock' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlbelthold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlbreakaway' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlclosingmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlconcealbabyswall' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlcounterattack' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdldarkcloudcover' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdldoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdldojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdldragonflydoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlengulfing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdleveningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdleveningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdlgapsidesidewhite' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlgravestonedoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhangingman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlharami' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlharamicross' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhighwave' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhikkake' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhikkakemod' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlhomingpigeon' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlidentical3crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlinneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlinvertedhammer' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlkicking' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlkickingbylength' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlladderbottom' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdllongleggeddoji' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdllongline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlmarubozu' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlmatchinglow' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlmathold' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdlmorningdojistar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdlmorningstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], -'trader_cdlonneck' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlpiercing' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlrickshawman' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlrisefall3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlseparatinglines' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlshootingstar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlshortline' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlspinningtop' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlstalledpattern' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlsticksandwich' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdltakuri' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdltasukigap' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlthrusting' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdltristar' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlunique3river' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlupsidegap2crows' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_cdlxsidegap3methods' => ['array', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_ceil' => ['array', 'real'=>'array'], -'trader_cmo' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_correl' => ['array', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], -'trader_cos' => ['array', 'real'=>'array'], -'trader_cosh' => ['array', 'real'=>'array'], -'trader_dema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_div' => ['array', 'real0'=>'array', 'real1'=>'array'], -'trader_dx' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_ema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_acos' => ['array|false', 'real'=>'array'], +'trader_ad' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array'], +'trader_add' => ['array|false', 'real0'=>'array', 'real1'=>'array'], +'trader_adosc' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int'], +'trader_adx' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_adxr' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_apo' => ['array|false', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], +'trader_aroon' => ['array|false', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_aroonosc' => ['array|false', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_asin' => ['array|false', 'real'=>'array'], +'trader_atan' => ['array|false', 'real'=>'array'], +'trader_atr' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_avgprice' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_bbands' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'nbDevUp='=>'float', 'nbDevDn='=>'float', 'mAType='=>'int'], +'trader_beta' => ['array|false', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], +'trader_bop' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cci' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_cdl2crows' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3blackcrows' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3inside' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3linestrike' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3outside' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3starsinsouth' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdl3whitesoldiers' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlabandonedbaby' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdladvanceblock' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlbelthold' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlbreakaway' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlclosingmarubozu' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlconcealbabyswall' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlcounterattack' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldarkcloudcover' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdldoji' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldojistar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdldragonflydoji' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlengulfing' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdleveningdojistar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdleveningstar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlgapsidesidewhite' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlgravestonedoji' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhammer' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhangingman' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlharami' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlharamicross' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhighwave' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhikkake' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhikkakemod' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlhomingpigeon' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlidentical3crows' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlinneck' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlinvertedhammer' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlkicking' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlkickingbylength' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlladderbottom' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdllongleggeddoji' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdllongline' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmarubozu' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmatchinglow' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlmathold' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlmorningdojistar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlmorningstar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'penetration='=>'float'], +'trader_cdlonneck' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlpiercing' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlrickshawman' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlrisefall3methods' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlseparatinglines' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlshootingstar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlshortline' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlspinningtop' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlstalledpattern' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlsticksandwich' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltakuri' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltasukigap' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlthrusting' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdltristar' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlunique3river' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlupsidegap2crows' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_cdlxsidegap3methods' => ['array|false', 'open'=>'array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_ceil' => ['array|false', 'real'=>'array'], +'trader_cmo' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_correl' => ['array|false', 'real0'=>'array', 'real1'=>'array', 'timePeriod='=>'int'], +'trader_cos' => ['array|false', 'real'=>'array'], +'trader_cosh' => ['array|false', 'real'=>'array'], +'trader_dema' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_div' => ['array|false', 'real0'=>'array', 'real1'=>'array'], +'trader_dx' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_ema' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], 'trader_errno' => ['int'], -'trader_exp' => ['array', 'real'=>'array'], -'trader_floor' => ['array', 'real'=>'array'], +'trader_exp' => ['array|false', 'real'=>'array'], +'trader_floor' => ['array|false', 'real'=>'array'], 'trader_get_compat' => ['int'], 'trader_get_unstable_period' => ['int', 'functionId'=>'int'], -'trader_ht_dcperiod' => ['array', 'real'=>'array'], -'trader_ht_dcphase' => ['array', 'real'=>'array'], -'trader_ht_phasor' => ['array', 'real'=>'array'], -'trader_ht_sine' => ['array', 'real'=>'array'], -'trader_ht_trendline' => ['array', 'real'=>'array'], -'trader_ht_trendmode' => ['array', 'real'=>'array'], -'trader_kama' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_linearreg' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_linearreg_angle' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_linearreg_intercept' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_linearreg_slope' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_ln' => ['array', 'real'=>'array'], -'trader_log10' => ['array', 'real'=>'array'], -'trader_ma' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'mAType='=>'int'], -'trader_macd' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'signalPeriod='=>'int'], -'trader_macdext' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'fastMAType='=>'int', 'slowPeriod='=>'int', 'slowMAType='=>'int', 'signalPeriod='=>'int', 'signalMAType='=>'int'], -'trader_macdfix' => ['array', 'real'=>'array', 'signalPeriod='=>'int'], -'trader_mama' => ['array', 'real'=>'array', 'fastLimit='=>'float', 'slowLimit='=>'float'], -'trader_mavp' => ['array', 'real'=>'array', 'periods'=>'array', 'minPeriod='=>'int', 'maxPeriod='=>'int', 'mAType='=>'int'], -'trader_max' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_maxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_medprice' => ['array', 'high'=>'array', 'low'=>'array'], -'trader_mfi' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'timePeriod='=>'int'], -'trader_midpoint' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_midprice' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], -'trader_min' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_minindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_minmax' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_minmaxindex' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_minus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_minus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], -'trader_mom' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_mult' => ['array', 'real0'=>'array', 'real1'=>'array'], -'trader_natr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_obv' => ['array', 'real'=>'array', 'volume'=>'array'], -'trader_plus_di' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_plus_dm' => ['array', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], -'trader_ppo' => ['array', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], -'trader_roc' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_rocp' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_rocr' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_rocr100' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_rsi' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_sar' => ['array', 'high'=>'array', 'low'=>'array', 'acceleration='=>'float', 'maximum='=>'float'], -'trader_sarext' => ['array', 'high'=>'array', 'low'=>'array', 'startValue='=>'float', 'offsetOnReverse='=>'float', 'accelerationInitLong='=>'float', 'accelerationLong='=>'float', 'accelerationMaxLong='=>'float', 'accelerationInitShort='=>'float', 'accelerationShort='=>'float', 'accelerationMaxShort='=>'float'], +'trader_ht_dcperiod' => ['array|false', 'real'=>'array'], +'trader_ht_dcphase' => ['array|false', 'real'=>'array'], +'trader_ht_phasor' => ['array|false', 'real'=>'array'], +'trader_ht_sine' => ['array|false', 'real'=>'array'], +'trader_ht_trendline' => ['array|false', 'real'=>'array'], +'trader_ht_trendmode' => ['array|false', 'real'=>'array'], +'trader_kama' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_angle' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_intercept' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_linearreg_slope' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_ln' => ['array|false', 'real'=>'array'], +'trader_log10' => ['array|false', 'real'=>'array'], +'trader_ma' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'mAType='=>'int'], +'trader_macd' => ['array|false', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'signalPeriod='=>'int'], +'trader_macdext' => ['array|false', 'real'=>'array', 'fastPeriod='=>'int', 'fastMAType='=>'int', 'slowPeriod='=>'int', 'slowMAType='=>'int', 'signalPeriod='=>'int', 'signalMAType='=>'int'], +'trader_macdfix' => ['array|false', 'real'=>'array', 'signalPeriod='=>'int'], +'trader_mama' => ['array|false', 'real'=>'array', 'fastLimit='=>'float', 'slowLimit='=>'float'], +'trader_mavp' => ['array|false', 'real'=>'array', 'periods'=>'array', 'minPeriod='=>'int', 'maxPeriod='=>'int', 'mAType='=>'int'], +'trader_max' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_maxindex' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_medprice' => ['array|false', 'high'=>'array', 'low'=>'array'], +'trader_mfi' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'volume'=>'array', 'timePeriod='=>'int'], +'trader_midpoint' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_midprice' => ['array|false', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_min' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minindex' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minmax' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minmaxindex' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_minus_di' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_minus_dm' => ['array|false', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_mom' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_mult' => ['array|false', 'real0'=>'array', 'real1'=>'array'], +'trader_natr' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_obv' => ['array|false', 'real'=>'array', 'volume'=>'array'], +'trader_plus_di' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_plus_dm' => ['array|false', 'high'=>'array', 'low'=>'array', 'timePeriod='=>'int'], +'trader_ppo' => ['array|false', 'real'=>'array', 'fastPeriod='=>'int', 'slowPeriod='=>'int', 'mAType='=>'int'], +'trader_roc' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocp' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocr' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rocr100' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_rsi' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_sar' => ['array|false', 'high'=>'array', 'low'=>'array', 'acceleration='=>'float', 'maximum='=>'float'], +'trader_sarext' => ['array|false', 'high'=>'array', 'low'=>'array', 'startValue='=>'float', 'offsetOnReverse='=>'float', 'accelerationInitLong='=>'float', 'accelerationLong='=>'float', 'accelerationMaxLong='=>'float', 'accelerationInitShort='=>'float', 'accelerationShort='=>'float', 'accelerationMaxShort='=>'float'], 'trader_set_compat' => ['void', 'compatId'=>'int'], 'trader_set_unstable_period' => ['void', 'functionId'=>'int', 'timePeriod'=>'int'], -'trader_sin' => ['array', 'real'=>'array'], -'trader_sinh' => ['array', 'real'=>'array'], -'trader_sma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_sqrt' => ['array', 'real'=>'array'], -'trader_stddev' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], -'trader_stoch' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'slowK_Period='=>'int', 'slowK_MAType='=>'int', 'slowD_Period='=>'int', 'slowD_MAType='=>'int'], -'trader_stochf' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], -'trader_stochrsi' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], -'trader_sub' => ['array', 'real0'=>'array', 'real1'=>'array'], -'trader_sum' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_t3' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'vFactor='=>'float'], -'trader_tan' => ['array', 'real'=>'array'], -'trader_tanh' => ['array', 'real'=>'array'], -'trader_tema' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_trange' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_trima' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_trix' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_tsf' => ['array', 'real'=>'array', 'timePeriod='=>'int'], -'trader_typprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_ultosc' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod1='=>'int', 'timePeriod2='=>'int', 'timePeriod3='=>'int'], -'trader_var' => ['array', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], -'trader_wclprice' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array'], -'trader_willr' => ['array', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], -'trader_wma' => ['array', 'real'=>'array', 'timePeriod='=>'int'], +'trader_sin' => ['array|false', 'real'=>'array'], +'trader_sinh' => ['array|false', 'real'=>'array'], +'trader_sma' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_sqrt' => ['array|false', 'real'=>'array'], +'trader_stddev' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], +'trader_stoch' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'slowK_Period='=>'int', 'slowK_MAType='=>'int', 'slowD_Period='=>'int', 'slowD_MAType='=>'int'], +'trader_stochf' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], +'trader_stochrsi' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'fastK_Period='=>'int', 'fastD_Period='=>'int', 'fastD_MAType='=>'int'], +'trader_sub' => ['array|false', 'real0'=>'array', 'real1'=>'array'], +'trader_sum' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_t3' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'vFactor='=>'float'], +'trader_tan' => ['array|false', 'real'=>'array'], +'trader_tanh' => ['array|false', 'real'=>'array'], +'trader_tema' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_trange' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_trima' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_trix' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_tsf' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], +'trader_typprice' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_ultosc' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod1='=>'int', 'timePeriod2='=>'int', 'timePeriod3='=>'int'], +'trader_var' => ['array|false', 'real'=>'array', 'timePeriod='=>'int', 'nbDev='=>'float'], +'trader_wclprice' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array'], +'trader_willr' => ['array|false', 'high'=>'array', 'low'=>'array', 'close'=>'array', 'timePeriod='=>'int'], +'trader_wma' => ['array|false', 'real'=>'array', 'timePeriod='=>'int'], 'trait_exists' => ['bool', 'traitname'=>'string', 'autoload='=>'bool'], 'Transliterator::create' => ['?Transliterator', 'id'=>'string', 'direction='=>'int'], 'Transliterator::createFromRules' => ['?Transliterator', 'rules'=>'string', 'direction='=>'int'], From f5627dcc02aa54d6980d6837e3723885afc0c6dc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 11 Feb 2025 11:05:27 +0100 Subject: [PATCH 1078/3097] Remove obsolete `setproctitle` function from the functionMap --- resources/functionMap.php | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 5148ffeb6dd..ddfde42e340 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10440,7 +10440,6 @@ 'setLine' => ['void', 'width'=>'int', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], 'setlocale' => ['string|false', 'category'=>'int', 'locale'=>'string|null', '...args='=>'string'], 'setlocale\'1' => ['string|false', 'category'=>'int', 'locale'=>'?array'], -'setproctitle' => ['void', 'title'=>'string'], 'setrawcookie' => ['bool', 'name'=>'string', 'value='=>'string', 'expires='=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], 'setrawcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array{ expires?:int, path?:string, domain?:string, secure?:bool, httponly?:bool, samesite?:\'None\'|\'Lax\'|\'Strict\'|\'none\'|\'lax\'|\'strict\'}'], 'setRightFill' => ['void', 'red'=>'int', 'green'=>'int', 'blue'=>'int', 'a='=>'int'], From b4f41c7eb01d3d7dd71ea4898e572048909162ea Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz <80641364+jakubtobiasz@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:34:50 +0100 Subject: [PATCH 1079/3097] Hooked property cannot be static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- Makefile | 2 ++ .../Properties/PropertiesInInterfaceRule.php | 9 ++++++++ src/Rules/Properties/PropertyInClassRule.php | 11 ++++++++++ .../PropertiesInInterfaceRuleTest.php | 22 +++++++++++++++++++ .../Properties/PropertyInClassRuleTest.php | 18 +++++++++++++++ .../data/static-hooked-properties.php | 18 +++++++++++++++ .../static-hooked-property-in-interface.php | 12 ++++++++++ 7 files changed, 92 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/static-hooked-properties.php create mode 100644 tests/PHPStan/Rules/Properties/data/static-hooked-property-in-interface.php diff --git a/Makefile b/Makefile index d1fd73e1ddd..47ccd330d4c 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,8 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/hooked-properties-without-bodies-in-class.php \ --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/readonly-property-hooks-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/static-hooked-properties.php \ + --exclude tests/PHPStan/Rules/Properties/data/static-hooked-property-in-interface.php \ --exclude tests/PHPStan/Rules/Properties/data/virtual-hooked-properties.php \ --exclude tests/PHPStan/Rules/Classes/data/bug-12281.php \ --exclude tests/PHPStan/Rules/Traits/data/bug-12281.php \ diff --git a/src/Rules/Properties/PropertiesInInterfaceRule.php b/src/Rules/Properties/PropertiesInInterfaceRule.php index b6a3c7d4933..3ff9546d353 100644 --- a/src/Rules/Properties/PropertiesInInterfaceRule.php +++ b/src/Rules/Properties/PropertiesInInterfaceRule.php @@ -66,6 +66,15 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($node->isStatic()) { + return [ + RuleErrorBuilder::message('Hooked properties cannot be static.') + ->nonIgnorable() + ->identifier('property.hookedStatic') + ->build(), + ]; + } + if ($this->hasAnyHookBody($node)) { return [ RuleErrorBuilder::message('Interfaces cannot include property hooks with bodies.') diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index 661fb52c4cc..f14c9730a09 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -92,6 +92,17 @@ public function processNode(Node $node, Scope $scope): array } } + if ($node->isStatic()) { + if ($node->hasHooks()) { + return [ + RuleErrorBuilder::message('Hooked properties cannot be static.') + ->nonIgnorable() + ->identifier('property.hookedStatic') + ->build(), + ]; + } + } + if ($node->isVirtual()) { if ($node->getDefault() !== null) { return [ diff --git a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php index dc011f8a825..3d6dffcb8c5 100644 --- a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php @@ -140,4 +140,26 @@ public function testPhp84AndReadonlyPropertyHooksInInterface(): void ]); } + public function testPhp84AndStaticHookedPropertyInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/static-hooked-property-in-interface.php'], [ + [ + 'Hooked properties cannot be static.', + 7, + ], + [ + 'Hooked properties cannot be static.', + 9, + ], + [ + 'Hooked properties cannot be static.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 6918499f755..54e041e6948 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -195,4 +195,22 @@ public function testPhp84AndVirtualHookedProperties(): void ]); } + public function testPhp84AndStaticHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/static-hooked-properties.php'], [ + [ + 'Hooked properties cannot be static.', + 7, + ], + [ + 'Hooked properties cannot be static.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/static-hooked-properties.php b/tests/PHPStan/Rules/Properties/data/static-hooked-properties.php new file mode 100644 index 00000000000..101f4b3b280 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/static-hooked-properties.php @@ -0,0 +1,18 @@ + $this->foo; + set => $this->foo = $value; + } +} + +abstract class HiWorld +{ + public static string $foo { + get => 'dummy'; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/static-hooked-property-in-interface.php b/tests/PHPStan/Rules/Properties/data/static-hooked-property-in-interface.php new file mode 100644 index 00000000000..66f45edf785 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/static-hooked-property-in-interface.php @@ -0,0 +1,12 @@ + Date: Tue, 11 Feb 2025 15:38:33 +0100 Subject: [PATCH 1080/3097] Fix `GenericStaticType` in `@phpstan-self-out` --- .../Dummy/ChangedTypeMethodReflection.php | 10 ++- ...ackUnresolvedMethodPrototypeReflection.php | 8 ++- ...ypeUnresolvedMethodPrototypeReflection.php | 68 ++++++++++++------- tests/PHPStan/Analyser/nsrt/bug-12575.php | 61 +++++++++++++++++ 4 files changed, 118 insertions(+), 29 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12575.php diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 932d100db21..9a309e3189f 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -20,7 +20,13 @@ final class ChangedTypeMethodReflection implements ExtendedMethodReflection * @param list $variants * @param list|null $namedArgumentsVariants */ - public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants) + public function __construct( + private ClassReflection $declaringClass, + private ExtendedMethodReflection $reflection, + private array $variants, + private ?array $namedArgumentsVariants, + private ?Type $selfOutType, + ) { } @@ -126,7 +132,7 @@ public function acceptsNamedArguments(): TrinaryLogic public function getSelfOutType(): ?Type { - return $this->reflection->getSelfOutType(); + return $this->selfOutType; } public function returnsByReference(): TrinaryLogic diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index 3b9572b61ea..d4147c1aeb2 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -114,7 +114,13 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, ? array_map($variantFn, $namedArgumentVariants) : null; - return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentVariants); + return new ChangedTypeMethodReflection( + $declaringClass, + $method, + $variants, + $namedArgumentVariants, + $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null, + ); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 6fce0b017fa..c78a435583a 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\StaticType; +use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use function array_map; @@ -79,39 +80,54 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - array_map( - fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( - $parameter->getName(), - $this->transformStaticType($parameter->getType()), - $parameter->isOptional(), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue(), - $parameter->getNativeType(), - $this->transformStaticType($parameter->getPhpDocType()), - $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, - $parameter->isImmediatelyInvokedCallable(), - $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, - $parameter->getAttributes(), + $selfOutType = $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null; + $variantFn = function (ExtendedParametersAcceptor $acceptor) use ($selfOutType): ExtendedParametersAcceptor { + $originalReturnType = $acceptor->getReturnType(); + if ($originalReturnType instanceof ThisType && $selfOutType !== null) { + $returnType = $selfOutType; + } else { + $returnType = $this->transformStaticType($originalReturnType); + } + return new ExtendedFunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + array_map( + fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( + $parameter->getName(), + $this->transformStaticType($parameter->getType()), + $parameter->isOptional(), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + $parameter->getNativeType(), + $this->transformStaticType($parameter->getPhpDocType()), + $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, + $parameter->isImmediatelyInvokedCallable(), + $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), + ), + $acceptor->getParameters(), ), - $acceptor->getParameters(), - ), - $acceptor->isVariadic(), - $this->transformStaticType($acceptor->getReturnType()), - $this->transformStaticType($acceptor->getPhpDocReturnType()), - $this->transformStaticType($acceptor->getNativeReturnType()), - $acceptor->getCallSiteVarianceMap(), - ); + $acceptor->isVariadic(), + $returnType, + $this->transformStaticType($acceptor->getPhpDocReturnType()), + $this->transformStaticType($acceptor->getNativeReturnType()), + $acceptor->getCallSiteVarianceMap(), + ); + }; $variants = array_map($variantFn, $method->getVariants()); $namedArgumentsVariants = $method->getNamedArgumentsVariants(); $namedArgumentsVariants = $namedArgumentsVariants !== null ? array_map($variantFn, $namedArgumentsVariants) : null; - return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentsVariants); + return new ChangedTypeMethodReflection( + $declaringClass, + $method, + $variants, + $namedArgumentsVariants, + $selfOutType, + ); } private function transformStaticType(Type $type): Type diff --git a/tests/PHPStan/Analyser/nsrt/bug-12575.php b/tests/PHPStan/Analyser/nsrt/bug-12575.php new file mode 100644 index 00000000000..97c1c94187d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12575.php @@ -0,0 +1,61 @@ + $class + * @return $this + * @phpstan-self-out static + */ + public function add(string $class) + { + return $this; + } + +} + +/** + * @template T of object + * @extends Foo + */ +class Bar extends Foo +{ + +} + +interface A +{ + +} + +interface B +{ + +} + +/** + * @param Bar $bar + * @return void + */ +function doFoo(Bar $bar): void { + assertType('Bug12575\\Bar', $bar->add(B::class)); + assertType('Bug12575\\Bar', $bar); +}; + +/** + * @param Bar $bar + * @return void + */ +function doBar(Bar $bar): void { + $bar->add(B::class); + assertType('Bug12575\\Bar', $bar); +}; From cf6476188b73036741e916e1e3e58972b53bb8b3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 11 Feb 2025 16:31:18 +0100 Subject: [PATCH 1081/3097] Fix `@phpstan-self-out` with GenericStaticType when method is called on `$this` --- ...ackUnresolvedMethodPrototypeReflection.php | 64 +++++++++++-------- tests/PHPStan/Analyser/nsrt/bug-12575.php | 24 +++++++ 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index d4147c1aeb2..980a3f293ff 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -10,7 +10,9 @@ use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\Reflection\ResolvedMethodReflection; +use PHPStan\Type\ThisType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function array_map; final class CallbackUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection @@ -82,32 +84,42 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - array_map( - fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( - $parameter->getName(), - $this->transformStaticType($parameter->getType()), - $parameter->isOptional(), - $parameter->passedByReference(), - $parameter->isVariadic(), - $parameter->getDefaultValue(), - $parameter->getNativeType(), - $this->transformStaticType($parameter->getPhpDocType()), - $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, - $parameter->isImmediatelyInvokedCallable(), - $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, - $parameter->getAttributes(), + $selfOutType = $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null; + $variantFn = function (ExtendedParametersAcceptor $acceptor) use (&$selfOutType): ExtendedParametersAcceptor { + $originalReturnType = $acceptor->getReturnType(); + if ($originalReturnType instanceof ThisType && $selfOutType !== null) { + $returnType = TypeCombinator::intersect($selfOutType, $this->transformStaticType($originalReturnType)); + $selfOutType = $returnType; + } else { + $returnType = $this->transformStaticType($originalReturnType); + } + return new ExtendedFunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + array_map( + fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter( + $parameter->getName(), + $this->transformStaticType($parameter->getType()), + $parameter->isOptional(), + $parameter->passedByReference(), + $parameter->isVariadic(), + $parameter->getDefaultValue(), + $parameter->getNativeType(), + $this->transformStaticType($parameter->getPhpDocType()), + $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, + $parameter->isImmediatelyInvokedCallable(), + $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), + ), + $acceptor->getParameters(), ), - $acceptor->getParameters(), - ), - $acceptor->isVariadic(), - $this->transformStaticType($acceptor->getReturnType()), - $this->transformStaticType($acceptor->getPhpDocReturnType()), - $this->transformStaticType($acceptor->getNativeReturnType()), - $acceptor->getCallSiteVarianceMap(), - ); + $acceptor->isVariadic(), + $returnType, + $this->transformStaticType($acceptor->getPhpDocReturnType()), + $this->transformStaticType($acceptor->getNativeReturnType()), + $acceptor->getCallSiteVarianceMap(), + ); + }; $variants = array_map($variantFn, $method->getVariants()); $namedArgumentVariants = $method->getNamedArgumentsVariants(); $namedArgumentVariants = $namedArgumentVariants !== null @@ -119,7 +131,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $method, $variants, $namedArgumentVariants, - $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null, + $selfOutType, ); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12575.php b/tests/PHPStan/Analyser/nsrt/bug-12575.php index 97c1c94187d..f5199523d44 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12575.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12575.php @@ -30,6 +30,28 @@ public function add(string $class) class Bar extends Foo { + public function doFoo(): void + { + assertType('$this(Bug12575\Bar)&static(Bug12575\Bar)', $this->add(A::class)); + assertType('$this(Bug12575\Bar)&static(Bug12575\Bar)', $this); + assertType('T of object (class Bug12575\Bar, argument)', $this->getT()); + } + + public function doBar(): void + { + $this->add(B::class); + assertType('$this(Bug12575\Bar)&static(Bug12575\Bar)', $this); + assertType('T of object (class Bug12575\Bar, argument)', $this->getT()); + } + + /** + * @return T + */ + public function getT() + { + + } + } interface A @@ -49,6 +71,7 @@ interface B function doFoo(Bar $bar): void { assertType('Bug12575\\Bar', $bar->add(B::class)); assertType('Bug12575\\Bar', $bar); + assertType('Bug12575\A&Bug12575\B', $bar->getT()); }; /** @@ -58,4 +81,5 @@ function doFoo(Bar $bar): void { function doBar(Bar $bar): void { $bar->add(B::class); assertType('Bug12575\\Bar', $bar); + assertType('Bug12575\A&Bug12575\B', $bar->getT()); }; From bd1a196c00fe190298f76200fd5e051895356629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Tue, 11 Feb 2025 17:40:14 +0100 Subject: [PATCH 1082/3097] Fix empty regex and empty alternation parse --- resources/RegexGrammar.pp | 12 +++-- src/Command/IgnoredRegexValidator.php | 20 +++----- src/Type/Regex/RegexGroupParser.php | 49 +++++++++++++++++++ .../Analyser/nsrt/preg_match_shapes.php | 42 ++++++++++++++++ .../Command/IgnoredRegexValidatorTest.php | 36 ++++++++++++++ 5 files changed, 142 insertions(+), 17 deletions(-) diff --git a/resources/RegexGrammar.pp b/resources/RegexGrammar.pp index ba174feb290..3f49912a366 100644 --- a/resources/RegexGrammar.pp +++ b/resources/RegexGrammar.pp @@ -135,7 +135,7 @@ alternation() alternation: - concatenation() ( ::alternation:: concatenation() #alternation )* + concatenation()? ( concatenation()? #alternation )* concatenation: ( internal_options() | assertion() | quantification() | condition() ) @@ -154,8 +154,8 @@ | ::assertion_reference_:: alternation() #assertioncondition ) - ::_capturing:: concatenation()? - ( ::alternation:: concatenation()? )? + ::_capturing:: + alternation() ::_capturing:: assertion: @@ -165,7 +165,8 @@ | ::lookbehind_:: #lookbehind | ::negative_lookbehind_:: #negativelookbehind ) - alternation() ::_capturing:: + alternation() + ::_capturing:: quantification: ( class() | simple() ) ( quantifier() #quantification )? @@ -208,7 +209,8 @@ | ::atomic_group_:: #atomicgroup | ::capturing_:: ) - alternation() ::_capturing:: + alternation() + ::_capturing:: non_capturing_internal_options: diff --git a/src/Command/IgnoredRegexValidator.php b/src/Command/IgnoredRegexValidator.php index 613779b2e64..4340e0bd991 100644 --- a/src/Command/IgnoredRegexValidator.php +++ b/src/Command/IgnoredRegexValidator.php @@ -12,8 +12,6 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function count; -use function str_contains; -use function str_starts_with; use function strrpos; use function substr; @@ -34,19 +32,17 @@ public function validate(string $regex): IgnoredRegexValidatorResult try { /** @var TreeNode $ast */ $ast = $this->parser->parse($regex); - } catch (Exception $e) { - if (str_starts_with($e->getMessage(), 'Unexpected token "|" (alternation) at line 1')) { - return new IgnoredRegexValidatorResult([], false, true, '||', '\|\|'); - } - if ( - str_contains($regex, '()') - && str_starts_with($e->getMessage(), 'Unexpected token ")" (_capturing) at line 1') - ) { - return new IgnoredRegexValidatorResult([], false, true, '()', '\(\)'); - } + } catch (Exception) { return new IgnoredRegexValidatorResult([], false, false); } + if (Strings::match($regex, '~(?getIgnoredTypes($ast), $this->hasAnchorsInTheMiddle($ast), diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index a8a9755e8a5..69eb455eaf3 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -20,6 +20,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use function array_values; use function count; use function in_array; use function is_int; @@ -84,6 +85,9 @@ public function parseGroups(string $regex): ?array return null; } + $this->updateAlternationAstRemoveVerticalBarsAndAddEmptyToken($ast); + $this->updateCapturingAstAddEmptyToken($ast); + $captureOnlyNamed = false; if ($this->phpVersion->supportsPregCaptureOnlyNamedGroups()) { $captureOnlyNamed = str_contains($modifiers, 'n'); @@ -104,6 +108,51 @@ public function parseGroups(string $regex): ?array return [$astWalkResult->getCapturingGroups(), $astWalkResult->getMarkVerbs()]; } + private function createEmptyTokenTreeNode(TreeNode $parentAst): TreeNode + { + return new TreeNode('token', ['token' => 'literal', 'value' => '', 'namespace' => 'default'], [], $parentAst); + } + + private function updateAlternationAstRemoveVerticalBarsAndAddEmptyToken(TreeNode $ast): void + { + $children = $ast->getChildren(); + + foreach ($children as $i => $child) { + $this->updateAlternationAstRemoveVerticalBarsAndAddEmptyToken($child); + + if ($ast->getId() !== '#alternation' || $child->getValueToken() !== 'alternation') { + continue; + } + + unset($children[$i]); + + if ($i !== 0 + && isset($children[$i + 1]) + && $children[$i + 1]->getValueToken() !== 'alternation') { + continue; + } + + $children[$i] = $this->createEmptyTokenTreeNode($ast); + } + + $ast->setChildren(array_values($children)); + } + + private function updateCapturingAstAddEmptyToken(TreeNode $ast): void + { + foreach ($ast->getChildren() as $child) { + $this->updateCapturingAstAddEmptyToken($child); + } + + if ($ast->getId() !== '#capturing' || $ast->getChildren() !== []) { + return; + } + + $emptyAlternationAst = new TreeNode('#alternation', null, [], $ast); + $emptyAlternationAst->setChildren([$this->createEmptyTokenTreeNode($emptyAlternationAst)]); + $ast->setChildren([$emptyAlternationAst]); + } + private function walkRegexAst( TreeNode $ast, ?RegexAlternation $alternation, diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 701dc1f049a..8861e9f0362 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -901,6 +901,48 @@ function bugUnescapedDashAfterRange (string $string): void } } +function bugEmptySubexpression (string $string): void { + if (preg_match('//', $string, $matches)) { + assertType("array{string}", $matches); // could be array{''} + } + + if (preg_match('/()/', $string, $matches)) { + assertType("array{string, ''}", $matches); // could be array{'', ''} + } + + if (preg_match('/|/', $string, $matches)) { + assertType("array{string}", $matches); // could be array{''} + } + + if (preg_match('~|(a)~', $string, $matches)) { + assertType("array{0: string, 1?: 'a'}", $matches); + } + + if (preg_match('~(a)|~', $string, $matches)) { + assertType("array{0: string, 1?: 'a'}", $matches); + } + + if (preg_match('~(a)||(b)~', $string, $matches)) { + assertType("array{0: string, 1?: 'a'}|array{string, '', 'b'}", $matches); + } + + if (preg_match('~(|(a))~', $string, $matches)) { + assertType("array{0: string, 1: ''|'a', 2?: 'a'}", $matches); + } + + if (preg_match('~((a)|)~', $string, $matches)) { + assertType("array{0: string, 1: ''|'a', 2?: 'a'}", $matches); + } + + if (preg_match('~((a)||(b))~', $string, $matches)) { + assertType("array{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: 'b'}", $matches); + } + + if (preg_match('~((a)|()|(b))~', $string, $matches)) { + assertType("array{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: '', 4?: 'b'}", $matches); + } +} + function bug11744(string $string): void { if (!preg_match('~^((/[a-z]+)?)~', $string, $matches)) { diff --git a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php index 98a6dc58cb7..39902aa01fe 100644 --- a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php +++ b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php @@ -100,12 +100,48 @@ public function dataValidate(): array false, false, ], + [ + '~(a\()~', + [], + false, + false, + ], + [ + '~b\\\()~', + [], + false, + true, + ], + [ + '~(c\\\\\()~', + [], + false, + false, + ], [ '~Result of || is always true.~', [], false, true, ], + [ + '~a\||~', + [], + false, + false, + ], + [ + '~b\\\||~', + [], + false, + true, + ], + [ + '~c\\\\\||~', + [], + false, + false, + ], [ '#Method PragmaRX\Notified\Data\Repositories\Notified::firstOrCreateByEvent() should return PragmaRX\Notified\Data\Models\Notified but returns Illuminate\Database\Eloquent\Model|null#', [], From e43abf89d96e35cdcacd364577e727d7f7c08805 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:19:57 +0000 Subject: [PATCH 1083/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8b85b7772e3..60067e1b509 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.56.0.0", - "phpstan/php-8-stubs": "0.4.10", + "phpstan/php-8-stubs": "0.4.11", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 21017bc41a9..83f895229ad 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ed330370de707366a77276c7da618015", + "content-hash": "ab846ea4db42bbdc5d346236cedb8aca", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.10", + "version": "0.4.11", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "97d994e9f3bc539ccabf2392a6e478cdf25a7940" + "reference": "8b29105305d85fa440ae0bce1d4d83fcdf5b47ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/97d994e9f3bc539ccabf2392a6e478cdf25a7940", - "reference": "97d994e9f3bc539ccabf2392a6e478cdf25a7940", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/8b29105305d85fa440ae0bce1d4d83fcdf5b47ca", + "reference": "8b29105305d85fa440ae0bce1d4d83fcdf5b47ca", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.10" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.11" }, - "time": "2025-02-09T09:39:32+00:00" + "time": "2025-02-12T00:19:27+00:00" }, { "name": "phpstan/phpdoc-parser", From 14faee1665ae02422705b9086b3a376b20ce2fe2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 12 Feb 2025 09:31:04 +0100 Subject: [PATCH 1084/3097] Fix negative offset false positive on constant string --- src/Type/Constant/ConstantStringType.php | 9 ++++++--- tests/PHPStan/Analyser/nsrt/string-offsets.php | 18 ++++++++++++++++-- ...onexistentOffsetInArrayDimFetchRuleTest.php | 5 +++++ tests/PHPStan/Rules/Arrays/data/bug-12122.php | 8 ++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12122.php diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index f0e78cf903c..7b0f109b7c3 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -366,7 +366,8 @@ public function isUppercaseString(): TrinaryLogic public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType->isInteger()->yes()) { - $strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1); + $strlen = strlen($this->value); + $strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1); return $strLenType->isSuperTypeOf($offsetType); } @@ -376,15 +377,17 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { if ($offsetType->isInteger()->yes()) { + $strlen = strlen($this->value); + $strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1); + if ($offsetType instanceof ConstantIntegerType) { - if ($offsetType->getValue() < strlen($this->value)) { + if ($strLenType->isSuperTypeOf($offsetType)->yes()) { return new self($this->value[$offsetType->getValue()]); } return new ErrorType(); } - $strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1); $intersected = TypeCombinator::intersect($strLenType, $offsetType); if ($intersected instanceof IntegerRangeType) { $finiteTypes = $intersected->getFiniteTypes(); diff --git a/tests/PHPStan/Analyser/nsrt/string-offsets.php b/tests/PHPStan/Analyser/nsrt/string-offsets.php index 6fc8eeabd0e..449246f7078 100644 --- a/tests/PHPStan/Analyser/nsrt/string-offsets.php +++ b/tests/PHPStan/Analyser/nsrt/string-offsets.php @@ -9,11 +9,12 @@ * @param int<3, 10> $threeToTen * @param int<10, max> $tenOrMore * @param int<-10, -5> $negative + * @param int $smallerMinusSix * @param lowercase-string $lowercase * * @return void */ -function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $lowercase) { +function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $smallerMinusSix, int $i, string $lowercase) { $s = "world"; if (rand(0, 1)) { $s = "hello"; @@ -26,10 +27,23 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $ assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]); assertType('*ERROR*', $s[$tenOrMore]); assertType("''|'d'|'l'|'o'", $s[$threeToTen]); - assertType("*ERROR*", $s[$negative]); + assertType("non-empty-string", $s[$negative]); + assertType("*ERROR*", $s[$smallerMinusSix]); $longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR"; assertType("non-empty-string", $longString[$i]); assertType("lowercase-string&non-empty-string", $lowercase[$i]); } + +function bug12122() +{ + // see https://3v4l.org/8EMdX + $foo = 'fo'; + assertType('*ERROR*', $foo[2]); + assertType("'o'", $foo[1]); + assertType("'f'", $foo[0]); + assertType("'o'", $foo[-1]); + assertType("'f'", $foo[-2]); + assertType('*ERROR*', $foo[-3]); +} diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 6699adb5f79..aa4d8cd83b9 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -924,4 +924,9 @@ public function testInternalClassesWithOverloadedOffsetAccessInvalid84(): void $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid-php84.php'], []); } + public function testBug12122(): void + { + $this->analyse([__DIR__ . '/data/bug-12122.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12122.php b/tests/PHPStan/Rules/Arrays/data/bug-12122.php new file mode 100644 index 00000000000..acd18166754 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12122.php @@ -0,0 +1,8 @@ + Date: Wed, 12 Feb 2025 11:23:35 +0100 Subject: [PATCH 1085/3097] Array shape from general array with single finite key --- phpstan-baseline.neon | 6 +++++ src/PhpDoc/TypeNodeResolver.php | 16 +++++++++--- .../Analyser/nsrt/array-intersect-key.php | 2 +- ...m-general-array-with-single-finite-key.php | 26 +++++++++++++++++++ .../PHPStan/Analyser/nsrt/bug-5287-php81.php | 2 +- .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 8 ------ 6 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3c27adf2bf2..8961bbda447 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -210,6 +210,12 @@ parameters: count: 1 path: src/PhpDoc/TypeNodeResolver.php + - + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + identifier: phpstanApi.instanceofType + count: 1 + path: src/PhpDoc/TypeNodeResolver.php + - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 1b9403b7c5d..22d4974aab1 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -664,11 +664,21 @@ static function (string $variance): TemplateTypeVariance { if (count($genericTypes) === 1) { // array $arrayType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $genericTypes[0]); } elseif (count($genericTypes) === 2) { // array - $keyType = TypeCombinator::intersect($genericTypes[0], new UnionType([ + $keyType = TypeCombinator::intersect($genericTypes[0]->toArrayKey(), new UnionType([ new IntegerType(), new StringType(), - ])); - $arrayType = new ArrayType($keyType->toArrayKey(), $genericTypes[1]); + ]))->toArrayKey(); + $finiteTypes = $keyType->getFiniteTypes(); + if ( + count($finiteTypes) === 1 + && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType) + ) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1]); + $arrayType = TypeCombinator::union($arrayBuilder->getArray(), ConstantArrayTypeBuilder::createEmpty()->getArray()); + } else { + $arrayType = new ArrayType($keyType, $genericTypes[1]); + } } else { return new ErrorType(); } diff --git a/tests/PHPStan/Analyser/nsrt/array-intersect-key.php b/tests/PHPStan/Analyser/nsrt/array-intersect-key.php index 288ba539a23..33690634449 100644 --- a/tests/PHPStan/Analyser/nsrt/array-intersect-key.php +++ b/tests/PHPStan/Analyser/nsrt/array-intersect-key.php @@ -45,7 +45,7 @@ public function normalArrays(array $arr, array $arr2, array $otherArrs): void /** @var array<17, int> $otherArrs */ assertType('array<17, string>', array_intersect_key($arr, $otherArrs)); /** @var array $otherArrs */ - assertType('array{}', array_intersect_key($arr, $otherArrs)); + assertType('array<\'\', string>', array_intersect_key($arr, $otherArrs)); if (array_key_exists(17, $arr2)) { assertType('non-empty-array<17, string>&hasOffset(17)', array_intersect_key($arr2, [17 => 'bar'])); diff --git a/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php b/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php new file mode 100644 index 00000000000..40d6f5957af --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php @@ -0,0 +1,26 @@ + $a + */ + public function doFoo(array $a): void + { + assertType('array{}|array{1: string}', $a); + } + + /** + * @param non-empty-array<1, string> $a + */ + public function doBar(array $a): void + { + assertType('array{1: string}', $a); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-5287-php81.php b/tests/PHPStan/Analyser/nsrt/bug-5287-php81.php index 7d57aea2c07..d1cb994c929 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5287-php81.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5287-php81.php @@ -48,7 +48,7 @@ function foo4(array $arr): void function foo5(array $arr): void { $arrSpread = [...$arr]; - assertType('non-empty-array', $arrSpread); + assertType('non-empty-array', $arrSpread); } /** diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index 1a4d0cbce93..e2c76ab516f 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -216,10 +216,6 @@ public function testBug3753(): void 'PHPDoc tag @param for parameter $foo contains unresolvable type.', 20, ], - [ - 'PHPDoc tag @param for parameter $bars contains unresolvable type.', - 28, - ], ]); } @@ -291,10 +287,6 @@ public function testParamOut(): void 'Parameter $i for PHPDoc tag @param-out is not passed by reference.', 37, ], - [ - 'PHPDoc tag @param-out for parameter $i contains unresolvable type.', - 44, - ], [ 'PHPDoc tag @param-out for parameter $i contains generic type Exception but class Exception is not generic.', 51, From 4618ba726ee4def5e01e8fed41ec9270a2318e3d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 11:35:34 +0100 Subject: [PATCH 1086/3097] Fix build --- tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php index cb14b900fe5..5bfcf976b2a 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php @@ -52,11 +52,11 @@ public function testRule(): void 29, ], [ - 'Array unpacking cannot be used on an array with potential string keys: array', + 'Array unpacking cannot be used on an array with potential string keys: array', 40, ], [ - 'Array unpacking cannot be used on an array with potential string keys: array', + 'Array unpacking cannot be used on an array with potential string keys: array', 52, ], [ @@ -87,11 +87,11 @@ public function testRuleDoNotCheckBenevolentUnion(): void 18, ], [ - 'Array unpacking cannot be used on an array with potential string keys: array', + 'Array unpacking cannot be used on an array with potential string keys: array', 40, ], [ - 'Array unpacking cannot be used on an array with potential string keys: array', + 'Array unpacking cannot be used on an array with potential string keys: array', 52, ], [ From 27f7b215e35e119c5f7c962811d75ff0a6dfed93 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 11:37:58 +0100 Subject: [PATCH 1087/3097] Try fixing issue bot --- .github/workflows/issue-bot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 3165350af2d..8eeb63a4d54 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -48,9 +48,9 @@ jobs: uses: actions/cache@v4 with: path: ./issue-bot/tmp - key: "issue-bot-download-v6-${{ github.run_id }}" + key: "issue-bot-download-v7-${{ github.run_id }}" restore-keys: | - issue-bot-download-v6- + issue-bot-download-v7- - name: "Download data" working-directory: "issue-bot" From 484574b7484511309302c4aceef6f7999dc77610 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 22:05:53 +0100 Subject: [PATCH 1088/3097] Fix build --- tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php index 5bfcf976b2a..89366314965 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php @@ -86,14 +86,6 @@ public function testRuleDoNotCheckBenevolentUnion(): void 'Array unpacking cannot be used on an array with string keys: array', 18, ], - [ - 'Array unpacking cannot be used on an array with potential string keys: array', - 40, - ], - [ - 'Array unpacking cannot be used on an array with potential string keys: array', - 52, - ], [ 'Array unpacking cannot be used on an array with string keys: array{foo: string, bar: int}', 63, From 8a5bfb9208891055ecff4a39586d542b70546f82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 22:06:00 +0100 Subject: [PATCH 1089/3097] Do not union array shape with empty array, use optional offset instead --- phpstan-baseline.neon | 2 +- src/PhpDoc/TypeNodeResolver.php | 4 ++-- src/Type/TypeCombinator.php | 22 +++++++++++++++++++ ...m-general-array-with-single-finite-key.php | 2 +- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8961bbda447..11a651dc006 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1698,7 +1698,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 14 + count: 16 path: src/Type/TypeCombinator.php - diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 22d4974aab1..975365a3418 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -674,8 +674,8 @@ static function (string $variance): TemplateTypeVariance { && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType) ) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1]); - $arrayType = TypeCombinator::union($arrayBuilder->getArray(), ConstantArrayTypeBuilder::createEmpty()->getArray()); + $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1], true); + $arrayType = $arrayBuilder->getArray(); } else { $arrayType = new ArrayType($keyType, $genericTypes[1]); } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 343482358cb..a13281d91f2 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -1216,6 +1216,28 @@ public static function intersect(Type ...$types): Type continue 2; } + if ( + $types[$i] instanceof ConstantArrayType + && count($types[$i]->getKeyTypes()) === 1 + && $types[$j] instanceof NonEmptyArrayType + ) { + $types[$i] = $types[$i]->makeOffsetRequired($types[$i]->getKeyTypes()[0]); + array_splice($types, $j--, 1); + $typesCount--; + continue; + } + + if ( + $types[$j] instanceof ConstantArrayType + && count($types[$j]->getKeyTypes()) === 1 + && $types[$i] instanceof NonEmptyArrayType + ) { + $types[$j] = $types[$j]->makeOffsetRequired($types[$j]->getKeyTypes()[0]); + array_splice($types, $i--, 1); + $typesCount--; + continue 2; + } + if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetValueType) { $offsetType = $types[$j]->getOffsetType(); $valueType = $types[$j]->getValueType(); diff --git a/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php b/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php index 40d6f5957af..50c027445cc 100644 --- a/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php +++ b/tests/PHPStan/Analyser/nsrt/array-shape-from-general-array-with-single-finite-key.php @@ -12,7 +12,7 @@ class Foo */ public function doFoo(array $a): void { - assertType('array{}|array{1: string}', $a); + assertType('array{1?: string}', $a); } /** From fb3618b7ecee9bacf86f4ca7a09dd3a95683e1a3 Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Wed, 12 Feb 2025 21:19:47 +0000 Subject: [PATCH 1090/3097] Update BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 60067e1b509..ac477d6fbf4 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "6.56.0.0", + "ondrejmirtes/better-reflection": "6.57.0.0", "phpstan/php-8-stubs": "0.4.11", "phpstan/phpdoc-parser": "2.0.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 83f895229ad..c60100716c3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ab846ea4db42bbdc5d346236cedb8aca", + "content-hash": "c5964ac036f0356f955a896dc4dfbe35", "packages": [ { "name": "clue/ndjson-react", @@ -2187,16 +2187,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "6.56.0.0", + "version": "6.57.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "61b25baaca1ea904447f46d975a4ae7c99b722e6" + "reference": "dcc22b90a63497f3450dd5eed62197bc46937297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/61b25baaca1ea904447f46d975a4ae7c99b722e6", - "reference": "61b25baaca1ea904447f46d975a4ae7c99b722e6", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/dcc22b90a63497f3450dd5eed62197bc46937297", + "reference": "dcc22b90a63497f3450dd5eed62197bc46937297", "shasum": "" }, "require": { @@ -2252,9 +2252,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.56.0.0" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.57.0.0" }, - "time": "2025-02-09T11:42:32+00:00" + "time": "2025-02-12T21:16:38+00:00" }, { "name": "phpstan/php-8-stubs", From a59dad3c1ce503034e5593d4b445b57d8a22d35e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 10 Feb 2025 11:06:56 +0100 Subject: [PATCH 1091/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/12553 --- .../WritingToReadOnlyPropertiesRuleTest.php | 10 +++++++++ .../Rules/Variables/NullCoalesceRuleTest.php | 10 +++++++++ .../Rules/Variables/data/bug-12553.php | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-12553.php diff --git a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php index 3dd095b13fd..21c70b7adb2 100644 --- a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php @@ -107,4 +107,14 @@ public function testPropertyHooks(): void ]); } + public function testBug12553(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/../Variables/data/bug-12553.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 2b32877d527..f876b3d8231 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -346,4 +346,14 @@ public function testBug10610(): void $this->analyse([__DIR__ . '/data/bug-10610.php'], []); } + public function testBug12553(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12553.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-12553.php b/tests/PHPStan/Rules/Variables/data/bug-12553.php new file mode 100644 index 00000000000..cfc7187e9ce --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12553.php @@ -0,0 +1,22 @@ +createdAt ??= new \DateTimeImmutable(); + } + } +} + +class Example implements TimestampsInterface +{ + use Timestamps; +} From 6f5b55276de4a55f7aa19de47f653c38a2bbca64 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 22:21:57 +0100 Subject: [PATCH 1092/3097] Fix --- src/Type/TypeCombinator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index a13281d91f2..f0e2f629a5a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -1219,6 +1219,7 @@ public static function intersect(Type ...$types): Type if ( $types[$i] instanceof ConstantArrayType && count($types[$i]->getKeyTypes()) === 1 + && $types[$i]->isOptionalKey(0) && $types[$j] instanceof NonEmptyArrayType ) { $types[$i] = $types[$i]->makeOffsetRequired($types[$i]->getKeyTypes()[0]); @@ -1230,6 +1231,7 @@ public static function intersect(Type ...$types): Type if ( $types[$j] instanceof ConstantArrayType && count($types[$j]->getKeyTypes()) === 1 + && $types[$j]->isOptionalKey(0) && $types[$i] instanceof NonEmptyArrayType ) { $types[$j] = $types[$j]->makeOffsetRequired($types[$j]->getKeyTypes()[0]); From 25b2525d0e41247fd4ba2e288765a3620df79cfa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 12 Feb 2025 22:29:27 +0100 Subject: [PATCH 1093/3097] Fix build --- tests/PHPStan/Rules/Variables/data/bug-12553.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Variables/data/bug-12553.php b/tests/PHPStan/Rules/Variables/data/bug-12553.php index cfc7187e9ce..74d56dc0e89 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-12553.php +++ b/tests/PHPStan/Rules/Variables/data/bug-12553.php @@ -1,4 +1,4 @@ -= 8.4 namespace Bug12553; From 0c8e9d2905371039cf453509e044d367529aa2b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Feb 2025 13:10:12 +0100 Subject: [PATCH 1094/3097] Readonly property can override get-only property declared in interface --- .../Properties/OverridingPropertyRule.php | 19 ++++++++++------- .../Properties/OverridingPropertyRuleTest.php | 10 +++++++++ .../Rules/Properties/data/bug-12586.php | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12586.php diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 66c0c62e0cf..6acc3ca7c3a 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -74,13 +74,18 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.readWrite')->nonIgnorable()->build(); } } elseif ($node->isReadOnly()) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Readonly property %s::$%s overrides readwrite property %s::$%s.', - $classReflection->getDisplayName(), - $node->getName(), - $prototype->getDeclaringClass()->getDisplayName(), - $node->getName(), - ))->identifier('property.readOnly')->nonIgnorable()->build(); + if ( + !$this->phpVersion->supportsPropertyHooks() + || $prototype->isWritable() + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Readonly property %s::$%s overrides readwrite property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.readOnly')->nonIgnorable()->build(); + } } if ($prototype->isPublic()) { diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index 44779f21620..deb20e58775 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -253,4 +253,14 @@ public function testBug12466(): void ]); } + public function testBug12586(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->reportMaybes = true; + $this->analyse([__DIR__ . '/data/bug-12586.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12586.php b/tests/PHPStan/Rules/Properties/data/bug-12586.php new file mode 100644 index 00000000000..8339c897879 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12586.php @@ -0,0 +1,21 @@ += 8.4 + +declare(strict_types=1); + +namespace Bug12586; + +interface Foo +{ + public string $bar { + get; + } +} + +readonly class FooImpl implements Foo +{ + public function __construct( + public string $bar, + ) + { + } +} From d25a815b1069174acf3efe97812617d679f30769 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Feb 2025 13:28:36 +0100 Subject: [PATCH 1095/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ac477d6fbf4..273df9aeb36 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", "phpstan/php-8-stubs": "0.4.11", - "phpstan/phpdoc-parser": "2.0.0", + "phpstan/phpdoc-parser": "2.0.1", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index c60100716c3..b97639f1769 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5964ac036f0356f955a896dc4dfbe35", + "content-hash": "e22fce83a5af2205f9b402b220b6b1d6", "packages": [ { "name": "clue/ndjson-react", @@ -2290,16 +2290,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" + "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/72e51f7c32c5aef7c8b462195b8c599b11199893", + "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893", "shasum": "" }, "require": { @@ -2331,9 +2331,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.1" }, - "time": "2024-10-13T11:29:49+00:00" + "time": "2025-02-13T12:25:43+00:00" }, { "name": "psr/container", From d3909c7fbf169069d8099ec67a3a0cd75ce873af Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Feb 2025 14:13:57 +0100 Subject: [PATCH 1096/3097] Test for set-hooked property being overriden by readonly property --- .../Rules/Properties/OverridingPropertyRuleTest.php | 7 ++++++- tests/PHPStan/Rules/Properties/data/bug-12586.php | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index deb20e58775..0df236c85a1 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -260,7 +260,12 @@ public function testBug12586(): void } $this->reportMaybes = true; - $this->analyse([__DIR__ . '/data/bug-12586.php'], []); + $this->analyse([__DIR__ . '/data/bug-12586.php'], [ + [ + 'Readonly property Bug12586\FooImpl::$baz overrides readwrite property Bug12586\Foo::$baz.', + 23, + ], + ]); } } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12586.php b/tests/PHPStan/Rules/Properties/data/bug-12586.php index 8339c897879..e2eac6ff7f1 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-12586.php +++ b/tests/PHPStan/Rules/Properties/data/bug-12586.php @@ -9,12 +9,18 @@ interface Foo public string $bar { get; } + + public string $baz { + get; + set; + } } readonly class FooImpl implements Foo { public function __construct( public string $bar, + public string $baz, ) { } From 924a7a2f647acd3c62a054c4358c13c0b1cc2886 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Feb 2025 14:26:20 +0100 Subject: [PATCH 1097/3097] Overriding property - when overriding readable property, the property has to be readable (same for writable) --- .../Properties/OverridingPropertyRule.php | 27 +++++++++++++- .../Properties/OverridingPropertyRuleTest.php | 12 ++++++ .../property-prototype-from-interface.php | 37 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 6acc3ca7c3a..c1806565e23 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -88,6 +88,32 @@ public function processNode(Node $node, Scope $scope): array } } + $propertyReflection = $classReflection->getNativeProperty($node->getName()); + if ($this->phpVersion->supportsPropertyHooks()) { + if ($prototype->isReadable()) { + if (!$propertyReflection->isReadable()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overriding readable property %s::$%s also has to be readable.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.notReadable')->nonIgnorable()->build(); + } + } + if ($prototype->isWritable()) { + if (!$propertyReflection->isWritable()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overriding writable property %s::$%s also has to be writable.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.notWritable')->nonIgnorable()->build(); + } + } + } + if ($prototype->isPublic()) { if (!$node->isPublic()) { $errors[] = RuleErrorBuilder::message(sprintf( @@ -198,7 +224,6 @@ public function processNode(Node $node, Scope $scope): array return $errors; } - $propertyReflection = $classReflection->getNativeProperty($node->getName()); if ($prototype->getReadableType()->equals($propertyReflection->getReadableType())) { return $errors; } diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index 0df236c85a1..c5f5cd929cf 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -216,6 +216,18 @@ public function testPropertyPrototypeFromInterface(): void 'Type string of property Bug12466\Bar::$a is not the same as type int of overridden property Bug12466\Foo::$a.', 15, ], + [ + 'Property Bug12466\TestMoreProps::$a overriding writable property Bug12466\MoreProps::$a also has to be writable.', + 34, + ], + [ + 'Property Bug12466\TestMoreProps::$b overriding readable property Bug12466\MoreProps::$b also has to be readable.', + 41, + ], + [ + 'Property Bug12466\TestMoreProps::$c overriding writable property Bug12466\MoreProps::$c also has to be writable.', + 48, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php b/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php index 014228effce..75f1a6accaf 100644 --- a/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php +++ b/tests/PHPStan/Rules/Properties/data/property-prototype-from-interface.php @@ -15,3 +15,40 @@ class Bar implements Foo public string $a; } + +interface MoreProps +{ + + public int $a { get; set; } + + public int $b { get; } + + public int $c { set; } + +} + +class TestMoreProps implements MoreProps +{ + + // not writable + public int $a { + get { + return 1; + } + } + + // not readable + public int $b { + set { + $this->a = 1; + } + } + + // not writable + public int $c { + get { + return 1; + } + } + +} From fde29a56d4f8ec58c80f159dfe27e0044c3e11f7 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 13 Feb 2025 14:48:30 +0100 Subject: [PATCH 1098/3097] feat: cache the result of ClassReflection::hasMethod method --- src/Reflection/ClassReflection.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 02705f32cd0..d031cb88169 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -142,6 +142,9 @@ class ClassReflection /** @var array */ private static array $resolvingTypeAliasImports = []; + /** @var array */ + private array $hasMethodCache = []; + /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions @@ -482,16 +485,26 @@ public function hasProperty(string $propertyName): bool public function hasMethod(string $methodName): bool { + if (array_key_exists($methodName, $this->hasMethodCache)) { + return $this->hasMethodCache[$methodName]; + } + foreach ($this->methodsClassReflectionExtensions as $extension) { if ($extension->hasMethod($this, $methodName)) { + $this->hasMethodCache[$methodName] = true; + return true; } } if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) { + $this->hasMethodCache[$methodName] = true; + return true; } + $this->hasMethodCache[$methodName] = false; + return false; } From 2ee974ed66a3d6790470ff67ffc774350fec2d8b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 14 Feb 2025 14:15:35 +0100 Subject: [PATCH 1099/3097] Cosmetics --- src/Reflection/ClassReflection.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 56a831ec94e..cd8b16a326d 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -482,21 +482,15 @@ public function hasMethod(string $methodName): bool foreach ($this->methodsClassReflectionExtensions as $extension) { if ($extension->hasMethod($this, $methodName)) { - $this->hasMethodCache[$methodName] = true; - - return true; + return $this->hasMethodCache[$methodName] = true; } } if ($this->requireExtendsMethodsClassReflectionExtension->hasMethod($this, $methodName)) { - $this->hasMethodCache[$methodName] = true; - - return true; + return $this->hasMethodCache[$methodName] = true; } - $this->hasMethodCache[$methodName] = false; - - return false; + return $this->hasMethodCache[$methodName] = false; } public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection From 73d7b88d60f4b8ebbff777060f8bc4e5e25bde12 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 14 Feb 2025 14:23:32 +0100 Subject: [PATCH 1100/3097] ClassReflection - hasPropertyCache --- src/Reflection/ClassReflection.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index cd8b16a326d..0c2bbd532cd 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -144,6 +144,9 @@ final class ClassReflection /** @var array */ private array $hasMethodCache = []; + /** @var array */ + private array $hasPropertyCache = []; + /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions @@ -454,8 +457,12 @@ private function allowsDynamicPropertiesExtensions(): bool public function hasProperty(string $propertyName): bool { + if (array_key_exists($propertyName, $this->hasPropertyCache)) { + return $this->hasPropertyCache[$propertyName]; + } + if ($this->isEnum()) { - return $this->hasNativeProperty($propertyName); + return $this->hasPropertyCache[$propertyName] = $this->hasNativeProperty($propertyName); } foreach ($this->propertiesClassReflectionExtensions as $i => $extension) { @@ -463,15 +470,15 @@ public function hasProperty(string $propertyName): bool break; } if ($extension->hasProperty($this, $propertyName)) { - return true; + return $this->hasPropertyCache[$propertyName] = true; } } if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) { - return true; + return $this->hasPropertyCache[$propertyName] = true; } - return false; + return $this->hasPropertyCache[$propertyName] = false; } public function hasMethod(string $methodName): bool From f2bf43c213819cc59f64f86c4421a664d1f75905 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Fri, 10 Jan 2025 12:08:49 +0100 Subject: [PATCH 1101/3097] Do not report constructor unused parameter if class is an Attribute class --- .../Classes/UnusedConstructorParametersRule.php | 3 +++ .../Classes/UnusedConstructorParametersRuleTest.php | 5 +++++ tests/PHPStan/Rules/Classes/data/bug-7165.php | 12 ++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-7165.php diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index 8b38392470a..d87581f69cc 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -44,6 +44,9 @@ public function processNode(Node $node, Scope $scope): array if (count($originalNode->params) === 0) { return []; } + if ($node->getClassReflection()->isAttributeClass()) { + return []; + } $message = sprintf( 'Constructor of class %s has an unused parameter $%%s.', diff --git a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php index beb402c2674..cf547b909c4 100644 --- a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php +++ b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php @@ -61,6 +61,11 @@ public function testBug1917(): void $this->analyse([__DIR__ . '/data/bug-1917.php'], []); } + public function testBug7165(): void + { + $this->analyse([__DIR__ . '/data/bug-7165.php'], []); + } + public function testBug10865(): void { $this->analyse([__DIR__ . '/data/bug-10865.php'], []); diff --git a/tests/PHPStan/Rules/Classes/data/bug-7165.php b/tests/PHPStan/Rules/Classes/data/bug-7165.php new file mode 100644 index 00000000000..3cbf90ed6df --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-7165.php @@ -0,0 +1,12 @@ += 8.0 + +namespace Bug7165; + +#[\Attribute] +class MyAttribute +{ + public function __construct(string $name) + { + } +} + From 701a3b4e020eb1b5b8806d14cf8716df5d4317bd Mon Sep 17 00:00:00 2001 From: Andrei Ivchenkov Date: Sun, 16 Feb 2025 18:18:16 +0300 Subject: [PATCH 1102/3097] fix `MongoLog::setCallback()` return type --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index ddfde42e340..ce6a8a5912c 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -7173,7 +7173,7 @@ 'MongoLog::getCallback' => ['callable'], 'MongoLog::getLevel' => ['int'], 'MongoLog::getModule' => ['int'], -'MongoLog::setCallback' => ['void', 'log_function'=>'callable'], +'MongoLog::setCallback' => ['bool', 'log_function'=>'callable'], 'MongoLog::setLevel' => ['void', 'level'=>'int'], 'MongoLog::setModule' => ['void', 'module'=>'int'], 'MongoPool::getSize' => ['int'], From e52dec71af4e47088f0a8f52db6776d6e39157b3 Mon Sep 17 00:00:00 2001 From: Andrei Ivchenkov Date: Sun, 16 Feb 2025 21:15:12 +0300 Subject: [PATCH 1103/3097] fix `MongoCollection::save()` return type --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index ce6a8a5912c..e2a6e572087 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6604,7 +6604,7 @@ 'MongoCollection::insert' => ['bool|array', 'a'=>'array', 'options='=>'array'], 'MongoCollection::parallelCollectionScan' => ['MongoCommandCursor[]', 'num_cursors'=>'int'], 'MongoCollection::remove' => ['bool|array', 'criteria='=>'array', 'options='=>'array'], -'MongoCollection::save' => ['mixed', 'a'=>'array', 'options='=>'array'], +'MongoCollection::save' => ['mixed', 'a'=>'array|object', 'options='=>'array'], 'MongoCollection::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], 'MongoCollection::setSlaveOkay' => ['bool', 'ok='=>'bool'], 'MongoCollection::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], From 5c0771e60af21bcc89422392c9b8c1046af2eb98 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:27:52 +0000 Subject: [PATCH 1104/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 273df9aeb36..213520adfed 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", "phpstan/php-8-stubs": "0.4.11", - "phpstan/phpdoc-parser": "2.0.1", + "phpstan/phpdoc-parser": "2.0.2", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index b97639f1769..0745c35da40 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e22fce83a5af2205f9b402b220b6b1d6", + "content-hash": "9a89c33cfda18b84eccba02fe1aafae2", "packages": [ { "name": "clue/ndjson-react", @@ -2290,16 +2290,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893" + "reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/72e51f7c32c5aef7c8b462195b8c599b11199893", - "reference": "72e51f7c32c5aef7c8b462195b8c599b11199893", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51087f87dcce2663e1fed4dfd4e56eccd580297e", + "reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e", "shasum": "" }, "require": { @@ -2331,9 +2331,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.2" }, - "time": "2025-02-13T12:25:43+00:00" + "time": "2025-02-17T20:25:51+00:00" }, { "name": "psr/container", From 24e2736d6af9b4f89a4c1a4dfa57d8be6e020e6f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 19 Feb 2025 14:46:23 +0100 Subject: [PATCH 1105/3097] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 213520adfed..6dbb056300b 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", "phpstan/php-8-stubs": "0.4.11", - "phpstan/phpdoc-parser": "2.0.2", + "phpstan/phpdoc-parser": "2.1.0", "psr/http-message": "^1.1", "react/async": "^3", "react/child-process": "^0.7", diff --git a/composer.lock b/composer.lock index 0745c35da40..1ab1cfb464f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9a89c33cfda18b84eccba02fe1aafae2", + "content-hash": "2c5308f6e71c5cdd76c9589a43b04326", "packages": [ { "name": "clue/ndjson-react", @@ -2290,16 +2290,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e" + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51087f87dcce2663e1fed4dfd4e56eccd580297e", - "reference": "51087f87dcce2663e1fed4dfd4e56eccd580297e", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", "shasum": "" }, "require": { @@ -2331,9 +2331,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" }, - "time": "2025-02-17T20:25:51+00:00" + "time": "2025-02-19T13:28:12+00:00" }, { "name": "psr/container", From 85ee4f990329ec0ea2160184052b2bc9950a66af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=BD=C3=A1=C4=8Dek?= Date: Mon, 3 Feb 2025 12:50:58 +0100 Subject: [PATCH 1106/3097] Teamcity - show rule identifier when verbose output is set --- .../ErrorFormatter/TeamcityErrorFormatter.php | 9 ++++++++- .../TeamcityErrorFormatterTest.php | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php index 8ea1d5bb1e3..070896a0514 100644 --- a/src/Command/ErrorFormatter/TeamcityErrorFormatter.php +++ b/src/Command/ErrorFormatter/TeamcityErrorFormatter.php @@ -10,6 +10,7 @@ use function count; use function is_string; use function preg_replace; +use function sprintf; use const PHP_EOL; /** @@ -41,9 +42,15 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in ]); foreach ($fileSpecificErrors as $fileSpecificError) { + $message = $fileSpecificError->getMessage(); + + if ($fileSpecificError->getIdentifier() !== null && $fileSpecificError->canBeIgnored()) { + $message .= sprintf(' (🪪 %s)', $fileSpecificError->getIdentifier()); + } + $result .= $this->createTeamcityLine('inspection', [ 'typeId' => 'phpstan', - 'message' => $fileSpecificError->getMessage(), + 'message' => $message, 'file' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), 'line' => $fileSpecificError->getLine(), // additional attributes diff --git a/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php index 9e91634634f..6543fbae67d 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TeamcityErrorFormatterTest.php @@ -18,6 +18,7 @@ public function dataFormatterOutputProvider(): iterable 0, 0, '', + '', ]; yield [ @@ -76,18 +77,29 @@ public function dataFormatterOutputProvider(): iterable ##teamcity[inspection typeId=\'phpstan\' message=\'Bar||nBar2\' file=\'foo.php\' line=\'5\' SEVERITY=\'ERROR\' ignorable=\'1\' tip=\'a tip\'] ##teamcity[inspection typeId=\'phpstan\' message=\'first generic error\' file=\'.\' SEVERITY=\'ERROR\'] ##teamcity[inspection typeId=\'phpstan\' message=\'second generic\' file=\'.\' SEVERITY=\'ERROR\'] +', + ]; + + yield [ + 'One file error', + 1, + [4, 2], + 0, + '##teamcity[inspectionType id=\'phpstan\' name=\'phpstan\' category=\'phpstan\' description=\'phpstan Inspection\'] +##teamcity[inspection typeId=\'phpstan\' message=\'Bar||nBar2\' file=\'foo.php\' line=\'\' SEVERITY=\'ERROR\' ignorable=\'1\' tip=\'\'] +##teamcity[inspection typeId=\'phpstan\' message=\'Foobar\Buz (🪪 foobar.buz)\' file=\'foo.php\' line=\'5\' SEVERITY=\'ERROR\' ignorable=\'1\' tip=\'a tip\'] ', ]; } /** * @dataProvider dataFormatterOutputProvider - * + * @param array{int, int}|int $numFileErrors */ public function testFormatErrors( string $message, int $exitCode, - int $numFileErrors, + array|int $numFileErrors, int $numGenericErrors, string $expected, ): void From 948f79d2da9e3767129b5432730fcbdec44995dc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Feb 2025 09:34:39 +0100 Subject: [PATCH 1107/3097] Fix `ClassLike::$namespacedName must not be accessed before initialization` --- src/Analyser/NodeScopeResolver.php | 2 +- src/Dependency/DependencyResolver.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-12627.php | 17 +++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12627.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d5fde308135..1d19ee75657 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6323,7 +6323,7 @@ private function processNodesForCalledMethod($node, string $fileName, MethodRefl $declaringClass = $methodReflection->getDeclaringClass(); if ( $node instanceof Node\Stmt\Class_ - && $node->namespacedName !== null + && isset($node->namespacedName) && $declaringClass->getName() === (string) $node->namespacedName && $declaringClass->getNativeReflection()->getStartLine() === $node->getStartLine() ) { diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index d8d87fd3522..77a4f957fe5 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -43,7 +43,7 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies $dependenciesReflections = []; if ($node instanceof Node\Stmt\Class_) { - if ($node->namespacedName !== null) { + if (isset($node->namespacedName)) { $this->addClassToDependencies($node->namespacedName->toString(), $dependenciesReflections); } if ($node->extends !== null) { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 76c62869a04..42dd7ec3bc2 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1505,6 +1505,12 @@ public function testBug12549(): void $this->assertNoErrors($errors); } + public function testBug12627(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12627.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12627.php b/tests/PHPStan/Analyser/data/bug-12627.php new file mode 100644 index 00000000000..ce75be8225b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12627.php @@ -0,0 +1,17 @@ +b(); + } + + private function b(): void + { + } +} + +$c = new class() {}; From d4d7e116a20b179ca1502b651fd0b779e8fede6a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Feb 2025 09:52:04 +0100 Subject: [PATCH 1108/3097] Fix referencing `%env%` in `includes` --- .github/workflows/e2e-tests.yml | 4 ++++ e2e/bug-12606/phpstan.neon | 2 ++ e2e/bug-12606/src/empty.php | 0 e2e/bug-12606/test.neon | 4 ++++ e2e/bug-12606/test.php | 0 src/DependencyInjection/ContainerFactory.php | 2 +- 6 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 e2e/bug-12606/phpstan.neon create mode 100644 e2e/bug-12606/src/empty.php create mode 100644 e2e/bug-12606/test.neon create mode 100644 e2e/bug-12606/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 208df4952fd..0d90c4116c3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -243,6 +243,10 @@ jobs: echo "$OUTPUT" ../bashunit -a matches "Note: Using configuration file .+phpstan.neon." "$OUTPUT" ../bashunit -a contains 'Result cache not used because the metadata do not match: metaExtensions' "$OUTPUT" + - script: | + cd e2e/bug-12606 + export CONFIGTEST=test + ../../bin/phpstan steps: - name: "Checkout" diff --git a/e2e/bug-12606/phpstan.neon b/e2e/bug-12606/phpstan.neon new file mode 100644 index 00000000000..1557144b26c --- /dev/null +++ b/e2e/bug-12606/phpstan.neon @@ -0,0 +1,2 @@ +includes: + - %env.CONFIGTEST%.neon diff --git a/e2e/bug-12606/src/empty.php b/e2e/bug-12606/src/empty.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/e2e/bug-12606/test.neon b/e2e/bug-12606/test.neon new file mode 100644 index 00000000000..c308dcf5421 --- /dev/null +++ b/e2e/bug-12606/test.neon @@ -0,0 +1,4 @@ +parameters: + level: 8 + paths: + - src diff --git a/e2e/bug-12606/test.php b/e2e/bug-12606/test.php new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 75c79d6678a..c28e08a77cc 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -136,11 +136,11 @@ public function create( 'generateBaselineFile' => $generateBaselineFile, 'usedLevel' => $usedLevel, 'cliAutoloadFile' => $cliAutoloadFile, + 'env' => getenv(), ]); $configurator->addDynamicParameters([ 'analysedPaths' => $analysedPaths, 'analysedPathsFromConfig' => $analysedPathsFromConfig, - 'env' => getenv(), ]); $configurator->addConfig($this->configDirectory . '/config.neon'); foreach ($additionalConfigFiles as $additionalConfigFile) { From ed54496111643cb10b660fce3ccfe3fa39a03c3e Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 21 Feb 2025 10:09:20 +0100 Subject: [PATCH 1109/3097] Introduce IgnoreErrorExtension (#3783) --- .github/workflows/e2e-tests.yml | 4 ++ conf/config.neon | 3 ++ e2e/ignore-error-extension/.gitignore | 2 + e2e/ignore-error-extension/composer.json | 7 +++ .../phpstan-baseline.neon | 44 +++++++++++++++++++ e2e/ignore-error-extension/phpstan.neon.dist | 25 +++++++++++ .../src/ClassCollector.php | 29 ++++++++++++ e2e/ignore-error-extension/src/ClassRule.php | 43 ++++++++++++++++++ ...trollerActionReturnTypeIgnoreExtension.php | 41 +++++++++++++++++ .../ControllerClassNameIgnoreExtension.php | 34 ++++++++++++++ .../src/HomepageController.php | 29 ++++++++++++ src/Analyser/AnalyserResultFinalizer.php | 13 +++++- src/Analyser/FileAnalyser.php | 13 +++++- src/Analyser/IgnoreErrorExtension.php | 32 ++++++++++++++ src/Analyser/IgnoreErrorExtensionProvider.php | 22 ++++++++++ src/Testing/RuleTestCase.php | 3 ++ tests/PHPStan/Analyser/AnalyserTest.php | 4 ++ 17 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 e2e/ignore-error-extension/.gitignore create mode 100644 e2e/ignore-error-extension/composer.json create mode 100644 e2e/ignore-error-extension/phpstan-baseline.neon create mode 100644 e2e/ignore-error-extension/phpstan.neon.dist create mode 100644 e2e/ignore-error-extension/src/ClassCollector.php create mode 100644 e2e/ignore-error-extension/src/ClassRule.php create mode 100644 e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php create mode 100644 e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php create mode 100644 e2e/ignore-error-extension/src/HomepageController.php create mode 100644 src/Analyser/IgnoreErrorExtension.php create mode 100644 src/Analyser/IgnoreErrorExtensionProvider.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 0d90c4116c3..5e574eb6d2f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -247,6 +247,10 @@ jobs: cd e2e/bug-12606 export CONFIGTEST=test ../../bin/phpstan + - script: | + cd e2e/ignore-error-extension + composer install + ../../bin/phpstan steps: - name: "Checkout" diff --git a/conf/config.neon b/conf/config.neon index b9f6445b081..b7e91bd3d9c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -442,6 +442,9 @@ services: arguments: parser: @defaultAnalysisParser + - + class: PHPStan\Analyser\IgnoreErrorExtensionProvider + - class: PHPStan\Analyser\LocalIgnoresProcessor diff --git a/e2e/ignore-error-extension/.gitignore b/e2e/ignore-error-extension/.gitignore new file mode 100644 index 00000000000..de4a392c331 --- /dev/null +++ b/e2e/ignore-error-extension/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/e2e/ignore-error-extension/composer.json b/e2e/ignore-error-extension/composer.json new file mode 100644 index 00000000000..f8a4e6ebedf --- /dev/null +++ b/e2e/ignore-error-extension/composer.json @@ -0,0 +1,7 @@ +{ + "autoload": { + "psr-4": { + "App\\": "src/" + } + } +} diff --git a/e2e/ignore-error-extension/phpstan-baseline.neon b/e2e/ignore-error-extension/phpstan-baseline.neon new file mode 100644 index 00000000000..8c53510373c --- /dev/null +++ b/e2e/ignore-error-extension/phpstan-baseline.neon @@ -0,0 +1,44 @@ +parameters: + ignoreErrors: + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ClassCollector.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ClassRule.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ControllerActionReturnTypeIgnoreExtension.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ControllerClassNameIgnoreExtension.php + + - + message: '#^Method App\\HomepageController\:\:contactAction\(\) has parameter \$someUnrelatedError with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/HomepageController.php + + - + message: '#^Method App\\HomepageController\:\:getSomething\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/HomepageController.php + + - + message: '#^Method App\\HomepageController\:\:homeAction\(\) has parameter \$someUnrelatedError with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/HomepageController.php + diff --git a/e2e/ignore-error-extension/phpstan.neon.dist b/e2e/ignore-error-extension/phpstan.neon.dist new file mode 100644 index 00000000000..bc04b24e675 --- /dev/null +++ b/e2e/ignore-error-extension/phpstan.neon.dist @@ -0,0 +1,25 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src + +services: + - + class: App\ClassCollector + tags: + - phpstan.collector + - + class: App\ClassRule + tags: + - phpstan.rules.rule + - + class: App\ControllerActionReturnTypeIgnoreExtension + tags: + - phpstan.ignoreErrorExtension + - + class: App\ControllerClassNameIgnoreExtension + tags: + - phpstan.ignoreErrorExtension diff --git a/e2e/ignore-error-extension/src/ClassCollector.php b/e2e/ignore-error-extension/src/ClassCollector.php new file mode 100644 index 00000000000..03011e44fcb --- /dev/null +++ b/e2e/ignore-error-extension/src/ClassCollector.php @@ -0,0 +1,29 @@ + + */ +final class ClassCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode(Node $node, Scope $scope) : ?array + { + if ($node->name === null) { + return null; + } + + return [$node->name->name, $node->getStartLine()]; + } +} diff --git a/e2e/ignore-error-extension/src/ClassRule.php b/e2e/ignore-error-extension/src/ClassRule.php new file mode 100644 index 00000000000..17283bafe56 --- /dev/null +++ b/e2e/ignore-error-extension/src/ClassRule.php @@ -0,0 +1,43 @@ + + */ +final class ClassRule implements Rule +{ + #[Override] + public function getNodeType() : string + { + return CollectedDataNode::class; + } + + #[Override] + public function processNode(Node $node, Scope $scope) : array + { + $errors = []; + + foreach ($node->get(ClassCollector::class) as $file => $data) { + foreach ($data as [$className, $line]) { + $errors[] = RuleErrorBuilder::message('This is an error from a rule that uses a collector') + ->file($file) + ->line($line) + ->identifier('class.name') + ->build(); + } + } + + return $errors; + } + +} diff --git a/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php b/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php new file mode 100644 index 00000000000..dc7b0dab5af --- /dev/null +++ b/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php @@ -0,0 +1,41 @@ +getIdentifier() !== 'missingType.iterableValue') { + return false; + } + + // @phpstan-ignore phpstanApi.instanceofAssumption + if (! $node instanceof InClassMethodNode) { + return false; + } + + if (! str_ends_with($node->getClassReflection()->getName(), 'Controller')) { + return false; + } + + if (! str_ends_with($node->getMethodReflection()->getName(), 'Action')) { + return false; + } + + if (! $node->getMethodReflection()->isPublic()) { + return false; + } + + return true; + } +} diff --git a/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php b/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php new file mode 100644 index 00000000000..b52b4f7ef10 --- /dev/null +++ b/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php @@ -0,0 +1,34 @@ +getIdentifier() !== 'class.name') { + return false; + } + + // @phpstan-ignore phpstanApi.instanceofAssumption + if (!$node instanceof CollectedDataNode) { + return false; + } + + if (!str_ends_with($error->getFile(), 'Controller.php')) { + return false; + } + + return true; + } +} diff --git a/e2e/ignore-error-extension/src/HomepageController.php b/e2e/ignore-error-extension/src/HomepageController.php new file mode 100644 index 00000000000..d55c9551579 --- /dev/null +++ b/e2e/ignore-error-extension/src/HomepageController.php @@ -0,0 +1,29 @@ + 'Homepage', + 'something' => $this->getSomething(), + ]; + } + + public function contactAction($someUnrelatedError): array + { + return [ + 'title' => 'Contact', + 'something' => $this->getSomething(), + ]; + } + + private function getSomething(): array + { + return []; + } +} diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index b88b3e0d310..56fde0132e0 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -19,6 +19,7 @@ final class AnalyserResultFinalizer public function __construct( private RuleRegistry $ruleRegistry, + private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider, private RuleErrorTransformer $ruleErrorTransformer, private ScopeFactory $scopeFactory, private LocalIgnoresProcessor $localIgnoresProcessor, @@ -88,7 +89,17 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ } foreach ($ruleErrors as $ruleError) { - $tempCollectorErrors[] = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + + if ($error->canBeIgnored()) { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { + continue 2; + } + } + } + + $tempCollectorErrors[] = $error; } } diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 570c6370927..969154c3c89 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -51,6 +51,7 @@ public function __construct( private NodeScopeResolver $nodeScopeResolver, private Parser $parser, private DependencyResolver $dependencyResolver, + private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider, private RuleErrorTransformer $ruleErrorTransformer, private LocalIgnoresProcessor $localIgnoresProcessor, ) @@ -142,7 +143,17 @@ public function analyseFile( } foreach ($ruleErrors as $ruleError) { - $temporaryFileErrors[] = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + + if ($error->canBeIgnored()) { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { + continue 2; + } + } + } + + $temporaryFileErrors[] = $error; } } diff --git a/src/Analyser/IgnoreErrorExtension.php b/src/Analyser/IgnoreErrorExtension.php new file mode 100644 index 00000000000..54ff6d24228 --- /dev/null +++ b/src/Analyser/IgnoreErrorExtension.php @@ -0,0 +1,32 @@ +container->getServicesByTag(IgnoreErrorExtension::EXTENSION_TAG); + } + +} diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b40a8ebca05..e48e757bfe7 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; use PHPStan\Analyser\FileAnalyser; +use PHPStan\Analyser\IgnoreErrorExtensionProvider; use PHPStan\Analyser\InternalError; use PHPStan\Analyser\LocalIgnoresProcessor; use PHPStan\Analyser\NodeScopeResolver; @@ -113,6 +114,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser $nodeScopeResolver, $this->getParser(), self::getContainer()->getByType(DependencyResolver::class), + new IgnoreErrorExtensionProvider(self::getContainer()), new RuleErrorTransformer(), new LocalIgnoresProcessor(), ); @@ -192,6 +194,7 @@ public function gatherAnalyserErrors(array $files): array $finalizer = new AnalyserResultFinalizer( $ruleRegistry, + new IgnoreErrorExtensionProvider(self::getContainer()), new RuleErrorTransformer(), $this->createScopeFactory($this->createReflectionProvider(), $this->getTypeSpecifier()), new LocalIgnoresProcessor(), diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 6162106ac58..bacd85a9675 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use Nette\DI\Container; use PhpParser\Lexer; use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser\Php7; @@ -10,6 +11,7 @@ use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; use PHPStan\Dependency\ExportedNodeResolver; +use PHPStan\DependencyInjection\Nette\NetteContainer; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; @@ -666,6 +668,7 @@ private function runAnalyser( $finalizer = new AnalyserResultFinalizer( new DirectRuleRegistry([]), + new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), new RuleErrorTransformer(), $this->createScopeFactory( $this->createReflectionProvider(), @@ -742,6 +745,7 @@ private function createAnalyser(): Analyser new IgnoreLexer(), ), new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), + new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), new RuleErrorTransformer(), new LocalIgnoresProcessor(), ); From 99a6a827dd41a5f6813523e94e085d7b76301cc2 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 21 Feb 2025 10:46:39 +0100 Subject: [PATCH 1110/3097] Add link to docs --- src/Analyser/IgnoreErrorExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/IgnoreErrorExtension.php b/src/Analyser/IgnoreErrorExtension.php index 54ff6d24228..3f8b11f4328 100644 --- a/src/Analyser/IgnoreErrorExtension.php +++ b/src/Analyser/IgnoreErrorExtension.php @@ -18,7 +18,7 @@ * - phpstan.ignoreErrorExtension * ``` * - * Learn more: https://phpstan.org + * Learn more: https://phpstan.org/developing-extensions/ignore-error-extensions * * @api */ From 8de182dbdeff1bcff34cc6c0b24ed379f77bbc42 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Feb 2025 15:52:10 +0100 Subject: [PATCH 1111/3097] Property can be written in get hook --- .../DeadCode/UnusedPrivatePropertyRule.php | 4 ++++ .../UnusedPrivatePropertyRuleTest.php | 12 +++++++++++ .../PHPStan/Rules/DeadCode/data/bug-12621.php | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-12621.php diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 137bd8f00e3..0564f2a9578 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -132,6 +132,10 @@ public function processNode(Node $node, Scope $scope): array $methodReflection instanceof PhpMethodFromParserNodeReflection && $methodReflection->isPropertyHook() && $methodReflection->getHookedPropertyName() === $propertyName + && ( + $methodReflection->getPropertyHookName() === 'set' + || $usage instanceof PropertyRead + ) ) { continue; } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index c56a986f1c0..88b2b619c2a 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -387,4 +387,16 @@ public function testPropertyHooks(): void ]); } + public function testBug12621(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + + $this->analyse([__DIR__ . '/data/bug-12621.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-12621.php b/tests/PHPStan/Rules/DeadCode/data/bug-12621.php new file mode 100644 index 00000000000..bcb8ff19586 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-12621.php @@ -0,0 +1,21 @@ += 8.4 + +declare(strict_types=1); + +namespace Bug12621; + +final class Test +{ + private string $a { + get => $this->a ??= $this->b; + } + + public function __construct( + private readonly string $b + ) {} + + public function test(): string + { + return $this->a; + } +} From de3720dc4e473505002301c247e77a939845be94 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Feb 2025 16:10:57 +0100 Subject: [PATCH 1112/3097] Issue bot - do not test PHP 7.2 anymore We need to save the size of the job matrix because right now we get this error: Job outputs (1049962 bytes) has exceeded maximum size 1048576 bytes. --- issue-bot/src/Console/DownloadCommand.php | 4 ++-- issue-bot/src/Console/EvaluateCommand.php | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/issue-bot/src/Console/DownloadCommand.php b/issue-bot/src/Console/DownloadCommand.php index ab3bce12d26..fb0855b7921 100644 --- a/issue-bot/src/Console/DownloadCommand.php +++ b/issue-bot/src/Console/DownloadCommand.php @@ -96,12 +96,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $matrix = []; - foreach ([70200, 70300, 70400, 80000, 80100, 80200, 80300, 80400] as $phpVersion) { + foreach ([70300, 70400, 80000, 80100, 80200, 80300, 80400] as $phpVersion) { $phpVersionHashes = []; foreach ($cachedResults as $hash => $result) { $resultPhpVersions = array_keys($result->getVersionedErrors()); if ($resultPhpVersions === [70400]) { - $resultPhpVersions = [70200, 70300, 70400, 80000]; + $resultPhpVersions = [70300, 70400, 80000]; } if (!in_array(80100, $resultPhpVersions, true)) { diff --git a/issue-bot/src/Console/EvaluateCommand.php b/issue-bot/src/Console/EvaluateCommand.php index 0f8d05a8d88..9836d85bb00 100644 --- a/issue-bot/src/Console/EvaluateCommand.php +++ b/issue-bot/src/Console/EvaluateCommand.php @@ -101,7 +101,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $originalPhpVersions = array_keys($originalErrors); $newResult = $newResults[$hash]; if (array_key_exists(70100, $originalErrors) || $originalPhpVersions === [70400]) { - $newResult[70100] = $newResult[70200]; + $newResult[70100] = $newResult[70300]; + } + if (array_key_exists(70200, $originalErrors)) { + $newResult[70200] = $newResult[70300]; } $newTabs = $this->tabCreator->create($this->filterErrors($originalErrors, $newResult)); From bca8902dc4ed45e27ba792901a61afeb86414e9d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 26 Feb 2025 13:59:59 +0100 Subject: [PATCH 1113/3097] `FileTypeMapper::getNameScope()` --- .../NameScopeAlreadyBeingCreatedException.php | 10 +++++++ src/Type/FileTypeMapper.php | 29 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/PhpDoc/NameScopeAlreadyBeingCreatedException.php diff --git a/src/PhpDoc/NameScopeAlreadyBeingCreatedException.php b/src/PhpDoc/NameScopeAlreadyBeingCreatedException.php new file mode 100644 index 00000000000..4d781e7f00c --- /dev/null +++ b/src/PhpDoc/NameScopeAlreadyBeingCreatedException.php @@ -0,0 +1,10 @@ +createResolvedPhpDocBlock($phpDocKey, new NameScope(null, []), $docComment, null); } + try { + $nameScope = $this->getNameScope($fileName, $className, $traitName, $functionName); + } catch (NameScopeAlreadyBeingCreatedException) { + return ResolvedPhpDocBlock::createEmpty(); + } + + return $this->createResolvedPhpDocBlock($phpDocKey, $nameScope, $docComment, $fileName); + } + + /** + * @throws NameScopeAlreadyBeingCreatedException + */ + public function getNameScope( + string $fileName, + ?string $className, + ?string $traitName, + ?string $functionName, + ): NameScope + { + $nameScopeKey = $this->getNameScopeKey($fileName, $className, $traitName, $functionName); $nameScopeMap = []; if (!isset($this->inProcess[$fileName])) { @@ -106,15 +127,15 @@ public function getResolvedPhpDoc( } if (isset($nameScopeMap[$nameScopeKey])) { - return $this->createResolvedPhpDocBlock($phpDocKey, $nameScopeMap[$nameScopeKey], $docComment, $fileName); + return $nameScopeMap[$nameScopeKey]; } if (!isset($this->inProcess[$fileName][$nameScopeKey])) { // wrong $fileName due to traits - return ResolvedPhpDocBlock::createEmpty(); + throw new NameScopeAlreadyBeingCreatedException(); } if ($this->inProcess[$fileName][$nameScopeKey] === true) { // PHPDoc has cyclic dependency - return ResolvedPhpDocBlock::createEmpty(); + throw new NameScopeAlreadyBeingCreatedException(); } if (is_callable($this->inProcess[$fileName][$nameScopeKey])) { @@ -123,7 +144,7 @@ public function getResolvedPhpDoc( $this->inProcess[$fileName][$nameScopeKey] = $resolveCallback(); } - return $this->createResolvedPhpDocBlock($phpDocKey, $this->inProcess[$fileName][$nameScopeKey], $docComment, $fileName); + return $this->inProcess[$fileName][$nameScopeKey]; } private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameScope, string $phpDocString, ?string $fileName): ResolvedPhpDocBlock From d56d0842ca297ad6cc4ac3cf3918433ed8a80394 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 26 Feb 2025 14:33:17 +0100 Subject: [PATCH 1114/3097] Class constants cannot be directly accessed on a trait --- src/Rules/Classes/ClassConstantRule.php | 19 +++++++++++++++++-- .../Rules/Classes/ClassConstantRuleTest.php | 15 +++++++++++++++ .../data/class-constant-accessed-on-trait.php | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/class-constant-accessed-on-trait.php diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 71636b8ca09..968b3d75f3c 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -111,9 +111,24 @@ public function processNode(Node $node, Scope $scope): array ]; } - $messages = $this->classCheck->checkClassNames([new ClassNameNodePair($className, $class)]); - $classType = $scope->resolveTypeByName($class); + if (strtolower($constantName) !== 'class') { + foreach ($classType->getObjectClassReflections() as $classTypeReflection) { + if (!$classTypeReflection->isTrait()) { + continue; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Cannot access constant %s on trait %s.', + $constantName, + $classTypeReflection->getDisplayName(), + ))->identifier('classConstant.onTrait')->build(), + ]; + } + } + + $messages = $this->classCheck->checkClassNames([new ClassNameNodePair($className, $class)]); } if (strtolower($constantName) === 'class') { diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index d8bb82d1417..32086476beb 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -420,4 +420,19 @@ public function testPhpstanInternalClass(): void ]); } + public function testClassConstantAccessedOnTrait(): void + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Test requires PHP 8.2.'); + } + + $this->phpVersion = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/class-constant-accessed-on-trait.php'], [ + [ + 'Cannot access constant TEST on trait ClassConstantAccessedOnTrait\Foo.', + 16, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-accessed-on-trait.php b/tests/PHPStan/Rules/Classes/data/class-constant-accessed-on-trait.php new file mode 100644 index 00000000000..8441b10819b --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/class-constant-accessed-on-trait.php @@ -0,0 +1,18 @@ += 8.2 + +namespace ClassConstantAccessedOnTrait; + +trait Foo +{ + public const TEST = 1; +} + +class Bar +{ + use Foo; +} + +function (): void { + echo Foo::TEST; + echo Foo::class; +}; From 5668c05d95d083f0dcb4a36dd9aac991752516fb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 26 Feb 2025 16:52:50 +0100 Subject: [PATCH 1115/3097] Recreate `@var` PHPDoc type from `Type::toPhpDocNode()` before reporting it as wrong --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 88 +++++++++++++++---- .../Analyser/AnalyserIntegrationTest.php | 4 +- .../VarTagChangedExpressionTypeRuleTest.php | 9 +- .../WrongVariableNameInVarTagRuleTest.php | 17 +++- tests/PHPStan/Rules/PhpDoc/data/bug-12458.php | 29 ++++++ 5 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-12458.php diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 0070554896c..0c1d4e1b490 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -4,12 +4,16 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PHPStan\Analyser\NameScope; use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException; use PHPStan\PhpDoc\Tag\VarTag; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; +use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -24,7 +28,12 @@ final class VarTagTypeRuleHelper { - public function __construct(private bool $checkTypeAgainstPhpDocType, private bool $strictWideningCheck) + public function __construct( + private TypeNodeResolver $typeNodeResolver, + private FileTypeMapper $fileTypeMapper, + private bool $checkTypeAgainstPhpDocType, + private bool $strictWideningCheck, + ) { } @@ -76,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): $errors = []; $exprNativeType = $scope->getNativeType($expr); $containsPhpStanType = $this->containsPhpStanType($varTagType); - if ($this->shouldVarTagTypeBeReported($expr, $exprNativeType, $varTagType)) { + if ($this->shouldVarTagTypeBeReported($scope, $expr, $exprNativeType, $varTagType)) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType); $errors[] = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @var with type %s is not subtype of native type %s.', @@ -86,7 +95,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): } else { $exprType = $scope->getType($expr); if ( - $this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType) + $this->shouldVarTagTypeBeReported($scope, $expr, $exprType, $varTagType) && ($this->checkTypeAgainstPhpDocType || $containsPhpStanType) ) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType); @@ -127,22 +136,22 @@ private function containsPhpStanType(Type $type): bool return false; } - private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $varTagType): bool + private function shouldVarTagTypeBeReported(Scope $scope, Node\Expr $expr, Type $type, Type $varTagType): bool { if ($expr instanceof Expr\Array_) { if ($expr->items === []) { $type = new ArrayType(new MixedType(), new MixedType()); } - return $type->isSuperTypeOf($varTagType)->no(); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Expr\ConstFetch) { - return $type->isSuperTypeOf($varTagType)->no(); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Node\Scalar) { - return $type->isSuperTypeOf($varTagType)->no(); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Expr\New_) { @@ -151,24 +160,24 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v } } - return $this->checkType($type, $varTagType); + return $this->checkType($scope, $type, $varTagType); } - private function checkType(Type $type, Type $varTagType, int $depth = 0): bool + private function checkType(Scope $scope, Type $type, Type $varTagType, int $depth = 0): bool { if ($this->strictWideningCheck) { - return !$type->isSuperTypeOf($varTagType)->yes(); + return !$this->isSuperTypeOfVarType($scope, $type, $varTagType); } if ($type->isConstantArray()->yes()) { if ($type->isIterableAtLeastOnce()->no()) { $type = new ArrayType(new MixedType(), new MixedType()); - return $type->isSuperTypeOf($varTagType)->no(); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } } if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) { - if ($type->isSuperTypeOf($varTagType)->no()) { + if (!$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType)) { return true; } @@ -176,17 +185,62 @@ private function checkType(Type $type, Type $varTagType, int $depth = 0): bool $innerVarTagType = $varTagType->getIterableValueType(); if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) { - return !$innerType->isSuperTypeOf($innerVarTagType)->yes(); + return !$this->isSuperTypeOfVarType($scope, $innerType, $innerVarTagType); } - return $this->checkType($innerType, $innerVarTagType, $depth + 1); + return $this->checkType($scope, $innerType, $innerVarTagType, $depth + 1); } - if ($type->isConstantValue()->yes() && $depth === 0) { - return $type->isSuperTypeOf($varTagType)->no(); + if ($depth === 0 && $type->isConstantValue()->yes()) { + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } - return !$type->isSuperTypeOf($varTagType)->yes(); + return !$this->isSuperTypeOfVarType($scope, $type, $varTagType); + } + + private function isSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool + { + if ($type->isSuperTypeOf($varTagType)->yes()) { + return true; + } + + try { + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); + } catch (NameScopeAlreadyBeingCreatedException) { + return true; + } + + return $type->isSuperTypeOf($varTagType)->yes(); + } + + private function isAtLeastMaybeSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool + { + if (!$type->isSuperTypeOf($varTagType)->no()) { + return true; + } + + try { + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); + } catch (NameScopeAlreadyBeingCreatedException) { + return true; + } + + return !$type->isSuperTypeOf($varTagType)->no(); + } + + /** + * @throws NameScopeAlreadyBeingCreatedException + */ + private function createNameScope(Scope $scope): NameScope + { + $function = $scope->getFunction(); + + return $this->fileTypeMapper->getNameScope( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + ); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index a4ee70e41f5..a73ff8ec727 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1198,9 +1198,7 @@ public function testBug5091(): void public function testBug9459(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php'); - $this->assertCount(1, $errors); - $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); - $this->assertSame(10, $errors[0]->getLine()); + $this->assertCount(0, $errors); } public function testBug9573(): void diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index f3ea56d12f3..08bb43b2fd2 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\FileTypeMapper; /** * @extends RuleTestCase @@ -13,7 +15,12 @@ class VarTagChangedExpressionTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper(true, true)); + return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper( + self::getContainer()->getByType(TypeNodeResolver::class), + self::getContainer()->getByType(FileTypeMapper::class), + true, + true, + )); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index e25e8df9246..c4bb1aec926 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; @@ -23,7 +24,12 @@ protected function getRule(): Rule { return new WrongVariableNameInVarTagRule( self::getContainer()->getByType(FileTypeMapper::class), - new VarTagTypeRuleHelper($this->checkTypeAgainstPhpDocType, $this->strictWideningCheck), + new VarTagTypeRuleHelper( + self::getContainer()->getByType(TypeNodeResolver::class), + self::getContainer()->getByType(FileTypeMapper::class), + $this->checkTypeAgainstPhpDocType, + $this->strictWideningCheck, + ), $this->checkTypeAgainstNativeType, ); } @@ -182,6 +188,15 @@ public function testBug4505(): void $this->analyse([__DIR__ . '/data/bug-4505.php'], []); } + public function testBug12458(): void + { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + + $this->analyse([__DIR__ . '/data/bug-12458.php'], []); + } + public function testEnums(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php new file mode 100644 index 00000000000..08e7e1c7103 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php @@ -0,0 +1,29 @@ + $a + */ + public function test(array $a): void + { + /** @var \Closure(): list $c */ + $c = function () use ($a): array { + return $a; + }; + } + + /** + * @template T of HelloWorld + * @param list $a + */ + public function testGeneric(array $a): void + { + /** @var \Closure(): list $c */ + $c = function () use ($a): array { + return $a; + }; + } +} From 49e49b0ce599c5c50ae40bd25b730af2c2c79fc2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 26 Feb 2025 16:55:02 +0100 Subject: [PATCH 1116/3097] Hooked properties cannot be both final and private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Mirtes --- Makefile | 6 ++ src/Node/ClassPropertyNode.php | 5 + src/Php/PhpVersion.php | 5 + .../Properties/PropertiesInInterfaceRule.php | 33 ++++++- src/Rules/Properties/PropertyInClassRule.php | 64 ++++++++++++ .../PropertiesInInterfaceRuleTest.php | 62 +++++++++++- .../Properties/PropertyInClassRuleTest.php | 97 +++++++++++++++++++ ...stract-final-property-hook-parse-error.php | 10 ++ .../data/abstract-final-property-hook.php | 15 +++ .../Properties/data/final-properties.php | 11 +++ .../final-property-hooks-in-interface.php | 20 ++++ .../Properties/data/final-property-hooks.php | 28 ++++++ .../data/private-final-property-hooks.php | 36 +++++++ .../data/properties-in-interface.php | 2 + ...roperty-in-interface-explicit-abstract.php | 15 +++ 15 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-final-property-hook.php create mode 100644 tests/PHPStan/Rules/Properties/data/final-properties.php create mode 100644 tests/PHPStan/Rules/Properties/data/final-property-hooks-in-interface.php create mode 100644 tests/PHPStan/Rules/Properties/data/final-property-hooks.php create mode 100644 tests/PHPStan/Rules/Properties/data/private-final-property-hooks.php create mode 100644 tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php diff --git a/Makefile b/Makefile index 47ccd330d4c..1122b4ab0fc 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,12 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php \ --exclude tests/PHPStan/Rules/Properties/data/overriding-final-property.php \ + --exclude tests/PHPStan/Rules/Properties/data/private-final-property-hooks.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-final-property-hook.php \ + --exclude tests/PHPStan/Rules/Properties/data/final-property-hooks-in-interface.php \ + --exclude tests/PHPStan/Rules/Properties/data/final-property-hooks.php \ + --exclude tests/PHPStan/Rules/Properties/data/final-properties.php \ + --exclude tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php \ src tests cs: diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index b567aa7f7b6..aae4446638c 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -86,6 +86,11 @@ public function isPrivate(): bool return (bool) ($this->flags & Modifiers::PRIVATE); } + public function isFinal(): bool + { + return (bool) ($this->flags & Modifiers::FINAL); + } + public function isStatic(): bool { return (bool) ($this->flags & Modifiers::STATIC); diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 651aff52052..6f55bdda15f 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -357,6 +357,11 @@ public function supportsPropertyHooks(): bool return $this->versionId >= 80400; } + public function supportsFinalProperties(): bool + { + return $this->versionId >= 80400; + } + public function supportsAsymmetricVisibility(): bool { return $this->versionId >= 80400; diff --git a/src/Rules/Properties/PropertiesInInterfaceRule.php b/src/Rules/Properties/PropertiesInInterfaceRule.php index 3ff9546d353..6126625da6c 100644 --- a/src/Rules/Properties/PropertiesInInterfaceRule.php +++ b/src/Rules/Properties/PropertiesInInterfaceRule.php @@ -32,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array if (!$this->phpVersion->supportsPropertyHooks()) { return [ - RuleErrorBuilder::message('Interfaces cannot include properties.') + RuleErrorBuilder::message('Interfaces can include properties only on PHP 8.4 and later.') ->nonIgnorable() ->identifier('property.inInterface') ->build(), @@ -75,6 +75,37 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($node->isAbstract()) { + return [ + RuleErrorBuilder::message('Property in interface cannot be explicitly abstract.') + ->nonIgnorable() + ->identifier('property.abstractInInterface') + ->build(), + ]; + } + + if ($node->isFinal()) { + return [ + RuleErrorBuilder::message('Interfaces cannot include final properties.') + ->nonIgnorable() + ->identifier('property.finalInInterface') + ->build(), + ]; + } + + foreach ($node->getHooks() as $hook) { + if (!$hook->isFinal()) { + continue; + } + + return [ + RuleErrorBuilder::message('Property hook cannot be both abstract and final.') + ->nonIgnorable() + ->identifier('property.abstractFinalHook') + ->build(), + ]; + } + if ($this->hasAnyHookBody($node)) { return [ RuleErrorBuilder::message('Interfaces cannot include property hooks with bodies.') diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index f14c9730a09..607fa307928 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -32,6 +32,18 @@ public function processNode(Node $node, Scope $scope): array return []; } + if ( + $node->isFinal() + && !$this->phpVersion->supportsFinalProperties() + ) { + return [ + RuleErrorBuilder::message('Final properties are supported only on PHP 8.4 and later.') + ->nonIgnorable() + ->identifier('property.final') + ->build(), + ]; + } + if (!$this->phpVersion->supportsPropertyHooks()) { if ($node->hasHooks()) { return [ @@ -81,6 +93,58 @@ public function processNode(Node $node, Scope $scope): array ]; } + if ($node->isPrivate()) { + if ($node->isFinal()) { + return [ + RuleErrorBuilder::message('Property cannot be both final and private.') + ->nonIgnorable() + ->identifier('property.finalPrivate') + ->build(), + ]; + } + + foreach ($node->getHooks() as $hook) { + if (!$hook->isFinal()) { + continue; + } + + return [ + RuleErrorBuilder::message('Private property cannot have a final hook.') + ->nonIgnorable() + ->identifier('property.finalPrivateHook') + ->build(), + ]; + } + } + + if ($node->isAbstract()) { + if ($node->isFinal()) { + return [ + RuleErrorBuilder::message('Property cannot be both abstract and final.') + ->nonIgnorable() + ->identifier('property.abstractFinal') + ->build(), + ]; + } + + foreach ($node->getHooks() as $hook) { + if ($hook->body !== null) { + continue; + } + + if (!$hook->isFinal()) { + continue; + } + + return [ + RuleErrorBuilder::message('Property cannot be both abstract and final.') + ->nonIgnorable() + ->identifier('property.abstractFinal') + ->build(), + ]; + } + } + if ($node->isReadOnly()) { if ($node->hasHooks()) { return [ diff --git a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php index 3d6dffcb8c5..5cfb5e7b58b 100644 --- a/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertiesInInterfaceRuleTest.php @@ -29,17 +29,21 @@ public function testPhp83AndPropertiesInInterface(): void $this->analyse([__DIR__ . '/data/properties-in-interface.php'], [ [ - 'Interfaces cannot include properties.', + 'Interfaces can include properties only on PHP 8.4 and later.', 7, ], [ - 'Interfaces cannot include properties.', + 'Interfaces can include properties only on PHP 8.4 and later.', 9, ], [ - 'Interfaces cannot include properties.', + 'Interfaces can include properties only on PHP 8.4 and later.', 11, ], + [ + 'Interfaces can include properties only on PHP 8.4 and later.', + 13, + ], ]); } @@ -54,11 +58,11 @@ public function testPhp83AndPropertyHooksInInterface(): void $this->analyse([__DIR__ . '/data/property-hooks-in-interface.php'], [ [ - 'Interfaces cannot include properties.', + 'Interfaces can include properties only on PHP 8.4 and later.', 7, ], [ - 'Interfaces cannot include properties.', + 'Interfaces can include properties only on PHP 8.4 and later.', 9, ], ]); @@ -79,6 +83,10 @@ public function testPhp84AndPropertiesInInterface(): void 'Interfaces can only include hooked properties.', 11, ], + [ + 'Interfaces can only include hooked properties.', + 13, + ], ]); } @@ -140,6 +148,50 @@ public function testPhp84AndReadonlyPropertyHooksInInterface(): void ]); } + public function testPhp84AndFinalPropertyHooksInInterface(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/final-property-hooks-in-interface.php'], [ + [ + 'Interfaces cannot include final properties.', + 7, + ], + [ + 'Interfaces cannot include final properties.', + 9, + ], + [ + 'Interfaces cannot include final properties.', + 11, + ], + [ + 'Property hook cannot be both abstract and final.', + 13, + ], + [ + 'Property hook cannot be both abstract and final.', + 17, + ], + ]); + } + + public function testPhp84AndExplicitAbstractProperty(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/property-in-interface-explicit-abstract.php'], [ + [ + 'Property in interface cannot be explicitly abstract.', + 8, + ], + ]); + } + public function testPhp84AndStaticHookedPropertyInInterface(): void { if (PHP_VERSION_ID < 80400) { diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 54e041e6948..7be86e2089e 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -213,4 +213,101 @@ public function testPhp84AndStaticHookedProperties(): void ]); } + public function testPhp84AndPrivateFinalHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/private-final-property-hooks.php'], [ + [ + 'Property cannot be both final and private.', + 7, + ], + [ + 'Private property cannot have a final hook.', + 11, + ], + ]); + } + + public function testPhp84AndAbstractFinalHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-final-property-hook.php'], [ + [ + 'Property cannot be both abstract and final.', + 7, + ], + ]); + } + + public function testPhp84AndAbstractFinalHookedPropertiesParseError(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + // errors when parsing with php-parser, see https://github.com/nikic/PHP-Parser/issues/1071 + $this->analyse([__DIR__ . '/data/abstract-final-property-hook-parse-error.php'], [ + [ + 'Cannot use the final modifier on an abstract class member on line 7', + 7, + ], + ]); + } + + public function testPhp84FinalProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/final-properties.php'], [ + [ + 'Property cannot be both final and private.', + 7, + ], + ]); + } + + public function testBeforePhp84FinalProperties(): void + { + if (PHP_VERSION_ID >= 80400) { + $this->markTestSkipped('Test requires PHP 8.3 or earlier.'); + } + + $this->analyse([__DIR__ . '/data/final-properties.php'], [ + [ + 'Final properties are supported only on PHP 8.4 and later.', + 7, + ], + [ + 'Final properties are supported only on PHP 8.4 and later.', + 8, + ], + [ + 'Final properties are supported only on PHP 8.4 and later.', + 9, + ], + ]); + } + + public function testPhp84FinalPropertyHooks(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/final-property-hooks.php'], [ + [ + 'Cannot use the final modifier on an abstract class member on line 19', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php b/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php new file mode 100644 index 00000000000..230de9d816e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php @@ -0,0 +1,10 @@ += 8.4 + +namespace AbstractFinalHookParseError; + +abstract class User +{ + final abstract public string $bar { + get; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook.php b/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook.php new file mode 100644 index 00000000000..baba303bf18 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-final-property-hook.php @@ -0,0 +1,15 @@ += 8.4 + +namespace AbstractFinalHook; + +abstract class User +{ + abstract public string $foo { + final get; + } +} + +abstract class Foo +{ + abstract public int $i { final get { return 1;} set; } +} diff --git a/tests/PHPStan/Rules/Properties/data/final-properties.php b/tests/PHPStan/Rules/Properties/data/final-properties.php new file mode 100644 index 00000000000..1e04ef49b68 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/final-properties.php @@ -0,0 +1,11 @@ + $this->firstName; + set => $this->firstName; + } + + public final string $middleName { get => $this->middleName; } + + public final string $lastName { set => $this->lastName; } +} + +abstract class HiWorld +{ + public abstract final string $firstName { get { return 'jake'; } set; } +} + +final class GoodMorningWorld +{ + public string $firstName { + get => $this->firstName; + set => $this->firstName; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/private-final-property-hooks.php b/tests/PHPStan/Rules/Properties/data/private-final-property-hooks.php new file mode 100644 index 00000000000..1ce2830a95a --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/private-final-property-hooks.php @@ -0,0 +1,36 @@ += 8.4 + +namespace PrivateFinalHook; + +final class User +{ + final private string $privatePropGet = 'mailto: example.org' { + get => 'private:' . $this->privatePropGet; + } + + private string $private = 'mailto: example.org' { + final set => 'private:' . $this->private; + get => 'private:' . $this->private; + } + + protected string $protected = 'mailto: example.org' { + final get => 'protected:' . $this->protected; + } + + public string $public = 'mailto: example.org' { + final get => 'public:' . $this->public; + } + + private string $email = 'mailto: example.org' { + get => 'mailto:' . $this->email; + } + + function doFoo(): void + { + $u = new User; + var_dump($u->private); + var_dump($u->protected); + var_dump($u->public); + var_dump($u->email); + } +} diff --git a/tests/PHPStan/Rules/Properties/data/properties-in-interface.php b/tests/PHPStan/Rules/Properties/data/properties-in-interface.php index 3d64511f643..4d104487fb7 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-in-interface.php +++ b/tests/PHPStan/Rules/Properties/data/properties-in-interface.php @@ -9,4 +9,6 @@ interface HelloWorld public \DateTimeInterface $dateTime; public static \Closure $callable; + + public final \DateTime $finalProperty; } diff --git a/tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php b/tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php new file mode 100644 index 00000000000..d91986b659b --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php @@ -0,0 +1,15 @@ + Date: Wed, 26 Feb 2025 17:05:57 +0100 Subject: [PATCH 1117/3097] Hooked properties cannot be both abstract and private --- Makefile | 1 + src/Rules/Properties/PropertyInClassRule.php | 9 +++++++++ .../Rules/Properties/PropertyInClassRuleTest.php | 14 ++++++++++++++ .../data/abstract-private-property-hook.php | 10 ++++++++++ 4 files changed, 34 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php diff --git a/Makefile b/Makefile index 1122b4ab0fc..eec3e3ba33c 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,7 @@ lint: --exclude tests/PHPStan/Rules/Classes/data/invalid-hooked-properties.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-before.php \ --exclude tests/PHPStan/Parser/data/cleaning-property-hooks-after.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php \ --exclude tests/PHPStan/Rules/Properties/data/existing-classes-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php \ --exclude tests/PHPStan/Rules/Properties/data/overriding-final-property.php \ diff --git a/src/Rules/Properties/PropertyInClassRule.php b/src/Rules/Properties/PropertyInClassRule.php index 607fa307928..18190223b2d 100644 --- a/src/Rules/Properties/PropertyInClassRule.php +++ b/src/Rules/Properties/PropertyInClassRule.php @@ -94,6 +94,15 @@ public function processNode(Node $node, Scope $scope): array } if ($node->isPrivate()) { + if ($node->isAbstract()) { + return [ + RuleErrorBuilder::message('Property cannot be both abstract and private.') + ->nonIgnorable() + ->identifier('property.abstractPrivate') + ->build(), + ]; + } + if ($node->isFinal()) { return [ RuleErrorBuilder::message('Property cannot be both final and private.') diff --git a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php index 7be86e2089e..e0b65f8bd1e 100644 --- a/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyInClassRuleTest.php @@ -245,6 +245,20 @@ public function testPhp84AndAbstractFinalHookedProperties(): void ]); } + public function testPhp84AndAbstractPrivateHookedProperties(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/abstract-private-property-hook.php'], [ + [ + 'Property cannot be both abstract and private.', + 7, + ], + ]); + } + public function testPhp84AndAbstractFinalHookedPropertiesParseError(): void { if (PHP_VERSION_ID < 80400) { diff --git a/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php b/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php new file mode 100644 index 00000000000..fc85379bc52 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/abstract-private-property-hook.php @@ -0,0 +1,10 @@ += 8.4 + +namespace AbstractPrivateHook; + +abstract class Foo +{ + abstract private int $i { get; } + abstract protected int $ii { get; } + abstract public int $iii { get; } +} From 595b18681386cf1f5ebf0d7e43efee1de9279408 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 26 Feb 2025 17:42:26 +0100 Subject: [PATCH 1118/3097] Implement FinalPrivateConstantRule --- Makefile | 1 + conf/config.level0.neon | 1 + .../Constants/FinalPrivateConstantRule.php | 49 +++++++++++++++++++ .../FinalPrivateConstantRuleTest.php | 27 ++++++++++ .../Constants/data/final-private-const.php | 11 +++++ 5 files changed, 89 insertions(+) create mode 100644 src/Rules/Constants/FinalPrivateConstantRule.php create mode 100644 tests/PHPStan/Rules/Constants/FinalPrivateConstantRuleTest.php create mode 100644 tests/PHPStan/Rules/Constants/data/final-private-const.php diff --git a/Makefile b/Makefile index eec3e3ba33c..d50e2497e1c 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/final-property-hooks.php \ --exclude tests/PHPStan/Rules/Properties/data/final-properties.php \ --exclude tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php \ + --exclude tests/PHPStan/Rules/Constants/data/final-private-const.php \ src tests cs: diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 91602816517..b5b1f2f793a 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -46,6 +46,7 @@ rules: - PHPStan\Rules\Constants\FinalConstantRule - PHPStan\Rules\Constants\MagicConstantContextRule - PHPStan\Rules\Constants\NativeTypedClassConstantRule + - PHPStan\Rules\Constants\FinalPrivateConstantRule - PHPStan\Rules\EnumCases\EnumCaseAttributesRule - PHPStan\Rules\Exceptions\NoncapturingCatchRule - PHPStan\Rules\Exceptions\ThrowExpressionRule diff --git a/src/Rules/Constants/FinalPrivateConstantRule.php b/src/Rules/Constants/FinalPrivateConstantRule.php new file mode 100644 index 00000000000..2be7d511651 --- /dev/null +++ b/src/Rules/Constants/FinalPrivateConstantRule.php @@ -0,0 +1,49 @@ + */ +final class FinalPrivateConstantRule implements Rule +{ + + public function getNodeType(): string + { + return ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new ShouldNotHappenException(); + } + $classReflection = $scope->getClassReflection(); + + if (!$node->isFinal()) { + return []; + } + + if (!$node->isPrivate()) { + return []; + } + + $errors = []; + foreach ($node->consts as $classConstNode) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Private constant %s::%s() cannot be final as it is never overridden by other classes.', + $classReflection->getDisplayName(), + $classConstNode->name->name, + ))->identifier('classConstant.finalPrivate')->nonIgnorable()->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Constants/FinalPrivateConstantRuleTest.php b/tests/PHPStan/Rules/Constants/FinalPrivateConstantRuleTest.php new file mode 100644 index 00000000000..8e98e04565f --- /dev/null +++ b/tests/PHPStan/Rules/Constants/FinalPrivateConstantRuleTest.php @@ -0,0 +1,27 @@ + */ +class FinalPrivateConstantRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new FinalPrivateConstantRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/final-private-const.php'], [ + [ + 'Private constant FinalPrivateConstants\User::FINAL_PRIVATE() cannot be final as it is never overridden by other classes.', + 8, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/final-private-const.php b/tests/PHPStan/Rules/Constants/data/final-private-const.php new file mode 100644 index 00000000000..23c21b12713 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/final-private-const.php @@ -0,0 +1,11 @@ + Date: Thu, 27 Feb 2025 17:20:42 +0100 Subject: [PATCH 1119/3097] Add regression tests --- .../WrongVariableNameInVarTagRuleTest.php | 27 +++++++++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-10861.php | 23 ++++++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-11015.php | 25 +++++++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-11535.php | 18 +++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-10861.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-11015.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-11535.php diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index c4bb1aec926..92693793878 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -197,6 +197,33 @@ public function testBug12458(): void $this->analyse([__DIR__ . '/data/bug-12458.php'], []); } + public function testBug11015(): void + { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + + $this->analyse([__DIR__ . '/data/bug-11015.php'], []); + } + + public function testBug10861(): void + { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + + $this->analyse([__DIR__ . '/data/bug-10861.php'], []); + } + + public function testBug11535(): void + { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + + $this->analyse([__DIR__ . '/data/bug-11535.php'], []); + } + public function testEnums(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-10861.php b/tests/PHPStan/Rules/PhpDoc/data/bug-10861.php new file mode 100644 index 00000000000..a9116442b19 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-10861.php @@ -0,0 +1,23 @@ + $array1 + * @param-out array $array1 + */ + public function sayHello(array &$array1): void + { + $values_1 = $array1; + + $values_1 = array_filter($values_1, function (mixed $value): bool { + return $value !== []; + }); + + /** @var array $values_1 */ + $array1 = $values_1; + } +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-11015.php b/tests/PHPStan/Rules/PhpDoc/data/bug-11015.php new file mode 100644 index 00000000000..2b648f77d7c --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-11015.php @@ -0,0 +1,25 @@ +fetch(); + if (empty($b)) { + return; + } + + /** @var array $b */ + echo $b['a']; + } + + public function sayHello2(PDOStatement $date): void + { + $b = $date->fetch(); + + /** @var array $b */ + echo $b['a']; + } +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-11535.php b/tests/PHPStan/Rules/PhpDoc/data/bug-11535.php new file mode 100644 index 00000000000..17feae9b103 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-11535.php @@ -0,0 +1,18 @@ + */ +$a = function(string $b) { + return [1,2,3]; +}; + +/** @var \Closure(array): array */ +$a = function(array $b) { + return $b; +}; + +/** @var \Closure(string): string */ +$a = function(string $b) { + return $b; +}; From 7a3bfabe304ecffdb7e88cc2a721ec9ea065b880 Mon Sep 17 00:00:00 2001 From: mat-se <200997070+mat-se@users.noreply.github.com> Date: Wed, 26 Feb 2025 20:17:47 +0000 Subject: [PATCH 1120/3097] Change return type of method Closes phpstan/phpstan#12579 --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 366c3da0b64..0900f12909d 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10169,7 +10169,7 @@ 'ResourceBundle::get' => ['', 'index'=>'string|int', 'fallback='=>'bool'], 'ResourceBundle::getErrorCode' => ['int'], 'ResourceBundle::getErrorMessage' => ['string'], -'ResourceBundle::getLocales' => ['array', 'bundlename'=>'string'], +'ResourceBundle::getLocales' => ['array|false', 'bundlename'=>'string'], 'resourcebundle_count' => ['int', 'r'=>'resourcebundle'], 'resourcebundle_create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], 'resourcebundle_get' => ['', 'r'=>'resourcebundle', 'index'=>'string|int', 'fallback='=>'bool'], From 234dd4227cf2c4be9aadf24fa082cce4014c80de Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 27 Feb 2025 17:26:40 +0100 Subject: [PATCH 1121/3097] Fix build after merge --- .../PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 8424ebff8cb..0306a35e97a 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -207,7 +207,6 @@ public function testBug12458(): void public function testBug11015(): void { - $this->checkTypeAgainstNativeType = true; $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; @@ -216,7 +215,6 @@ public function testBug11015(): void public function testBug10861(): void { - $this->checkTypeAgainstNativeType = true; $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; @@ -225,7 +223,6 @@ public function testBug10861(): void public function testBug11535(): void { - $this->checkTypeAgainstNativeType = true; $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; From 192fefa5f09ed10a2d5e0ccc046271e960834b9d Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 6 Feb 2025 02:46:17 +0900 Subject: [PATCH 1122/3097] Make precise scandir() argument and return type --- resources/functionMap.php | 2 +- resources/functionMap_php80delta.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 0900f12909d..2a5b075d2d6 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10283,7 +10283,7 @@ 'scalebarObj::set' => ['int', 'property_name'=>'string', 'new_value'=>''], 'scalebarObj::setImageColor' => ['int', 'red'=>'int', 'green'=>'int', 'blue'=>'int'], 'scalebarObj::updateFromString' => ['int', 'snippet'=>'string'], -'scandir' => ['list|false', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING| SCANDIR_SORT_NONE', 'context='=>'resource'], +'scandir' => ['__benevolent|false>', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING|SCANDIR_SORT_NONE', 'context='=>'resource'], 'SDO_DAS_ChangeSummary::beginLogging' => [''], 'SDO_DAS_ChangeSummary::endLogging' => [''], 'SDO_DAS_ChangeSummary::getChangedDataObjects' => ['SDO_List'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index f1e0771eb4a..bca5fef36bb 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -99,6 +99,7 @@ 'PhpToken::getTokenName' => ['non-falsy-string'], 'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], + 'scandir' => ['__benevolent|false>', 'dir'=>'string', 'sorting_order='=>'SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING|SCANDIR_SORT_NONE', 'context='=>'?resource'], 'set_error_handler' => ['?callable', 'callback'=>'null|callable(int,string,string,int):bool', 'error_types='=>'int'], 'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], 'socket_select' => ['int|false', '&w_read'=>'Socket[]|null', '&w_write'=>'Socket[]|null', '&w_except'=>'Socket[]|null', 'seconds'=>'int|null', 'microseconds='=>'int'], From 4efa22b409775ab086e10b03af0fb218c08b8f46 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 28 Feb 2025 10:23:44 +0100 Subject: [PATCH 1123/3097] Readonly properties cannot be `unset()` --- src/Rules/Variables/UnsetRule.php | 34 ++++++ .../PHPStan/Rules/Variables/UnsetRuleTest.php | 33 +++++- .../Rules/Variables/data/bug-12421.php | 108 ++++++++++++++++++ 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-12421.php diff --git a/src/Rules/Variables/UnsetRule.php b/src/Rules/Variables/UnsetRule.php index dc75eb024ec..8240e35e95a 100644 --- a/src/Rules/Variables/UnsetRule.php +++ b/src/Rules/Variables/UnsetRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; @@ -17,6 +18,12 @@ final class UnsetRule implements Rule { + public function __construct( + private PropertyReflectionFinder $propertyReflectionFinder, + ) + { + } + public function getNodeType(): string { return Node\Stmt\Unset_::class; @@ -69,6 +76,33 @@ private function canBeUnset(Node $node, Scope $scope): ?IdentifierRuleError } return $this->canBeUnset($node->var, $scope); + } elseif ( + $node instanceof Node\Expr\PropertyFetch + && $node->name instanceof Node\Identifier + ) { + $foundPropertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $scope); + if ($foundPropertyReflection === null) { + return null; + } + + $propertyReflection = $foundPropertyReflection->getNativeReflection(); + if ($propertyReflection === null) { + return null; + } + + if ($propertyReflection->isReadOnly() || $propertyReflection->isReadOnlyByPhpDoc()) { + return RuleErrorBuilder::message( + sprintf( + 'Cannot unset %s %s::$%s property.', + $propertyReflection->isReadOnly() ? 'readonly' : '@readonly', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($node->getStartLine()) + ->identifier($propertyReflection->isReadOnly() ? 'unset.readOnlyProperty' : 'unset.readOnlyPropertyByPhpDoc') + ->build(); + } } return null; diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index 036d09c9749..0b5861c1c00 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Variables; +use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -13,7 +14,7 @@ class UnsetRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnsetRule(); + return new UnsetRule(self::getContainer()->getByType(PropertyReflectionFinder::class)); } public function testUnsetRule(): void @@ -91,4 +92,34 @@ public function testBug4565(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-4565.php'], []); } + public function testBug12421(): void + { + $this->analyse([__DIR__ . '/data/bug-12421.php'], [ + [ + 'Cannot unset readonly Bug12421\NativeReadonlyClass::$y property.', + 11, + ], + [ + 'Cannot unset readonly Bug12421\NativeReadonlyProperty::$y property.', + 15, + ], + [ + 'Cannot unset @readonly Bug12421\PhpdocReadonlyClass::$y property.', + 19, + ], + [ + 'Cannot unset @readonly Bug12421\PhpdocReadonlyProperty::$y property.', + 23, + ], + [ + 'Cannot unset @readonly Bug12421\PhpdocImmutableClass::$y property.', + 27, + ], + [ + 'Cannot unset readonly Bug12421\NativeReadonlyProperty::$y property.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-12421.php b/tests/PHPStan/Rules/Variables/data/bug-12421.php new file mode 100644 index 00000000000..eb0c53f7557 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12421.php @@ -0,0 +1,108 @@ += 8.2 + +namespace Bug12421; + +function doFoo() { + $x = new RegularProperty(); + unset($x->y); + var_dump($x->y); + + $x = new NativeReadonlyClass(); + unset($x->y); + var_dump($x->y); + + $x = new NativeReadonlyProperty(); + unset($x->y); + var_dump($x->y); + + $x = new PhpdocReadonlyClass(); + unset($x->y); + var_dump($x->y); + + $x = new PhpdocReadonlyProperty(); + unset($x->y); + var_dump($x->y); + + $x = new PhpdocImmutableClass(); + unset($x->y); + var_dump($x->y); + + $x = new \stdClass(); + unset($x->y); + + $x = new NativeReadonlyPropertySubClass(); + unset($x->y); + var_dump($x->y); +} + +readonly class NativeReadonlyClass +{ + public Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +class NativeReadonlyProperty +{ + public readonly Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +/** @readonly */ +class PhpdocReadonlyClass +{ + public Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +class PhpdocReadonlyProperty +{ + /** @readonly */ + public Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +/** @immutable */ +class PhpdocImmutableClass +{ + public Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +class RegularProperty +{ + public Y $y; + + public function __construct() + { + $this->y = new Y(); + } +} + +class NativeReadonlyPropertySubClass extends NativeReadonlyProperty +{ +} + +class Y +{ +} + From a5e4bfae41554e26ff1dd3b1c5a40ae4a1fe6511 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 1 Mar 2025 09:44:32 +0100 Subject: [PATCH 1124/3097] MissingTypehintCheck: reduce duplicate work --- src/Rules/MissingTypehintCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 2f1317e9209..a46edf26c45 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -91,7 +91,7 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array } if ($type instanceof IntersectionType) { if ($type->isList()->yes()) { - return $traverse($type->getIterableValueType()); + return $traverse($iterableValue); } return $type; From 087fdebb2a86cb25ba15e28e61acdaa01ccf2bd6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 1 Mar 2025 16:08:28 +0100 Subject: [PATCH 1125/3097] StrlenFunctionReturnTypeExtension: Cleanup `instanceof ConstantString` --- phpstan-baseline.neon | 12 ------------ src/Type/Php/MbStrlenFunctionReturnTypeExtension.php | 12 ++++-------- src/Type/Php/StrlenFunctionReturnTypeExtension.php | 11 +++-------- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 11a651dc006..6190c0a39ac 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1545,12 +1545,6 @@ parameters: count: 2 path: src/Type/Php/LtrimFunctionReturnTypeExtension.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Php/MbStrlenFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1629,12 +1623,6 @@ parameters: count: 1 path: src/Type/Php/StrRepeatFunctionReturnTypeExtension.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Php/StrlenFunctionReturnTypeExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php index 84464f30bd1..8f6fd258193 100644 --- a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php @@ -92,24 +92,20 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($args[0]->value); - $constantScalars = $argType->getConstantScalarTypes(); + $constantScalars = $argType->getConstantScalarValues(); $lengths = []; foreach ($constantScalars as $constantScalar) { - $stringScalar = $constantScalar->toString(); - if (!($stringScalar instanceof ConstantStringType)) { - $lengths = []; - break; - } + $stringScalar = (string) $constantScalar; foreach ($encodings as $encoding) { if (!$this->isSupportedEncoding($encoding)) { continue; } - $length = @mb_strlen($stringScalar->getValue(), $encoding); + $length = @mb_strlen($stringScalar, $encoding); if ($length === false) { - throw new ShouldNotHappenException(sprintf('Got false on a supported encoding %s and value %s', $encoding, var_export($stringScalar->getValue(), true))); + throw new ShouldNotHappenException(sprintf('Got false on a supported encoding %s and value %s', $encoding, var_export($stringScalar, true))); } $lengths[] = $length; } diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index 40b1c855872..bc91f8595b6 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerRangeType; @@ -42,16 +41,12 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($args[0]->value); - $constantScalars = $argType->getConstantScalarTypes(); + $constantScalars = $argType->getConstantScalarValues(); $lengths = []; foreach ($constantScalars as $constantScalar) { - $stringScalar = $constantScalar->toString(); - if (!($stringScalar instanceof ConstantStringType)) { - $lengths = []; - break; - } - $length = strlen($stringScalar->getValue()); + $stringScalar = (string) $constantScalar; + $length = strlen($stringScalar); $lengths[] = $length; } From b80968f2e7ed2a7810d9e6e531e6cb9fb1de92f0 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 10 Jan 2025 18:04:55 +0100 Subject: [PATCH 1126/3097] More precise return type for mysqli_fetch_all() --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 2a5b075d2d6..341b7261c90 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -7473,7 +7473,7 @@ 'mysqli_errno' => ['int', 'link'=>'mysqli'], 'mysqli_error' => ['string|null', 'link'=>'mysqli'], 'mysqli_error_list' => ['array', 'connection'=>'mysqli'], -'mysqli_fetch_all' => ['array', 'result'=>'mysqli_result', 'resulttype='=>'int'], +'mysqli_fetch_all' => ['list', 'result'=>'mysqli_result', 'resulttype='=>'int'], 'mysqli_fetch_array' => ['array|null|false', 'result'=>'mysqli_result', 'resulttype='=>'int'], 'mysqli_fetch_assoc' => ['array|null|false', 'result'=>'mysqli_result'], 'mysqli_fetch_column' => ['null|int|float|string|false', 'result' => 'mysqli_result', 'column'=>'int'], @@ -7525,7 +7525,7 @@ 'mysqli_result::__construct' => ['void', 'link'=>'mysqli', 'resultmode='=>'int'], 'mysqli_result::close' => ['void'], 'mysqli_result::data_seek' => ['bool', 'offset'=>'int'], -'mysqli_result::fetch_all' => ['array', 'resulttype='=>'int'], +'mysqli_result::fetch_all' => ['list', 'resulttype='=>'int'], 'mysqli_result::fetch_array' => ['array|null|false', 'resulttype='=>'int'], 'mysqli_result::fetch_assoc' => ['array|null|false'], 'mysqli_result::fetch_column' => ['null|int|float|string|false', 'column'=>'int'], From f87b890fcf55dbf4a2505c8ddaf206392b1ee67d Mon Sep 17 00:00:00 2001 From: Dmytro Dymarchuk Date: Fri, 17 Jan 2025 20:56:52 +0200 Subject: [PATCH 1127/3097] Fix scope in enum match arm body --- src/Analyser/NodeScopeResolver.php | 46 ++++++++++++------- .../Analyser/NodeScopeResolverTest.php | 1 + .../data/scope-in-enum-match-arm-body.php | 26 +++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/scope-in-enum-match-arm-body.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1d19ee75657..31c726f6dea 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3644,6 +3644,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $condNodes = []; $conditionCases = []; + $conditionExprs = []; foreach ($arm->conds as $cond) { if (!$cond instanceof Expr\ClassConstFetch) { continue 2; @@ -3701,6 +3702,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $armConditionScope, $cond->getStartLine(), ); + $conditionExprs[] = $cond; unset($unusedIndexedEnumCases[$loweredFetchedClassName][$caseName]); } @@ -3714,10 +3716,11 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $conditionCaseType = new UnionType($conditionCases); } + $filteringExpr = $this->getFilteringExprForMatchArm($expr, $conditionExprs); $matchArmBodyScope = $matchScope->addTypeToExpression( $expr->cond, $conditionCaseType, - ); + )->filterByTruthyValue($filteringExpr); $matchArmBody = new MatchExpressionArmBody($matchArmBodyScope, $arm->body); $armNodes[$i] = new MatchExpressionArm($matchArmBody, $condNodes, $arm->getStartLine()); @@ -3793,22 +3796,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $filteringExprs[] = $armCond; } - if (count($filteringExprs) === 1) { - $filteringExpr = new BinaryOp\Identical($expr->cond, $filteringExprs[0]); - } else { - $items = []; - foreach ($filteringExprs as $filteringExpr) { - $items[] = new Node\ArrayItem($filteringExpr); - } - $filteringExpr = new FuncCall( - new Name\FullyQualified('in_array'), - [ - new Arg($expr->cond), - new Arg(new Array_($items)), - new Arg(new ConstFetch(new Name\FullyQualified('true'))), - ], - ); - } + $filteringExpr = $this->getFilteringExprForMatchArm($expr, $filteringExprs); $bodyScope = $this->processExprNode($stmt, $filteringExpr, $matchScope, static function (): void { }, $deepContext)->getTruthyScope(); @@ -6591,4 +6579,28 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding): return $stmts; } + /** + * @param array $conditions + */ + public function getFilteringExprForMatchArm(Expr\Match_ $expr, array $conditions): BinaryOp\Identical|FuncCall + { + if (count($conditions) === 1) { + return new BinaryOp\Identical($expr->cond, $conditions[0]); + } + + $items = []; + foreach ($conditions as $filteringExpr) { + $items[] = new Node\ArrayItem($filteringExpr); + } + + return new FuncCall( + new Name\FullyQualified('in_array'), + [ + new Arg($expr->cond), + new Arg(new Array_($items)), + new Arg(new ConstFetch(new Name\FullyQualified('true'))), + ], + ); + } + } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index cc9f6112fc0..7f3d9638b2b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -102,6 +102,7 @@ private static function findTestFiles(): iterable define('TEST_ARRAY_CONSTANT', [true, false, null]); define('TEST_ENUM_CONSTANT', Foo::ONE); yield __DIR__ . '/data/new-in-initializers-runtime.php'; + yield __DIR__ . '/data/scope-in-enum-match-arm-body.php'; } yield __DIR__ . '/../Rules/Comparison/data/bug-6473.php'; diff --git a/tests/PHPStan/Analyser/data/scope-in-enum-match-arm-body.php b/tests/PHPStan/Analyser/data/scope-in-enum-match-arm-body.php new file mode 100644 index 00000000000..a46af9b5304 --- /dev/null +++ b/tests/PHPStan/Analyser/data/scope-in-enum-match-arm-body.php @@ -0,0 +1,26 @@ + assertType('int', $nullable), + self::ALLOW_NULLABLE_INT => assertType('int|null', $nullable), + }; + } +} + + + From b14344963d5a870acc903cdacaf2ac8b0503b998 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 2 Mar 2025 10:14:22 +0100 Subject: [PATCH 1128/3097] Hooked properties cannot be `unset()` --- src/Reflection/Php/PhpPropertyReflection.php | 5 ++ src/Rules/Variables/UnsetRule.php | 32 +++++++++ .../PHPStan/Rules/Variables/UnsetRuleTest.php | 63 +++++++++++++++++- .../Variables/data/unset-hooked-property.php | 66 +++++++++++++++++++ 4 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Variables/data/unset-hooked-property.php diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index c667c6e5e38..05228b7016b 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -256,6 +256,11 @@ public function hasHook(string $hookType): bool return $this->setHook !== null; } + public function isHooked(): bool + { + return $this->getHook !== null || $this->setHook !== null; + } + public function getHook(string $hookType): ExtendedMethodReflection { if ($hookType === 'get') { diff --git a/src/Rules/Variables/UnsetRule.php b/src/Rules/Variables/UnsetRule.php index 8240e35e95a..a376744bc2b 100644 --- a/src/Rules/Variables/UnsetRule.php +++ b/src/Rules/Variables/UnsetRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Rules\Rule; @@ -20,6 +21,7 @@ final class UnsetRule implements Rule public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, + private PhpVersion $phpVersion, ) { } @@ -103,6 +105,36 @@ private function canBeUnset(Node $node, Scope $scope): ?IdentifierRuleError ->identifier($propertyReflection->isReadOnly() ? 'unset.readOnlyProperty' : 'unset.readOnlyPropertyByPhpDoc') ->build(); } + + if ($propertyReflection->isHooked()) { + return RuleErrorBuilder::message( + sprintf( + 'Cannot unset hooked %s::$%s property.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($node->getStartLine()) + ->identifier('unset.hookedProperty') + ->build(); + } elseif ($this->phpVersion->supportsPropertyHooks()) { + if ( + !$propertyReflection->isPrivate() + && !$propertyReflection->isFinal()->yes() + && !$propertyReflection->getDeclaringClass()->isFinal() + ) { + return RuleErrorBuilder::message( + sprintf( + 'Cannot unset property %s::$%s because it might have hooks in a subclass.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($node->getStartLine()) + ->identifier('unset.possiblyHookedProperty') + ->build(); + } + } } return null; diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index 0b5861c1c00..0564fbe5095 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -2,9 +2,12 @@ namespace PHPStan\Rules\Variables; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use function array_merge; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -14,7 +17,10 @@ class UnsetRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnsetRule(self::getContainer()->getByType(PropertyReflectionFinder::class)); + return new UnsetRule( + self::getContainer()->getByType(PropertyReflectionFinder::class), + self::getContainer()->getByType(PhpVersion::class), + ); } public function testUnsetRule(): void @@ -55,7 +61,18 @@ public function testBug2752(): void public function testBug4289(): void { - $this->analyse([__DIR__ . '/data/bug-4289.php'], []); + $errors = []; + + if (PHP_VERSION_ID >= 80400) { + $errors = [ + [ + 'Cannot unset property Bug4289\BaseClass::$fields because it might have hooks in a subclass.', + 25, + ], + ]; + } + + $this->analyse([__DIR__ . '/data/bug-4289.php'], $errors); } public function testBug5223(): void @@ -94,7 +111,15 @@ public function testBug4565(): void public function testBug12421(): void { - $this->analyse([__DIR__ . '/data/bug-12421.php'], [ + $errors = []; + if (PHP_VERSION_ID >= 80400) { + $errors[] = [ + 'Cannot unset property Bug12421\RegularProperty::$y because it might have hooks in a subclass.', + 7, + ]; + } + + $errors = array_merge($errors, [ [ 'Cannot unset readonly Bug12421\NativeReadonlyClass::$y property.', 11, @@ -120,6 +145,38 @@ public function testBug12421(): void 34, ], ]); + + $this->analyse([__DIR__ . '/data/bug-12421.php'], $errors); + } + + public function testUnsetHookedProperty(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4 or later.'); + } + + $this->analyse([__DIR__ . '/data/unset-hooked-property.php'], [ + [ + 'Cannot unset hooked UnsetHookedProperty\User::$name property.', + 6, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', + 7, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\Foo::$ii property.', + 9, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\Foo::$iii property.', + 10, + ], + [ + 'Cannot unset property UnsetHookedProperty\NonFinalClass::$publicProperty because it might have hooks in a subclass.', + 13, + ], + ]); } } diff --git a/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php new file mode 100644 index 00000000000..109b09b5075 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php @@ -0,0 +1,66 @@ += 8.4 + +namespace UnsetHookedProperty; + +function doUnset(Foo $foo, User $user, NonFinalClass $nonFinalClass, FinalClass $finalClass): void { + unset($user->name); + unset($user->fullName); + + unset($foo->ii); + unset($foo->iii); + + unset($nonFinalClass->publicFinalProperty); + unset($nonFinalClass->publicProperty); + + unset($finalClass->publicFinalProperty); + unset($finalClass->publicProperty); +} + +class User +{ + public string $name { + set { + if (strlen($value) === 0) { + throw new \ValueError("Name must be non-empty"); + } + $this->name = $value; + } + } + + public string $fullName { + get { + return "Yennefer of Vengerberg"; + } + } + + public function __construct(string $name) { + $this->name = $name; + } +} + +abstract class Foo +{ + abstract protected int $ii { get; } + + abstract public int $iii { get; } +} + +class NonFinalClass { + private string $privateProperty; + public string $publicProperty; + final public string $publicFinalProperty; + + function doFoo() { + unset($this->privateProperty); + } +} + +final class FinalClass { + private string $privateProperty; + public string $publicProperty; + final public string $publicFinalProperty; + + function doFoo() { + unset($this->privateProperty); + } +} From 253903a83bc6a426ef464cee051dc465029b8f4d Mon Sep 17 00:00:00 2001 From: schlndh Date: Sun, 2 Mar 2025 10:43:27 +0100 Subject: [PATCH 1129/3097] Fix integer range pow issues --- src/Type/TypeCombinator.php | 16 ++++++++++ tests/PHPStan/Analyser/nsrt/pow.php | 8 ++--- .../BooleanAndConstantConditionRuleTest.php | 6 ++++ .../BooleanNotConstantConditionRuleTest.php | 6 ++++ .../Rules/Comparison/data/bug-7937.php | 31 +++++++++++++++++++ .../Rules/Comparison/data/bug-8555.php | 14 +++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 28 +++++++++++++++++ 7 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-7937.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8555.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index f0e2f629a5a..42a1a8ea38b 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -37,6 +37,8 @@ use function md5; use function sprintf; use function usort; +use const PHP_INT_MAX; +use const PHP_INT_MIN; /** * @api @@ -185,6 +187,7 @@ public static function union(Type ...$types): Type $scalarTypes = []; $hasGenericScalarTypes = []; $enumCaseTypes = []; + $integerRangeTypes = []; for ($i = 0; $i < $typesCount; $i++) { if ($types[$i] instanceof ConstantScalarType) { $type = $types[$i]; @@ -212,6 +215,13 @@ public static function union(Type ...$types): Type continue; } + if ($types[$i] instanceof IntegerRangeType) { + $integerRangeTypes[] = $types[$i]; + unset($types[$i]); + + continue; + } + if (!$types[$i]->isArray()->yes()) { continue; } @@ -225,6 +235,12 @@ public static function union(Type ...$types): Type } $enumCaseTypes = array_values($enumCaseTypes); + usort( + $integerRangeTypes, + static fn (IntegerRangeType $a, IntegerRangeType $b): int => ($a->getMin() ?? PHP_INT_MIN) <=> ($b->getMin() ?? PHP_INT_MIN) + ?: ($a->getMax() ?? PHP_INT_MAX) <=> ($b->getMax() ?? PHP_INT_MAX) + ); + $types = array_merge($types, $integerRangeTypes); $types = array_values($types); $typesCount = count($types); diff --git a/tests/PHPStan/Analyser/nsrt/pow.php b/tests/PHPStan/Analyser/nsrt/pow.php index 328de3f1729..3ca27690db9 100644 --- a/tests/PHPStan/Analyser/nsrt/pow.php +++ b/tests/PHPStan/Analyser/nsrt/pow.php @@ -47,11 +47,11 @@ function (): void { $x = 4; } - assertType('int<4, 27>|int<16, 81>', pow($range, $x)); - assertType('int<4, 27>|int<16, 81>', $range ** $x); + assertType('int<4, 81>', pow($range, $x)); + assertType('int<4, 81>', $range ** $x); - assertType('int<4, 27>|int<16, 64>', pow($x, $range)); - assertType('int<4, 27>|int<16, 64>', $x ** $range); + assertType('int<4, 64>', pow($x, $range)); + assertType('int<4, 64>', $x ** $range); assertType('int<4, 27>', pow($range, $range)); assertType('int<4, 27>', $range ** $range); diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index 566c651fd48..3115a897b7f 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -433,4 +433,10 @@ public function testBug5365(): void $this->analyse([__DIR__ . '/data/bug-5365.php'], []); } + public function testBug8555(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8555.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php index ce5d1615f4b..f9bef9b5d13 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanNotConstantConditionRuleTest.php @@ -146,6 +146,12 @@ public function testBug8797(): void $this->analyse([__DIR__ . '/data/bug-8797.php'], []); } + public function testBug7937(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-7937.php'], []); + } + public function dataReportAlwaysTrueInLastCondition(): iterable { yield [false, [ diff --git a/tests/PHPStan/Rules/Comparison/data/bug-7937.php b/tests/PHPStan/Rules/Comparison/data/bug-7937.php new file mode 100644 index 00000000000..d82bc1e4650 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-7937.php @@ -0,0 +1,31 @@ + + */ +function test(int $first, int $second): array +{ + return [ + 'test' => $first && $second ? $first : null, + 'test2' => $first && $second ? $first : null, + ]; +} diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index a844a30065f..b615d9deea1 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1560,6 +1560,34 @@ public function dataUnion(): iterable UnionType::class, 'int<1, 3>|int<7, 9>', ], + [ + [ + IntegerRangeType::fromInterval(4, 9), + IntegerRangeType::fromInterval(16, 81), + IntegerRangeType::fromInterval(8, 27), + ], + IntegerRangeType::class, + 'int<4, 81>', + ], + [ + [ + IntegerRangeType::fromInterval(8, 27), + IntegerRangeType::fromInterval(4, 6), + new ConstantIntegerType(7), + IntegerRangeType::fromInterval(16, 81), + ], + IntegerRangeType::class, + 'int<4, 81>', + ], + [ + [ + new IntegerType(), + IntegerRangeType::fromInterval(null, -1), + IntegerRangeType::fromInterval(1, null), + ], + IntegerType::class, + 'int', + ], [ [ IntegerRangeType::fromInterval(1, 3), From 2be8600900a05a9f02fdbf8381c1104cb6860d36 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 2 Mar 2025 10:45:15 +0100 Subject: [PATCH 1130/3097] Added regression test --- .../Rules/Methods/CallMethodsRuleTest.php | 14 ++++++++++ .../PHPStan/Rules/Methods/data/bug-12422.php | 28 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12422.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 48caf8f4f1a..b82eb1596fc 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3514,4 +3514,18 @@ public function testBug12544(): void ]); } + public function testBug12422(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12422.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12422.php b/tests/PHPStan/Rules/Methods/data/bug-12422.php new file mode 100644 index 00000000000..19ae6e4e6a2 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12422.php @@ -0,0 +1,28 @@ += 8.1 + +namespace Bug12422; + +enum MyEnum +{ + case A; + case B; +} + +class MyClass +{ + public function fooo(): void + { + } +} + +function test(MyEnum $enum, ?MyClass $bar): void +{ + if ($enum === MyEnum::A && $bar === null) { + return; + } + + match ($enum) { + MyEnum::A => $bar->fooo(), + MyEnum::B => null, + }; +} From 6b0d8c3229f98ec3865da57b035e29a52ab127fc Mon Sep 17 00:00:00 2001 From: schlndh Date: Sun, 2 Mar 2025 11:29:59 +0100 Subject: [PATCH 1131/3097] Handle BcMath\Number operators for simple cases Co-authored-by: Ondrej Mirtes --- conf/config.neon | 4 + src/Php/PhpVersion.php | 5 + .../InitializerExprTypeResolver.php | 15 +- src/Type/ObjectType.php | 15 +- ...hNumberOperatorTypeSpecifyingExtension.php | 53 +++ tests/PHPStan/Analyser/nsrt/bcmath-number.php | 408 ++++++++++++++++++ 6 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/bcmath-number.php diff --git a/conf/config.neon b/conf/config.neon index b7e91bd3d9c..54f4ff31862 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1053,6 +1053,10 @@ services: arguments: reportMagicProperties: %reportMagicProperties% checkDynamicProperties: %checkDynamicProperties% + - + class: PHPStan\Type\Php\BcMathNumberOperatorTypeSpecifyingExtension + tags: + - phpstan.broker.operatorTypeSpecifyingExtension - class: PHPStan\Rules\Properties\UninitializedPropertyRule diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6f55bdda15f..5215a306062 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -400,4 +400,9 @@ public function substrReturnFalseInsteadOfEmptyString(): bool return $this->versionId < 80000; } + public function supportsBcMathNumberOperatorOverloading(): bool + { + return $this->versionId >= 80400; + } + } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 9fef24a5875..fb935ac6306 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -872,6 +872,11 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback): return $this->getNeverType($leftType, $rightType); } + $extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Mod($left, $right), $leftType, $rightType); + if ($extensionSpecified !== null) { + return $extensionSpecified; + } + if ($leftType->toNumber() instanceof ErrorType || $rightType->toNumber() instanceof ErrorType) { return new ErrorType(); } @@ -1234,16 +1239,16 @@ public function getPowType(Expr $left, Expr $right, callable $getTypeCallback): $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); - $exponentiatedTyped = $leftType->exponentiate($rightType); - if (!$exponentiatedTyped instanceof ErrorType) { - return $exponentiatedTyped; - } - $extensionSpecified = $this->callOperatorTypeSpecifyingExtensions(new BinaryOp\Pow($left, $right), $leftType, $rightType); if ($extensionSpecified !== null) { return $extensionSpecified; } + $exponentiatedTyped = $leftType->exponentiate($rightType); + if (!$exponentiatedTyped instanceof ErrorType) { + return $exponentiatedTyped; + } + return new ErrorType(); } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0ad78520f25..5a055750596 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -31,6 +31,8 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; @@ -593,6 +595,14 @@ public function toFloat(): Type public function toString(): Type { + if ($this->isInstanceOf('BcMath\Number')->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + new AccessoryNonEmptyStringType(), + ]); + } + $classReflection = $this->getClassReflection(); if ($classReflection === null) { return new ErrorType(); @@ -678,7 +688,10 @@ public function toCoercedArgumentType(bool $strictTypes): Type public function toBoolean(): BooleanType { - if ($this->isInstanceOf('SimpleXMLElement')->yes()) { + if ( + $this->isInstanceOf('SimpleXMLElement')->yes() + || $this->isInstanceOf('BcMath\Number')->yes() + ) { return new BooleanType(); } diff --git a/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php b/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php new file mode 100644 index 00000000000..3e0d793052d --- /dev/null +++ b/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php @@ -0,0 +1,53 @@ +phpVersion->supportsBcMathNumberOperatorOverloading() || $leftSide instanceof NeverType || $rightSide instanceof NeverType) { + return false; + } + + $bcMathNumberType = new ObjectType('BcMath\Number'); + + return in_array($operatorSigil, ['-', '+', '*', '/', '**', '%'], true) + && ( + $bcMathNumberType->isSuperTypeOf($leftSide)->yes() + || $bcMathNumberType->isSuperTypeOf($rightSide)->yes() + ); + } + + public function specifyType(string $operatorSigil, Type $leftSide, Type $rightSide): Type + { + $bcMathNumberType = new ObjectType('BcMath\Number'); + $otherSide = $bcMathNumberType->isSuperTypeOf($leftSide)->yes() + ? $rightSide + : $leftSide; + + if ( + $otherSide->isInteger()->yes() + || $otherSide->isNumericString()->yes() + || $bcMathNumberType->isSuperTypeOf($otherSide)->yes() + ) { + return $bcMathNumberType; + } + + return new ErrorType(); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/bcmath-number.php b/tests/PHPStan/Analyser/nsrt/bcmath-number.php new file mode 100644 index 00000000000..2bdd9611b45 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bcmath-number.php @@ -0,0 +1,408 @@ += 8.4 + +declare(strict_types = 1); + +namespace BcMathNumber; + +use BcMath\Number; +use function PHPStan\Testing\assertType; + +class Foo +{ + public function bcVsBc(Number $a, Number $b): void + { + assertType('BcMath\Number', $a + $b); + assertType('BcMath\Number', $a - $b); + assertType('BcMath\Number', $a * $b); + assertType('BcMath\Number', $a / $b); + assertType('BcMath\Number', $a % $b); + assertType('non-falsy-string', $a . $b); + assertType('BcMath\Number', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsInt(Number $a, int $b): void + { + assertType('BcMath\Number', $a + $b); + assertType('BcMath\Number', $a - $b); + assertType('BcMath\Number', $a * $b); + assertType('BcMath\Number', $a / $b); + assertType('BcMath\Number', $a % $b); + assertType('non-falsy-string', $a . $b); + assertType('BcMath\Number', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsFloat(Number $a, float $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-falsy-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + /** @param numeric-string $b */ + public function bcVsNumericString(Number $a, string $b): void + { + assertType('BcMath\Number', $a + $b); + assertType('BcMath\Number', $a - $b); + assertType('BcMath\Number', $a * $b); + assertType('BcMath\Number', $a / $b); + assertType('BcMath\Number', $a % $b); + assertType('non-falsy-string', $a . $b); + assertType('BcMath\Number', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsNonNumericString(Number $a, string $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-empty-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsBool(Number $a, bool $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-empty-string&numeric-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsNull(Number $a): void + { + $b = null; + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('0', $a * $b); // BUG: This throws type error, but getMulType assumes that since null (mostly) behaves like zero, it will be zero. + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-empty-string&numeric-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('false', $a && $b); + assertType('bool', $a || $b); + assertType('false', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsArray(Number $a, array $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('*ERROR*', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsObject(Number $a, object $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('*ERROR*', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('true', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('true', $a or $b); + } + + /** @param resource $b */ + public function bcVsResource(Number $a, $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-empty-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('true', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('true', $a or $b); + } + + public function bcVsCallable(Number $a, callable $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('*ERROR*', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('true', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('true', $a or $b); + } + + public function bcVsIterable(Number $a, iterable $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('*ERROR*', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + + public function bcVsStringable(Number $a, \Stringable $b): void + { + assertType('*ERROR*', $a + $b); + assertType('*ERROR*', $a - $b); + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*ERROR*', $a % $b); + assertType('non-empty-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*ERROR*', $a << $b); + assertType('*ERROR*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('int<-1, 1>', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*ERROR*', $a & $b); + assertType('*ERROR*', $a ^ $b); + assertType('*ERROR*', $a | $b); + assertType('bool', $a && $b); + assertType('true', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('true', $a or $b); + } + + public function bcVsNever(Number $a): void + { + for ($b = 1; $b < count([]); $b++) { + assertType('*NEVER*', $a + $b); + assertType('*ERROR*', $a - $b); // Inconsistency: getPlusType handles never types right at the beginning, getMinusType doesn't. + assertType('*ERROR*', $a * $b); + assertType('*ERROR*', $a / $b); + assertType('*NEVER*', $a % $b); + assertType('non-empty-string&numeric-string', $a . $b); + assertType('*ERROR*', $a ** $b); + assertType('*NEVER*', $a << $b); + assertType('*NEVER*', $a >> $b); + assertType('bool', $a < $b); + assertType('bool', $a <= $b); + assertType('bool', $a > $b); + assertType('bool', $a >= $b); + assertType('*NEVER*', $a <=> $b); + assertType('bool', $a == $b); + assertType('bool', $a != $b); + assertType('*NEVER*', $a & $b); + assertType('*NEVER*', $a ^ $b); + assertType('*NEVER*', $a | $b); + assertType('bool', $a && $b); + assertType('bool', $a || $b); + assertType('bool', $a and $b); + assertType('bool', $a xor $b); + assertType('bool', $a or $b); + } + } +} From 2d24ffced607f0769c9964dbb1a821abdc1ba898 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 2 Mar 2025 12:41:31 +0100 Subject: [PATCH 1132/3097] Fix GetNonVirtualPropertyHookReadRule on abstract property --- .../GetNonVirtualPropertyHookReadRule.php | 4 ++++ .../GetNonVirtualPropertyHookReadRuleTest.php | 9 +++++++++ .../data/get-abstract-property-hook-read.php | 15 +++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/get-abstract-property-hook-read.php diff --git a/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php index 9a2fc917e7f..a8a95540620 100644 --- a/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php +++ b/src/Rules/Properties/GetNonVirtualPropertyHookReadRule.php @@ -76,6 +76,10 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($hook->body === null) { + continue; + } + $hasGetHook = true; break; } diff --git a/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php b/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php index 8eedc782144..64745b60d9b 100644 --- a/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php +++ b/tests/PHPStan/Rules/Properties/GetNonVirtualPropertyHookReadRuleTest.php @@ -35,4 +35,13 @@ public function testRule(): void ]); } + public function testAbstractProperty(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->analyse([__DIR__ . '/data/get-abstract-property-hook-read.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/get-abstract-property-hook-read.php b/tests/PHPStan/Rules/Properties/data/get-abstract-property-hook-read.php new file mode 100644 index 00000000000..4c26243016d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/get-abstract-property-hook-read.php @@ -0,0 +1,15 @@ += 8.4 + +namespace GetAbstractPropertyHook; + +class NonFinalClass +{ + public string $publicProperty; +} + +abstract class Foo extends NonFinalClass +{ + abstract public string $publicProperty { + get; + } +} From a6f295e5a869c63b8bc492e59c4dc44e3d59aa95 Mon Sep 17 00:00:00 2001 From: stepo2 <127047691+stepo2@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:51:03 +0100 Subject: [PATCH 1133/3097] TypeNodeResolver - check for existing `Integer` class before resolving to `int` --- src/PhpDoc/TypeNodeResolver.php | 8 ++++++++ tests/PHPStan/Analyser/nsrt/bug-12660.php | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12660.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 975365a3418..6abb943d735 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -199,7 +199,15 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco { switch (strtolower($typeNode->name)) { case 'int': + return new IntegerType(); + case 'integer': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new IntegerType(); case 'positive-int': diff --git a/tests/PHPStan/Analyser/nsrt/bug-12660.php b/tests/PHPStan/Analyser/nsrt/bug-12660.php new file mode 100644 index 00000000000..44c79a04443 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12660.php @@ -0,0 +1,21 @@ + Date: Tue, 4 Mar 2025 09:26:52 +0100 Subject: [PATCH 1134/3097] Adjust and make space for tests for new behaviour of `new` --- .../Analyser/data/array-destructuring.php | 3 +- .../Analyser/data/nested-functions.php | 3 +- tests/PHPStan/Analyser/nsrt/bug-6462.php | 28 +++--- .../PHPStan/Analyser/nsrt/get-debug-type.php | 3 +- tests/PHPStan/Analyser/nsrt/instanceof.php | 3 +- ...mpossibleCheckTypeFunctionCallRuleTest.php | 86 ++++++++++--------- .../data/check-type-function-call.php | 25 ++++++ ...odStatementWithoutImpurePointsRuleTest.php | 8 +- .../call-to-method-without-impure-points.php | 21 +++++ .../CatchWithUnthrownExceptionRuleTest.php | 4 +- .../Rules/Exceptions/data/bug-4852.php | 10 ++- .../Rules/Functions/CallCallablesRuleTest.php | 24 +++--- .../Rules/Functions/data/callables.php | 9 ++ .../Rules/Methods/CallMethodsRuleTest.php | 37 ++++---- .../Rules/Methods/ReturnTypeRuleTest.php | 20 ++--- .../Rules/Methods/data/object-shapes.php | 3 +- .../Rules/Methods/data/returnTypes.php | 3 +- .../Properties/AccessPropertiesRuleTest.php | 51 ++++++++--- .../Properties/data/dynamic-properties.php | 6 ++ .../data/php-82-dynamic-properties.php | 7 ++ .../PHPStan/Rules/Variables/UnsetRuleTest.php | 18 ++-- .../Rules/Variables/data/bug-12421.php | 6 +- 22 files changed, 251 insertions(+), 127 deletions(-) diff --git a/tests/PHPStan/Analyser/data/array-destructuring.php b/tests/PHPStan/Analyser/data/array-destructuring.php index abf9bff20cc..69e8b037864 100644 --- a/tests/PHPStan/Analyser/data/array-destructuring.php +++ b/tests/PHPStan/Analyser/data/array-destructuring.php @@ -1,5 +1,5 @@ true, 'value' => '123']; diff --git a/tests/PHPStan/Analyser/data/nested-functions.php b/tests/PHPStan/Analyser/data/nested-functions.php index 1d12b75157f..b33ed3150a1 100644 --- a/tests/PHPStan/Analyser/data/nested-functions.php +++ b/tests/PHPStan/Analyser/data/nested-functions.php @@ -12,8 +12,7 @@ public function doFoo(): self } -function () { - $foo = new Foo(); +function (Foo $foo) { $foo->doFoo() ->doFoo() ->doFoo() diff --git a/tests/PHPStan/Analyser/nsrt/bug-6462.php b/tests/PHPStan/Analyser/nsrt/bug-6462.php index 84c475d48a4..51854608d72 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6462.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6462.php @@ -37,21 +37,19 @@ public function getThis(): self } } -$base = new Base(); -$child = new Child(); -$fixedChild = new FixedChild(); +function (Base $base, Child $child, FixedChild $fixedChild): void { + assertType('Bug6462\Base', $base->getThis()); + assertType('Bug6462\Child', $child->getThis()); -assertType('Bug6462\Base', $base->getThis()); -assertType('Bug6462\Child', $child->getThis()); - -if ($base instanceof \Traversable) { - assertType('Bug6462\Base&Traversable', $base->getThis()); -} + if ($base instanceof \Traversable) { + assertType('Bug6462\Base&Traversable', $base->getThis()); + } -if ($child instanceof \Traversable) { - assertType('Bug6462\Child&Traversable', $child->getThis()); -} + if ($child instanceof \Traversable) { + assertType('Bug6462\Child&Traversable', $child->getThis()); + } -if ($fixedChild instanceof \Traversable) { - assertType('Bug6462\FixedChild&Traversable', $fixedChild->getThis()); -} + if ($fixedChild instanceof \Traversable) { + assertType('Bug6462\FixedChild&Traversable', $fixedChild->getThis()); + } +}; diff --git a/tests/PHPStan/Analyser/nsrt/get-debug-type.php b/tests/PHPStan/Analyser/nsrt/get-debug-type.php index 322c33547fd..bc3823a68b1 100644 --- a/tests/PHPStan/Analyser/nsrt/get-debug-type.php +++ b/tests/PHPStan/Analyser/nsrt/get-debug-type.php @@ -15,7 +15,7 @@ class D {} * @param int|string $intOrString * @param array|A $arrayOrObject */ -function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrString, $arrayOrObject) { +function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrString, $arrayOrObject, \stdClass $std) { $null = null; $resource = fopen('php://memory', 'r'); $o = new \stdClass(); @@ -34,6 +34,7 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr assertType("'string'", get_debug_type($s)); assertType("'array'", get_debug_type($a)); assertType("string", get_debug_type($o)); + assertType("string", get_debug_type($std)); assertType("'GetDebugType\\\\A'", get_debug_type($A)); assertType("string", get_debug_type($r)); assertType("'bool'|string", get_debug_type($resource)); diff --git a/tests/PHPStan/Analyser/nsrt/instanceof.php b/tests/PHPStan/Analyser/nsrt/instanceof.php index 9ad5cceea7d..fe111fb0e47 100644 --- a/tests/PHPStan/Analyser/nsrt/instanceof.php +++ b/tests/PHPStan/Analyser/nsrt/instanceof.php @@ -19,11 +19,10 @@ abstract class BarParent class Foo extends BarParent { - public function someMethod(Expr $foo) + public function someMethod(Expr $foo, Foo $intersected) { $bar = $foo; $baz = doFoo(); - $intersected = new Foo(); $parent = doFoo(); if ($baz instanceof Foo) { diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 5480eb394c8..c2f1edee232 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -96,169 +96,177 @@ public function testImpossibleCheckTypeFunctionCall(): void 'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'doFoo\' will always evaluate to true.', 179, ], + [ + 'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'doFoo\' will always evaluate to true.', + 189, + ], [ 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doFoo\' will always evaluate to true.', - 191, + 201, ], [ 'Call to function method_exists() with $this(CheckTypeFunctionCall\FinalClassWithMethodExists) and \'doBar\' will always evaluate to false.', - 194, + 204, ], [ 'Call to function property_exists() with $this(CheckTypeFunctionCall\FinalClassWithPropertyExists) and \'fooProperty\' will always evaluate to true.', - 210, + 220, ], [ 'Call to function in_array() with arguments int, array{\'foo\', \'bar\'} and true will always evaluate to false.', - 236, + 246, ], [ 'Call to function in_array() with arguments \'bar\'|\'foo\', array{\'baz\', \'lorem\'} and true will always evaluate to false.', - 245, + 255, ], [ 'Call to function in_array() with arguments \'foo\', array{\'foo\'} and true will always evaluate to true.', - 253, + 263, ], [ 'Call to function in_array() with arguments \'foo\', array{\'foo\', \'bar\'} and true will always evaluate to true.', - 257, + 267, ], [ 'Call to function in_array() with arguments \'bar\', array{}|array{\'foo\'} and true will always evaluate to false.', - 321, + 331, ], [ 'Call to function in_array() with arguments \'baz\', array{0: \'bar\', 1?: \'foo\'} and true will always evaluate to false.', - 337, + 347, ], [ 'Call to function in_array() with arguments \'foo\', array{} and true will always evaluate to false.', - 344, + 354, ], [ 'Call to function array_key_exists() with \'a\' and array{a: 1, b?: 2} will always evaluate to true.', - 361, + 371, ], [ 'Call to function array_key_exists() with \'c\' and array{a: 1, b?: 2} will always evaluate to false.', - 367, + 377, ], [ 'Call to function is_string() with mixed will always evaluate to false.', - 561, + 571, ], [ 'Call to function is_callable() with mixed will always evaluate to false.', - 572, + 582, ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExists\' and \'testWithStringFirst…\' will always evaluate to true.', - 586, + 596, ], [ 'Call to function method_exists() with \'UndefinedClass\' and string will always evaluate to false.', - 595, + 605, ], [ 'Call to function method_exists() with \'UndefinedClass\' and \'test\' will always evaluate to false.', - 598, + 608, + ], + [ + 'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.', + 620, ], [ 'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.', - 610, + 635, ], [ 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'method\' will always evaluate to true.', - 625, + 650, ], [ 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'someAnother\' will always evaluate to true.', - 628, + 653, ], [ 'Call to function method_exists() with $this(CheckTypeFunctionCall\MethodExistsWithTrait) and \'unknown\' will always evaluate to false.', - 631, + 656, ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', - 634, + 659, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', - 637, + 662, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 640, + 665, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', - 643, + 668, ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', - 646, + 671, ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', - 649, + 674, ], [ 'Call to function is_string() with string will always evaluate to true.', - 678, + 703, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function assert() with true will always evaluate to true.', - 693, + 718, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function is_numeric() with \'123\' will always evaluate to true.', - 693, + 718, ], [ 'Call to function assert() with false will always evaluate to false.', - 694, + 719, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', - 694, + 719, ], [ 'Call to function assert() with true will always evaluate to true.', - 701, + 726, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function is_numeric() with 123|float will always evaluate to true.', - 701, + 726, ], [ 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', - 784, + 809, ], [ 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', - 788, + 813, ], [ 'Call to function testIsInt() with int will always evaluate to true.', - 875, + 900, ], [ 'Call to function is_int() with int will always evaluate to true.', - 889, + 914, 'Remove remaining cases below this one and this error will disappear too.', ], [ 'Call to function in_array() with arguments 1, array and true will always evaluate to false.', - 927, + 952, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ], diff --git a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php index 814d93ae60b..e84f4f09014 100644 --- a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php +++ b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php @@ -181,6 +181,16 @@ public function doFoo() } } + public function doBar(Foo $foo) + { + if (method_exists($foo, 'test')) { + + } + if (method_exists($foo, 'doFoo')) { + + } + } + } final class FinalClassWithMethodExists @@ -616,6 +626,21 @@ public function testWithNewObjectInFirstArgument(): void if (method_exists((new MethodExists()), $string)) { } } + + public function testWithTypehintedObject(MethodExists $methodExists): void + { + /** @var string $string */ + $string = doFoo(); + + if (method_exists($methodExists, 'testWithNewObjectInFirstArgument')) { + } + + if (method_exists($methodExists, 'undefinedMethod')) { + } + + if (method_exists($methodExists, $string)) { + } + } } trait MethodExistsTrait diff --git a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php index 67f6f42b4e3..83000808019 100644 --- a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php @@ -48,13 +48,17 @@ public function testRule(): void 'Call to method CallToMethodWithoutImpurePoints\y::myFinalBaseFunc() on a separate line has no effect.', 41, ], + [ + 'Call to method CallToMethodWithoutImpurePoints\y::myFinalBaseFunc() on a separate line has no effect.', + 62, + ], [ 'Call to method CallToMethodWithoutImpurePoints\AbstractFoo::myFunc() on a separate line has no effect.', - 119, + 140, ], [ 'Call to method CallToMethodWithoutImpurePoints\CallsPrivateMethodWithoutImpurePoints::doBar() on a separate line has no effect.', - 127, + 148, ], ]); } diff --git a/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php b/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php index e9f82b14769..e22db7862ef 100644 --- a/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php +++ b/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php @@ -41,6 +41,27 @@ function (): void { $subSubY->myFinalBaseFunc(); }; +function (y $xy, finalX $finalX): void { + $xy = new y(); + if (rand(0,1)) { + $xy = $finalX; + } + $xy->myFunc(); +}; + +function (Y $xy, finalX $finalX): void { + // case-insensitive class name + if (rand(0,1)) { + $xy = $finalX; + } + $xy->myFunc(); +}; + +function (subY $subY): void { + $subY->myFunc(); + $subY->myFinalBaseFunc(); +}; + class y { function myFunc() diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 6eacf1535d4..adcf6472a61 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -363,11 +363,11 @@ public function testBug4852(): void $this->analyse([__DIR__ . '/data/bug-4852.php'], [ [ 'Dead catch - Exception is never thrown in the try block.', - 70, + 78, ], [ 'Dead catch - Exception is never thrown in the try block.', - 77, + 85, ], ]); } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-4852.php b/tests/PHPStan/Rules/Exceptions/data/bug-4852.php index 058f6c1a1f9..dfc01e41c8e 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-4852.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-4852.php @@ -61,9 +61,17 @@ public function offsetUnset ($offset) {} try { $buz[] = 'value'; } catch (Exception $e) { - // not dead + // dead because $buz cannot be a subclass } +function (MaybeThrows2 $buz): void { + try { + $buz[] = 'value'; + } catch (Exception $e) { + // not dead + } +}; + $baz = new DefinitelyNoThrows(); try { $baz[] = 'value'; diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index a8ffcaf8003..cde66da1c80 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -106,48 +106,52 @@ public function testRule(): void 'Trying to invoke CallCallables\Baz but it might not be a callable.', 113, ], + [ + 'Trying to invoke CallCallables\Baz but it might not be a callable.', + 122, + ], [ 'Trying to invoke array{object, \'bar\'} but it might not be a callable.', - 131, + 140, ], [ 'Closure invoked with 0 parameters, 3 required.', - 146, + 155, ], [ 'Closure invoked with 1 parameter, 3 required.', - 147, + 156, ], [ 'Closure invoked with 2 parameters, 3 required.', - 148, + 157, ], [ 'Trying to invoke array{object, \'yo\'} but it might not be a callable.', - 163, + 172, ], [ 'Trying to invoke array{object, \'yo\'} but it might not be a callable.', - 167, + 176, ], [ 'Trying to invoke array{\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\'} but it might not be a callable.', - 179, + 188, ], [ 'Trying to invoke array{\'CallCallables\\\\ConstantArrayUnionCallables\'|\'DateTimeImmutable\', \'doFoo\'} but it might not be a callable.', - 205, + 214, ], [ 'Trying to invoke array{\'CallCallables\\\ConstantArrayUnionCallables\', \'doBaz\'|\'doFoo\'} but it might not be a callable.', - 212, + 221, ], ]; if (PHP_VERSION_ID >= 80000) { $errors[] = [ 'Trying to invoke array{\'CallCallables\\\ConstantArrayUnionCallables\'|\'CallCallables\\\ConstantArrayUnionCallablesTest\', \'doBar\'|\'doFoo\'} but it\'s not a callable.', - 220, + 229, ]; } diff --git a/tests/PHPStan/Rules/Functions/data/callables.php b/tests/PHPStan/Rules/Functions/data/callables.php index e2364e5aa5e..def053f9027 100644 --- a/tests/PHPStan/Rules/Functions/data/callables.php +++ b/tests/PHPStan/Rules/Functions/data/callables.php @@ -117,6 +117,15 @@ public function doBar() } } + public function doBaz(Baz $baz) + { + $baz(); + + if (method_exists($baz, '__invoke')) { + $baz(); + } + } + } class MethodExistsCheckFirst diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index b82eb1596fc..bedf530cdc4 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3054,82 +3054,87 @@ public function testObjectShapes(): void 14, 'Exception might not have property $foo.', ], + [ + 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.', + 15, + 'Exception might not have property $foo.', + ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int} given.', - 36, + 37, 'Property ($foo) type int does not accept type string.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo?: int, bar: string} given.', - 37, + 38, 'object{foo?: int, bar: string} might not have property $foo.', ], [ 'Parameter #1 $std of method ObjectShapesAcceptance\Foo::requireStdClass() expects stdClass, object{foo: string, bar: int} given.', - 40, + 41, ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int}&stdClass given.', - 43, + 44, 'Property ($foo) type int does not accept type string.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object given.', - 54, + 55, '• object might not have property $foo. • object might not have property $bar.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, stdClass given.', - 55, + 56, ], [ 'Parameter #1 $bar of method ObjectShapesAcceptance\Bar::requireBar() expects ObjectShapesAcceptance\Bar, object{a: int} given.', - 71, + 72, ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Bar::doBar() expects object{a: string}, ObjectShapesAcceptance\Bar given.', - 77, + 78, 'Property ($a) type string does not accept type int.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Baz::doBar() expects object{a: int}, $this(ObjectShapesAcceptance\Baz) given.', - 105, + 106, 'Property ObjectShapesAcceptance\Baz::$a is not public.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Baz::doBaz() expects object{b: int}, $this(ObjectShapesAcceptance\Baz) given.', - 106, + 107, 'Property ObjectShapesAcceptance\Baz::$b is static.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Baz::doLorem() expects object{c: int}, $this(ObjectShapesAcceptance\Baz) given.', - 107, + 108, 'Property ObjectShapesAcceptance\Baz::$c is not readable.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Baz::doIpsum() expects object{d: array{foo: string}}, $this(ObjectShapesAcceptance\Baz) given.', - 108, + 109, 'Property ($d) type array{foo: string} does not accept type array{foo: int}: Offset \'foo\' (string) does not accept type int.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\OptionalProperty::doBar() expects object{foo?: int}, object{foo?: string} given.', - 156, + 157, 'Property ($foo) type int does not accept type string.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\OptionalProperty::doBaz() expects object{foo: int}, object{foo?: string} given.', - 157, + 158, 'Property ($foo) type int does not accept type string.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, Traversable given.', - 209, + 210, 'Traversable might not have property $foo.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, ObjectShapesAcceptance\FinalClass given.', - 210, + 211, PHP_VERSION_ID < 80200 ? 'ObjectShapesAcceptance\FinalClass might not have property $foo.' : 'ObjectShapesAcceptance\FinalClass does not have property $foo.', ], ]); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 8de2c7a5d9a..0c658ce936d 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -242,43 +242,43 @@ public function testReturnTypeRule(): void ], [ 'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.', - 840, + 839, ], [ 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', - 860, + 859, ], [ 'Method ReturnTypes\NestedArrayCheck::doBar() should return array but returns array>.', - 875, + 874, ], [ 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns string.', - 950, + 949, ], [ 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns ReturnTypes\integer.', - 953, + 952, ], [ 'Method ReturnTypes\VariableOverwrittenInForeach::doFoo() should return int but returns int|string.', - 1011, + 1010, ], [ 'Method ReturnTypes\VariableOverwrittenInForeach::doBar() should return int but returns int|string.', - 1026, + 1025, ], [ 'Method ReturnTypes\ReturnStaticGeneric::instanceReturnsStatic() should return static(ReturnTypes\ReturnStaticGeneric) but returns ReturnTypes\ReturnStaticGeneric.', - 1066, + 1065, ], [ 'Method ReturnTypes\NeverReturn::doFoo() should never return but return statement found.', - 1241, + 1240, ], [ 'Method ReturnTypes\NeverReturn::doBaz3() should never return but return statement found.', - 1254, + 1253, ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/object-shapes.php b/tests/PHPStan/Rules/Methods/data/object-shapes.php index 248eeecc307..8df09d5f93f 100644 --- a/tests/PHPStan/Rules/Methods/data/object-shapes.php +++ b/tests/PHPStan/Rules/Methods/data/object-shapes.php @@ -8,10 +8,11 @@ class Foo { - public function doFoo(): void + public function doFoo(Exception $e): void { $this->doBar(new stdClass()); $this->doBar(new Exception()); + $this->doBar($e); } /** diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index b1c453648a8..e7030f8ad85 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -833,9 +833,8 @@ public function doFoo() /** * @return $this */ - public function doBar() + public function doBar(self $otherInstance) { - $otherInstance = new self(); assert($otherInstance instanceof FooInterface); return $otherInstance; } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 1b79507bad9..57d68802f91 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -634,7 +634,7 @@ public function dataDynamicProperties(): array $errors = [ [ 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 23, + 29, $tipText, ], ]; @@ -670,22 +670,37 @@ public function dataDynamicProperties(): array 16, $tipText, ], + [ + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 20, + $tipText, + ], + [ + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 21, + $tipText, + ], + [ + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 22, + $tipText, + ], ], $errors); $errorsWithMore = array_merge($errorsWithMore, [ [ 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 26, + 32, $tipText, ], [ 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 27, + 33, $tipText, ], [ 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 28, + 34, $tipText, ], ]); @@ -693,32 +708,32 @@ public function dataDynamicProperties(): array $otherErrors = [ [ 'Access to an undefined property DynamicProperties\FinalFoo::$dynamicProperty.', - 36, + 42, $tipText, ], [ 'Access to an undefined property DynamicProperties\FinalFoo::$dynamicProperty.', - 37, + 43, $tipText, ], [ 'Access to an undefined property DynamicProperties\FinalFoo::$dynamicProperty.', - 38, + 44, $tipText, ], [ 'Access to an undefined property DynamicProperties\FinalBar::$dynamicProperty.', - 41, + 47, $tipText, ], [ 'Access to an undefined property DynamicProperties\FinalBar::$dynamicProperty.', - 42, + 48, $tipText, ], [ 'Access to an undefined property DynamicProperties\FinalBar::$dynamicProperty.', - 43, + 49, $tipText, ], ]; @@ -727,7 +742,7 @@ public function dataDynamicProperties(): array [false, PHP_VERSION_ID < 80200 ? [ [ 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 23, + 29, $tipText, ], ] : array_merge($errors, $otherErrors)], @@ -799,10 +814,15 @@ public function testPhp82AndDynamicProperties(bool $b): void 71, $tipText, ]; + $errors[] = [ + 'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.', + 78, + $tipText, + ]; } $errors[] = [ 'Access to an undefined property Php82DynamicProperties\FinalHelloWorld::$world.', - 105, + 112, $tipText, ]; } elseif ($b) { @@ -811,9 +831,14 @@ public function testPhp82AndDynamicProperties(bool $b): void 71, $tipText, ]; + $errors[] = [ + 'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.', + 78, + $tipText, + ]; $errors[] = [ 'Access to an undefined property Php82DynamicProperties\FinalHelloWorld::$world.', - 105, + 112, $tipText, ]; } diff --git a/tests/PHPStan/Rules/Properties/data/dynamic-properties.php b/tests/PHPStan/Rules/Properties/data/dynamic-properties.php index 055029152eb..d354c028eae 100644 --- a/tests/PHPStan/Rules/Properties/data/dynamic-properties.php +++ b/tests/PHPStan/Rules/Properties/data/dynamic-properties.php @@ -15,6 +15,12 @@ public function doBar() { empty($bar->dynamicProperty); $bar->dynamicProperty ?? 'test'; } + + public function doBaz(Bar $bar) { + isset($bar->dynamicProperty); + empty($bar->dynamicProperty); + $bar->dynamicProperty ?? 'test'; + } } #[\AllowDynamicProperties] diff --git a/tests/PHPStan/Rules/Properties/data/php-82-dynamic-properties.php b/tests/PHPStan/Rules/Properties/data/php-82-dynamic-properties.php index 92e14eaba29..44bd9190273 100644 --- a/tests/PHPStan/Rules/Properties/data/php-82-dynamic-properties.php +++ b/tests/PHPStan/Rules/Properties/data/php-82-dynamic-properties.php @@ -74,6 +74,13 @@ function (): void { } }; +function (HelloWorld $hello): void { + if(isset($hello->world)) + { + echo $hello->world; + } +}; + final class FinalHelloWorld { public function __get(string $attribute): mixed diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index 0564fbe5095..8f7081b6799 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -115,34 +115,38 @@ public function testBug12421(): void if (PHP_VERSION_ID >= 80400) { $errors[] = [ 'Cannot unset property Bug12421\RegularProperty::$y because it might have hooks in a subclass.', - 7, + 6, + ]; + $errors[] = [ + 'Cannot unset property Bug12421\RegularProperty::$y because it might have hooks in a subclass.', + 9, ]; } $errors = array_merge($errors, [ [ 'Cannot unset readonly Bug12421\NativeReadonlyClass::$y property.', - 11, + 13, ], [ 'Cannot unset readonly Bug12421\NativeReadonlyProperty::$y property.', - 15, + 17, ], [ 'Cannot unset @readonly Bug12421\PhpdocReadonlyClass::$y property.', - 19, + 21, ], [ 'Cannot unset @readonly Bug12421\PhpdocReadonlyProperty::$y property.', - 23, + 25, ], [ 'Cannot unset @readonly Bug12421\PhpdocImmutableClass::$y property.', - 27, + 29, ], [ 'Cannot unset readonly Bug12421\NativeReadonlyProperty::$y property.', - 34, + 36, ], ]); diff --git a/tests/PHPStan/Rules/Variables/data/bug-12421.php b/tests/PHPStan/Rules/Variables/data/bug-12421.php index eb0c53f7557..9ed7c9b217e 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-12421.php +++ b/tests/PHPStan/Rules/Variables/data/bug-12421.php @@ -2,8 +2,10 @@ namespace Bug12421; -function doFoo() { - $x = new RegularProperty(); +function doFoo(RegularProperty $x) { + unset($x->y); + var_dump($x->y); + unset($x->y); var_dump($x->y); From 8873cdc4b6cc94e45d6088183e86bfc2fb7eb4d2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 4 Mar 2025 11:14:39 +0100 Subject: [PATCH 1135/3097] Fix --- .../CallToMethodStatementWithoutImpurePointsRuleTest.php | 6 +++--- .../DeadCode/data/call-to-method-without-impure-points.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php index 83000808019..444aaeb25fc 100644 --- a/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/CallToMethodStatementWithoutImpurePointsRuleTest.php @@ -50,15 +50,15 @@ public function testRule(): void ], [ 'Call to method CallToMethodWithoutImpurePoints\y::myFinalBaseFunc() on a separate line has no effect.', - 62, + 61, ], [ 'Call to method CallToMethodWithoutImpurePoints\AbstractFoo::myFunc() on a separate line has no effect.', - 140, + 139, ], [ 'Call to method CallToMethodWithoutImpurePoints\CallsPrivateMethodWithoutImpurePoints::doBar() on a separate line has no effect.', - 148, + 147, ], ]); } diff --git a/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php b/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php index e22db7862ef..b3710267457 100644 --- a/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php +++ b/tests/PHPStan/Rules/DeadCode/data/call-to-method-without-impure-points.php @@ -42,7 +42,6 @@ function (): void { }; function (y $xy, finalX $finalX): void { - $xy = new y(); if (rand(0,1)) { $xy = $finalX; } From 3019d391130102baa91af881771211caceff2325 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 3 Mar 2025 10:39:52 +0100 Subject: [PATCH 1136/3097] Understand that `new Foo()` cannot be a subclass --- src/Analyser/MutatingScope.php | 47 +++++++++--- src/Reflection/ClassReflection.php | 51 +++++++++++++ src/Type/Constant/ConstantStringType.php | 4 +- src/Type/ObjectType.php | 42 ++++++++--- .../PHPStan/Analyser/nsrt/get-debug-type.php | 2 +- .../Classes/ImpossibleInstanceOfRuleTest.php | 11 +++ ...ossible-instanceof-new-is-always-final.php | 26 +++++++ ...mpossibleCheckTypeFunctionCallRuleTest.php | 8 ++ ...odStatementWithoutImpurePointsRuleTest.php | 12 +++ .../CatchWithUnthrownExceptionRuleTest.php | 4 + .../Rules/Functions/CallCallablesRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 2 +- .../Properties/AccessPropertiesRuleTest.php | 73 +++++++++++++------ .../null-coalesce-new-is-always-final.php | 13 ++++ tests/PHPStan/Type/TypeCombinatorTest.php | 68 +++++++++++++++++ 15 files changed, 316 insertions(+), 49 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php create mode 100644 tests/PHPStan/Rules/Properties/data/null-coalesce-new-is-always-final.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0c0af430e8a..9fd81c9e900 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5599,6 +5599,10 @@ private function exactInstantiation(New_ $node, string $className): ?Type } $classReflection = $this->reflectionProvider->getClass($resolvedClassName); + $nonFinalClassReflection = $classReflection; + if (!$isStatic) { + $classReflection = $classReflection->asFinal(); + } if ($classReflection->hasConstructor()) { $constructorMethod = $classReflection->getConstructor(); } else { @@ -5648,7 +5652,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return $methodResult; } - $objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName); + $objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName, null, $classReflection); if (!$classReflection->isGeneric()) { return $objectType; } @@ -5674,7 +5678,8 @@ private function exactInstantiation(New_ $node, string $className): ?Type if (count($classTemplateTypes) === count($originalClassTemplateTypes)) { $propertyType = TypeCombinator::removeNull($this->getType($assignedToProperty)); - if ($objectType->isSuperTypeOf($propertyType)->yes()) { + $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, null, $nonFinalClassReflection); + if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) { return $propertyType; } } @@ -5689,9 +5694,13 @@ private function exactInstantiation(New_ $node, string $className): ?Type [], ); } + + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } @@ -5706,9 +5715,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } $newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); @@ -5723,9 +5735,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } $ancestorClassReflections = $ancestorType->getObjectClassReflections(); @@ -5739,9 +5754,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } @@ -5758,9 +5776,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } $newParentTypeClassReflection = $newParentTypeClassReflections[0]; @@ -5803,9 +5824,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); } + $types = $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)); return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); } @@ -5817,14 +5841,17 @@ private function exactInstantiation(New_ $node, string $className): ?Type ); $resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); + $types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); $newGenericType = new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), + $types, + null, + $classReflection->withTypes($types)->asFinal(), ); if ($isStatic) { $newGenericType = new GenericStaticType( $classReflection, - $classReflection->typeMapToList($classReflection->getTemplateTypeMap()), + $types, null, [], ); diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 0c2bbd532cd..6ec2c329d47 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -175,6 +175,7 @@ public function __construct( private array $universalObjectCratesClasses, private ?string $extraCacheKey = null, private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null, + private ?bool $finalByKeywordOverride = null, ) { } @@ -306,6 +307,10 @@ public function getCacheKey(): string $cacheKey .= '<' . implode(',', $templateTypes) . '>'; } + if ($this->hasFinalByKeywordOverride()) { + $cacheKey .= '-f=' . ($this->isFinalByKeyword() ? 't' : 'f'); + } + if ($this->extraCacheKey !== null) { $cacheKey .= '-' . $this->extraCacheKey; } @@ -1276,12 +1281,21 @@ public function acceptsNamedArguments(): bool return $this->acceptsNamedArguments; } + public function hasFinalByKeywordOverride(): bool + { + return $this->isClass() && $this->finalByKeywordOverride !== null; + } + public function isFinalByKeyword(): bool { if ($this->isAnonymous()) { return true; } + if ($this->isClass() && $this->finalByKeywordOverride !== null) { + return $this->finalByKeywordOverride; + } + return $this->reflection->isFinal(); } @@ -1543,6 +1557,7 @@ public function withTypes(array $types): self $this->universalObjectCratesClasses, null, $this->resolvedCallSiteVarianceMap, + $this->finalByKeywordOverride, ); } @@ -1573,6 +1588,42 @@ public function withVariances(array $variances): self $this->universalObjectCratesClasses, null, $this->varianceMapFromList($variances), + $this->finalByKeywordOverride, + ); + } + + public function asFinal(): self + { + if ($this->getNativeReflection()->isFinal()) { + return $this; + } + if ($this->finalByKeywordOverride === true) { + return $this; + } + + return new self( + $this->reflectionProvider, + $this->initializerExprTypeResolver, + $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, + $this->phpVersion, + $this->signatureMapProvider, + $this->attributeReflectionFactory, + $this->propertiesClassReflectionExtensions, + $this->methodsClassReflectionExtensions, + $this->allowedSubTypesClassReflectionExtensions, + $this->requireExtendsPropertiesClassReflectionExtension, + $this->requireExtendsMethodsClassReflectionExtension, + $this->displayName, + $this->reflection, + $this->anonymousFilename, + $this->resolvedTemplateTypeMap, + $this->stubPhpDocBlock, + $this->universalObjectCratesClasses, + null, + $this->resolvedCallSiteVarianceMap, + true, ); } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index db7e3f2a322..e4c34e609f6 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -223,7 +223,7 @@ public function isCallable(): TrinaryLogic return TrinaryLogic::createYes(); } - if (!$classRef->getNativeReflection()->isFinal()) { + if (!$classRef->isFinalByKeyword()) { return TrinaryLogic::createMaybe(); } @@ -265,7 +265,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) return FunctionCallableVariant::createFromVariants($method, $method->getVariants()); } - if (!$classReflection->getNativeReflection()->isFinal()) { + if (!$classReflection->isFinalByKeyword()) { return [new TrivialParametersAcceptor()]; } } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 5a055750596..19764b31ff3 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -377,21 +377,37 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult throw new ShouldNotHappenException(); } - if ($thatClassNames[0] === $thisClassName) { - return $transformResult(IsSuperTypeOfResult::createYes()); - } - - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); $thisClassReflection = $this->getClassReflection(); + $thatClassReflections = $type->getObjectClassReflections(); + if (count($thatClassReflections) === 1) { + $thatClassReflection = $thatClassReflections[0]; + } else { + $thatClassReflection = null; + } - if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) { + if ($thisClassReflection === null || $thatClassReflection === null) { + if ($thatClassNames[0] === $thisClassName) { + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); + } return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - $thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]); + if ($thatClassNames[0] === $thisClassName) { + if ($thisClassReflection->getNativeReflection()->isFinal()) { + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); + } + + if ($thisClassReflection->hasFinalByKeywordOverride()) { + if (!$thatClassReflection->hasFinalByKeywordOverride()) { + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createMaybe()); + } + } + + return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); + } if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) { - return IsSuperTypeOfResult::createNo(); + return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo(); } if ($thisClassReflection->getName() === $thatClassReflection->getName()) { @@ -406,11 +422,11 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) { + if ($thisClassReflection->isInterface() && !$thatClassReflection->isFinalByKeyword()) { return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } - if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) { + if ($thatClassReflection->isInterface() && !$thisClassReflection->isFinalByKeyword()) { return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } @@ -550,6 +566,10 @@ private function describeCache(): string $description .= '-'; $description .= (string) $reflection->getNativeReflection()->getStartLine(); $description .= '-'; + + if ($reflection->hasFinalByKeywordOverride()) { + $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f'); + } } return $this->cachedDescription = $description; @@ -1331,7 +1351,7 @@ private function findCallableParametersAcceptors(): ?array ); } - if (!$classReflection->getNativeReflection()->isFinal()) { + if (!$classReflection->isFinalByKeyword()) { return [new TrivialParametersAcceptor()]; } diff --git a/tests/PHPStan/Analyser/nsrt/get-debug-type.php b/tests/PHPStan/Analyser/nsrt/get-debug-type.php index bc3823a68b1..2408a6acde6 100644 --- a/tests/PHPStan/Analyser/nsrt/get-debug-type.php +++ b/tests/PHPStan/Analyser/nsrt/get-debug-type.php @@ -33,7 +33,7 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr assertType("'float'", get_debug_type($d)); assertType("'string'", get_debug_type($s)); assertType("'array'", get_debug_type($a)); - assertType("string", get_debug_type($o)); + assertType("'stdClass'", get_debug_type($o)); assertType("string", get_debug_type($std)); assertType("'GetDebugType\\\\A'", get_debug_type($A)); assertType("string", get_debug_type($r)); diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 19f40e94214..2ead0c53c59 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -504,4 +504,15 @@ public function testBug3632(): void ]); } + public function testNewIsAlwaysFinalClass(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/impossible-instanceof-new-is-always-final.php'], [ + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php new file mode 100644 index 00000000000..1141e6c3dda --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php @@ -0,0 +1,26 @@ +analyse([__DIR__ . '/data/bug-4852.php'], [ + [ + 'Dead catch - Exception is never thrown in the try block.', + 63, + ], [ 'Dead catch - Exception is never thrown in the try block.', 78, diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index cde66da1c80..b61120eb27f 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -103,7 +103,7 @@ public function testRule(): void 106, ], [ - 'Trying to invoke CallCallables\Baz but it might not be a callable.', + 'Trying to invoke CallCallables\Baz but it\'s not a callable.', 113, ], [ diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index bedf530cdc4..95a55073ede 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3052,7 +3052,7 @@ public function testObjectShapes(): void [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.', 14, - 'Exception might not have property $foo.', + PHP_VERSION_ID >= 80200 ? 'Exception does not have property $foo.' : 'Exception might not have property $foo.', ], [ 'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.', diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 57d68802f91..f1e6d16f503 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -633,8 +633,18 @@ public function dataDynamicProperties(): array $tipText = 'Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property'; $errors = [ [ - 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', - 29, + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 14, + $tipText, + ], + [ + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 15, + $tipText, + ], + [ + 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', + 16, $tipText, ], ]; @@ -655,21 +665,15 @@ public function dataDynamicProperties(): array 11, $tipText, ], - [ - 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', - 14, - $tipText, - ], - [ - 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', - 15, - $tipText, - ], - [ - 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', - 16, - $tipText, - ], + ], $errors); + + $errors[] = [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 29, + $tipText, + ]; + + $errorsWithMore = array_merge($errorsWithMore, [ [ 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', 20, @@ -685,7 +689,12 @@ public function dataDynamicProperties(): array 22, $tipText, ], - ], $errors); + [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 29, + $tipText, + ], + ]); $errorsWithMore = array_merge($errorsWithMore, [ [ @@ -808,12 +817,12 @@ public function testPhp82AndDynamicProperties(bool $b): void 34, $tipText, ]; + $errors[] = [ + 'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.', + 71, + $tipText, + ]; if ($b) { - $errors[] = [ - 'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.', - 71, - $tipText, - ]; $errors[] = [ 'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.', 78, @@ -997,4 +1006,22 @@ public function testAsymmetricVisibility(): void $this->analyse([__DIR__ . '/data/read-asymmetric-visibility.php'], []); } + public function testNewIsAlwaysFinalClass(): void + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Test requires PHP 8.2.'); + } + + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = false; + $this->analyse([__DIR__ . '/data/null-coalesce-new-is-always-final.php'], [ + [ + 'Access to an undefined property NullCoalesceIsAlwaysFinal\Foo::$bar.', + 12, + 'Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/null-coalesce-new-is-always-final.php b/tests/PHPStan/Rules/Properties/data/null-coalesce-new-is-always-final.php new file mode 100644 index 00000000000..3a02557bd5a --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/null-coalesce-new-is-always-final.php @@ -0,0 +1,13 @@ +bar ?? 'no'; +}; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index b615d9deea1..7dd88c8e695 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -57,6 +57,7 @@ use Traversable; use function array_map; use function array_reverse; +use function get_class; use function implode; use function sprintf; use const PHP_VERSION_ID; @@ -2740,6 +2741,18 @@ public function dataUnion(): iterable ObjectType::class, $c->getName(), ]; + + $nonFinalClass = $reflectionProvider->getClass(\NullCoalesceIsAlwaysFinal\Foo::class); + $finalClass = $nonFinalClass->asFinal(); + + yield [ + [ + new ObjectType($finalClass->getName(), null, $finalClass), + new ObjectType($nonFinalClass->getName(), null, $nonFinalClass), + ], + ObjectType::class, + $nonFinalClass->getDisplayName(), + ]; } /** @@ -2762,6 +2775,16 @@ public function testUnion( $actualTypeDescription .= '=implicit'; } } + if (get_class($actualType) === ObjectType::class) { + $actualClassReflection = $actualType->getClassReflection(); + if ( + $actualClassReflection !== null + && $actualClassReflection->hasFinalByKeywordOverride() + && $actualClassReflection->isFinal() + ) { + $actualTypeDescription .= '=final'; + } + } $this->assertSame( $expectedTypeDescription, @@ -2809,6 +2832,16 @@ public function testUnionInversed( $actualTypeDescription .= '=implicit'; } } + if (get_class($actualType) === ObjectType::class) { + $actualClassReflection = $actualType->getClassReflection(); + if ( + $actualClassReflection !== null + && $actualClassReflection->hasFinalByKeywordOverride() + && $actualClassReflection->isFinal() + ) { + $actualTypeDescription .= '=final'; + } + } $this->assertSame( $expectedTypeDescription, $actualTypeDescription, @@ -4618,6 +4651,18 @@ public function dataIntersect(): iterable GenericStaticType::class, 'static(PHPStan\Generics\FunctionsAssertType\C)', ]; + + $nonFinalClass = $reflectionProvider->getClass(\NullCoalesceIsAlwaysFinal\Foo::class); + $finalClass = $nonFinalClass->asFinal(); + + yield [ + [ + new ObjectType($finalClass->getName(), null, $finalClass), + new ObjectType($nonFinalClass->getName(), null, $nonFinalClass), + ], + ObjectType::class, + $nonFinalClass->getDisplayName() . '=final', + ]; } /** @@ -4647,6 +4692,18 @@ public function testIntersect( $actualTypeDescription .= '=implicit'; } } + + if (get_class($actualType) === ObjectType::class && $actualType->isEnum()->no()) { + $actualClassReflection = $actualType->getClassReflection(); + if ( + $actualClassReflection !== null + && $actualClassReflection->hasFinalByKeywordOverride() + && $actualClassReflection->isFinal() + ) { + $actualTypeDescription .= '=final'; + } + } + $this->assertSame($expectedTypeDescription, $actualTypeDescription); $this->assertInstanceOf($expectedTypeClass, $actualType); } @@ -4678,6 +4735,17 @@ public function testIntersectInversed( $actualTypeDescription .= '=implicit'; } } + + if (get_class($actualType) === ObjectType::class && $actualType->isEnum()->no()) { + $actualClassReflection = $actualType->getClassReflection(); + if ( + $actualClassReflection !== null + && $actualClassReflection->hasFinalByKeywordOverride() + && $actualClassReflection->isFinal() + ) { + $actualTypeDescription .= '=final'; + } + } $this->assertSame($expectedTypeDescription, $actualTypeDescription); $this->assertInstanceOf($expectedTypeClass, $actualType); } From 189a4cc4c3875c1e8f4c19b3ea4b7aa89e88f919 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 4 Mar 2025 16:01:06 +0100 Subject: [PATCH 1137/3097] VarTagTypeRuleHelper - remove namespace and uses from NameScope Node provided by `Type::toPhpDocNode()` is already FQN. --- src/Analyser/NameScope.php | 14 +++++++++++ src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 3 ++- .../WrongVariableNameInVarTagRuleTest.php | 14 ++++++++++- .../data/new-is-always-final-var-tag-type.php | 23 +++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index 1426b804a10..2ce18d91be5 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -174,6 +174,20 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self ); } + public function withoutNamespaceAndUses(): self + { + return new self( + null, + [], + $this->className, + $this->functionName, + $this->templateTypeMap, + $this->typeAliasesMap, + $this->bypassTypeAliases, + $this->constUses, + ); + } + public function withClassName(string $className): self { return new self( diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index de4f3bee493..8fee63615ca 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -240,7 +240,7 @@ private function createNameScope(Scope $scope): NameScope $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, $function !== null ? $function->getName() : null, - ); + )->withoutNamespaceAndUses(); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e505fb0511e..34f65e70354 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1212,7 +1212,8 @@ public function testBug5091(): void public function testBug9459(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php'); - $this->assertCount(0, $errors); + $this->assertCount(1, $errors); + $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); } public function testBug9573(): void diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 0306a35e97a..5c54234da5d 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -226,7 +226,12 @@ public function testBug11535(): void $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; - $this->analyse([__DIR__ . '/data/bug-11535.php'], []); + $this->analyse([__DIR__ . '/data/bug-11535.php'], [ + [ + 'PHPDoc tag @var with type Closure(string): array is not subtype of native type Closure(string): array{1, 2, 3}.', + 6, + ], + ]); } public function testEnums(): void @@ -561,4 +566,11 @@ public function testBug12457(): void ]); } + public function testNewIsAlwaysFinalClass(): void + { + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/new-is-always-final-var-tag-type.php'], []); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php b/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php new file mode 100644 index 00000000000..3b705fbf072 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php @@ -0,0 +1,23 @@ +returnStatic(); +}; From 5120049bdcc8c7a8194c7971508d4eadbd35a2d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 4 Mar 2025 16:24:36 +0100 Subject: [PATCH 1138/3097] Update crate-ci/typos --- .github/workflows/spelling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index d34bbec06a0..b2f810732c2 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -18,6 +18,6 @@ jobs: uses: actions/checkout@v4 - name: "Check for typos" - uses: "crate-ci/typos@v1.27.0" + uses: "crate-ci/typos@v1" with: files: "README.md src/" From 772f2979425574897b525de95dd8a535e1882f39 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 5 Mar 2025 13:55:56 +0100 Subject: [PATCH 1139/3097] Object type narrowed after `$a::class` and `get_class($a)` cannot be a subclass --- phpstan-baseline.neon | 2 +- src/Analyser/TypeSpecifier.php | 26 +++++++++++- .../Classes/ImpossibleInstanceOfRuleTest.php | 20 +++++++++ ...ossible-instanceof-new-is-always-final.php | 42 ++++++++++++++++++- 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6190c0a39ac..38713aa8ce8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -75,7 +75,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 3 + count: 4 path: src/Analyser/TypeSpecifier.php - diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4430e7fe451..2f57e11abd9 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2205,6 +2205,14 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty && in_array(strtolower($unwrappedLeftExpr->name->toString()), ['get_class', 'get_debug_type'], true) && isset($unwrappedLeftExpr->getArgs()[0]) ) { + if ($rightType instanceof ConstantStringType && $this->reflectionProvider->hasClass($rightType->getValue())) { + return $this->create( + $unwrappedLeftExpr->getArgs()[0]->value, + new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), + $context, + $scope, + )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); + } if ($rightType->getClassStringObjectType()->isObject()->yes()) { return $this->create( $unwrappedLeftExpr->getArgs()[0]->value, @@ -2215,7 +2223,6 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty } } - // get_class($a) === 'Foo' if ( $context->truthy() && $unwrappedLeftExpr instanceof FuncCall @@ -2305,6 +2312,14 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $rightType->getValue() !== '' && strtolower($unwrappedLeftExpr->name->toString()) === 'class' ) { + if ($this->reflectionProvider->hasClass($rightType->getValue())) { + return $this->create( + $unwrappedLeftExpr->class, + new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), + $context, + $scope, + )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); + } return $this->specifyTypesInCondition( $scope, new Instanceof_( @@ -2328,6 +2343,15 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty $leftType->getValue() !== '' && strtolower($unwrappedRightExpr->name->toString()) === 'class' ) { + if ($this->reflectionProvider->hasClass($leftType->getValue())) { + return $this->create( + $unwrappedRightExpr->class, + new ObjectType($leftType->getValue(), null, $this->reflectionProvider->getClass($leftType->getValue())->asFinal()), + $context, + $scope, + )->unionWith($this->create($rightExpr, $leftType, $context, $scope)->setRootExpr($expr)); + } + return $this->specifyTypesInCondition( $scope, new Instanceof_( diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 2ead0c53c59..29b51998e6a 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -506,12 +506,32 @@ public function testBug3632(): void public function testNewIsAlwaysFinalClass(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('This test needs PHP 8.0.'); + } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/impossible-instanceof-new-is-always-final.php'], [ [ 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', 17, ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 33, + ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 43, + ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 53, + ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 63, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php index 1141e6c3dda..1ac47551ece 100644 --- a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php @@ -1,4 +1,4 @@ -= 8.0 namespace ImpossibleInstanceofNewIsAlwaysFinal; @@ -24,3 +24,43 @@ function (Bar $bar): void { } }; + +function (Bar $bar): void { + if ($bar::class !== Bar::class) { + return; + } + + if ($bar instanceof Foo) { + + } +}; + +function (Bar $bar): void { + if (Bar::class !== $bar::class) { + return; + } + + if ($bar instanceof Foo) { + + } +}; + +function (Bar $bar): void { + if (get_class($bar) !== Bar::class) { + return; + } + + if ($bar instanceof Foo) { + + } +}; + +function (Bar $bar): void { + if (Bar::class !== get_class($bar)) { + return; + } + + if ($bar instanceof Foo) { + + } +}; From 51b7acda55208ec8c856fd2486eeb7572c53d087 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 6 Mar 2025 10:53:13 +0100 Subject: [PATCH 1140/3097] Fix typo false-positive --- .typos.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.typos.toml b/.typos.toml index 2687f239edf..78c99b1d76a 100644 --- a/.typos.toml +++ b/.typos.toml @@ -8,3 +8,7 @@ ignore-hidden = false # Known typos NonRemoveableTypeTrait = "NonRemoveableTypeTrait" supportsLessOverridenParametersWithVariadic = "supportsLessOverridenParametersWithVariadic" + +[default.extend-words] +# override false-positives +Excluder = "Excluder" From ca25c55d408cc5b5fb09fa1a486fc276f00cefa4 Mon Sep 17 00:00:00 2001 From: Andrei Ivchenkov Date: Tue, 4 Mar 2025 22:28:48 +0300 Subject: [PATCH 1141/3097] fix `MongoCollection::findOne()` return type --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index e2a6e572087..f83a55d4aa3 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6593,7 +6593,7 @@ 'MongoCollection::ensureIndex' => ['bool', 'keys'=>'array', 'options='=>'array'], 'MongoCollection::find' => ['MongoCursor', 'query='=>'array', 'fields='=>'array'], 'MongoCollection::findAndModify' => ['array', 'query'=>'array', 'update='=>'array', 'fields='=>'array', 'options='=>'array'], -'MongoCollection::findOne' => ['array', 'query='=>'array', 'fields='=>'array'], +'MongoCollection::findOne' => ['array|null', 'query='=>'array', 'fields='=>'array'], 'MongoCollection::getDBRef' => ['array', 'ref'=>'array'], 'MongoCollection::getIndexInfo' => ['array'], 'MongoCollection::getName' => ['string'], From 274e7662912197be3d01e13ba2ed1d4750157ca9 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 4 Mar 2025 00:03:33 +0000 Subject: [PATCH 1142/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- tests/PHPStan/Reflection/ReflectionProviderTest.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 6dbb056300b..bef103a992c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#6c6bf204cbdf39006f12a6c923b8217444acd67f", + "jetbrains/phpstorm-stubs": "dev-master#7385d3075dc365911c4a3168fa762de6aa4550c9", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 1ab1cfb464f..bf524029dd3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2c5308f6e71c5cdd76c9589a43b04326", + "content-hash": "4ea576b5718d373ded2bcea605f90eba", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "6c6bf204cbdf39006f12a6c923b8217444acd67f" + "reference": "7385d3075dc365911c4a3168fa762de6aa4550c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/6c6bf204cbdf39006f12a6c923b8217444acd67f", - "reference": "6c6bf204cbdf39006f12a6c923b8217444acd67f", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7385d3075dc365911c4a3168fa762de6aa4550c9", + "reference": "7385d3075dc365911c4a3168fa762de6aa4550c9", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-01-27T10:32:46+00:00" + "time": "2025-02-28T14:37:15+00:00" }, { "name": "nette/bootstrap", diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 02dfd6869f0..db8896c9cb0 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -114,7 +114,7 @@ public function dataMethodThrowType(): array [ DateTime::class, '__construct', - new ObjectType('DateMalformedStringException'), + PHP_VERSION_ID >= 80300 ? new ObjectType('DateMalformedStringException') : new ObjectType('Exception'), ], [ DateTime::class, From 9bb2ed5b90d76d416a985eeb67411999be187263 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 13:44:59 +0100 Subject: [PATCH 1143/3097] Cannot override being final for abstract classes --- src/Reflection/ClassReflection.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 6ec2c329d47..eb59d4ec005 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1283,7 +1283,7 @@ public function acceptsNamedArguments(): bool public function hasFinalByKeywordOverride(): bool { - return $this->isClass() && $this->finalByKeywordOverride !== null; + return $this->finalByKeywordOverride !== null; } public function isFinalByKeyword(): bool @@ -1292,7 +1292,7 @@ public function isFinalByKeyword(): bool return true; } - if ($this->isClass() && $this->finalByKeywordOverride !== null) { + if ($this->finalByKeywordOverride !== null) { return $this->finalByKeywordOverride; } @@ -1600,6 +1600,12 @@ public function asFinal(): self if ($this->finalByKeywordOverride === true) { return $this; } + if (!$this->isClass()) { + return $this; + } + if ($this->isAbstract()) { + return $this; + } return new self( $this->reflectionProvider, From 5b68bd7a7905536bed7d04a836dfa16c4901effb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 13:49:58 +0100 Subject: [PATCH 1144/3097] ImpossibleInstanceofRule - test a tricky situation --- .../Rules/Classes/ImpossibleInstanceOfRuleTest.php | 4 ++++ .../data/impossible-instanceof-new-is-always-final.php | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 29b51998e6a..99f52f62926 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -532,6 +532,10 @@ public function testNewIsAlwaysFinalClass(): void 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', 63, ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar|null and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', + 73, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php index 1ac47551ece..466ccc8e930 100644 --- a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php @@ -64,3 +64,13 @@ function (Bar $bar): void { } }; + +function (): void { + $bar = null; + if (rand(0,1)===1) { + $bar = new Bar(); + } + if ($bar instanceof Foo) { + + } +}; From ed4ea0a3b5784e3e39b28cff2fc92b6445a48419 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 14:04:27 +0100 Subject: [PATCH 1145/3097] ClassReflection - cannot be a subclass of final-overriden class --- build/PHPStan/Build/FinalClassRule.php | 2 +- phpstan-baseline.neon | 27 +++++++------------ src/Analyser/MutatingScope.php | 24 ++++++++--------- src/Analyser/NodeScopeResolver.php | 9 +++---- src/Analyser/Scope.php | 4 +++ src/Reflection/ClassReflection.php | 25 ++++++++++++----- ...pClientMethodsClassReflectionExtension.php | 2 +- ...alObjectCratesClassReflectionExtension.php | 5 +--- src/Rules/Api/ApiInstanceofRule.php | 2 +- src/Rules/Api/ApiInstanceofTypeRule.php | 2 +- .../DefaultExceptionTypeResolver.php | 12 ++------- .../Functions/ArrowFunctionReturnTypeRule.php | 2 -- src/Rules/Functions/ClosureReturnTypeRule.php | 2 -- src/Rules/Methods/ReturnTypeRule.php | 2 +- src/Rules/Methods/StaticMethodCallCheck.php | 2 +- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 9 ++++++- src/Type/ObjectType.php | 13 ++++----- ...lementClassPropertyReflectionExtension.php | 2 +- .../Classes/ImpossibleInstanceOfRuleTest.php | 4 +++ ...ossible-instanceof-new-is-always-final.php | 15 +++++++++++ .../TooWideFunctionThrowTypeRuleTest.php | 4 +++ .../TooWideMethodThrowTypeRuleTest.php | 4 +++ .../TooWidePropertyHookThrowTypeRuleTest.php | 4 +++ .../data/too-wide-throws-function.php | 8 +++++- .../data/too-wide-throws-method.php | 2 +- .../data/too-wide-throws-property-hook.php | 11 +++++++- .../VarTagChangedExpressionTypeRuleTest.php | 1 + .../WrongVariableNameInVarTagRuleTest.php | 1 + .../TypesAssignedToPropertiesRuleTest.php | 2 +- 29 files changed, 122 insertions(+), 80 deletions(-) diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index a4758648c6d..018e1da4f8e 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array if ($classReflection->isFinal()) { return []; } - if ($classReflection->isSubclassOf(Type::class)) { + if ($classReflection->is(Type::class)) { return []; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 38713aa8ce8..d252aaef595 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -300,6 +300,15 @@ parameters: count: 1 path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php + - + message: ''' + #^Call to deprecated method isSubclassOf\(\) of class PHPStan\\Reflection\\ClassReflection\: + Use isSubclassOfClass instead\.$# + ''' + identifier: method.deprecated + count: 1 + path: src/Reflection/ClassReflection.php + - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType @@ -1317,12 +1326,6 @@ parameters: count: 2 path: src/Type/IntegerType.php - - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' - identifier: phpstanApi.instanceofType - count: 3 - path: src/Type/IntersectionType.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1647,12 +1650,6 @@ parameters: count: 2 path: src/Type/StringType.php - - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/TypeCombinator.php - - message: '#^Doing instanceof PHPStan\\Type\\ArrayType is error\-prone and deprecated\. Use Type\:\:isArray\(\) or Type\:\:getArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1815,12 +1812,6 @@ parameters: count: 1 path: src/Type/UnionType.php - - - message: '#^Doing instanceof PHPStan\\Type\\Accessory\\AccessoryType is error\-prone and deprecated\. Use methods on PHPStan\\Type\\Type instead\.$#' - identifier: phpstanApi.instanceofType - count: 3 - path: src/Type/UnionTypeHelper.php - - message: '#^Doing instanceof PHPStan\\Type\\CallableType is error\-prone and deprecated\. Use Type\:\:isCallable\(\) and Type\:\:getCallableParametersAcceptors\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 9fd81c9e900..fd0404dc46a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5447,22 +5447,22 @@ public function canWriteProperty(ExtendedPropertyReflection $propertyReflection) return $this->canAccessClassMember($propertyReflection); } - $classReflectionName = $propertyReflection->getDeclaringClass()->getName(); - $canAccessClassMember = static function (ClassReflection $classReflection) use ($propertyReflection, $classReflectionName) { + $propertyDeclaringClass = $propertyReflection->getDeclaringClass(); + $canAccessClassMember = static function (ClassReflection $classReflection) use ($propertyReflection, $propertyDeclaringClass) { if ($propertyReflection->isPrivateSet()) { - return $classReflection->getName() === $classReflectionName; + return $classReflection->getName() === $propertyDeclaringClass->getName(); } // protected set if ( - $classReflection->getName() === $classReflectionName - || $classReflection->isSubclassOf($classReflectionName) + $classReflection->getName() === $propertyDeclaringClass->getName() + || $classReflection->isSubclassOfClass($propertyDeclaringClass) ) { return true; } - return $propertyReflection->getDeclaringClass()->isSubclassOf($classReflection->getName()); + return $propertyReflection->getDeclaringClass()->isSubclassOfClass($classReflection); }; foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { @@ -5504,22 +5504,22 @@ private function canAccessClassMember(ClassMemberReflection $classMemberReflecti return true; } - $classReflectionName = $classMemberReflection->getDeclaringClass()->getName(); - $canAccessClassMember = static function (ClassReflection $classReflection) use ($classMemberReflection, $classReflectionName) { + $classMemberDeclaringClass = $classMemberReflection->getDeclaringClass(); + $canAccessClassMember = static function (ClassReflection $classReflection) use ($classMemberReflection, $classMemberDeclaringClass) { if ($classMemberReflection->isPrivate()) { - return $classReflection->getName() === $classReflectionName; + return $classReflection->getName() === $classMemberDeclaringClass->getName(); } // protected if ( - $classReflection->getName() === $classReflectionName - || $classReflection->isSubclassOf($classReflectionName) + $classReflection->getName() === $classMemberDeclaringClass->getName() + || $classReflection->isSubclassOfClass($classMemberDeclaringClass) ) { return true; } - return $classMemberReflection->getDeclaringClass()->isSubclassOf($classReflection->getName()); + return $classMemberReflection->getDeclaringClass()->isSubclassOfClass($classReflection); }; foreach ($this->inClosureBindScopeClasses as $inClosureBindScopeClass) { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 31c726f6dea..12672c9d53f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2970,10 +2970,7 @@ static function (): void { && $scopeFunction instanceof MethodReflection && !$scopeFunction->isStatic() && $scope->isInClass() - && ( - $scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName() - || $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName()) - ) + && $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) ) { $scope = $scope->invalidateExpression(new Variable('this'), true); } @@ -2985,7 +2982,7 @@ static function (): void { && $scopeFunction instanceof MethodReflection && !$scopeFunction->isStatic() && $scope->isInClass() - && $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName()) + && $scope->getClassReflection()->isSubclassOfClass($methodReflection->getDeclaringClass()) ) { $thisType = $scope->getType(new Variable('this')); $methodClassReflection = $methodReflection->getDeclaringClass(); @@ -4215,7 +4212,7 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio return ThrowPoint::createExplicit($scope, $throwType, $new, true); } } elseif ($this->implicitThrows) { - if ($classReflection->getName() !== Throwable::class && !$classReflection->isSubclassOf(Throwable::class)) { + if (!$classReflection->is(Throwable::class)) { return ThrowPoint::createImplicit($scope, $methodCall); } } diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index bf89cbdf481..1134614b2f7 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -85,6 +85,10 @@ public function getIterableKeyType(Type $iteratee): Type; public function getIterableValueType(Type $iteratee): Type; + /** + * @phpstan-assert-if-true !null $this->getAnonymousFunctionReflection() + * @phpstan-assert-if-true !null $this->getAnonymousFunctionReturnType() + */ public function isInAnonymousFunction(): bool; public function getAnonymousFunctionReflection(): ?ParametersAcceptor; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index eb59d4ec005..14a1d496438 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -837,20 +837,33 @@ public function is(string $className): bool return $this->getName() === $className || $this->isSubclassOf($className); } + /** + * @deprecated Use isSubclassOfClass instead. + */ public function isSubclassOf(string $className): bool { - if (isset($this->subclasses[$className])) { - return $this->subclasses[$className]; + if (!$this->reflectionProvider->hasClass($className)) { + return false; } - if (!$this->reflectionProvider->hasClass($className)) { - return $this->subclasses[$className] = false; + return $this->isSubclassOfClass($this->reflectionProvider->getClass($className)); + } + + public function isSubclassOfClass(self $class): bool + { + $cacheKey = $class->getCacheKey(); + if (isset($this->subclasses[$cacheKey])) { + return $this->subclasses[$cacheKey]; + } + + if ($class->isFinal() || $class->isAnonymous()) { + return $this->subclasses[$cacheKey] = false; } try { - return $this->subclasses[$className] = $this->reflection->isSubclassOf($className); + return $this->subclasses[$cacheKey] = $this->reflection->isSubclassOf($class->getName()); } catch (ReflectionException) { - return $this->subclasses[$className] = false; + return $this->subclasses[$cacheKey] = false; } } diff --git a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php index 3db627179e3..431026f938f 100644 --- a/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php +++ b/src/Reflection/Php/Soap/SoapClientMethodsClassReflectionExtension.php @@ -11,7 +11,7 @@ final class SoapClientMethodsClassReflectionExtension implements MethodsClassRef public function hasMethod(ClassReflection $classReflection, string $methodName): bool { - return $classReflection->getName() === 'SoapClient' || $classReflection->isSubclassOf('SoapClient'); + return $classReflection->is('SoapClient'); } public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index df064434368..c26ef1dcfed 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -59,10 +59,7 @@ private static function isUniversalObjectCrateImplementation( continue; } - if ( - $classReflection->getName() === $className - || $classReflection->isSubclassOf($className) - ) { + if ($classReflection->is($className)) { return true; } } diff --git a/src/Rules/Api/ApiInstanceofRule.php b/src/Rules/Api/ApiInstanceofRule.php index db7c28afffc..a1769055631 100644 --- a/src/Rules/Api/ApiInstanceofRule.php +++ b/src/Rules/Api/ApiInstanceofRule.php @@ -81,7 +81,7 @@ public function processNode(Node $node, Scope $scope): array */ private function processCoveredClass(Node\Expr\Instanceof_ $node, Scope $scope, ClassReflection $classReflection): array { - if ($classReflection->getName() === Type::class || $classReflection->isSubclassOf(Type::class)) { + if ($classReflection->is(Type::class)) { return []; } if ($classReflection->isInterface()) { diff --git a/src/Rules/Api/ApiInstanceofTypeRule.php b/src/Rules/Api/ApiInstanceofTypeRule.php index 73697156604..3913479a89a 100644 --- a/src/Rules/Api/ApiInstanceofTypeRule.php +++ b/src/Rules/Api/ApiInstanceofTypeRule.php @@ -128,7 +128,7 @@ public function processNode(Node $node, Scope $scope): array if ($this->reflectionProvider->hasClass($className)) { $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->isSubclassOf(AccessoryType::class)) { + if ($classReflection->is(AccessoryType::class)) { if ($className === $classReflection->getName()) { return []; } diff --git a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php index 75f020fe771..f428436b489 100644 --- a/src/Rules/Exceptions/DefaultExceptionTypeResolver.php +++ b/src/Rules/Exceptions/DefaultExceptionTypeResolver.php @@ -49,11 +49,7 @@ public function isCheckedException(string $className, Scope $scope): bool $classReflection = $this->reflectionProvider->getClass($className); foreach ($this->uncheckedExceptionClasses as $uncheckedExceptionClass) { - if ($classReflection->getName() === $uncheckedExceptionClass) { - return false; - } - - if (!$classReflection->isSubclassOf($uncheckedExceptionClass)) { + if (!$classReflection->is($uncheckedExceptionClass)) { continue; } @@ -83,11 +79,7 @@ private function isCheckedExceptionInternal(string $className): bool $classReflection = $this->reflectionProvider->getClass($className); foreach ($this->checkedExceptionClasses as $checkedExceptionClass) { - if ($classReflection->getName() === $checkedExceptionClass) { - return true; - } - - if (!$classReflection->isSubclassOf($checkedExceptionClass)) { + if (!$classReflection->is($checkedExceptionClass)) { continue; } diff --git a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php index b9b489c12a4..045f66913bd 100644 --- a/src/Rules/Functions/ArrowFunctionReturnTypeRule.php +++ b/src/Rules/Functions/ArrowFunctionReturnTypeRule.php @@ -11,7 +11,6 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; /** * @implements Rule @@ -34,7 +33,6 @@ public function processNode(Node $node, Scope $scope): array throw new ShouldNotHappenException(); } - /** @var Type $returnType */ $returnType = $scope->getAnonymousFunctionReturnType(); $generatorType = new ObjectType(Generator::class); diff --git a/src/Rules/Functions/ClosureReturnTypeRule.php b/src/Rules/Functions/ClosureReturnTypeRule.php index 218edf57346..415da4d6bcb 100644 --- a/src/Rules/Functions/ClosureReturnTypeRule.php +++ b/src/Rules/Functions/ClosureReturnTypeRule.php @@ -7,7 +7,6 @@ use PHPStan\Node\ClosureReturnStatementsNode; use PHPStan\Rules\FunctionReturnTypeCheck; use PHPStan\Rules\Rule; -use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; /** @@ -31,7 +30,6 @@ public function processNode(Node $node, Scope $scope): array return []; } - /** @var Type $returnType */ $returnType = $scope->getAnonymousFunctionReturnType(); $containsNull = TypeCombinator::containsNull($returnType); $hasNativeTypehint = $node->getClosureExpr()->returnType !== null; diff --git a/src/Rules/Methods/ReturnTypeRule.php b/src/Rules/Methods/ReturnTypeRule.php index 58abdef6a63..d580b0c2b9b 100644 --- a/src/Rules/Methods/ReturnTypeRule.php +++ b/src/Rules/Methods/ReturnTypeRule.php @@ -94,7 +94,7 @@ public function processNode(Node $node, Scope $scope): array && $errors[0]->getIdentifier() === 'return.type' && !$errors[0] instanceof TipRuleError && $errors[0] instanceof LineRuleError - && $method->getDeclaringClass()->isSubclassOf(Rule::class) + && $method->getDeclaringClass()->is(Rule::class) && strtolower($method->getName()) === 'processnode' && $node->expr !== null ) { diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index 6eca4510d93..b9ffc20442e 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -221,7 +221,7 @@ public function check( $classType->getObjectClassNames(), static fn (string $objectClassName) => TrinaryLogic::createFromBoolean( $scope->isInClass() - && ($scope->getClassReflection()->getName() === $objectClassName || $scope->getClassReflection()->isSubclassOf($objectClassName)), + && $scope->getClassReflection()->is($objectClassName), ), ); if ( diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 8fee63615ca..4ad442ac428 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -10,6 +10,7 @@ use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException; use PHPStan\PhpDoc\Tag\VarTag; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; @@ -31,6 +32,7 @@ final class VarTagTypeRuleHelper public function __construct( private TypeNodeResolver $typeNodeResolver, private FileTypeMapper $fileTypeMapper, + private ReflectionProvider $reflectionProvider, private bool $checkTypeAgainstPhpDocType, private bool $strictWideningCheck, ) @@ -125,8 +127,13 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): private function containsPhpStanType(Type $type): bool { $classReflections = TypeUtils::toBenevolentUnion($type)->getObjectClassReflections(); + if (!$this->reflectionProvider->hasClass(Type::class)) { + return false; + } + + $typeClass = $this->reflectionProvider->getClass(Type::class); foreach ($classReflections as $classReflection) { - if (!$classReflection->isSubclassOf(Type::class)) { + if (!$classReflection->isSubclassOfClass($typeClass)) { continue; } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 19764b31ff3..0859825701a 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -414,11 +414,11 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } - if ($thatClassReflection->isSubclassOf($thisClassName)) { + if ($thatClassReflection->isSubclassOfClass($thisClassReflection)) { return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes()); } - if ($thisClassReflection->isSubclassOf($thatClassNames[0])) { + if ($thisClassReflection->isSubclassOfClass($thatClassReflection)) { return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe(); } @@ -485,7 +485,7 @@ private function checkSubclassAcceptability(string $thatClass): AcceptsResult } return AcceptsResult::createFromBoolean( - $thatReflection->isSubclassOf($thisReflection->getName()), + $thatReflection->isSubclassOfClass($thisReflection), ); } @@ -1127,10 +1127,7 @@ private function isExtraOffsetAccessibleClass(): TrinaryLogic } foreach (self::EXTRA_OFFSET_CLASSES as $extraOffsetClass) { - if ($classReflection->getName() === $extraOffsetClass) { - return TrinaryLogic::createYes(); - } - if ($classReflection->isSubclassOf($extraOffsetClass)) { + if ($classReflection->is($extraOffsetClass)) { return TrinaryLogic::createYes(); } } @@ -1370,7 +1367,7 @@ public function isInstanceOf(string $className): TrinaryLogic return TrinaryLogic::createMaybe(); } - if ($classReflection->getName() === $className || $classReflection->isSubclassOf($className)) { + if ($classReflection->is($className)) { return TrinaryLogic::createYes(); } diff --git a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php index 6b43f932c4f..35ba562a16b 100644 --- a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php +++ b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php @@ -15,7 +15,7 @@ final class SimpleXMLElementClassPropertyReflectionExtension implements Properti public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { - return $classReflection->getName() === 'SimpleXMLElement' || $classReflection->isSubclassOf('SimpleXMLElement'); + return $classReflection->is('SimpleXMLElement'); } public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 99f52f62926..2ef0c3d1842 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -536,6 +536,10 @@ public function testNewIsAlwaysFinalClass(): void 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar|null and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.', 73, ], + [ + 'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar|null and ImpossibleInstanceofNewIsAlwaysFinal\Baz will always evaluate to false.', + 88, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php index 466ccc8e930..9faf5f715a9 100644 --- a/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php +++ b/tests/PHPStan/Rules/Classes/data/impossible-instanceof-new-is-always-final.php @@ -74,3 +74,18 @@ function (): void { } }; + +class Baz extends Bar +{ + +} + +function (): void { + $bar = null; + if (rand(0,1)===1) { + $bar = new Bar(); + } + if ($bar instanceof Baz) { + + } +}; diff --git a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php index affb0fc679e..8de4ae7bedd 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideFunctionThrowTypeRuleTest.php @@ -21,6 +21,10 @@ protected function getRule(): Rule public function testRule(): void { $this->analyse([__DIR__ . '/data/too-wide-throws-function.php'], [ + [ + 'Function TooWideThrowsFunction\doFoo3() has InvalidArgumentException in PHPDoc @throws tag but it\'s not thrown.', + 20, + ], [ 'Function TooWideThrowsFunction\doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', 26, diff --git a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php index 1ebd091d9d7..c3f2887c988 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php @@ -23,6 +23,10 @@ protected function getRule(): Rule public function testRule(): void { $this->analyse([__DIR__ . '/data/too-wide-throws-method.php'], [ + [ + 'Method TooWideThrowsMethod\Foo::doFoo3() has InvalidArgumentException in PHPDoc @throws tag but it\'s not thrown.', + 23, + ], [ 'Method TooWideThrowsMethod\Foo::doFoo4() has DomainException in PHPDoc @throws tag but it\'s not thrown.', 29, diff --git a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php index 6fed25e6c22..a74effd6e03 100644 --- a/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/TooWidePropertyHookThrowTypeRuleTest.php @@ -27,6 +27,10 @@ public function testRule(): void } $this->analyse([__DIR__ . '/data/too-wide-throws-property-hook.php'], [ + [ + 'Get hook for property TooWideThrowsPropertyHook\Foo::$c has InvalidArgumentException in PHPDoc @throws tag but it\'s not thrown.', + 26, + ], [ 'Get hook for property TooWideThrowsPropertyHook\Foo::$d has DomainException in PHPDoc @throws tag but it\'s not thrown.', 33, diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php index f4bae0e0ae1..d5375431390 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-function.php @@ -17,7 +17,7 @@ function doFoo2(): void // ok } /** @throws \InvalidArgumentException */ -function doFoo3(): void // ok +function doFoo3(): void // new LogicException cannot be InvalidArgumentException { throw new \LogicException(); } @@ -64,3 +64,9 @@ function doFoo9(): void // error - DomainException unused { } + +/** @throws \InvalidArgumentException */ +function doFoo10(\LogicException $e): void // ok +{ + throw $e; +} diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php index a5c79a5ec8c..5e28b2186e8 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-method.php @@ -20,7 +20,7 @@ public function doFoo2(): void // ok } /** @throws \InvalidArgumentException */ - public function doFoo3(): void // ok + public function doFoo3(): void // // new LogicException cannot be InvalidArgumentException { throw new \LogicException(); } diff --git a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php index 6cf4b550724..6de7bc4073b 100644 --- a/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php +++ b/tests/PHPStan/Rules/Exceptions/data/too-wide-throws-property-hook.php @@ -24,7 +24,7 @@ class Foo public int $c { /** @throws \InvalidArgumentException */ get { - throw new \LogicException(); + throw new \LogicException(); // new LogicException cannot be InvalidArgumentException } } @@ -83,4 +83,13 @@ class Foo get => 11; // error - DomainException unused } + public int $l { + /** @throws \InvalidArgumentException */ + get { + throw $this->logicException; + } + } + + public \LogicException $logicException; + } diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index 07dc6314264..f20482f72f4 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -18,6 +18,7 @@ protected function getRule(): Rule return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper( self::getContainer()->getByType(TypeNodeResolver::class), self::getContainer()->getByType(FileTypeMapper::class), + $this->createReflectionProvider(), true, true, )); diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 5c54234da5d..ea87452e904 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -25,6 +25,7 @@ protected function getRule(): Rule new VarTagTypeRuleHelper( self::getContainer()->getByType(TypeNodeResolver::class), self::getContainer()->getByType(FileTypeMapper::class), + $this->createReflectionProvider(), $this->checkTypeAgainstPhpDocType, $this->strictWideningCheck, ), diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index b374da4943b..5e57ee2994a 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -330,7 +330,7 @@ public function testAppendedArrayItemType(): void 45, ], [ - 'Property AppendedArrayItem\Baz::$staticProperty (array) does not accept array.', + 'Property AppendedArrayItem\Baz::$staticProperty (array) does not accept array.', 79, ], ], From 2d7dd088d0107863cb1228d9474d967efa3ea97d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 6 Mar 2025 14:38:29 +0100 Subject: [PATCH 1146/3097] Fix unsetting array item triggers unset.possiblyHookedProperty --- src/Rules/Variables/UnsetRule.php | 118 +++++++++--------- .../PHPStan/Rules/Variables/UnsetRuleTest.php | 61 +++++++-- .../Variables/data/unset-hooked-property.php | 84 +++++++++++++ 3 files changed, 194 insertions(+), 69 deletions(-) diff --git a/src/Rules/Variables/UnsetRule.php b/src/Rules/Variables/UnsetRule.php index a376744bc2b..91e5260488d 100644 --- a/src/Rules/Variables/UnsetRule.php +++ b/src/Rules/Variables/UnsetRule.php @@ -37,6 +37,67 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($functionArguments as $argument) { + if ( + $argument instanceof Node\Expr\PropertyFetch + && $argument->name instanceof Node\Identifier + ) { + $foundPropertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($argument, $scope); + if ($foundPropertyReflection === null) { + continue; + } + + $propertyReflection = $foundPropertyReflection->getNativeReflection(); + if ($propertyReflection === null) { + continue; + } + + if ($propertyReflection->isReadOnly() || $propertyReflection->isReadOnlyByPhpDoc()) { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Cannot unset %s %s::$%s property.', + $propertyReflection->isReadOnly() ? 'readonly' : '@readonly', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($argument->getStartLine()) + ->identifier($propertyReflection->isReadOnly() ? 'unset.readOnlyProperty' : 'unset.readOnlyPropertyByPhpDoc') + ->build(); + continue; + } + + if ($propertyReflection->isHooked()) { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Cannot unset hooked %s::$%s property.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($argument->getStartLine()) + ->identifier('unset.hookedProperty') + ->build(); + continue; + } elseif ($this->phpVersion->supportsPropertyHooks()) { + if ( + !$propertyReflection->isPrivate() + && !$propertyReflection->isFinal()->yes() + && !$propertyReflection->getDeclaringClass()->isFinal() + ) { + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Cannot unset property %s::$%s because it might have hooks in a subclass.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $foundPropertyReflection->getName(), + ), + ) + ->line($argument->getStartLine()) + ->identifier('unset.possiblyHookedProperty') + ->build(); + continue; + } + } + } $error = $this->canBeUnset($argument, $scope); if ($error === null) { continue; @@ -78,63 +139,6 @@ private function canBeUnset(Node $node, Scope $scope): ?IdentifierRuleError } return $this->canBeUnset($node->var, $scope); - } elseif ( - $node instanceof Node\Expr\PropertyFetch - && $node->name instanceof Node\Identifier - ) { - $foundPropertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $scope); - if ($foundPropertyReflection === null) { - return null; - } - - $propertyReflection = $foundPropertyReflection->getNativeReflection(); - if ($propertyReflection === null) { - return null; - } - - if ($propertyReflection->isReadOnly() || $propertyReflection->isReadOnlyByPhpDoc()) { - return RuleErrorBuilder::message( - sprintf( - 'Cannot unset %s %s::$%s property.', - $propertyReflection->isReadOnly() ? 'readonly' : '@readonly', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $foundPropertyReflection->getName(), - ), - ) - ->line($node->getStartLine()) - ->identifier($propertyReflection->isReadOnly() ? 'unset.readOnlyProperty' : 'unset.readOnlyPropertyByPhpDoc') - ->build(); - } - - if ($propertyReflection->isHooked()) { - return RuleErrorBuilder::message( - sprintf( - 'Cannot unset hooked %s::$%s property.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $foundPropertyReflection->getName(), - ), - ) - ->line($node->getStartLine()) - ->identifier('unset.hookedProperty') - ->build(); - } elseif ($this->phpVersion->supportsPropertyHooks()) { - if ( - !$propertyReflection->isPrivate() - && !$propertyReflection->isFinal()->yes() - && !$propertyReflection->getDeclaringClass()->isFinal() - ) { - return RuleErrorBuilder::message( - sprintf( - 'Cannot unset property %s::$%s because it might have hooks in a subclass.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $foundPropertyReflection->getName(), - ), - ) - ->line($node->getStartLine()) - ->identifier('unset.possiblyHookedProperty') - ->build(); - } - } } return null; diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index 8f7081b6799..df34c15d502 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -61,18 +61,7 @@ public function testBug2752(): void public function testBug4289(): void { - $errors = []; - - if (PHP_VERSION_ID >= 80400) { - $errors = [ - [ - 'Cannot unset property Bug4289\BaseClass::$fields because it might have hooks in a subclass.', - 25, - ], - ]; - } - - $this->analyse([__DIR__ . '/data/bug-4289.php'], $errors); + $this->analyse([__DIR__ . '/data/bug-4289.php'], []); } public function testBug5223(): void @@ -180,6 +169,54 @@ public function testUnsetHookedProperty(): void 'Cannot unset property UnsetHookedProperty\NonFinalClass::$publicProperty because it might have hooks in a subclass.', 13, ], + [ + 'Cannot unset property UnsetHookedProperty\ContainerClass::$finalClass because it might have hooks in a subclass.', + 83, + ], + [ + 'Cannot unset property UnsetHookedProperty\ContainerClass::$nonFinalClass because it might have hooks in a subclass.', + 87, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\Foo::$iii property.', + 89, + ], + [ + 'Cannot unset property UnsetHookedProperty\ContainerClass::$foo because it might have hooks in a subclass.', + 90, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$name property.', + 92, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', + 93, + ], + [ + 'Cannot unset property UnsetHookedProperty\ContainerClass::$user because it might have hooks in a subclass.', + 94, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$name property.', + 96, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$name property.', + 97, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', + 98, + ], + [ + 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', + 99, + ], + [ + 'Cannot unset property UnsetHookedProperty\ContainerClass::$arrayOfUsers because it might have hooks in a subclass.', + 100, + ], ]); } diff --git a/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php index 109b09b5075..d98eed672a8 100644 --- a/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php +++ b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php @@ -64,3 +64,87 @@ function doFoo() { unset($this->privateProperty); } } + +class ContainerClass { + public FinalClass $finalClass; + public FinalClass $nonFinalClass; + + public Foo $foo; + + public User $user; + + /** @var array */ + public array $arrayOfUsers; +} + +function dooNestedUnset(ContainerClass $containerClass) { + unset($containerClass->finalClass->publicFinalProperty); + unset($containerClass->finalClass->publicProperty); + unset($containerClass->finalClass); + + unset($containerClass->nonFinalClass->publicFinalProperty); + unset($containerClass->nonFinalClass->publicProperty); + unset($containerClass->nonFinalClass); + + unset($containerClass->foo->iii); + unset($containerClass->foo); + + unset($containerClass->user->name); + unset($containerClass->user->fullName); + unset($containerClass->user); + + unset($containerClass->arrayOfUsers[0]->name); + unset($containerClass->arrayOfUsers[0]->name); + unset($containerClass->arrayOfUsers['hans']->fullName); + unset($containerClass->arrayOfUsers['hans']->fullName); + unset($containerClass->arrayOfUsers); +} + +class Bug12695 +{ + /** @var int[] */ + public array $values = [1]; + public function test(): void + { + unset($this->values[0]); + } +} + +abstract class Bug12695_AbstractJsonView +{ + protected array $variables = []; + + public function render(): array + { + return $this->variables; + } +} + +class Bug12695_GetSeminarDateJsonView extends Bug12695_AbstractJsonView +{ + public function render(): array + { + unset($this->variables['settings']); + return parent::render(); + } +} + +class Bug12695_AddBookingsJsonView extends Bug12695_GetSeminarDateJsonView +{ + public function render(): array + { + unset($this->variables['seminarDate']); + return parent::render(); + } +} + +class UnsetReadonly +{ + /** @var int[][] */ + public readonly array $a; + + public function doFoo(): void + { + unset($this->a[5]); + } +} From 7848b388dedb6f4a4360098ece8591f83f787608 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 16:12:46 +0100 Subject: [PATCH 1147/3097] Fix various issues with final-overriden class assumptions --- phpstan-baseline.neon | 2 +- src/Reflection/ClassReflection.php | 2 +- src/Type/ObjectType.php | 87 ++++++++++-------------------- 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d252aaef595..294480459cd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1419,7 +1419,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 3 + count: 2 path: src/Type/ObjectType.php - diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 14a1d496438..4c4e4fd5d8f 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -856,7 +856,7 @@ public function isSubclassOfClass(self $class): bool return $this->subclasses[$cacheKey]; } - if ($class->isFinal() || $class->isAnonymous()) { + if ($class->isFinalByKeyword() || $class->isAnonymous()) { return $this->subclasses[$cacheKey] = false; } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0859825701a..57b1eb38e92 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -182,7 +182,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember } else { $canAccessProperty = $scope->getClassReflection()->getName(); } - $description = $this->describeCache(); + $description = $this->describe(VerbosityLevel::cache()); if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) { return self::$properties[$description][$propertyName][$canAccessProperty]; @@ -321,14 +321,8 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createNo(); } - $thisDescription = $this->describeCache(); - - if ($type instanceof self) { - $description = $type->describeCache(); - } else { - $description = $type->describe(VerbosityLevel::cache()); - } - + $thisDescription = $this->describe(VerbosityLevel::cache()); + $description = $type->describe(VerbosityLevel::cache()); if (isset(self::$superTypes[$thisDescription][$description])) { return self::$superTypes[$thisDescription][$description]; } @@ -516,15 +510,33 @@ public function describe(VerbosityLevel $level): string $preciseNameCallback, $preciseWithSubtracted, function () use ($preciseWithSubtracted): string { + if ($this->cachedDescription !== null) { + return $this->cachedDescription; + } + + $description = $preciseWithSubtracted(); + + if ($this instanceof GenericObjectType) { + $description .= '<'; + $typeDescriptions = []; + foreach ($this->getTypes() as $type) { + $typeDescriptions[] = $type->describe(VerbosityLevel::cache()); + } + $description .= '<' . implode(', ', $typeDescriptions) . '>'; + } + $reflection = $this->classReflection; - $line = ''; if ($reflection !== null) { - $line .= '-'; - $line .= (string) $reflection->getNativeReflection()->getStartLine(); - $line .= '-'; + $description .= '-'; + $description .= (string) $reflection->getNativeReflection()->getStartLine(); + $description .= '-'; + + if ($reflection->hasFinalByKeywordOverride()) { + $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f'); + } } - return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey(); + return $this->cachedDescription = $description . $this->describeAdditionalCacheKey(); }, ); } @@ -534,47 +546,6 @@ protected function describeAdditionalCacheKey(): string return ''; } - private function describeCache(): string - { - if ($this->cachedDescription !== null) { - return $this->cachedDescription; - } - - if (static::class !== self::class) { - return $this->cachedDescription = $this->describe(VerbosityLevel::cache()); - } - - $description = $this->className; - - if ($this instanceof GenericObjectType) { - $description .= '<'; - $typeDescriptions = []; - foreach ($this->getTypes() as $type) { - $typeDescriptions[] = $type->describe(VerbosityLevel::cache()); - } - $description .= '<' . implode(', ', $typeDescriptions) . '>'; - } - - if ($this->subtractedType !== null) { - $description .= $this->subtractedType instanceof UnionType - ? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache())) - : sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); - } - - $reflection = $this->classReflection; - if ($reflection !== null) { - $description .= '-'; - $description .= (string) $reflection->getNativeReflection()->getStartLine(); - $description .= '-'; - - if ($reflection->hasFinalByKeywordOverride()) { - $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f'); - } - } - - return $this->cachedDescription = $description; - } - public function toNumber(): Type { if ($this->isInstanceOf('SimpleXMLElement')->yes()) { @@ -777,7 +748,7 @@ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAcce } else { $canCallMethod = $scope->getClassReflection()->getName(); } - $description = $this->describeCache(); + $description = $this->describe(VerbosityLevel::cache()); if (isset(self::$methods[$description][$methodName][$canCallMethod])) { return self::$methods[$description][$methodName][$canCallMethod]; } @@ -1266,7 +1237,7 @@ public function getEnumCases(): array return []; } - $cacheKey = $this->describeCache(); + $cacheKey = $this->describe(VerbosityLevel::cache()); if (array_key_exists($cacheKey, self::$enumCases)) { return self::$enumCases[$cacheKey]; } @@ -1529,7 +1500,7 @@ public function getAncestorWithClassName(string $className): ?self return $this->currentAncestors[$className]; } - $description = $this->describeCache(); + $description = $this->describe(VerbosityLevel::cache()); if ( array_key_exists($description, self::$ancestors) && array_key_exists($className, self::$ancestors[$description]) From 8d4796d28975b9c60399b228fe23decbe7ef7a1f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 16:32:31 +0100 Subject: [PATCH 1148/3097] Revert "Fix various issues with final-overriden class assumptions" This reverts commit 7848b388dedb6f4a4360098ece8591f83f787608. --- phpstan-baseline.neon | 2 +- src/Type/ObjectType.php | 87 +++++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 294480459cd..d252aaef595 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1419,7 +1419,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 2 + count: 3 path: src/Type/ObjectType.php - diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 57b1eb38e92..0859825701a 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -182,7 +182,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember } else { $canAccessProperty = $scope->getClassReflection()->getName(); } - $description = $this->describe(VerbosityLevel::cache()); + $description = $this->describeCache(); if (isset(self::$properties[$description][$propertyName][$canAccessProperty])) { return self::$properties[$description][$propertyName][$canAccessProperty]; @@ -321,8 +321,14 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult return IsSuperTypeOfResult::createNo(); } - $thisDescription = $this->describe(VerbosityLevel::cache()); - $description = $type->describe(VerbosityLevel::cache()); + $thisDescription = $this->describeCache(); + + if ($type instanceof self) { + $description = $type->describeCache(); + } else { + $description = $type->describe(VerbosityLevel::cache()); + } + if (isset(self::$superTypes[$thisDescription][$description])) { return self::$superTypes[$thisDescription][$description]; } @@ -510,33 +516,15 @@ public function describe(VerbosityLevel $level): string $preciseNameCallback, $preciseWithSubtracted, function () use ($preciseWithSubtracted): string { - if ($this->cachedDescription !== null) { - return $this->cachedDescription; - } - - $description = $preciseWithSubtracted(); - - if ($this instanceof GenericObjectType) { - $description .= '<'; - $typeDescriptions = []; - foreach ($this->getTypes() as $type) { - $typeDescriptions[] = $type->describe(VerbosityLevel::cache()); - } - $description .= '<' . implode(', ', $typeDescriptions) . '>'; - } - $reflection = $this->classReflection; + $line = ''; if ($reflection !== null) { - $description .= '-'; - $description .= (string) $reflection->getNativeReflection()->getStartLine(); - $description .= '-'; - - if ($reflection->hasFinalByKeywordOverride()) { - $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f'); - } + $line .= '-'; + $line .= (string) $reflection->getNativeReflection()->getStartLine(); + $line .= '-'; } - return $this->cachedDescription = $description . $this->describeAdditionalCacheKey(); + return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey(); }, ); } @@ -546,6 +534,47 @@ protected function describeAdditionalCacheKey(): string return ''; } + private function describeCache(): string + { + if ($this->cachedDescription !== null) { + return $this->cachedDescription; + } + + if (static::class !== self::class) { + return $this->cachedDescription = $this->describe(VerbosityLevel::cache()); + } + + $description = $this->className; + + if ($this instanceof GenericObjectType) { + $description .= '<'; + $typeDescriptions = []; + foreach ($this->getTypes() as $type) { + $typeDescriptions[] = $type->describe(VerbosityLevel::cache()); + } + $description .= '<' . implode(', ', $typeDescriptions) . '>'; + } + + if ($this->subtractedType !== null) { + $description .= $this->subtractedType instanceof UnionType + ? sprintf('~(%s)', $this->subtractedType->describe(VerbosityLevel::cache())) + : sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); + } + + $reflection = $this->classReflection; + if ($reflection !== null) { + $description .= '-'; + $description .= (string) $reflection->getNativeReflection()->getStartLine(); + $description .= '-'; + + if ($reflection->hasFinalByKeywordOverride()) { + $description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f'); + } + } + + return $this->cachedDescription = $description; + } + public function toNumber(): Type { if ($this->isInstanceOf('SimpleXMLElement')->yes()) { @@ -748,7 +777,7 @@ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAcce } else { $canCallMethod = $scope->getClassReflection()->getName(); } - $description = $this->describe(VerbosityLevel::cache()); + $description = $this->describeCache(); if (isset(self::$methods[$description][$methodName][$canCallMethod])) { return self::$methods[$description][$methodName][$canCallMethod]; } @@ -1237,7 +1266,7 @@ public function getEnumCases(): array return []; } - $cacheKey = $this->describe(VerbosityLevel::cache()); + $cacheKey = $this->describeCache(); if (array_key_exists($cacheKey, self::$enumCases)) { return self::$enumCases[$cacheKey]; } @@ -1500,7 +1529,7 @@ public function getAncestorWithClassName(string $className): ?self return $this->currentAncestors[$className]; } - $description = $this->describe(VerbosityLevel::cache()); + $description = $this->describeCache(); if ( array_key_exists($description, self::$ancestors) && array_key_exists($className, self::$ancestors[$description]) From da737711e4017bac54ce133e0c074d094daf1969 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 6 Mar 2025 17:48:28 +0100 Subject: [PATCH 1149/3097] UnusedPrivatePropertyRule - handle virtual properties that can only be read or only written --- src/Node/ClassPropertyNode.php | 10 ++++++ .../DeadCode/UnusedPrivatePropertyRule.php | 4 +-- .../UnusedPrivatePropertyRuleTest.php | 12 +++++++ .../PHPStan/Rules/DeadCode/data/bug-12702.php | 35 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-12702.php diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index aae4446638c..2dfc3cee7a9 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -170,4 +170,14 @@ public function isVirtual(): bool return $this->classReflection->getNativeProperty($this->name)->isVirtual()->yes(); } + public function isWritable(): bool + { + return $this->classReflection->getNativeProperty($this->name)->isWritable(); + } + + public function isReadable(): bool + { + return $this->classReflection->getNativeProperty($this->name)->isReadable(); + } + } diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 0564f2a9578..b32efc480df 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -59,8 +59,8 @@ public function processNode(Node $node, Scope $scope): array continue; } - $alwaysRead = false; - $alwaysWritten = false; + $alwaysRead = !$property->isReadable(); + $alwaysWritten = !$property->isWritable(); if ($property->getPhpDoc() !== null) { $text = $property->getPhpDoc(); foreach ($this->alwaysReadTags as $tag) { diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 88b2b619c2a..630036d8cd8 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -399,4 +399,16 @@ public function testBug12621(): void $this->analyse([__DIR__ . '/data/bug-12621.php'], []); } + public function testBug12702(): void + { + if (PHP_VERSION_ID < 80400) { + $this->markTestSkipped('Test requires PHP 8.4.'); + } + + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + + $this->analyse([__DIR__ . '/data/bug-12702.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-12702.php b/tests/PHPStan/Rules/DeadCode/data/bug-12702.php new file mode 100644 index 00000000000..0d650837353 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-12702.php @@ -0,0 +1,35 @@ += 8.4 + +namespace Bug12702; + +class Foo +{ + /** + * @var string[] + */ + public array $x = []; + private ?string $i { get => $this->x[$this->k] ?? null; } + private int $k = 0; + + public function x(): void { + echo $this->i; + } +} + +class Bar +{ + /** + * @var string[] + */ + public array $x = []; + private ?string $i { + set { + $this->x[$this->k] = $value; + } + } + private int $k = 0; + + public function x(): void { + $this->i = 'foo'; + } +} From 9f34449ce8ee9190119fbd5430215d3114e391f0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 7 Mar 2025 09:44:46 +0100 Subject: [PATCH 1150/3097] `@readonly` property cannot be passed by-ref --- src/Rules/FunctionCallParametersCheck.php | 21 ++++++++--- .../CallToFunctionParametersRuleTest.php | 37 +++++++++++++++++++ .../Rules/Functions/data/bug-12676.php | 37 +++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-12676.php diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index bd637913bc1..aed8352077f 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -409,16 +409,25 @@ public function check( if ($nativePropertyReflection === null) { continue; } - if (!$nativePropertyReflection->isReadOnly()) { - continue; - } - if ($nativePropertyReflection->isStatic()) { - $propertyDescription = sprintf('static readonly property %s::$%s', $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + if ($nativePropertyReflection->isReadOnly()) { + if ($nativePropertyReflection->isStatic()) { + $errorFormat = 'static readonly property %s::$%s'; + } else { + $errorFormat = 'readonly property %s::$%s'; + } + } elseif ($nativePropertyReflection->isReadOnlyByPhpDoc()) { + if ($nativePropertyReflection->isStatic()) { + $errorFormat = 'static @readonly property %s::$%s'; + } else { + $errorFormat = '@readonly property %s::$%s'; + } } else { - $propertyDescription = sprintf('readonly property %s::$%s', $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + continue; } + $propertyDescription = sprintf($errorFormat, $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( '%s is passed by reference so it does not accept %s.', $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4eb95d84492..0bf4133b795 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1982,4 +1982,41 @@ public function testBug3107(): void $this->analyse([__DIR__ . '/data/bug-3107.php'], []); } + public function testBug12676(): void + { + $errors = [ + [ + 'Parameter #1 $array is passed by reference so it does not accept @readonly property Bug12676\A::$a.', + 15, + ], + [ + 'Parameter #1 $array is passed by reference so it does not accept @readonly property Bug12676\B::$readonlyArr.', + 25, + ], + [ + 'Parameter #1 $array is passed by reference so it does not accept static @readonly property Bug12676\C::$readonlyArr.', + 35, + ], + ]; + + if (PHP_VERSION_ID < 80000) { + $errors = [ + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept @readonly property Bug12676\A::$a.', + 15, + ], + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept @readonly property Bug12676\B::$readonlyArr.', + 25, + ], + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept static @readonly property Bug12676\C::$readonlyArr.', + 35, + ], + ]; + } + + $this->analyse([__DIR__ . '/data/bug-12676.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12676.php b/tests/PHPStan/Rules/Functions/data/bug-12676.php new file mode 100644 index 00000000000..419705e89c0 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12676.php @@ -0,0 +1,37 @@ + */ + public array $a; + + public function __construct() { + $this->a = ['b' => 2, 'a' => 1]; + ksort($this->a); + } +} + +class B { + /** @readonly */ + public array $readonlyArr; + + public function __construct() { + $this->readonlyArr = ['b' => 2, 'a' => 1]; + ksort($this->readonlyArr); + } +} + +class C { + /** @readonly */ + static public array $readonlyArr; + + public function __construct() { + self::$readonlyArr = ['b' => 2, 'a' => 1]; + ksort(self::$readonlyArr); + } +} From 69369f4dcbaa969db362b8bec2eadbc2cb506062 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 7 Mar 2025 09:22:33 +0100 Subject: [PATCH 1151/3097] Reduce method calls in ExpressionTypeHolder --- src/Analyser/ExpressionTypeHolder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analyser/ExpressionTypeHolder.php b/src/Analyser/ExpressionTypeHolder.php index a3ea8273e69..f343ffe6ca3 100644 --- a/src/Analyser/ExpressionTypeHolder.php +++ b/src/Analyser/ExpressionTypeHolder.php @@ -35,15 +35,15 @@ public function equals(self $other): bool public function and(self $other): self { - if ($this->getType()->equals($other->getType())) { - $type = $this->getType(); + if ($this->type->equals($other->type)) { + $type = $this->type; } else { - $type = TypeCombinator::union($this->getType(), $other->getType()); + $type = TypeCombinator::union($this->type, $other->type); } return new self( $this->expr, $type, - $this->getCertainty()->and($other->getCertainty()), + $this->certainty->and($other->certainty), ); } From faef8e0ae918479de054540121b522101b14c424 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 7 Mar 2025 09:36:59 +0100 Subject: [PATCH 1152/3097] Faster TrinaryLogic->and() --- src/TrinaryLogic.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index a5870998446..538f81fc664 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -79,9 +79,15 @@ public function toBooleanType(): BooleanType public function and(self ...$operands): self { - $operandValues = array_column($operands, 'value'); - $operandValues[] = $this->value; - return self::create(min($operandValues)); + $min = $this->value; + foreach ($operands as $operand) { + if ($operand->value >= $min) { + continue; + } + + $min = $operand->value; + } + return self::create($min); } /** From dc576b98b5a94c9c3d15db469f0d3d62547adbf4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 7 Mar 2025 10:57:18 +0100 Subject: [PATCH 1153/3097] Faster MutatingScope->mergeWith(Scope) --- src/Analyser/ExpressionTypeHolder.php | 4 ++++ src/Analyser/MutatingScope.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Analyser/ExpressionTypeHolder.php b/src/Analyser/ExpressionTypeHolder.php index f343ffe6ca3..bb598d8fb17 100644 --- a/src/Analyser/ExpressionTypeHolder.php +++ b/src/Analyser/ExpressionTypeHolder.php @@ -36,6 +36,10 @@ public function equals(self $other): bool public function and(self $other): self { if ($this->type->equals($other->type)) { + if ($this->certainty->equals($other->certainty)) { + return $this; + } + $type = $this->type; } else { $type = TypeCombinator::union($this->type, $other->type); diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index fd0404dc46a..8369ac2430b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4773,6 +4773,11 @@ private function mergeVariableHolders(array $ourVariableTypeHolders, array $thei $intersectedVariableTypeHolders = []; foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) { if (isset($theirVariableTypeHolders[$exprString])) { + if ($variableTypeHolder === $theirVariableTypeHolders[$exprString]) { + $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder; + continue; + } + $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder->and($theirVariableTypeHolders[$exprString]); } else { $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); From 12185abf062ea03e51dae9226b35a6639bee0cd5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 7 Mar 2025 09:55:13 +0100 Subject: [PATCH 1154/3097] Better error message for only readable/only writable properties in UnusedPrivatePropertyRule --- .../DeadCode/UnusedPrivatePropertyRule.php | 33 ++++++++++++++----- .../UnusedPrivatePropertyRuleTest.php | 11 ++++++- .../PHPStan/Rules/DeadCode/data/bug-12702.php | 26 +++++++++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index b32efc480df..239e7320561 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -16,6 +16,7 @@ use function array_map; use function count; use function is_string; +use function lcfirst; use function sprintf; use function str_contains; @@ -111,6 +112,8 @@ public function processNode(Node $node, Scope $scope): array 'read' => $read, 'written' => $written, 'node' => $property, + 'onlyReadable' => $property->isReadable() && !$property->isWritable(), + 'onlyWritable' => $property->isWritable() && !$property->isReadable(), ]; } @@ -222,18 +225,32 @@ public function processNode(Node $node, Scope $scope): array ->identifier('property.unused') ->build(); } else { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName)) + if ($data['onlyReadable']) { + $errors[] = RuleErrorBuilder::message(sprintf('Readable %s is never read.', lcfirst($propertyName))) + ->line($propertyNode->getStartLine()) + ->identifier('property.neverRead') + ->build(); + } else { + $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName)) + ->line($propertyNode->getStartLine()) + ->identifier('property.onlyWritten') + ->tip($tip) + ->build(); + } + } + } elseif (!$data['written'] && (!array_key_exists($name, $uninitializedProperties) || !$this->checkUninitializedProperties)) { + if ($data['onlyWritable']) { + $errors[] = RuleErrorBuilder::message(sprintf('Writable %s is never written.', lcfirst($propertyName))) ->line($propertyNode->getStartLine()) - ->identifier('property.onlyWritten') + ->identifier('property.neverWritten') + ->build(); + } else { + $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName)) + ->line($propertyNode->getStartLine()) + ->identifier('property.onlyRead') ->tip($tip) ->build(); } - } elseif (!$data['written'] && (!array_key_exists($name, $uninitializedProperties) || !$this->checkUninitializedProperties)) { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName)) - ->line($propertyNode->getStartLine()) - ->identifier('property.onlyRead') - ->tip($tip) - ->build(); } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 630036d8cd8..c83a84d4257 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -408,7 +408,16 @@ public function testBug12702(): void $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; - $this->analyse([__DIR__ . '/data/bug-12702.php'], []); + $this->analyse([__DIR__ . '/data/bug-12702.php'], [ + [ + 'Readable property Bug12702\Foo2::$i is never read.', + 43, + ], + [ + 'Writable property Bug12702\Bar2::$i is never written.', + 54, + ], + ]); } } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-12702.php b/tests/PHPStan/Rules/DeadCode/data/bug-12702.php index 0d650837353..1b5896c788e 100644 --- a/tests/PHPStan/Rules/DeadCode/data/bug-12702.php +++ b/tests/PHPStan/Rules/DeadCode/data/bug-12702.php @@ -33,3 +33,29 @@ public function x(): void { $this->i = 'foo'; } } + +class Foo2 +{ + /** + * @var string[] + */ + public array $x = []; + private ?string $i { get => $this->x[$this->k] ?? null; } + private int $k = 0; + +} + +class Bar2 +{ + /** + * @var string[] + */ + public array $x = []; + private ?string $i { + set { + $this->x[$this->k] = $value; + } + } + private int $k = 0; + +} From 5920c9861a61851dd81e10495d825910ed5b7960 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 8 Mar 2025 08:59:25 +0100 Subject: [PATCH 1155/3097] Correctly infer template type from various callables --- src/Type/CallableType.php | 5 +- src/Type/ClosureType.php | 5 +- tests/PHPStan/Analyser/nsrt/bug-12691.php | 74 +++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12691.php diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index f182372fc08..855b0155299 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -18,6 +18,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\DummyParameter; use PHPStan\ShouldNotHappenException; @@ -405,10 +406,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap { - $typeMap = TemplateTypeMap::createEmpty(); + $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters()); + $parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false); $args = $parametersAcceptor->getParameters(); $returnType = $parametersAcceptor->getReturnType(); + $typeMap = TemplateTypeMap::createEmpty(); foreach ($this->getParameters() as $i => $param) { $paramType = $param->getType(); if (isset($args[$i])) { diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index d12dda936e1..d3809e95f5c 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -23,6 +23,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\ClosureCallUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Php\DummyParameter; @@ -524,10 +525,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap { - $typeMap = TemplateTypeMap::createEmpty(); + $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters()); + $parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false); $args = $parametersAcceptor->getParameters(); $returnType = $parametersAcceptor->getReturnType(); + $typeMap = TemplateTypeMap::createEmpty(); foreach ($this->getParameters() as $i => $param) { $paramType = $param->getType(); if (isset($args[$i])) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12691.php b/tests/PHPStan/Analyser/nsrt/bug-12691.php new file mode 100644 index 00000000000..44246463faf --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12691.php @@ -0,0 +1,74 @@ + + */ + public function map($callable): self + { + return new self($callable($this->value)); + } + + /** + * @template S + * @param Closure(T): S $callable + * @return self + */ + public function mapClosure($callable): self + { + return new self($callable($this->value)); + } +} + +/** + * @param Option> $ints + */ +function doFoo(Option $ints): void { + assertType('Bug12691\\Option>', $ints->map(array_values(...))); + assertType('Bug12691\\Option>', $ints->map('array_values')); + assertType('Bug12691\\Option>', $ints->map(static fn ($value) => array_values($value))); +}; + +/** + * @param Option> $ints + */ +function doFooClosure(Option $ints): void { + assertType('Bug12691\\Option>', $ints->mapClosure(array_values(...))); + assertType('Bug12691\\Option>', $ints->mapClosure(static fn ($value) => array_values($value))); +}; + +/** + * @template T + * @param array $a + * @return ($a is non-empty-array ? non-empty-list : list) + */ +function myArrayValues(array $a): array { + +} + +/** + * @param Option> $ints + */ +function doBar(Option $ints): void { + assertType('Bug12691\\Option>', $ints->mapClosure(myArrayValues(...))); + assertType('Bug12691\\Option>', $ints->mapClosure(static fn ($value) => myArrayValues($value))); +}; From 0e10531bb0a62d93ee54ae9ccf74079cc0997e88 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 8 Mar 2025 19:33:07 +0100 Subject: [PATCH 1156/3097] Fix accepting generic callable in CallableType and ClosureType --- src/Type/CallableType.php | 6 ++++++ src/Type/ClosureType.php | 7 ++++++- tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 855b0155299..e9d101faa7e 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -183,8 +183,14 @@ private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSup return $isCallable; } + $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters()); + $variantsResult = null; foreach ($type->getCallableParametersAcceptors($scope) as $variant) { + $variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$variant], false); + if (!$variant instanceof CallableParametersAcceptor) { + return IsSuperTypeOfResult::createNo([]); + } $isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny); if ($variantsResult === null) { $variantsResult = $isSuperType; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index d3809e95f5c..73f784325f3 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -217,9 +217,14 @@ public function isSuperTypeOfWithReason(Type $type): IsSuperTypeOfResult private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult { if ($type instanceof self) { + $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters()); + $variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$type], false); + if (!$variant instanceof CallableParametersAcceptor) { + return IsSuperTypeOfResult::createNo([]); + } return CallableTypeHelper::isParametersAcceptorSuperTypeOf( $this, - $type, + $variant, $treatMixedAsAny, ); } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 5c417800f1e..cd14b78cece 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3424,4 +3424,13 @@ public function testBug4801(): void $this->analyse([__DIR__ . '/data/bug-4801.php'], []); } + public function testBug12691(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12691.php'], []); + } + } From cc6c20024cb69a6bf2689c19112ecf2603b0c9b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 08:09:19 +0100 Subject: [PATCH 1157/3097] Regression tests Closes https://github.com/phpstan/phpstan/issues/11942 Closes https://github.com/phpstan/phpstan/issues/11861 Closes https://github.com/phpstan/phpstan/issues/6828 Closes https://github.com/phpstan/phpstan/issues/9167 --- tests/PHPStan/Analyser/nsrt/bug-11861.php | 59 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 18 ++++++ .../Rules/Functions/data/bug-11942.php | 23 ++++++++ .../PHPStan/Rules/Functions/data/bug-9167.php | 32 ++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 9 +++ tests/PHPStan/Rules/Methods/data/bug-6828.php | 51 ++++++++++++++++ 6 files changed, 192 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11861.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-11942.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-9167.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-6828.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11861.php b/tests/PHPStan/Analyser/nsrt/bug-11861.php new file mode 100644 index 00000000000..a3e4addc62d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11861.php @@ -0,0 +1,59 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug11861; + +use function PHPStan\Testing\assertType; + +/** + * @template T + * @template K of array-key + * @template R + * + * @param array $source + * @param callable(T, K): R $mappingFunction + * @return array + */ +function mapArray(array $source, callable $mappingFunction): array +{ + $result = []; + + foreach ($source as $key => $value) { + $result[$key] = $mappingFunction($value, $key); + } + + return $result; +} + +/** + * @template K + * @template T + * + * @param array $source + * @return array + */ +function filterArrayNotNull(array $source): array +{ + return array_filter( + $source, + fn($item) => $item !== null, + ARRAY_FILTER_USE_BOTH + ); +} + +/** @var list> $a */ +$a = []; + +$mappedA = mapArray( + $a, + static fn(array $entry) => filterArrayNotNull($entry) +); + +$mappedAWithFirstClassSyntax = mapArray( + $a, + filterArrayNotNull(...) +); + +assertType('array, array>', $mappedA); +assertType('array, array>', $mappedAWithFirstClassSyntax); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a3ffa071b79..7ce9a166c76 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1921,4 +1921,22 @@ public function testBug12051(): void $this->analyse([__DIR__ . '/data/bug-12051.php'], []); } + public function testBug11942(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-11942.php'], []); + } + + public function testBug9167(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-9167.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-11942.php b/tests/PHPStan/Rules/Functions/data/bug-11942.php new file mode 100644 index 00000000000..33bd0ff1abf --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-11942.php @@ -0,0 +1,23 @@ +analyse([__DIR__ . '/../../Analyser/nsrt/bug-12691.php'], []); } + public function testBug6828(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-6828.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-6828.php b/tests/PHPStan/Rules/Methods/data/bug-6828.php new file mode 100644 index 00000000000..738766d8a7d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-6828.php @@ -0,0 +1,51 @@ += 8.1 + +declare(strict_types=1); + +namespace Bug6828; + +/** @template T */ +interface Option +{ + /** + * @template U + * @param \Closure(T):U $c + * @return Option + */ + function map(\Closure $c); +} + +/** + * @template T + * @template E + */ +abstract class Result +{ + /** @return T */ + function unwrap() + { + + } + + /** + * @template U + * @param U $v + * @return Result + */ + static function ok($v) + { + + } +} + +/** + * @template U + * @template F + * @param Result, F> $result + * @return Option> + */ +function f(Result $result): Option +{ + /** @var Option> */ + return $result->unwrap()->map(Result::ok(...)); +} From 843be53eef1aee1622a22c2b06e062763d047f92 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Mar 2025 08:29:38 +0100 Subject: [PATCH 1158/3097] Faster analysis with a big const array in a class --- src/Type/TypeCombinator.php | 4 + .../Analyser/AnalyserIntegrationTest.php | 10 + tests/PHPStan/Analyser/data/bug-12159.php | 2681 +++++++++++++++++ 3 files changed, 2695 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12159.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 42a1a8ea38b..bc51e1d102a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -897,6 +897,10 @@ private static function optimizeConstantArrays(array $types): array $keyType = self::union(...$keyTypes); $valueType = self::union(...$valueTypes); + if ($valueType instanceof UnionType && count($valueType->getTypes()) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + $valueType = $valueType->generalize(GeneralizePrecision::lessSpecific()); + } + $arrayType = new ArrayType($keyType, $valueType); if ($eachIsList) { $arrayType = self::intersect($arrayType, new AccessoryArrayListType()); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 34f65e70354..6c3f1fd567e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1510,6 +1510,16 @@ public function testBug12627(): void $this->assertNoErrors($errors); } + public function testBug12159(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12159.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12159.php b/tests/PHPStan/Analyser/data/bug-12159.php new file mode 100644 index 00000000000..d39ae6bd946 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12159.php @@ -0,0 +1,2681 @@ + TestMatrix::Values[ $Point ][ 0 ]; +$foo = + [ + [ 'val' => $func( 1237123 ) ], + [ 'val' => $func( 4379284 ) ], + [ 'val' => $func( 4534895 ) ], + [ 'val' => $func( 9483754 ) ], + [ 'val' => $func( 8127361 ) ], + [ 'val' => $func( 1287129 ) ], + [ 'val' => $func( 7244590 ) ], + ]; + +//for( $i = 0; $i < 100; $i++ ) +//{ +if( $_GET['a'] < $foo[ 1 ][ 'val' ] ) echo '1'; +if( $_GET['a'] < $foo[ 2 ][ 'val' ] ) echo '2'; +if( $_GET['a'] < $foo[ 3 ][ 'val' ] ) echo '3'; +if( $_GET['a'] < $foo[ 4 ][ 'val' ] ) echo '4'; +if( $_GET['a'] < $foo[ 5 ][ 'val' ] ) echo '5'; +if( $_GET['a'] < $foo[ 6 ][ 'val' ] ) echo '6'; +//} + +class TestMatrix +{ + public const array Values = array ( + 0 => + array ( + 0 => 5874481165396689108, + 1 => 8662405580715299972, + 2 => 1838729323137802481, + 3 => 1296254215171686394, + 4 => 240787718805128243, + 5 => 2569399932576470543, + 6 => 2666865512562476674, + 7 => 7440800791997798335, + 8 => 9017504029234684124, + 9 => 1943700815897404988, + 10 => 4807266916823040232, + 11 => 5651791337534958850, + 12 => 7002607381155865437, + 13 => 4533265986128849713, + 14 => 5376300349620761130, + 15 => 7905874742842521971, + 16 => 909744888026452130, + 17 => 7282766239447087930, + 18 => 1346776530840371545, + 19 => 5241686368013809035, + 20 => 4960581668501873164, + 21 => 4216999787816457923, + 22 => 7206618997006790711, + 23 => 1737316659480734612, + 24 => 1396564421397776612, + 25 => 1225052620751257798, + 26 => 5524782971343881599, + 27 => 2259152306650062736, + 28 => 3668358132158286281, + 29 => 6329278711234406504, + 30 => 1398072019509396341, + 31 => 8955588514252493507, + 32 => 1767105836397175082, + 33 => 7034230021779190326, + 34 => 6905169987039336897, + 35 => 389472364053965244, + 36 => 2784078665352126084, + 37 => 2778618770698283740, + 38 => 1378766762279037262, + 39 => 3618227099446145118, + 40 => 1276484677607692690, + 41 => 3099202919675399195, + 42 => 8794553594722463315, + 43 => 9220965608516075037, + 44 => 464961969218490186, + 45 => 8431789941543532605, + 46 => 2220000829371936407, + 47 => 673824151036998803, + 48 => 5433256145723805103, + 49 => 3825003899632634051, + ), + 1 => + array ( + 0 => 8154052500937462248, + 1 => 5576807385765339137, + 2 => 1100518481621993286, + 3 => 3205600232505774719, + 4 => 239730811793443707, + 5 => 4054412049366275439, + 6 => 941723216813015420, + 7 => 6470087431894049191, + 8 => 2716337345328343897, + 9 => 953683961010207742, + 10 => 2738362684680615246, + 11 => 3535184723439979851, + 12 => 4105453969139039388, + 13 => 1769008182819306978, + 14 => 7161610946609102827, + 15 => 7459169462458964034, + 16 => 7200589149413721938, + 17 => 1842332918081972441, + 18 => 7021770893406632400, + 19 => 8342890679809897170, + 20 => 3229267769836612196, + 21 => 8621895098320821041, + 22 => 8192020378402459973, + 23 => 646879134901493937, + 24 => 7644597118411382877, + 25 => 2669227552611432887, + 26 => 4264746695750690265, + 27 => 830616027307888589, + 28 => 6989343698662199702, + 29 => 220940944081390977, + 30 => 2991501672354298249, + 31 => 8565280316848910077, + 32 => 7453367854425505710, + 33 => 8407476888139000249, + 34 => 9141118524169411532, + 35 => 3417565007599042997, + 36 => 7929540029455947300, + 37 => 6341525159423457135, + 38 => 1136401477976290415, + 39 => 815721375348001867, + 40 => 9122672261212021062, + 41 => 3038993244577792661, + 42 => 8902537870044219933, + 43 => 6742257712143705646, + 44 => 305160917138533628, + 45 => 1944434793172827222, + 46 => 5335522480652404622, + 47 => 6226560700086665665, + 48 => 8307974418320309240, + 49 => 6806303928471061067, + ), + 2 => + array ( + 0 => 4975290592490926991, + 1 => 6131701571914965130, + 2 => 9127520861288523557, + 3 => 1405655716825922037, + 4 => 2953339211295438483, + 5 => 7640526281049794652, + 6 => 1453014071818130187, + 7 => 2738989913949892618, + 8 => 6000021482380697144, + 9 => 1037973965313154250, + 10 => 528984535358733067, + 11 => 3417642461931931383, + 12 => 8343011794702923220, + 13 => 7507168997195646060, + 14 => 1831280245495928841, + 15 => 6774996787168120603, + 16 => 99498811159382374, + 17 => 336866722933543741, + 18 => 8971742337733516403, + 19 => 3959481408560435649, + 20 => 4194447658835901918, + 21 => 4698189036403840281, + 22 => 1868877229552777436, + 23 => 3782558119442101296, + 24 => 8612829831567636140, + 25 => 2999918364775393717, + 26 => 4457456312209359735, + 27 => 1911400307152511590, + 28 => 5342632524118518101, + 29 => 7582753306401387624, + 30 => 2891552232599249434, + 31 => 6722331618838222538, + 32 => 1863871167267174088, + 33 => 4721864064741949167, + 34 => 6921608495105963351, + 35 => 2787258830853121593, + 36 => 6318006494535492932, + 37 => 8758213181123797132, + 38 => 2817595964341381484, + 39 => 2189508344516984329, + 40 => 8595851620765258356, + 41 => 4675797867402162161, + 42 => 8664216558549206169, + 43 => 8392353657675228864, + 44 => 3523827866624970939, + 45 => 3125081307911204903, + 46 => 7613092314757778536, + 47 => 5262826170155900761, + 48 => 5156363701596412744, + 49 => 4334292640529862435, + ), + 3 => + array ( + 0 => 6680271612749645767, + 1 => 1038897265710563469, + 2 => 3125268357134460497, + 3 => 3448035616856209350, + 4 => 2290547007394087177, + 5 => 3202782379344553998, + 6 => 8856642337182845360, + 7 => 7006619529055284271, + 8 => 7469279615622781778, + 9 => 3271987266513004287, + 10 => 5561282669998343625, + 11 => 2124822921183299442, + 12 => 5756164387055634612, + 13 => 5552937428984643025, + 14 => 7064113750641600855, + 15 => 5328246101339893619, + 16 => 7333438201129908387, + 17 => 3828772120818252593, + 18 => 8174834386774866076, + 19 => 7786829975211333555, + 20 => 3981203765539870334, + 21 => 7797235689763652673, + 22 => 4165615128733961575, + 23 => 5981144219284475327, + 24 => 3418001781831286846, + 25 => 1492200888573448114, + 26 => 2317318866594527246, + 27 => 2688445214897280589, + 28 => 8929138296967524205, + 29 => 2942491267302746123, + 30 => 4529371813136470715, + 31 => 8181894960585438448, + 32 => 4403301414553068732, + 33 => 3650365933794415107, + 34 => 1802263228403420039, + 35 => 2837949245046582415, + 36 => 8103859399717457751, + 37 => 6523233038597037591, + 38 => 2417678247431759747, + 39 => 8539067974167032946, + 40 => 7239446630166406222, + 41 => 953227842772238130, + 42 => 2061891981074579091, + 43 => 9197132456777724388, + 44 => 4195535321569363259, + 45 => 7802646953768156569, + 46 => 1214202025857093854, + 47 => 2732892716731283275, + 48 => 6422702740355331603, + 49 => 314586223118274101, + ), + 4 => + array ( + 0 => 8932746737511960046, + 1 => 4420639939134831872, + 2 => 4015851428934836080, + 3 => 226942641444362166, + 4 => 7379063053453580291, + 5 => 5408297350760256023, + 6 => 7097728592049579553, + 7 => 2088253461945304456, + 8 => 2832527342827628633, + 9 => 4095360511140466509, + 10 => 8915545429197506654, + 11 => 7454633280949469211, + 12 => 426687349009436650, + 13 => 7558889905023459316, + 14 => 8409879617073507015, + 15 => 3709130785449676075, + 16 => 3916481028234348945, + 17 => 2080313258004748980, + 18 => 8454584147558376449, + 19 => 955650473035618219, + 20 => 8403398466426708496, + 21 => 7925840390455252607, + 22 => 6538000854800071609, + 23 => 5234246074356462331, + 24 => 1419480003652519257, + 25 => 4717025934655073480, + 26 => 7133440962553291054, + 27 => 1216670874596372868, + 28 => 3415520219011084806, + 29 => 6371251457962253684, + 30 => 7343082864680649875, + 31 => 1922360266759830594, + 32 => 2974660376862656509, + 33 => 858418194500090476, + 34 => 6356554697026476948, + 35 => 5114619950190199429, + 36 => 1904140895976090164, + 37 => 3593944879807201662, + 38 => 5719530069829191694, + 39 => 3031668473907288497, + 40 => 3448169104312841979, + 41 => 8122204554627926094, + 42 => 8518863644712353970, + 43 => 8169769218626969757, + 44 => 1659634164765638384, + 45 => 3477793331064103721, + 46 => 5434872090056290754, + 47 => 4276460341063621887, + 48 => 4640639099260040225, + 49 => 9009468365945428002, + ), + 5 => + array ( + 0 => 4931694814167754074, + 1 => 7183568584903649742, + 2 => 8275777720925639086, + 3 => 8419817439480667175, + 4 => 8604323712237915569, + 5 => 3352541925538437922, + 6 => 4199420257337885962, + 7 => 1106468391590120959, + 8 => 8507355052862836844, + 9 => 4331895772301917877, + 10 => 7920254998068325755, + 11 => 8996973071477628119, + 12 => 455008091719671736, + 13 => 3815293412644646732, + 14 => 436955111200922011, + 15 => 7013986275832485803, + 16 => 5688297970433003962, + 17 => 2011158629907362985, + 18 => 7951175882360459923, + 19 => 5742765642824123605, + 20 => 1216836110798583420, + 21 => 8679387052777060181, + 22 => 1985688926071711354, + 23 => 1831808276654186998, + 24 => 102085979594198107, + 25 => 2340187189218681369, + 26 => 1925730779370056452, + 27 => 4041628961826241780, + 28 => 4907429270936661782, + 29 => 999802994114419710, + 30 => 8230876938618101144, + 31 => 7777792290797940668, + 32 => 8058836789085030797, + 33 => 9145042694186638609, + 34 => 1490700942820470088, + 35 => 8080486113090366780, + 36 => 9012927814276117762, + 37 => 4817168063146379030, + 38 => 5887513675240220051, + 39 => 2170648251352279216, + 40 => 8660441599773259447, + 41 => 2566734480158371883, + 42 => 7877381935713445782, + 43 => 3424535784008708938, + 44 => 6138477295423731789, + 45 => 531408866931128281, + 46 => 8118255972970448317, + 47 => 6658517893844506220, + 48 => 5192765725571041390, + 49 => 4484573374403032898, + ), + 6 => + array ( + 0 => 7817725724153064283, + 1 => 676044540944783015, + 2 => 165795045891931505, + 3 => 8628574277625575660, + 4 => 4623749438938771988, + 5 => 7377760708842913949, + 6 => 4835799984105487685, + 7 => 8736269977412248326, + 8 => 5713305870673689087, + 9 => 1512747349895463886, + 10 => 1297398582506845786, + 11 => 4536497787871480498, + 12 => 7859883546974534383, + 13 => 7451785769304448658, + 14 => 1566047154522047144, + 15 => 2681185467507861604, + 16 => 3282533773867812887, + 17 => 7710783454818853777, + 18 => 757388808200637902, + 19 => 1267453957031758382, + 20 => 3067184561283064874, + 21 => 1075927338235603309, + 22 => 5040384382667600121, + 23 => 4470519589820835295, + 24 => 6446347166686380336, + 25 => 5133211229242848498, + 26 => 4799307086528142991, + 27 => 2417256161533702584, + 28 => 5004748314990362737, + 29 => 5654624457458575102, + 30 => 7831168243158770416, + 31 => 2438361643495966584, + 32 => 3331080805559396049, + 33 => 2332998025596953248, + 34 => 4955322642679292607, + 35 => 2823206402722454329, + 36 => 7864363481035388949, + 37 => 3972282083392565017, + 38 => 7397491981841336255, + 39 => 2077760290151467781, + 40 => 7444037508733373992, + 41 => 5693183530497128762, + 42 => 8635051873130500468, + 43 => 2725415837048413228, + 44 => 8350394208673414293, + 45 => 6573719025342292543, + 46 => 7882248774735967651, + 47 => 3621593747653440124, + 48 => 1381288049786560954, + 49 => 4271094963880511320, + ), + 7 => + array ( + 0 => 7374574356170927085, + 1 => 7717377238321849930, + 2 => 1617648016491363337, + 3 => 3920182728377977038, + 4 => 4864055338550692898, + 5 => 3852374904000741108, + 6 => 8014130603489499273, + 7 => 1266780406787288918, + 8 => 5745877767288766328, + 9 => 3755007514011162686, + 10 => 4988518671877958110, + 11 => 2765009033270152436, + 12 => 2933921152637177103, + 13 => 6289527477470818356, + 14 => 1566901334856634296, + 15 => 3847215702911572949, + 16 => 2464205579185886331, + 17 => 567922664555183884, + 18 => 8419671061374781092, + 19 => 5347381431422400203, + 20 => 2474093747941658240, + 21 => 947490060863904786, + 22 => 7728796299957089994, + 23 => 1958274075678411402, + 24 => 5787113707153405868, + 25 => 4770823972103532340, + 26 => 2782424094528669314, + 27 => 3927604835193670320, + 28 => 5880856123044238820, + 29 => 6247063793366641001, + 30 => 1003445960799983811, + 31 => 189188513499196933, + 32 => 6931085745898288806, + 33 => 5494959985724584020, + 34 => 6299501471987452338, + 35 => 6409426315745727087, + 36 => 6827715490929856161, + 37 => 144065419718686829, + 38 => 3427330871133407325, + 39 => 6708849578158375260, + 40 => 7821502350946541873, + 41 => 2579683792204579398, + 42 => 2174599388328183916, + 43 => 4939750476289377673, + 44 => 6195818835433786206, + 45 => 7844070692861182367, + 46 => 8223755928113469032, + 47 => 4630348997781506319, + 48 => 6784991557794232291, + 49 => 2456684460705773063, + ), + 8 => + array ( + 0 => 4165061665861516758, + 1 => 9208696398120276634, + 2 => 617283688467018612, + 3 => 8866309294196812992, + 4 => 8468066831561872950, + 5 => 5496080959195095216, + 6 => 3043457951940840139, + 7 => 107430864991081073, + 8 => 4854421891895873824, + 9 => 312505545755152719, + 10 => 5466206170978851220, + 11 => 7331656214488538183, + 12 => 8861441230750933712, + 13 => 1440020651481286548, + 14 => 8744438879784230686, + 15 => 7373332827225771759, + 16 => 858317805219293532, + 17 => 4035142104918609164, + 18 => 7415794421717864075, + 19 => 524830805408747363, + 20 => 9104056005409942822, + 21 => 5515188152570953140, + 22 => 7936119942383904460, + 23 => 9196672433903288853, + 24 => 4323078042756619284, + 25 => 8709662277893494773, + 26 => 7774114341997140065, + 27 => 326561711760595822, + 28 => 5659596638817237154, + 29 => 1800665601458267317, + 30 => 4226834709095391595, + 31 => 1934477928162442275, + 32 => 7861332517555509512, + 33 => 7305864724756284932, + 34 => 2107144327061685536, + 35 => 808722588857488630, + 36 => 2539437580044035869, + 37 => 3832604034556654476, + 38 => 4736821570536711823, + 39 => 8426922577642729511, + 40 => 7833992549454389473, + 41 => 1997039369012839251, + 42 => 5639683077508943280, + 43 => 8229475878766589103, + 44 => 2485173465855721469, + 45 => 974771202823843715, + 46 => 7104091963794213783, + 47 => 5736613206714171302, + 48 => 1452529717609521722, + 49 => 2512573977897891429, + ), + 9 => + array ( + 0 => 2723583737373680948, + 1 => 1942679677192434943, + 2 => 4992464429137244820, + 3 => 2108955603623244091, + 4 => 6715661544124588869, + 5 => 6784211158418344847, + 6 => 2174143361816980918, + 7 => 3159957296428237653, + 8 => 4642033571093997804, + 9 => 4516721609521486085, + 10 => 513419668552982043, + 11 => 8225856962710238974, + 12 => 76645791112297512, + 13 => 3838900370577780978, + 14 => 4377406039801675939, + 15 => 4248126498854180353, + 16 => 8514256144540280083, + 17 => 6624238216265012006, + 18 => 5512630561682499018, + 19 => 3151801592612715911, + 20 => 5682544206404299992, + 21 => 625026099893613569, + 22 => 5598008756980903917, + 23 => 4096496250937305812, + 24 => 542097768283614600, + 25 => 4214286372500783945, + 26 => 1065561831812197596, + 27 => 28230230818721266, + 28 => 7776756249499921877, + 29 => 7812792067516739818, + 30 => 1215883148035906041, + 31 => 2293132077185823077, + 32 => 2759052538028995446, + 33 => 5016491194647680439, + 34 => 6818634536467227486, + 35 => 4768244996115591062, + 36 => 7628778154079405816, + 37 => 1512766685186132967, + 38 => 1002281579513027848, + 39 => 4585799281945843823, + 40 => 7731707092844578819, + 41 => 4828769242619016876, + 42 => 2316143876529283991, + 43 => 7528436633751214560, + 44 => 1924628512711298773, + 45 => 6926054778707896318, + 46 => 3389519864922952866, + 47 => 3128371853095208573, + 48 => 50187235618483355, + 49 => 6349194033693131776, + ), + 10 => + array ( + 0 => 6322682526646271316, + 1 => 3095227202547366285, + 2 => 7395278893937465675, + 3 => 5200574266009884104, + 4 => 6279574000505636735, + 5 => 3352978839878696682, + 6 => 9191320712818604654, + 7 => 2262271016363943052, + 8 => 3214808318418256558, + 9 => 7853553360971957989, + 10 => 6297850452490597028, + 11 => 6224291870945590443, + 12 => 907950940123667978, + 13 => 8059430599577153641, + 14 => 3965322572900601193, + 15 => 8152944950051729202, + 16 => 5468985755335978628, + 17 => 6253800414625619123, + 18 => 4796881012575806886, + 19 => 809498396850796356, + 20 => 8761295074351369989, + 21 => 8211306778988175688, + 22 => 6425079682030866983, + 23 => 2208897775637467049, + 24 => 4037060769503045276, + 25 => 5982687341576200957, + 26 => 7321281395460426978, + 27 => 6813789423889591740, + 28 => 8652271626734070437, + 29 => 7655412007544743994, + 30 => 6318582516903548321, + 31 => 8120943312182510842, + 32 => 898459381905385629, + 33 => 1006272515095367404, + 34 => 5853432631494184641, + 35 => 2488930447849334827, + 36 => 5627991830205858315, + 37 => 8435986012941786135, + 38 => 500021810061656317, + 39 => 6086585656093606353, + 40 => 7799777209506835195, + 41 => 2564479240266255407, + 42 => 5830890894601088186, + 43 => 875317478781921464, + 44 => 4890435028615059637, + 45 => 6066524404263227777, + 46 => 8796437456649755382, + 47 => 671650050322048833, + 48 => 2996153661244103038, + 49 => 6141392984453555407, + ), + 11 => + array ( + 0 => 2113829968014464580, + 1 => 3604420855252515310, + 2 => 5566530360687933014, + 3 => 2638942722197379719, + 4 => 6197686530435577362, + 5 => 8804367326165731912, + 6 => 1374734978881384983, + 7 => 4121531290119521118, + 8 => 7025324650905800704, + 9 => 8632620634376756999, + 10 => 3493769733810379690, + 11 => 1446564299587766735, + 12 => 1548774894197112857, + 13 => 8755145460632063828, + 14 => 1599414219607213507, + 15 => 8326310746484899674, + 16 => 1438171968793473616, + 17 => 5739936335339518886, + 18 => 1230631109087403411, + 19 => 6085929453678720567, + 20 => 5517475317864770480, + 21 => 7544841164146387441, + 22 => 4413366135606076191, + 23 => 474656466728891395, + 24 => 1777603850640216995, + 25 => 3913561919378601733, + 26 => 5990372623719211725, + 27 => 8127855600690678186, + 28 => 7991862497915474195, + 29 => 4883200076616379029, + 30 => 5001010733372830540, + 31 => 6545802952205727101, + 32 => 8579592114269580287, + 33 => 1719858225414994089, + 34 => 2914370630968622228, + 35 => 6487456062856131622, + 36 => 1457230405126956623, + 37 => 5450438075766678977, + 38 => 4316797174379978326, + 39 => 356289589153760201, + 40 => 6152162952764411308, + 41 => 2095918233946250545, + 42 => 6846022177534448180, + 43 => 3138034230639707092, + 44 => 9076383662453007017, + 45 => 7766302103119169599, + 46 => 7318895974015143966, + 47 => 7844345536610967416, + 48 => 303771157892538553, + 49 => 1830013023076642241, + ), + 12 => + array ( + 0 => 8296851030827013358, + 1 => 3112251186986342163, + 2 => 1670409722450829600, + 3 => 7761113342030329019, + 4 => 8460561445500753222, + 5 => 4908257398338387298, + 6 => 1778895275579039127, + 7 => 3380500509985904841, + 8 => 5879289665279498918, + 9 => 1553159928549418822, + 10 => 311430609625452179, + 11 => 394936916444712045, + 12 => 5127641876166108248, + 13 => 6568988002955423611, + 14 => 8650085268993266854, + 15 => 5903427408450114483, + 16 => 2263226697604701659, + 17 => 8727279632415987896, + 18 => 3842911696821754254, + 19 => 5490803589488953024, + 20 => 7936352037551275551, + 21 => 1802719271321128297, + 22 => 7959093330975432496, + 23 => 1557009731146154818, + 24 => 1473872816908980020, + 25 => 1418764498156927753, + 26 => 2301176459661145867, + 27 => 2286352418548464686, + 28 => 1194621763940317472, + 29 => 6606061027604696484, + 30 => 8084518688858422568, + 31 => 2208900834543651741, + 32 => 5755194898079572507, + 33 => 7320839167101439960, + 34 => 9029972412529258306, + 35 => 5889791403139418397, + 36 => 6344044519932199509, + 37 => 5662962995408376380, + 38 => 1793535773221710787, + 39 => 6776030508990122856, + 40 => 7477111423046883661, + 41 => 3028777341102868090, + 42 => 4057757640110568728, + 43 => 5986048017637921779, + 44 => 9125552214661206232, + 45 => 7852264129484078269, + 46 => 4446147301234138628, + 47 => 5507063673112794235, + 48 => 6332855026822695011, + 49 => 4020513967214987505, + ), + 13 => + array ( + 0 => 2837617673724062427, + 1 => 7125850334112735147, + 2 => 6063426842568747128, + 3 => 1449956004993771688, + 4 => 8233038711924343980, + 5 => 1624050510578207334, + 6 => 201045653760070683, + 7 => 6425618561397260897, + 8 => 1736775056718544457, + 9 => 4283281796416168155, + 10 => 8943407918198470419, + 11 => 5174416738774162884, + 12 => 8282242448652142434, + 13 => 3483110946752360937, + 14 => 9172098532505635523, + 15 => 4919860276458045393, + 16 => 1508811892472366358, + 17 => 2543702316937780378, + 18 => 5391494775097463950, + 19 => 1646737894557870150, + 20 => 3840251377981664631, + 21 => 5557055980319270631, + 22 => 614458087357624962, + 23 => 3049172204044066528, + 24 => 4147916760406968728, + 25 => 8609446583426508961, + 26 => 2242391100589192563, + 27 => 5436112318641652346, + 28 => 4618310365458346019, + 29 => 2077318216555261390, + 30 => 3059989963664577310, + 31 => 7848793921431254972, + 32 => 1203430412948043756, + 33 => 2729600696821765392, + 34 => 791147694547888137, + 35 => 3707975566214340037, + 36 => 8601861547198440141, + 37 => 8535418355338386385, + 38 => 7608939352612737337, + 39 => 329873792714069411, + 40 => 2476061428301616271, + 41 => 8636330979861967347, + 42 => 4895768550130850937, + 43 => 4385109140267411446, + 44 => 8630975950112663906, + 45 => 6540002935581557630, + 46 => 3308964414877219337, + 47 => 5153842433409053720, + 48 => 253675177384576905, + 49 => 8423529847500341694, + ), + 14 => + array ( + 0 => 6993713031298341935, + 1 => 5326414593476770012, + 2 => 5440814550066802105, + 3 => 141762543875879518, + 4 => 5685816950122979356, + 5 => 3092600055577005256, + 6 => 484524073179592456, + 7 => 3023390118292269707, + 8 => 6864350520979702465, + 9 => 164326004277162557, + 10 => 1061461362432115174, + 11 => 2224051270026522509, + 12 => 6168787883393523744, + 13 => 7674793873689286403, + 14 => 1911946231027664781, + 15 => 8744291606724379208, + 16 => 9014519428529976331, + 17 => 3879593031828012380, + 18 => 619709744505846015, + 19 => 9116163054436980499, + 20 => 7832149942441221423, + 21 => 8108528699446988884, + 22 => 1971792629433296522, + 23 => 2640898620660261083, + 24 => 4826688299073541883, + 25 => 8208909046876680841, + 26 => 5721944470113654305, + 27 => 4086878983333595985, + 28 => 3777491165352231027, + 29 => 8919919327161482714, + 30 => 1411839390869133003, + 31 => 6507835402545136011, + 32 => 6630143811048135593, + 33 => 9162986904570452659, + 34 => 2158137837160408572, + 35 => 8083368029763836496, + 36 => 1089926883319054315, + 37 => 8268575358599231390, + 38 => 8199472199423672208, + 39 => 2280879658381489781, + 40 => 5576217042829441238, + 41 => 1546113314666207528, + 42 => 314235395477009613, + 43 => 1154159462456870581, + 44 => 6430125602104326521, + 45 => 4141336619453788776, + 46 => 8123765325147860838, + 47 => 1072475769909743664, + 48 => 3275082594725811702, + 49 => 35188985155813154, + ), + 15 => + array ( + 0 => 4491251610507865005, + 1 => 5013670103317501847, + 2 => 1908586816547780374, + 3 => 5528080847159743054, + 4 => 5104328648855753448, + 5 => 7599385267220236891, + 6 => 2776409469017349441, + 7 => 4575596226800948948, + 8 => 6369321928571414671, + 9 => 1618971068284703013, + 10 => 6277448308413490415, + 11 => 511988212940164645, + 12 => 3099316290169034108, + 13 => 3954426873623717044, + 14 => 4442835296439196398, + 15 => 8527786574257820049, + 16 => 541480700139692845, + 17 => 7258546318137865130, + 18 => 2111094668206075978, + 19 => 7746803879177003947, + 20 => 807752852058787647, + 21 => 6303558981146631063, + 22 => 1612288856991150333, + 23 => 3477957171986545461, + 24 => 2903449324702960216, + 25 => 4847163341110332855, + 26 => 8152405596867347396, + 27 => 8338399885984045224, + 28 => 5649959999977342668, + 29 => 5720423269116660296, + 30 => 965246675443819514, + 31 => 4402398597112098409, + 32 => 7574584563321041436, + 33 => 5672360046774743378, + 34 => 2546837547808280354, + 35 => 7971394139153078563, + 36 => 7369689550706069809, + 37 => 8866894908552724322, + 38 => 764751270312312614, + 39 => 3417051346281355094, + 40 => 7229557916866124768, + 41 => 7261498631961135330, + 42 => 5400611949698217702, + 43 => 4379197429476731239, + 44 => 944076077759636497, + 45 => 3343096502531647942, + 46 => 1460414845122217807, + 47 => 5886003542955764528, + 48 => 294146151341598816, + 49 => 7553441789861934638, + ), + 16 => + array ( + 0 => 8741958986469974724, + 1 => 6215975541860594564, + 2 => 2030793673351821656, + 3 => 7664541364665664906, + 4 => 8470810402228401978, + 5 => 4313164655146288908, + 6 => 4839977850635283703, + 7 => 4651535922908649829, + 8 => 81623039571201672, + 9 => 5879786151984355685, + 10 => 2652375748969362868, + 11 => 1412377869821067484, + 12 => 7764752987880077980, + 13 => 3232608468180411697, + 14 => 5219774171360183259, + 15 => 276757970441762536, + 16 => 2157050254663254778, + 17 => 4772180464617334572, + 18 => 4850998942845193572, + 19 => 2543311538514698065, + 20 => 8050994584586108828, + 21 => 2815479474551748381, + 22 => 5971023239458235291, + 23 => 4067859276180314903, + 24 => 7748875825149588576, + 25 => 7607843825928354150, + 26 => 1115863343729652284, + 27 => 968665230690300207, + 28 => 2344103572208289990, + 29 => 4915922776603825251, + 30 => 7899341581173719583, + 31 => 3270638032084051342, + 32 => 7922829911756040174, + 33 => 6901237696263042089, + 34 => 103197869659722557, + 35 => 527606972626448062, + 36 => 205932143123493544, + 37 => 4666962621159430172, + 38 => 6025147156756276603, + 39 => 2569017618097790149, + 40 => 2782270428022887692, + 41 => 2110342899579201191, + 42 => 4866511434611918196, + 43 => 8287772446542779705, + 44 => 1240825666152673689, + 45 => 7318857828118583203, + 46 => 7395325360634556807, + 47 => 1537320824630196486, + 48 => 6236055319334356730, + 49 => 8913567671596838634, + ), + 17 => + array ( + 0 => 3043470933854885224, + 1 => 6714301203475713128, + 2 => 860257064799129134, + 3 => 9041321571746388930, + 4 => 288738229336661630, + 5 => 3371616536887951610, + 6 => 1598608002069517570, + 7 => 5345879053451417291, + 8 => 4605770882480547648, + 9 => 8046129185750429146, + 10 => 7471568780314310293, + 11 => 1891596127269858319, + 12 => 2648872195739917662, + 13 => 2923983151863274145, + 14 => 78950419940827592, + 15 => 5925091477994177417, + 16 => 5731829744992297031, + 17 => 4296592622666844395, + 18 => 2419286681494585306, + 19 => 7283688448528986472, + 20 => 3321477450763978371, + 21 => 3064657579201514684, + 22 => 5374614556206587782, + 23 => 9107664630361570410, + 24 => 6890980300156013295, + 25 => 1165636160761295363, + 26 => 7068550182564021171, + 27 => 1118884285637398925, + 28 => 4520356901520518371, + 29 => 3906256068453096126, + 30 => 5334730629419585704, + 31 => 8867104621512809498, + 32 => 3070185485814636491, + 33 => 200199337477590437, + 34 => 4949494895124137875, + 35 => 8951288005981893499, + 36 => 2222242921160594363, + 37 => 4156807003305590083, + 38 => 3482462562024041320, + 39 => 2635205157596707857, + 40 => 840204241790569977, + 41 => 7563496981822937374, + 42 => 1582663658368798766, + 43 => 2736234992581107731, + 44 => 7016727431215779519, + 45 => 4968847729299064149, + 46 => 1216274414653489790, + 47 => 353213425186274929, + 48 => 4727317845209199005, + 49 => 8297576197853361424, + ), + 18 => + array ( + 0 => 159828459226828317, + 1 => 5228910350354088371, + 2 => 3800521216652551335, + 3 => 2253147546468586805, + 4 => 106796071918441752, + 5 => 2814225221080495171, + 6 => 3053238596951743089, + 7 => 4477943349369572147, + 8 => 8952510351557581107, + 9 => 2368476941075762366, + 10 => 561925318975977237, + 11 => 329670233355618662, + 12 => 5208937722910779587, + 13 => 3060450901088187935, + 14 => 4659097015012886378, + 15 => 5039174786713818080, + 16 => 3018568769194342498, + 17 => 1240769854944228955, + 18 => 2817285542073135861, + 19 => 3934900710974648820, + 20 => 8482919410301897152, + 21 => 8481234644320051096, + 22 => 4171591109421777684, + 23 => 5034695506354661667, + 24 => 4092817754517451666, + 25 => 4560986042376585682, + 26 => 3054876512309742094, + 27 => 6753222229261142602, + 28 => 5849041337477188797, + 29 => 7938201530168412349, + 30 => 6670314596868727397, + 31 => 7259960116747972664, + 32 => 2061159009576901210, + 33 => 5516856451150141519, + 34 => 5562142725910270159, + 35 => 4428036293610195147, + 36 => 7825136895944119800, + 37 => 6528157703864613968, + 38 => 7077699025224950556, + 39 => 3958424612440598778, + 40 => 4382670869650741676, + 41 => 4907831461290595051, + 42 => 2955573740056960677, + 43 => 2467864051452085508, + 44 => 2771440083868176870, + 45 => 2126983384946487140, + 46 => 694858885292569525, + 47 => 7420632173785167556, + 48 => 17990672710647105, + 49 => 2041959591437784652, + ), + 19 => + array ( + 0 => 7317139211076919878, + 1 => 1282655490899029874, + 2 => 7762517756959954308, + 3 => 7307406843013483032, + 4 => 8440361575264531800, + 5 => 4557610895832592743, + 6 => 4647166194384492730, + 7 => 7836965747539165421, + 8 => 661449650495111113, + 9 => 5905003857595880068, + 10 => 6058292247968883017, + 11 => 7707813779197067451, + 12 => 2765003876415774360, + 13 => 1642519878811525518, + 14 => 6488644034703432506, + 15 => 443516601408995930, + 16 => 8681252700158220179, + 17 => 7213878451268575925, + 18 => 4957060309915020583, + 19 => 8614133085282346831, + 20 => 4469738621889306141, + 21 => 3619072342991403987, + 22 => 1288952461195174914, + 23 => 3127547882180992688, + 24 => 5243033781121657206, + 25 => 430262612273204082, + 26 => 351924028121170974, + 27 => 290830022617614644, + 28 => 4426032873476367010, + 29 => 3298187746051695086, + 30 => 3300882353921382357, + 31 => 2998867997974943651, + 32 => 6335244123367408722, + 33 => 1562080616401434152, + 34 => 3622026051437015416, + 35 => 3063104137993823287, + 36 => 4908105192604607913, + 37 => 8108507327674564482, + 38 => 8078582610832559796, + 39 => 4545970688996026128, + 40 => 7575511062471729436, + 41 => 6668469679406886222, + 42 => 2055949106569645003, + 43 => 8084940231047228149, + 44 => 7809492702601105062, + 45 => 5958456976930763443, + 46 => 4479320839357515450, + 47 => 1286213746222420831, + 48 => 1329535666848852083, + 49 => 5437370777345146572, + ), + 20 => + array ( + 0 => 4857706934552326839, + 1 => 8604356504209431307, + 2 => 5916200389864426149, + 3 => 1972127778835616323, + 4 => 4466838903146615187, + 5 => 5189584875258487647, + 6 => 8206235570971558685, + 7 => 5664557861400693721, + 8 => 76554600264032963, + 9 => 1375414045028523191, + 10 => 314604821407701077, + 11 => 542962474657268177, + 12 => 3763797168773875653, + 13 => 7696660931594638607, + 14 => 7657860041931331157, + 15 => 3684023238413049415, + 16 => 1288136826482098114, + 17 => 6538391815793689011, + 18 => 1539691100482100899, + 19 => 6697889143180391350, + 20 => 689391216106492212, + 21 => 8558737790467168778, + 22 => 9114955107747374239, + 23 => 3516848329603263424, + 24 => 7951243507168588495, + 25 => 1278745189874536837, + 26 => 1110763008585829835, + 27 => 387695939753230271, + 28 => 6327450490177456303, + 29 => 4763569094147725981, + 30 => 4431527363687509033, + 31 => 2176672561786634376, + 32 => 4103216092069297204, + 33 => 1012903945494380106, + 34 => 6519217886324143112, + 35 => 891551299177755208, + 36 => 5286097396474065445, + 37 => 4872647425260736893, + 38 => 5504723327489075283, + 39 => 5240238322856169756, + 40 => 2121810588737684596, + 41 => 2995943731837790863, + 42 => 6363242933036886665, + 43 => 6437009869752649523, + 44 => 6010597810129509157, + 45 => 6031054356983231858, + 46 => 7604333500356670964, + 47 => 2040711769022116167, + 48 => 1223016982760333922, + 49 => 5656644529208310713, + ), + 21 => + array ( + 0 => 3692883075057948045, + 1 => 8341745748715868269, + 2 => 4153798986434369105, + 3 => 6190685996571843058, + 4 => 4581011959289663915, + 5 => 6889228844290451861, + 6 => 386651216501620503, + 7 => 2641657213163165536, + 8 => 1335417413810890798, + 9 => 1195325223121027856, + 10 => 1950382984503804487, + 11 => 2018980923444633939, + 12 => 4535200343609863955, + 13 => 4532391651609183606, + 14 => 3091765872963829161, + 15 => 1725514701875724129, + 16 => 8802608053136199660, + 17 => 2501886360038703766, + 18 => 7936720140765753419, + 19 => 6148499603267943045, + 20 => 7684043930850667486, + 21 => 7670255701399237573, + 22 => 8188367993869462016, + 23 => 8735440608656363427, + 24 => 5410649862262562695, + 25 => 4925080728400948351, + 26 => 2176929635680360748, + 27 => 4807048413318271132, + 28 => 6010622872835781146, + 29 => 6303972123625327278, + 30 => 8749397688527702840, + 31 => 5314599595601066296, + 32 => 4101592221080075628, + 33 => 5839125295374380379, + 34 => 4446671680471125606, + 35 => 7858211664287691753, + 36 => 5246910991839856164, + 37 => 4482566883724413138, + 38 => 4817024681224994802, + 39 => 7185912174789012378, + 40 => 1962027438001045790, + 41 => 2609804510626860868, + 42 => 4880788808493006624, + 43 => 8013142836916761691, + 44 => 7099794532571876632, + 45 => 5714190209300556809, + 46 => 4074292082754804563, + 47 => 7118110499688626233, + 48 => 3740645108594423970, + 49 => 3319563052739345108, + ), + 22 => + array ( + 0 => 3635597747626063519, + 1 => 2164524562859423435, + 2 => 5400922439277929330, + 3 => 5638755949943251895, + 4 => 345060876821584997, + 5 => 6346953969578339165, + 6 => 1258767325705790159, + 7 => 5557965573836848627, + 8 => 3701462982527702467, + 9 => 617315811096399620, + 10 => 6224692550136567962, + 11 => 6933758326188267012, + 12 => 1349620962154589916, + 13 => 3090293583685603526, + 14 => 3138811343989032784, + 15 => 4085195644063384467, + 16 => 1750741553651055209, + 17 => 1375307368490389063, + 18 => 2676576903521551168, + 19 => 2480373025920539306, + 20 => 2382891362135228642, + 21 => 7945241691905708930, + 22 => 1298017934480368845, + 23 => 5446902565524747023, + 24 => 1729116730968347711, + 25 => 4147150133130736401, + 26 => 1427843070559773159, + 27 => 1780551772808485451, + 28 => 7917259692730601273, + 29 => 7349523907545971585, + 30 => 2123698404678043325, + 31 => 2028478562293619435, + 32 => 3650204844478402782, + 33 => 1048742987380935661, + 34 => 4919093645065853713, + 35 => 4735521395278711667, + 36 => 4263061631352778668, + 37 => 4990281965597796595, + 38 => 6572930134587784857, + 39 => 6345249396950527073, + 40 => 5357728545494608011, + 41 => 2251117625226850611, + 42 => 9094453220809515443, + 43 => 589604802378396739, + 44 => 6612910280471354751, + 45 => 321052347772933560, + 46 => 3531910257691624990, + 47 => 5723107334369389887, + 48 => 1934550046285941562, + 49 => 2408405055455205691, + ), + 23 => + array ( + 0 => 3182544926194816683, + 1 => 4135791120284976973, + 2 => 9038384596036099199, + 3 => 3360257051495387930, + 4 => 3067116657795906868, + 5 => 9189263530066581983, + 6 => 8810029068987713437, + 7 => 4181405060040733093, + 8 => 6789036062736737414, + 9 => 4180258664806222317, + 10 => 5206301288833582003, + 11 => 7404138723681179874, + 12 => 7584189287131670526, + 13 => 2431867746107884339, + 14 => 1875792223611432089, + 15 => 3459055032035616268, + 16 => 86592156086271429, + 17 => 8483421072516128642, + 18 => 8294151068735231921, + 19 => 5802441801608907744, + 20 => 8382169087571134445, + 21 => 6175256394403582016, + 22 => 8680936151108964764, + 23 => 8028075470000659146, + 24 => 3934209999818180592, + 25 => 2376976355793312353, + 26 => 7412806587346857250, + 27 => 3271699019268501922, + 28 => 8643725002057836189, + 29 => 5272966637925117582, + 30 => 1956416735411967379, + 31 => 2276572757067924478, + 32 => 5452481299602682727, + 33 => 2879185636264199317, + 34 => 3746042541108156691, + 35 => 1429252009136254500, + 36 => 2743586749321822426, + 37 => 7671817618041762252, + 38 => 5465526680667937836, + 39 => 1408302065483439410, + 40 => 3146408973387714635, + 41 => 9144752839124785415, + 42 => 3055389789080167063, + 43 => 2920916448116928028, + 44 => 5096541788581167409, + 45 => 9140954567743705011, + 46 => 8334927526779853673, + 47 => 26254271172604416, + 48 => 6044180175352828659, + 49 => 4905444378844812527, + ), + 24 => + array ( + 0 => 8778804630631233758, + 1 => 2128773536485951925, + 2 => 3156292813293586100, + 3 => 5479506868479360061, + 4 => 5255521102514434059, + 5 => 6127102471628856136, + 6 => 3445428007543458351, + 7 => 4552536685857991488, + 8 => 181461191819877432, + 9 => 7659452559481153647, + 10 => 6208548587363259414, + 11 => 871845240942600698, + 12 => 1566686596856397432, + 13 => 5085136758745300568, + 14 => 48239442416834900, + 15 => 5249326187208137968, + 16 => 6679940152503118125, + 17 => 5672910834000683796, + 18 => 5654888840313725373, + 19 => 2964751030779185994, + 20 => 2596948428062872680, + 21 => 3886836164888421968, + 22 => 2801687144774483114, + 23 => 1435564309420727411, + 24 => 7823551093266275640, + 25 => 8317900161982747716, + 26 => 7670105986978675742, + 27 => 3293880832832782226, + 28 => 6852392738548947525, + 29 => 2399226343154695689, + 30 => 4623705354315297526, + 31 => 1337335133852864718, + 32 => 3142692742052820504, + 33 => 1110904463022289965, + 34 => 1709325244942754172, + 35 => 7781064800373664699, + 36 => 5538479197098539926, + 37 => 2601033748890917211, + 38 => 2003881498784293691, + 39 => 2112085745486960080, + 40 => 4310240847154287634, + 41 => 2308476327942257873, + 42 => 4962068776322463085, + 43 => 9219870942954359516, + 44 => 275448173853618795, + 45 => 3636000360048724646, + 46 => 6515795951916562220, + 47 => 6592664636514711945, + 48 => 5553810843268514647, + 49 => 7475257716026832493, + ), + 25 => + array ( + 0 => 3934912217800888461, + 1 => 7374561905709187569, + 2 => 6362524244007135673, + 3 => 7545292000266069826, + 4 => 3688385979393175809, + 5 => 8944760284319862423, + 6 => 5719514110377594126, + 7 => 2687367137215149026, + 8 => 373362793523307917, + 9 => 4037229058581099439, + 10 => 2760450080990531277, + 11 => 1331755606287071328, + 12 => 8903956594658100019, + 13 => 3017060200190361567, + 14 => 9067522733796185567, + 15 => 7841088764654616386, + 16 => 3325815798413485528, + 17 => 2008486325220794910, + 18 => 8175990495435770767, + 19 => 8700862870804434417, + 20 => 3037197994434502453, + 21 => 2612473879337278307, + 22 => 6960714636653288891, + 23 => 2599077756695892188, + 24 => 1117179736310225927, + 25 => 4567773530476414377, + 26 => 4647243747058620445, + 27 => 2321813451349409720, + 28 => 3865738658487181873, + 29 => 605370897901752710, + 30 => 3561298430528888930, + 31 => 482212088563126217, + 32 => 1123821138794575444, + 33 => 3644559737915817503, + 34 => 3169168436100744951, + 35 => 6684837151528598524, + 36 => 949624257943655438, + 37 => 2363265038683742192, + 38 => 6975100778960739566, + 39 => 1088106952368155082, + 40 => 9031071114875912453, + 41 => 7186957180246026588, + 42 => 748047347237757320, + 43 => 1829271522380212151, + 44 => 5948031981348174897, + 45 => 8287940031417741995, + 46 => 2505752838050649804, + 47 => 5099014870862750432, + 48 => 8588087635974285280, + 49 => 6421123552582880483, + ), + 26 => + array ( + 0 => 5075038906546001565, + 1 => 5575772665085918239, + 2 => 7690213268403706123, + 3 => 2561367337985348087, + 4 => 9198633483003604625, + 5 => 8176611681804800368, + 6 => 1034749991224969288, + 7 => 5413951329712953070, + 8 => 6843474764774872589, + 9 => 2988363423107848960, + 10 => 6905745081169982630, + 11 => 3584472635889546143, + 12 => 2868065303409280569, + 13 => 5721763934857011046, + 14 => 51272945170672251, + 15 => 110898137231783043, + 16 => 3261624826775449864, + 17 => 4290905888212127901, + 18 => 3598331731128937800, + 19 => 5485918646765189403, + 20 => 3199925657249673765, + 21 => 4687523998068607431, + 22 => 3547242790293341951, + 23 => 5878605187637781812, + 24 => 1329701316071700626, + 25 => 3852165965733157158, + 26 => 5568308703939857000, + 27 => 712159736152581729, + 28 => 3942040367433932618, + 29 => 7579188707060844698, + 30 => 699748792621735028, + 31 => 8984741049761024565, + 32 => 3630987657323419332, + 33 => 6921833013055677001, + 34 => 5427985014679601453, + 35 => 8808271519225071503, + 36 => 4711070269125981849, + 37 => 3373227369288191129, + 38 => 6126028385690479496, + 39 => 5863162538755040589, + 40 => 260615166567030749, + 41 => 6169978680851501167, + 42 => 4358818732555163540, + 43 => 8518740114556884065, + 44 => 7958754409966373094, + 45 => 573152438257673709, + 46 => 331267994190726417, + 47 => 8356096694878241479, + 48 => 1272080648927188078, + 49 => 8719394796985858664, + ), + 27 => + array ( + 0 => 1713112773270000284, + 1 => 3674491511347012363, + 2 => 2816944677731995110, + 3 => 8169516782327556549, + 4 => 1079881425235210838, + 5 => 7358305538760468281, + 6 => 5817013438134320577, + 7 => 8544277047549920689, + 8 => 2612693494334873504, + 9 => 2410205570754317675, + 10 => 4765074328332257479, + 11 => 3200927192423204576, + 12 => 993571942634740218, + 13 => 4127024137323817041, + 14 => 7931819328137732593, + 15 => 6004980101535875403, + 16 => 2593996430156591924, + 17 => 3344245560034769530, + 18 => 7758136653194498132, + 19 => 8094110195572556176, + 20 => 3118267944711071984, + 21 => 7186275536405421706, + 22 => 7796826921442172507, + 23 => 456647124663036567, + 24 => 2295505108146214194, + 25 => 845993445877474996, + 26 => 2281582727100964735, + 27 => 8590622392767984276, + 28 => 5335525485978198826, + 29 => 6532961240982760621, + 30 => 618136707885506589, + 31 => 2277579219937808739, + 32 => 1847684410351490936, + 33 => 3121950859776251309, + 34 => 1373846454651465108, + 35 => 8429372726308291726, + 36 => 4202058483673705428, + 37 => 2102701678608686168, + 38 => 5292586743616572656, + 39 => 1141103091656692614, + 40 => 2452537960493978322, + 41 => 1799252082873228399, + 42 => 8139542680960213645, + 43 => 2220323688842873613, + 44 => 6085583203942625976, + 45 => 1390191550131234271, + 46 => 2556428103448636739, + 47 => 7978764410120570984, + 48 => 7452825238242091899, + 49 => 4906989274116857274, + ), + 28 => + array ( + 0 => 1462805255444649928, + 1 => 8343560722428820573, + 2 => 3858360165264612091, + 3 => 5987775446304932519, + 4 => 8243926019807501861, + 5 => 259792547847858263, + 6 => 8523293594423809996, + 7 => 1022732337636159834, + 8 => 3213715358666985280, + 9 => 5868573469829409213, + 10 => 8466678775818920229, + 11 => 5868366253057791812, + 12 => 6208045679919712986, + 13 => 4828029670603764478, + 14 => 1536764228551143006, + 15 => 7944654398075334736, + 16 => 6540004857400283412, + 17 => 8356652291598372276, + 18 => 7473778899420566941, + 19 => 12515907380719664, + 20 => 3045657915092005947, + 21 => 9076819206325981963, + 22 => 5523885183662623808, + 23 => 3643583187697051931, + 24 => 6047814813088565655, + 25 => 6607907412556680407, + 26 => 3704065470050326981, + 27 => 4943669158459086917, + 28 => 5364952723168287348, + 29 => 3462662330667826688, + 30 => 8701473005455226322, + 31 => 1758611190548459715, + 32 => 4406707928828976418, + 33 => 4888811657431037264, + 34 => 4957013862266587794, + 35 => 2524559341906780414, + 36 => 7047810700820786417, + 37 => 7771528433217430898, + 38 => 8370077425980690940, + 39 => 6794459757583249402, + 40 => 7352324777543603408, + 41 => 6524367095060281956, + 42 => 5781828331196203330, + 43 => 9003794765183902323, + 44 => 2773806512634632982, + 45 => 2478330167704433223, + 46 => 5133311010011475355, + 47 => 6062915138609666101, + 48 => 1366333151612500973, + 49 => 2633440997389232656, + ), + 29 => + array ( + 0 => 2946701720143929750, + 1 => 979159154486554783, + 2 => 3800430233049834405, + 3 => 2403077969463716904, + 4 => 5684238811566745468, + 5 => 733901519881574856, + 6 => 7982886017501491491, + 7 => 5095751087179294980, + 8 => 5458658444971789613, + 9 => 3558216986214111636, + 10 => 1421140469558251152, + 11 => 5589596901034330396, + 12 => 660126764196440588, + 13 => 1626742210305838928, + 14 => 3004209297107836086, + 15 => 4339709620670315423, + 16 => 4601546315149483637, + 17 => 3300906351479838877, + 18 => 8818742378911532918, + 19 => 7650541207121115820, + 20 => 2467475790644867462, + 21 => 8212973278184867174, + 22 => 6170747021793458782, + 23 => 554473080159560355, + 24 => 8109061402513869721, + 25 => 4935184950522172622, + 26 => 2836070912624371173, + 27 => 5863104186443070794, + 28 => 5066322367034512452, + 29 => 7515904293548439617, + 30 => 859595352069190526, + 31 => 5444872038822103090, + 32 => 3909093526439632101, + 33 => 4778069109418293990, + 34 => 1050678055158717675, + 35 => 6090768910048938533, + 36 => 1999585673717811001, + 37 => 5599213870610749390, + 38 => 5985534876910914488, + 39 => 1280817401435980253, + 40 => 1607456235317077015, + 41 => 4706933717031109339, + 42 => 4063064640509200643, + 43 => 8093028299255604600, + 44 => 5250545038454364236, + 45 => 7822988978679330097, + 46 => 1432284631859890921, + 47 => 6076775734848570758, + 48 => 5016187889233898325, + 49 => 6200896567050378142, + ), + 30 => + array ( + 0 => 886091043385175502, + 1 => 6107304329680384151, + 2 => 4487808133923722915, + 3 => 1572718359237314418, + 4 => 7182589849836822147, + 5 => 8552310449666824121, + 6 => 839834575767730160, + 7 => 3704725190344708521, + 8 => 3419433146617460448, + 9 => 4583683278208878013, + 10 => 4287173717136287019, + 11 => 2023580108484110495, + 12 => 139396265302617537, + 13 => 2695133350856059405, + 14 => 3375601802434923130, + 15 => 7543307316188800487, + 16 => 166137300174459592, + 17 => 2037951619903114622, + 18 => 6556111035886676158, + 19 => 6842491202596981334, + 20 => 6427960512489248432, + 21 => 1366064619968751026, + 22 => 3380087751220567269, + 23 => 1844240660623953672, + 24 => 8917750134472624943, + 25 => 3529706961223209031, + 26 => 413567163584414095, + 27 => 7204467989140882562, + 28 => 2600697917552335595, + 29 => 5504588681600388754, + 30 => 5185102754012983553, + 31 => 8437022723812659702, + 32 => 8946155770277578791, + 33 => 5364720908803041297, + 34 => 561598278573040523, + 35 => 2372698569561196055, + 36 => 4633419157760179115, + 37 => 3220279843598497436, + 38 => 155088913438442781, + 39 => 4858459018003785580, + 40 => 2683868126975220053, + 41 => 8232077000531421659, + 42 => 5622386414546408187, + 43 => 3723224767708117380, + 44 => 681397607067024437, + 45 => 3412495988269800265, + 46 => 5291514015537221343, + 47 => 4827703663950925572, + 48 => 465582164685264367, + 49 => 185016645248110044, + ), + 31 => + array ( + 0 => 8937537024875823665, + 1 => 1710633894284092377, + 2 => 8894642741914578266, + 3 => 8664119568507171411, + 4 => 2379779599812168746, + 5 => 4205412394192548097, + 6 => 7956809385605578280, + 7 => 8996315485331930942, + 8 => 6111233478685486620, + 9 => 8498569945704516150, + 10 => 2297507561583664303, + 11 => 8972169406416037499, + 12 => 1195619691522435232, + 13 => 4717523340848578881, + 14 => 2232481570914083203, + 15 => 3150101794125719823, + 16 => 6354655699945953482, + 17 => 4318642052172430270, + 18 => 5106084537983843572, + 19 => 1664777159510717072, + 20 => 2751967262693138443, + 21 => 5773248984841535745, + 22 => 4209805512870706679, + 23 => 898477103160193176, + 24 => 4666007108426825973, + 25 => 7211869303597657401, + 26 => 2666974192884884367, + 27 => 3480320594345135329, + 28 => 7950389503094974352, + 29 => 1336265817527650754, + 30 => 1310171618122281865, + 31 => 2291592408450733899, + 32 => 8959026177580877450, + 33 => 6740618986473816432, + 34 => 2615501683646827626, + 35 => 8729274792135371503, + 36 => 7327723571053828489, + 37 => 2576476113940077551, + 38 => 1363992834319357767, + 39 => 6831197456638087042, + 40 => 4166364003648427849, + 41 => 3320269676092112238, + 42 => 1124856159597645905, + 43 => 6031181692767874674, + 44 => 587104996978489946, + 45 => 4609207886116121379, + 46 => 8030301603141448722, + 47 => 8714941587912486385, + 48 => 2397527085071463971, + 49 => 8713607253404744721, + ), + 32 => + array ( + 0 => 7361044476014135852, + 1 => 5943379752510709458, + 2 => 7063923696520971270, + 3 => 1098056062291977944, + 4 => 8751701111653162376, + 5 => 8299307866581014768, + 6 => 531487442231113133, + 7 => 800424898181663787, + 8 => 3572053471303813275, + 9 => 5820132396104405712, + 10 => 5231045148117457196, + 11 => 8794966624701729985, + 12 => 4426083511481337255, + 13 => 6684150774213996097, + 14 => 6195586616970758831, + 15 => 4540547196657677940, + 16 => 3176763528575786006, + 17 => 3016704037695981978, + 18 => 6744125090676683568, + 19 => 7314666039928310381, + 20 => 6776756960956103555, + 21 => 2819577541088759121, + 22 => 808394245928260098, + 23 => 7623836821615698462, + 24 => 9181513438115673210, + 25 => 4841213581850083788, + 26 => 1702688065381194280, + 27 => 5055789798320038739, + 28 => 3380105065975209047, + 29 => 4599440295762820917, + 30 => 9177830387841628385, + 31 => 6834565555694329853, + 32 => 8205003401511565956, + 33 => 270865630133328421, + 34 => 3209340440005987283, + 35 => 6274281444150890611, + 36 => 6624332540249377414, + 37 => 2587527519812771104, + 38 => 7332647062080436425, + 39 => 5005771960338902691, + 40 => 3468699152815339036, + 41 => 1049194951778930910, + 42 => 3935584475848725335, + 43 => 9085753827045089064, + 44 => 9005661771391728938, + 45 => 5200913379481214357, + 46 => 3232030195284048767, + 47 => 7473765017672637593, + 48 => 8372990784366189599, + 49 => 900533435598411787, + ), + 33 => + array ( + 0 => 8594859403573976797, + 1 => 1056469051697018343, + 2 => 7981819807984249663, + 3 => 6700723553123759699, + 4 => 7901581591457502220, + 5 => 1310800509390325152, + 6 => 499750275117256505, + 7 => 1071702412840450245, + 8 => 3633047946110581667, + 9 => 6585929724644917875, + 10 => 3416110053601876100, + 11 => 4136922603327478165, + 12 => 1198179981647639256, + 13 => 5364443461276255613, + 14 => 2584348601509212450, + 15 => 5120324315937730250, + 16 => 522836777497002224, + 17 => 552034138319415545, + 18 => 4587724350149825427, + 19 => 5710345816705425453, + 20 => 2214162093303859919, + 21 => 7551406637754997327, + 22 => 801129753984345927, + 23 => 1694443285187760235, + 24 => 1607467601520276272, + 25 => 2446055389537726020, + 26 => 1269354020728556364, + 27 => 7711661596009824915, + 28 => 9071667294651872920, + 29 => 4913652187114691065, + 30 => 3050691115879208133, + 31 => 6934534687990289192, + 32 => 6067912219752470094, + 33 => 8220841418446711910, + 34 => 972116675376438178, + 35 => 1344284661582616006, + 36 => 1513685892785327687, + 37 => 164825221202889849, + 38 => 68873197765246129, + 39 => 6777363252419567909, + 40 => 825244377168104549, + 41 => 2681971304420594537, + 42 => 3883311224134497028, + 43 => 2672973131901906080, + 44 => 6820460877454352312, + 45 => 3037320540320603458, + 46 => 3664002611712155615, + 47 => 6952694747682406149, + 48 => 2596464038043400667, + 49 => 7366177260837591685, + ), + 34 => + array ( + 0 => 292350062840987499, + 1 => 7344240818539750031, + 2 => 1609631560856080955, + 3 => 7228986135093966777, + 4 => 8608191835886862036, + 5 => 1163066080309899072, + 6 => 70532433777463622, + 7 => 924319004301312418, + 8 => 2140581494331315574, + 9 => 7169352686836314056, + 10 => 638443241571752306, + 11 => 853780255615345105, + 12 => 1739147682244541523, + 13 => 8567050146095229043, + 14 => 4779753847101001513, + 15 => 5875986820860264955, + 16 => 4779366679965644631, + 17 => 3400913573370824391, + 18 => 6562992562650988324, + 19 => 1686033803026870867, + 20 => 3253521295475704978, + 21 => 5825470707331639173, + 22 => 1796220638478598798, + 23 => 7270350964116185434, + 24 => 991647800200356728, + 25 => 3606088830973856550, + 26 => 443145163029070989, + 27 => 7183190472191538757, + 28 => 15901392383005115, + 29 => 5758362578091923125, + 30 => 8971571545023320382, + 31 => 1971232438561079318, + 32 => 1430868415766661534, + 33 => 3332871680198107404, + 34 => 7743844596465737135, + 35 => 6711974713948763843, + 36 => 7944739695979211083, + 37 => 3612624505570525766, + 38 => 6683708597460602577, + 39 => 4247736755630511721, + 40 => 448594595469079611, + 41 => 4045026055920591555, + 42 => 2292968395929078788, + 43 => 6306296644449068379, + 44 => 3706306833466702788, + 45 => 6665090138939911651, + 46 => 7888274755113851365, + 47 => 6086132437729850665, + 48 => 3839356044209629608, + 49 => 985183048708961512, + ), + 35 => + array ( + 0 => 3325224500063830265, + 1 => 8065460522493303762, + 2 => 2150977162718404844, + 3 => 2513095501676670864, + 4 => 8233290220021652200, + 5 => 8463376693561504977, + 6 => 6027691299865433222, + 7 => 4331413006856090578, + 8 => 2113829432426161123, + 9 => 1835559938513239524, + 10 => 3760589369569864168, + 11 => 1322344057131535225, + 12 => 3357990062355066404, + 13 => 4121143615077418688, + 14 => 7327001177941823182, + 15 => 9173437920051811149, + 16 => 7016399979746488251, + 17 => 1850523048176725335, + 18 => 3983576576818988739, + 19 => 5840312242176026548, + 20 => 1259214195820192258, + 21 => 6447647888325876110, + 22 => 4470490824147858804, + 23 => 6784267568304408636, + 24 => 821472665125293996, + 25 => 664019338943997056, + 26 => 4076926184150142025, + 27 => 4319387561386893749, + 28 => 8201171442534002237, + 29 => 5644788835480447906, + 30 => 2649447979175035529, + 31 => 2468996022215338736, + 32 => 5728463485280084198, + 33 => 8394329974141246714, + 34 => 1352483190536383684, + 35 => 8736243844338540400, + 36 => 790017721772835965, + 37 => 6338377763947825681, + 38 => 4264781310792118564, + 39 => 2705874621155713282, + 40 => 2593771310831765496, + 41 => 6634404399979259606, + 42 => 1294697555944562153, + 43 => 7529861579645268978, + 44 => 2078202749952120215, + 45 => 1396951686711735132, + 46 => 7171446141795263687, + 47 => 1516242630777191319, + 48 => 2210141497417437861, + 49 => 2744225556700338124, + ), + 36 => + array ( + 0 => 4600032135784053351, + 1 => 6713153269903552616, + 2 => 1524432997499412451, + 3 => 9085663892181184132, + 4 => 5140890333166193573, + 5 => 6415370570842750449, + 6 => 605209017130950974, + 7 => 778544994861874783, + 8 => 3713470051168290047, + 9 => 6851658133011496782, + 10 => 7521089360523036379, + 11 => 55470468240461548, + 12 => 4424723957480851091, + 13 => 3847530157992312256, + 14 => 8823616067821477758, + 15 => 8222436034397097533, + 16 => 2778414665128527248, + 17 => 7369251459457795788, + 18 => 4071388805764854010, + 19 => 4287747405081384406, + 20 => 6793671831132673172, + 21 => 3114148111762093180, + 22 => 384788139844541605, + 23 => 8249529710893372789, + 24 => 2157714201190551670, + 25 => 3314092267069056806, + 26 => 5433532917470695439, + 27 => 4060442883699127140, + 28 => 8771445583039981773, + 29 => 660319066543258122, + 30 => 1439816300286172314, + 31 => 7548093856347548369, + 32 => 1176802104448457513, + 33 => 2151633963287867818, + 34 => 7069149822341864080, + 35 => 3586634261392759215, + 36 => 2115719774775525555, + 37 => 3750140748264703566, + 38 => 5440490869326034310, + 39 => 5562736766219798862, + 40 => 8671375179968291392, + 41 => 499467303889880326, + 42 => 3052671933762117788, + 43 => 4654562233905362880, + 44 => 246193436748305982, + 45 => 6081020084404882682, + 46 => 1890761200179428934, + 47 => 847208909396042167, + 48 => 660301253897618064, + 49 => 6043165264656513894, + ), + 37 => + array ( + 0 => 6757639381915063463, + 1 => 4555324169353791928, + 2 => 8453433396043507231, + 3 => 7367975479284018964, + 4 => 2045542141502434678, + 5 => 2417676962307568300, + 6 => 186796869025639582, + 7 => 6246410055497566153, + 8 => 5849524973108094068, + 9 => 7106427541300957855, + 10 => 7262467490409045349, + 11 => 2625620544100850907, + 12 => 5171818870073954920, + 13 => 4623173366355609469, + 14 => 6630131502569099114, + 15 => 440063833278550381, + 16 => 6849776851137649663, + 17 => 2923628760438352403, + 18 => 5161648914320285977, + 19 => 9012034856361683200, + 20 => 4809767844500753446, + 21 => 635793370674531070, + 22 => 8444782472750918628, + 23 => 625119645145838644, + 24 => 1711050135195889262, + 25 => 6050146214025761878, + 26 => 2961578381937462178, + 27 => 4950745578012323237, + 28 => 8058763614061937163, + 29 => 6706111980476478039, + 30 => 5630792510411418295, + 31 => 8540298519551869302, + 32 => 5260738543895906421, + 33 => 5752971984351257314, + 34 => 1029160814473166884, + 35 => 1070704459591052615, + 36 => 3163994785792740338, + 37 => 1720500792367303567, + 38 => 4882806516087351766, + 39 => 8332253764632666699, + 40 => 5408659686407782182, + 41 => 5747746595516938623, + 42 => 3091560264708997801, + 43 => 5031101333688462343, + 44 => 8668987231476535653, + 45 => 5512602314817607471, + 46 => 7034669407608555298, + 47 => 2753102589796145363, + 48 => 1040884794404786919, + 49 => 3301428685472618392, + ), + 38 => + array ( + 0 => 5806979224365754074, + 1 => 1823229587162456230, + 2 => 2409728391803915007, + 3 => 3100954313368161394, + 4 => 6748311504469801793, + 5 => 2356721932680740467, + 6 => 2902595942118706670, + 7 => 9051556021252197471, + 8 => 8962436333537015158, + 9 => 1517751113887997394, + 10 => 7225950201013444459, + 11 => 2546087118437497882, + 12 => 4762377893858011208, + 13 => 779517291694285424, + 14 => 591839542358627284, + 15 => 5367686008521738386, + 16 => 783759746685788842, + 17 => 6116167441793213306, + 18 => 365815152326631591, + 19 => 8538677316958860389, + 20 => 5763153366233756599, + 21 => 5172130961491337193, + 22 => 6476396834007136821, + 23 => 6029836709564845298, + 24 => 408859033973015916, + 25 => 2209578524914201998, + 26 => 1127460121968579950, + 27 => 4246938370582055905, + 28 => 7297154488844828783, + 29 => 887923979202184226, + 30 => 3797040851850177616, + 31 => 8422393952121634468, + 32 => 20093478403899945, + 33 => 4886029618655351877, + 34 => 1864670258343019198, + 35 => 6183162109700895400, + 36 => 8119022212844878386, + 37 => 6370184042463066692, + 38 => 2157074710623202259, + 39 => 5195630894378703024, + 40 => 8360267727451888827, + 41 => 5613959301389216697, + 42 => 9200021631961180630, + 43 => 3011698433767435578, + 44 => 2646864648891248756, + 45 => 875594231654088324, + 46 => 5829254964574199142, + 47 => 5122073606137572977, + 48 => 6311992841960630320, + 49 => 3643912288953336149, + ), + 39 => + array ( + 0 => 2016566775146603089, + 1 => 8155079696619330380, + 2 => 2349389095752690292, + 3 => 4151708271097970529, + 4 => 5956829747782558827, + 5 => 8010100026456592115, + 6 => 2505786602051303335, + 7 => 4300295331001627854, + 8 => 6463313572684094538, + 9 => 3188827801685229116, + 10 => 7166293507027402765, + 11 => 5308514333273976656, + 12 => 4329555584359014245, + 13 => 5827346015406135785, + 14 => 6082988395039652687, + 15 => 2040516223980318253, + 16 => 2355417926414154923, + 17 => 4421881569720670438, + 18 => 1254048473373079942, + 19 => 143797357942920964, + 20 => 927159441847638900, + 21 => 9125656825790404665, + 22 => 3124874662529471393, + 23 => 237811715952482964, + 24 => 4997756459605186732, + 25 => 7348703874299424624, + 26 => 3127587511289385911, + 27 => 1838541085162730889, + 28 => 4971047307131513593, + 29 => 4496474097197643920, + 30 => 367900424813379579, + 31 => 6775300006857949729, + 32 => 7619709866842274577, + 33 => 2775169379458413451, + 34 => 5284696862615186585, + 35 => 98821901901066233, + 36 => 17527048592384211, + 37 => 4082619363789565760, + 38 => 1364416818122311591, + 39 => 8702556109554689709, + 40 => 7532793199729130150, + 41 => 4429646224542368281, + 42 => 8073534022127423854, + 43 => 6676615686384802225, + 44 => 919929188796408866, + 45 => 286463990828219372, + 46 => 8005591956436239675, + 47 => 3561122318417636367, + 48 => 7141613405689295298, + 49 => 4674503104866902121, + ), + 40 => + array ( + 0 => 3813891411377184995, + 1 => 7632928882910031881, + 2 => 1768137945981350311, + 3 => 6967634063234579249, + 4 => 2797976415019455260, + 5 => 7699168543238033240, + 6 => 7432355352439791743, + 7 => 3645360421841482982, + 8 => 8718943265536988858, + 9 => 7860220420066677853, + 10 => 4763524853016814130, + 11 => 746840427234091900, + 12 => 5163828695552919478, + 13 => 1914579265302922277, + 14 => 689044418060530410, + 15 => 3618489164297541063, + 16 => 61740947671020857, + 17 => 69467365830002889, + 18 => 2671124054414225336, + 19 => 6973968054449700665, + 20 => 5299840293325810092, + 21 => 4406112937255197737, + 22 => 7381541188822397852, + 23 => 3851762677347053740, + 24 => 7774469060249240663, + 25 => 6570726928612528603, + 26 => 3395723399289035971, + 27 => 93132544109401309, + 28 => 4181666026703237142, + 29 => 4173220807245945620, + 30 => 1233658091284053386, + 31 => 7500417950540395060, + 32 => 5162558696816248917, + 33 => 4738704079029496989, + 34 => 5295465061085161680, + 35 => 2592686572074882270, + 36 => 5178062672665528753, + 37 => 3178681861046476155, + 38 => 8142717135943049359, + 39 => 7783366835369323136, + 40 => 4456894141017658808, + 41 => 7842953574286147550, + 42 => 6810660917429813178, + 43 => 1554967904521840183, + 44 => 2713208514599819836, + 45 => 8532571816947514100, + 46 => 2089061501092911099, + 47 => 5321751266006750346, + 48 => 4895100493652081813, + 49 => 7234927795872127236, + ), + 41 => + array ( + 0 => 570088149450372795, + 1 => 1164232867661654054, + 2 => 2051552701357333167, + 3 => 5748419436007641146, + 4 => 7783682776645521105, + 5 => 6819552605786190114, + 6 => 7318884777199516126, + 7 => 5612491547583719061, + 8 => 763362897480930168, + 9 => 2463527279394751288, + 10 => 2611183880210585068, + 11 => 2986123354152687234, + 12 => 536686882744942805, + 13 => 5785237831191992332, + 14 => 2353350353624615635, + 15 => 461563559902372411, + 16 => 3175928654331211916, + 17 => 8080764706949616609, + 18 => 1410104464983705407, + 19 => 3262924143780834586, + 20 => 302129610278924770, + 21 => 3322796116246466854, + 22 => 4557521703859536401, + 23 => 3106092074572377085, + 24 => 4061779031248296017, + 25 => 1052804171374219391, + 26 => 2933561146296323762, + 27 => 1547346571335950347, + 28 => 6670520874064353354, + 29 => 283284163449062547, + 30 => 5896134536118292404, + 31 => 2209186875345740824, + 32 => 7444825709765803627, + 33 => 8953938364148905200, + 34 => 529033946703848914, + 35 => 7617202261886253770, + 36 => 9002313668944222518, + 37 => 4420623052744828643, + 38 => 2815968007325006508, + 39 => 2897293259084092647, + 40 => 7647576686317618160, + 41 => 7857788746596263074, + 42 => 6613528439398696350, + 43 => 4549926203279553663, + 44 => 415197260070137892, + 45 => 353967515100095697, + 46 => 1581348573539914790, + 47 => 4575328744085652766, + 48 => 5652419264096532247, + 49 => 7564682658483551039, + ), + 42 => + array ( + 0 => 3368031215514411691, + 1 => 6403987853643742458, + 2 => 7829107364149630452, + 3 => 2525493651196271018, + 4 => 2605186910814181395, + 5 => 757360054341229592, + 6 => 4614500769664121530, + 7 => 2773523826174019485, + 8 => 785515330228690897, + 9 => 1678326496442532536, + 10 => 4767587424228283278, + 11 => 1566791991750875373, + 12 => 4194593116166275321, + 13 => 101803838930387949, + 14 => 6025597034517161086, + 15 => 9039555063501814438, + 16 => 7516747679772046036, + 17 => 7337849071970777609, + 18 => 7048274146685765803, + 19 => 2490912036172562890, + 20 => 1430944179399369897, + 21 => 925714316602446056, + 22 => 213612474969070880, + 23 => 8424214291092099066, + 24 => 8128177527341340913, + 25 => 8877880304739835373, + 26 => 7348226364467796897, + 27 => 7912285523981974709, + 28 => 6684442297345397749, + 29 => 8027317014132309176, + 30 => 2949299310666118859, + 31 => 4606455232748928322, + 32 => 537190475010555064, + 33 => 6794274034248069286, + 34 => 4905834084409988810, + 35 => 5856514390852846329, + 36 => 676904489229921322, + 37 => 7847259829335428809, + 38 => 603314800360596006, + 39 => 7638251964110811153, + 40 => 1371516712379551103, + 41 => 455159667265152652, + 42 => 2852656949187768322, + 43 => 8754867695377231411, + 44 => 4566890769600741779, + 45 => 6528607434570235134, + 46 => 5624469704540827131, + 47 => 8228007257195895475, + 48 => 7630458432617230689, + 49 => 2078246302561992743, + ), + 43 => + array ( + 0 => 3249494586804550869, + 1 => 7910382034839657896, + 2 => 5902349133912960872, + 3 => 8762482164340726662, + 4 => 1417781288491979680, + 5 => 844189552183628289, + 6 => 105413071487348004, + 7 => 8048457843956318159, + 8 => 5673311589853157392, + 9 => 6394325769311212492, + 10 => 4670193095650677760, + 11 => 8507743819188635864, + 12 => 139715791730778277, + 13 => 2828117459503705560, + 14 => 341500275608800353, + 15 => 3179155592867479561, + 16 => 3844098293834220289, + 17 => 6312133208951470938, + 18 => 4244537775216380097, + 19 => 3534019033218030565, + 20 => 531876127245908088, + 21 => 6356952659942689745, + 22 => 6903196638771436642, + 23 => 6264480881865375007, + 24 => 3043474304227856010, + 25 => 7204330142071132784, + 26 => 3258647143114570790, + 27 => 37530236043757607, + 28 => 8551339878345091900, + 29 => 3299033420331349394, + 30 => 1978400900541748461, + 31 => 4540820036346133652, + 32 => 5769958510090889842, + 33 => 4938302165368205991, + 34 => 2113170122425780104, + 35 => 3098919758489925708, + 36 => 7470625425112337200, + 37 => 3975646892811123257, + 38 => 1645901075149631834, + 39 => 9070335151525780158, + 40 => 321397114888729766, + 41 => 4858454928755911814, + 42 => 3981083195911464670, + 43 => 1395664503592753426, + 44 => 7609480547019305390, + 45 => 3144647483655816046, + 46 => 5367370826883676615, + 47 => 5691916664283445633, + 48 => 4097634742120685165, + 49 => 6206863622131666366, + ), + 44 => + array ( + 0 => 4639603337079770337, + 1 => 6548770155749868574, + 2 => 2398214353718128319, + 3 => 7769879793141674175, + 4 => 6653347574412558883, + 5 => 7662016057612580511, + 6 => 7398655524569027645, + 7 => 6851643413417681191, + 8 => 767893431760439718, + 9 => 7062231732012763354, + 10 => 1298522423205031553, + 11 => 9195400374449744963, + 12 => 7494564530328238174, + 13 => 4099393498420092502, + 14 => 7660141477782417762, + 15 => 7571561870936051249, + 16 => 2033832064371960684, + 17 => 1357940161911104815, + 18 => 4527552584379370680, + 19 => 4920386769880277627, + 20 => 7886876756994925893, + 21 => 5832098476387845665, + 22 => 5512731950665409254, + 23 => 2043217959321410708, + 24 => 2083802338082358116, + 25 => 1384683054614545685, + 26 => 4839557418307744826, + 27 => 5661331976091812887, + 28 => 4804139735155990158, + 29 => 8840757128394199723, + 30 => 6994106153308457833, + 31 => 6154002278329028450, + 32 => 1087677666205341750, + 33 => 244141496875194498, + 34 => 1749204419113682048, + 35 => 421165274352980092, + 36 => 5872506030742618511, + 37 => 1200060348916246255, + 38 => 3454491290431278359, + 39 => 1219257449633606432, + 40 => 4229125875404665514, + 41 => 6551830068625350328, + 42 => 5372851860553691735, + 43 => 6345832380887692654, + 44 => 3971581644890599026, + 45 => 866215337358616677, + 46 => 6222937111762488779, + 47 => 4170730568607952413, + 48 => 4775735845523718382, + 49 => 7698396865646553849, + ), + 45 => + array ( + 0 => 6761524961601885404, + 1 => 3414416428243153487, + 2 => 3476888942937972132, + 3 => 7718425341207686339, + 4 => 8419186405106913149, + 5 => 8181554895640464429, + 6 => 4539063265997904631, + 7 => 1341007880286744441, + 8 => 6587067129391736831, + 9 => 7595362866891711786, + 10 => 2619190805311603421, + 11 => 5894249203443113671, + 12 => 5146393704896672701, + 13 => 4168043008359189211, + 14 => 9192964933144107984, + 15 => 3826491577932810596, + 16 => 7457298538606960883, + 17 => 4631755125743095501, + 18 => 161044355279124271, + 19 => 3010202514295283717, + 20 => 8059680371160325698, + 21 => 6595863138615917742, + 22 => 7386436897584571650, + 23 => 4701072006369199271, + 24 => 5996028913549021625, + 25 => 1385370845897888047, + 26 => 1397103345833615254, + 27 => 6535851722157254355, + 28 => 5421499465701269131, + 29 => 4306592338655903107, + 30 => 2593858251430297414, + 31 => 2205415075000559542, + 32 => 7461708601258226738, + 33 => 6407679053280239442, + 34 => 2564946185453642548, + 35 => 6475278799604297299, + 36 => 6152028983295708415, + 37 => 4983683457561829607, + 38 => 1178635810974040117, + 39 => 5759567876755428883, + 40 => 1322053749775238205, + 41 => 555619562242405497, + 42 => 6807341749451169414, + 43 => 7728241440378147950, + 44 => 5319688939770816921, + 45 => 8330530233447972957, + 46 => 6805864273766790458, + 47 => 6855715184295822442, + 48 => 2702091193560632332, + 49 => 4825710705888288991, + ), + 46 => + array ( + 0 => 7913695790750124353, + 1 => 8653387588604917050, + 2 => 4501536607873700808, + 3 => 2843454544178669405, + 4 => 7545222646060711543, + 5 => 5801657953352687385, + 6 => 3498405413117909679, + 7 => 2011575796963252385, + 8 => 5578854291917523326, + 9 => 1365804745939196076, + 10 => 1357356852128713007, + 11 => 2068518443315367314, + 12 => 5421584461688818784, + 13 => 9198502025719176988, + 14 => 8520230833383963213, + 15 => 5976540176867322880, + 16 => 484326789728010926, + 17 => 8808985841675418815, + 18 => 5659291383374410947, + 19 => 4861489845790677877, + 20 => 4055288565625686302, + 21 => 4161104036273753697, + 22 => 7529431841640584049, + 23 => 3780989685567154300, + 24 => 6401764981519833149, + 25 => 1197899620746247058, + 26 => 3863318676314471965, + 27 => 8795731749285657215, + 28 => 7747084535925253860, + 29 => 2655012824519025259, + 30 => 7682684206889080095, + 31 => 6025078434347081324, + 32 => 1615255103987886735, + 33 => 2259104565619831085, + 34 => 8709526609996605559, + 35 => 3216850528061239271, + 36 => 7915732078002834192, + 37 => 1720325163337754822, + 38 => 4501251746367269130, + 39 => 1025003535384033006, + 40 => 4493455961601113968, + 41 => 7225901443203618256, + 42 => 6616030311715042976, + 43 => 8669939462384114992, + 44 => 2383786445621405332, + 45 => 6929317520695291133, + 46 => 8937147448915996388, + 47 => 912539491837161693, + 48 => 6697268964085367094, + 49 => 8420369060589102425, + ), + 47 => + array ( + 0 => 3700850510367048260, + 1 => 8461232830075913452, + 2 => 4033262673722395063, + 3 => 7356544964393530878, + 4 => 2888676921803219667, + 5 => 7156299039118135574, + 6 => 8202406955783229598, + 7 => 2478528791317009258, + 8 => 4041439523008240005, + 9 => 6517161524906758763, + 10 => 7560096391704973842, + 11 => 6918879401634146730, + 12 => 401187887246168573, + 13 => 3195446384835002696, + 14 => 3367835345440063506, + 15 => 5116864212349157532, + 16 => 7218461634201387045, + 17 => 5906860779382038858, + 18 => 1319187094568417224, + 19 => 646696649961507734, + 20 => 6775047794651263682, + 21 => 9210598354519468496, + 22 => 814342682513204669, + 23 => 5028007859672831065, + 24 => 8673166025973962669, + 25 => 7143844887140264089, + 26 => 7149779640084513266, + 27 => 5327255293644503614, + 28 => 7740041835523082539, + 29 => 7157231891001033180, + 30 => 6880425606155238561, + 31 => 616395685636568226, + 32 => 1506343751416503448, + 33 => 6764045249172563223, + 34 => 4152136705025707998, + 35 => 3882959765415441419, + 36 => 3429371676537325573, + 37 => 96371800125123629, + 38 => 2044610538400451737, + 39 => 5934883755423690609, + 40 => 3088928761050459291, + 41 => 9166606688652394872, + 42 => 3172278448305727210, + 43 => 6258859854653782146, + 44 => 2370253363001932727, + 45 => 888613293568417738, + 46 => 566878523618938599, + 47 => 6807770796752629799, + 48 => 5268390502059375586, + 49 => 1369560507235967025, + ), + 48 => + array ( + 0 => 5969062734480091517, + 1 => 4258635298619589230, + 2 => 1239915403139647092, + 3 => 5090551864665397530, + 4 => 4983253304814937482, + 5 => 615571454853585349, + 6 => 1591783394356870228, + 7 => 3856456619073176967, + 8 => 4163682545845256068, + 9 => 6190387025904069066, + 10 => 6778629022847096049, + 11 => 7466609877102224863, + 12 => 1943975059967845995, + 13 => 7095909378083018591, + 14 => 2455897788796317876, + 15 => 5856674661467767271, + 16 => 2508324967447032828, + 17 => 1353417238913596355, + 18 => 4084639979570954526, + 19 => 3989307496196329143, + 20 => 3632865819435603525, + 21 => 8882842337316352089, + 22 => 7977727799862244196, + 23 => 806283432102605935, + 24 => 1545497087649195615, + 25 => 3557464393355285756, + 26 => 1703046612747353147, + 27 => 6901053312597805596, + 28 => 3193951683541817674, + 29 => 7142082117921271648, + 30 => 535259647425267513, + 31 => 1896436629335605015, + 32 => 4301705775874893248, + 33 => 8739743395429789192, + 34 => 6384324837173611357, + 35 => 6184503691135320603, + 36 => 332261142286366890, + 37 => 1207159795507319703, + 38 => 6098310336187064859, + 39 => 7657813151254044828, + 40 => 5694573187490330619, + 41 => 4325278518662817383, + 42 => 7159800258223705431, + 43 => 7983853345760488509, + 44 => 3245495653004469177, + 45 => 3887580662207195375, + 46 => 5890827052852695685, + 47 => 128559302317612711, + 48 => 3228480891169079160, + 49 => 1174439836486132859, + ), + 49 => + array ( + 0 => 4864039696068472075, + 1 => 8834124669575979344, + 2 => 5652678881475382548, + 3 => 2379635065223177717, + 4 => 293009543092963596, + 5 => 7945471327883416577, + 6 => 6689198029790926423, + 7 => 1921885854372196611, + 8 => 7525825208427230268, + 9 => 6893487881916577839, + 10 => 2286912634732295118, + 11 => 3638013130157988052, + 12 => 3440656755054773459, + 13 => 86991074017549167, + 14 => 6849234719062098564, + 15 => 368261341327680170, + 16 => 2398309716273344165, + 17 => 1995084157513314738, + 18 => 8199815484722779866, + 19 => 4555122545756624787, + 20 => 8438849263629566283, + 21 => 2220359438567702571, + 22 => 8177509177722963039, + 23 => 2999854020616151054, + 24 => 6824704403354290481, + 25 => 6275807546493426104, + 26 => 9090799789147344934, + 27 => 7949534743488954263, + 28 => 5624411741410589483, + 29 => 7277332826188252059, + 30 => 6459979453897856951, + 31 => 1648740848473399197, + 32 => 403884512548425336, + 33 => 4874507963546786037, + 34 => 1320751360825837637, + 35 => 8588053377246754896, + 36 => 1046831925576638044, + 37 => 7651453133008971076, + 38 => 5081048334666394086, + 39 => 8573555156262460241, + 40 => 2011704186137013088, + 41 => 7716460786009597267, + 42 => 8041214376909827919, + 43 => 2860046413430702208, + 44 => 8698080270427320899, + 45 => 7104210477142509900, + 46 => 2288000021596943068, + 47 => 9032553461555290826, + 48 => 1211098135104524011, + 49 => 494075524174801193, + ), + ); +} From c27879c207bfee31d3688a34d96bfd411f1daa95 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 08:47:50 +0100 Subject: [PATCH 1159/3097] Fix build --- .../Rules/Functions/CallToFunctionParametersRuleTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index df4a1204775..ab04c623f43 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1961,6 +1961,10 @@ public function testBug12051(): void public function testBug8046(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/bug-8046.php'], []); } @@ -1975,6 +1979,10 @@ public function testBug11942(): void public function testBug11418(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/bug-11418.php'], []); } From 8734057fed407949994e79eb3785cc0bed8f5520 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 08:59:32 +0100 Subject: [PATCH 1160/3097] Fix generalizing constant arrays when the array is getting smaller --- src/Analyser/MutatingScope.php | 11 +++++++++-- tests/PHPStan/Analyser/nsrt/bug-1021.php | 2 +- tests/PHPStan/Analyser/nsrt/bug7856.php | 2 +- .../StrictComparisonOfDifferentTypesRuleTest.php | 6 ++++++ tests/PHPStan/Rules/Comparison/data/bug-8030.php | 16 ++++++++++++++++ tests/PHPStan/Rules/Variables/EmptyRuleTest.php | 8 ++++++++ tests/PHPStan/Rules/Variables/data/bug-12658.php | 14 ++++++++++++++ 7 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8030.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-12658.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0e7b7c328da..4350b592526 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5045,7 +5045,10 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type } else { $constantArraysA = TypeCombinator::union(...$constantArrays['a']); $constantArraysB = TypeCombinator::union(...$constantArrays['b']); - if ($constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType())) { + if ( + $constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType()) + && $constantArraysA->getArraySize()->equals($constantArraysB->getArraySize()) + ) { $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { $resultArrayBuilder->setOffsetValueType( @@ -5065,7 +5068,11 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)), TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)), ); - if ($constantArraysA->isIterableAtLeastOnce()->yes() && $constantArraysB->isIterableAtLeastOnce()->yes()) { + if ( + $constantArraysA->isIterableAtLeastOnce()->yes() + && $constantArraysB->isIterableAtLeastOnce()->yes() + && $constantArraysA->getArraySize()->getGreaterOrEqualType()->isSuperTypeOf($constantArraysB->getArraySize())->yes() + ) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); } if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-1021.php b/tests/PHPStan/Analyser/nsrt/bug-1021.php index b085b9a781b..189cba0b5a3 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1021.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1021.php @@ -13,7 +13,7 @@ function foobar() { } } - assertType('array{0?: int<1, max>, 1?: 2|3, 2?: 3}', $x); + assertType('array<1|2|3>&list', $x); if ($x) { } diff --git a/tests/PHPStan/Analyser/nsrt/bug7856.php b/tests/PHPStan/Analyser/nsrt/bug7856.php index fcdca30b442..8da8b7343e2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug7856.php +++ b/tests/PHPStan/Analyser/nsrt/bug7856.php @@ -10,7 +10,7 @@ function doFoo() { $endDate = new DateTimeImmutable('+1year'); do { - assertType("array{'+1week', '+1months', '+6months', '+17months'}|array{0: literal-string&lowercase-string&non-falsy-string, 1?: literal-string&lowercase-string&non-falsy-string, 2?: '+17months'}", $intervals); + assertType("list", $intervals); $periodEnd = $periodEnd->modify(array_shift($intervals)); } while (count($intervals) > 0 && $periodEnd->format('U') < $endDate); } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 32a755653aa..843f550880f 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -940,6 +940,12 @@ public function testLastMatchArm(bool $reportAlwaysTrueInLastCondition, array $e $this->analyse([__DIR__ . '/data/strict-comparison-last-match-arm.php'], $expectedErrors); } + public function testBug8030(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-8030.php'], []); + } + public function testBug8776Part1(): void { $this->checkAlwaysTrueStrictComparison = true; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8030.php b/tests/PHPStan/Rules/Comparison/data/bug-8030.php new file mode 100644 index 00000000000..3f1b50318b0 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8030.php @@ -0,0 +1,16 @@ +analyse([__DIR__ . '/data/bug-9403.php'], []); } + public function testBug12658(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->strictUnnecessaryNullsafePropertyFetch = false; + + $this->analyse([__DIR__ . '/data/bug-12658.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-12658.php b/tests/PHPStan/Rules/Variables/data/bug-12658.php new file mode 100644 index 00000000000..8b8d4eec3ed --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12658.php @@ -0,0 +1,14 @@ + $paragraph) { + if (!empty($ads)) { + $ad = array_shift($ads); + } + } +}; From d7e46d2bccc2b0f41ca687b49bb857b31d64e210 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Mar 2025 08:45:55 +0100 Subject: [PATCH 1161/3097] Added regression test --- tests/PHPStan/Analyser/nsrt/bug-10717.php | 1052 +++++++++++++++++++++ 1 file changed, 1052 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10717.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10717.php b/tests/PHPStan/Analyser/nsrt/bug-10717.php new file mode 100644 index 00000000000..fb0d0a1f9e5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10717.php @@ -0,0 +1,1052 @@ + */ + const DATA = [ + 'af' => [ + 'code' => 'af', + 'english' => "Afrikaans", + 'local' => "Afrikaans", + 'rtl' => false, + 'country' => 'za', + 'variant' => false, + ], + 'am' => [ + 'code' => 'am', + 'english' => "Amharic", + 'local' => "አማርኛ", + 'rtl' => false, + 'country' => 'et', + 'variant' => false, + ], + 'ar' => [ + 'code' => 'ar', + 'english' => "Arabic", + 'local' => "العربية‏", + 'rtl' => true, + 'country' => 'sa', + 'variant' => false, + ], + 'az' => [ + 'code' => 'az', + 'english' => "Azerbaijani", + 'local' => "Azərbaycan dili", + 'rtl' => false, + 'country' => 'az', + 'variant' => false, + ], + 'ba' => [ + 'code' => 'ba', + 'english' => "Bashkir", + 'local' => "башҡорт теле", + 'rtl' => false, + 'country' => 'ru', + 'variant' => false, + ], + 'be' => [ + 'code' => 'be', + 'english' => "Belarusian", + 'local' => "Беларуская", + 'rtl' => false, + 'country' => 'by', + 'variant' => false, + ], + 'bg' => [ + 'code' => 'bg', + 'english' => "Bulgarian", + 'local' => "Български", + 'rtl' => false, + 'country' => 'bg', + 'variant' => false, + ], + 'bn' => [ + 'code' => 'bn', + 'english' => "Bengali", + 'local' => "বাংলা", + 'rtl' => false, + 'country' => 'bd', + 'variant' => false, + ], + 'br' => [ + 'code' => 'br', + 'english' => "Brazilian Portuguese", + 'local' => "Português Brasileiro", + 'rtl' => false, + 'country' => 'br', + 'variant' => false, + ], + 'bs' => [ + 'code' => 'bs', + 'english' => "Bosnian", + 'local' => "Bosanski", + 'rtl' => false, + 'country' => 'ba', + 'variant' => false, + ], + 'ca' => [ + 'code' => 'ca', + 'english' => "Catalan", + 'local' => "Català", + 'rtl' => false, + 'country' => 'es-ca', + 'variant' => false, + ], + 'co' => [ + 'code' => 'co', + 'english' => "Corsican", + 'local' => "Corsu", + 'rtl' => false, + 'country' => 'fr-co', + 'variant' => false, + ], + 'cs' => [ + 'code' => 'cs', + 'english' => "Czech", + 'local' => "Čeština", + 'rtl' => false, + 'country' => 'cz', + 'variant' => false, + ], + 'cy' => [ + 'code' => 'cy', + 'english' => "Welsh", + 'local' => "Cymraeg", + 'rtl' => false, + 'country' => 'gb-wls', + 'variant' => false, + ], + 'da' => [ + 'code' => 'da', + 'english' => "Danish", + 'local' => "Dansk", + 'rtl' => false, + 'country' => 'dk', + 'variant' => false, + ], + 'de' => [ + 'code' => 'de', + 'english' => "German", + 'local' => "Deutsch", + 'rtl' => false, + 'country' => 'de', + 'variant' => false, + ], + 'el' => [ + 'code' => 'el', + 'english' => "Greek", + 'local' => "Ελληνικά", + 'rtl' => false, + 'country' => 'gr', + 'variant' => false, + ], + 'en' => [ + 'code' => 'en', + 'english' => "English", + 'local' => "English", + 'rtl' => false, + 'country' => 'gb', + 'variant' => false, + ], + 'eo' => [ + 'code' => 'eo', + 'english' => "Esperanto", + 'local' => "Esperanto", + 'rtl' => false, + 'country' => 'eo', + 'variant' => false, + ], + 'es' => [ + 'code' => 'es', + 'english' => "Spanish", + 'local' => "Español", + 'rtl' => false, + 'country' => 'es', + 'variant' => false, + ], + 'et' => [ + 'code' => 'et', + 'english' => "Estonian", + 'local' => "Eesti", + 'rtl' => false, + 'country' => 'ee', + 'variant' => false, + ], + 'eu' => [ + 'code' => 'eu', + 'english' => "Basque", + 'local' => "Euskara", + 'rtl' => false, + 'country' => 'eus', + 'variant' => false, + ], + 'fa' => [ + 'code' => 'fa', + 'english' => "Persian", + 'local' => "فارسی", + 'rtl' => true, + 'country' => 'ir', + 'variant' => false, + ], + 'fi' => [ + 'code' => 'fi', + 'english' => "Finnish", + 'local' => "Suomi", + 'rtl' => false, + 'country' => 'fi', + 'variant' => false, + ], + 'fj' => [ + 'code' => 'fj', + 'english' => "Fijian", + 'local' => "Vosa Vakaviti", + 'rtl' => false, + 'country' => 'fj', + 'variant' => false, + ], + 'fl' => [ + 'code' => 'fl', + 'english' => "Filipino", + 'local' => "Filipino", + 'rtl' => false, + 'country' => 'ph', + 'variant' => false, + ], + 'fr' => [ + 'code' => 'fr', + 'english' => "French", + 'local' => "Français", + 'rtl' => false, + 'country' => 'fr', + 'variant' => false, + ], + 'fy' => [ + 'code' => 'fy', + 'english' => "Western Frisian", + 'local' => "frysk", + 'rtl' => false, + 'country' => 'nl', + 'variant' => false, + ], + 'ga' => [ + 'code' => 'ga', + 'english' => "Irish", + 'local' => "Gaeilge", + 'rtl' => false, + 'country' => 'ie', + 'variant' => false, + ], + 'gd' => [ + 'code' => 'gd', + 'english' => "Scottish Gaelic", + 'local' => "Gàidhlig", + 'rtl' => false, + 'country' => 'gb-sct', + 'variant' => false, + ], + 'gl' => [ + 'code' => 'gl', + 'english' => "Galician", + 'local' => "Galego", + 'rtl' => false, + 'country' => 'es-ga', + 'variant' => false, + ], + 'gu' => [ + 'code' => 'gu', + 'english' => "Gujarati", + 'local' => "ગુજરાતી", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'ha' => [ + 'code' => 'ha', + 'english' => "Hausa", + 'local' => "هَوُسَ", + 'rtl' => false, + 'country' => 'ne', + 'variant' => false, + ], + 'he' => [ + 'code' => 'he', + 'english' => "Hebrew", + 'local' => "עברית", + 'rtl' => true, + 'country' => 'il', + 'variant' => false, + ], + 'hi' => [ + 'code' => 'hi', + 'english' => "Hindi", + 'local' => "हिंदी", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'hr' => [ + 'code' => 'hr', + 'english' => "Croatian", + 'local' => "Hrvatski", + 'rtl' => false, + 'country' => 'hr', + 'variant' => false, + ], + 'ht' => [ + 'code' => 'ht', + 'english' => "Haitian Creole", + 'local' => "Kreyòl ayisyen", + 'rtl' => false, + 'country' => 'ht', + 'variant' => false, + ], + 'hu' => [ + 'code' => 'hu', + 'english' => "Hungarian", + 'local' => "Magyar", + 'rtl' => false, + 'country' => 'hu', + 'variant' => false, + ], + 'hw' => [ + 'code' => 'hw', + 'english' => "Hawaiian", + 'local' => "‘Ōlelo Hawai‘i", + 'rtl' => false, + 'country' => 'hw', + 'variant' => false, + ], + 'hy' => [ + 'code' => 'hy', + 'english' => "Armenian", + 'local' => "հայերեն", + 'rtl' => false, + 'country' => 'am', + 'variant' => false, + ], + 'id' => [ + 'code' => 'id', + 'english' => "Indonesian", + 'local' => "Bahasa Indonesia", + 'rtl' => false, + 'country' => 'id', + 'variant' => false, + ], + 'ig' => [ + 'code' => 'ig', + 'english' => "Igbo", + 'local' => "Igbo", + 'rtl' => false, + 'country' => 'ne', + 'variant' => false, + ], + 'is' => [ + 'code' => 'is', + 'english' => "Icelandic", + 'local' => "Íslenska", + 'rtl' => false, + 'country' => 'is', + 'variant' => false, + ], + 'it' => [ + 'code' => 'it', + 'english' => "Italian", + 'local' => "Italiano", + 'rtl' => false, + 'country' => 'it', + 'variant' => false, + ], + 'ja' => [ + 'code' => 'ja', + 'english' => "Japanese", + 'local' => "日本語", + 'rtl' => false, + 'country' => 'jp', + 'variant' => false, + ], + 'jv' => [ + 'code' => 'jv', + 'english' => "Javanese", + 'local' => "Wong Jawa", + 'rtl' => false, + 'country' => 'id', + 'variant' => false, + ], + 'ka' => [ + 'code' => 'ka', + 'english' => "Georgian", + 'local' => "ქართული", + 'rtl' => false, + 'country' => 'ge', + 'variant' => false, + ], + 'kk' => [ + 'code' => 'kk', + 'english' => "Kazakh", + 'local' => "Қазақша", + 'rtl' => false, + 'country' => 'kz', + 'variant' => false, + ], + 'km' => [ + 'code' => 'km', + 'english' => "Central Khmer", + 'local' => "ភាសាខ្មែរ", + 'rtl' => false, + 'country' => 'kh', + 'variant' => false, + ], + 'kn' => [ + 'code' => 'kn', + 'english' => "Kannada", + 'local' => "ಕನ್ನಡ", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'ko' => [ + 'code' => 'ko', + 'english' => "Korean", + 'local' => "한국어", + 'rtl' => false, + 'country' => 'kr', + 'variant' => false, + ], + 'ku' => [ + 'code' => 'ku', + 'english' => "Kurdish", + 'local' => "كوردی", + 'rtl' => true, + 'country' => 'iq', + 'variant' => false, + ], + 'ky' => [ + 'code' => 'ky', + 'english' => "Kyrgyz", + 'local' => "кыргызча", + 'rtl' => false, + 'country' => 'kg', + 'variant' => false, + ], + 'la' => [ + 'code' => 'la', + 'english' => "Latin", + 'local' => "Latine", + 'rtl' => false, + 'country' => 'it', + 'variant' => false, + ], + 'lb' => [ + 'code' => 'lb', + 'english' => "Luxembourgish", + 'local' => "Lëtzebuergesch", + 'rtl' => false, + 'country' => 'lu', + 'variant' => false, + ], + 'lo' => [ + 'code' => 'lo', + 'english' => "Lao", + 'local' => "ພາສາລາວ", + 'rtl' => false, + 'country' => 'la', + 'variant' => false, + ], + 'lt' => [ + 'code' => 'lt', + 'english' => "Lithuanian", + 'local' => "Lietuvių", + 'rtl' => false, + 'country' => 'lt', + 'variant' => false, + ], + 'lv' => [ + 'code' => 'lv', + 'english' => "Latvian", + 'local' => "Latviešu", + 'rtl' => false, + 'country' => 'lv', + 'variant' => false, + ], + 'lg' => [ + 'code' => 'lg', + 'english' => "Luganda", + 'local' => "Oluganda", + 'rtl' => false, + 'country' => 'ug', + 'variant' => false, + ], + 'mg' => [ + 'code' => 'mg', + 'english' => "Malagasy", + 'local' => "Malagasy", + 'rtl' => false, + 'country' => 'mg', + 'variant' => false, + ], + 'mi' => [ + 'code' => 'mi', + 'english' => "Māori", + 'local' => "te reo Māori", + 'rtl' => false, + 'country' => 'nz', + 'variant' => false, + ], + 'mk' => [ + 'code' => 'mk', + 'english' => "Macedonian", + 'local' => "Македонски", + 'rtl' => false, + 'country' => 'mk', + 'variant' => false, + ], + 'ml' => [ + 'code' => 'ml', + 'english' => "Malayalam", + 'local' => "മലയാളം", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'mn' => [ + 'code' => 'mn', + 'english' => "Mongolian", + 'local' => "Монгол", + 'rtl' => false, + 'country' => 'mn', + 'variant' => false, + ], + 'mr' => [ + 'code' => 'mr', + 'english' => "Marathi", + 'local' => "मराठी", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'ms' => [ + 'code' => 'ms', + 'english' => "Malay", + 'local' => "Bahasa Melayu", + 'rtl' => false, + 'country' => 'my', + 'variant' => false, + ], + 'mt' => [ + 'code' => 'mt', + 'english' => "Maltese", + 'local' => "Malti", + 'rtl' => false, + 'country' => 'mt', + 'variant' => false, + ], + 'my' => [ + 'code' => 'my', + 'english' => "Burmese", + 'local' => "မျန္မာစာ", + 'rtl' => false, + 'country' => 'mm', + 'variant' => false, + ], + 'ne' => [ + 'code' => 'ne', + 'english' => "Nepali", + 'local' => "नेपाली", + 'rtl' => false, + 'country' => 'np', + 'variant' => false, + ], + 'nl' => [ + 'code' => 'nl', + 'english' => "Dutch", + 'local' => "Nederlands", + 'rtl' => false, + 'country' => 'nl', + 'variant' => false, + ], + 'no' => [ + 'code' => 'no', + 'english' => "Norwegian", + 'local' => "Norsk", + 'rtl' => false, + 'country' => 'no', + 'variant' => false, + ], + 'ny' => [ + 'code' => 'ny', + 'english' => "Chichewa", + 'local' => "chiCheŵa", + 'rtl' => false, + 'country' => 'mw', + 'variant' => false, + ], + 'pa' => [ + 'code' => 'pa', + 'english' => "Punjabi", + 'local' => "ਪੰਜਾਬੀ", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'pl' => [ + 'code' => 'pl', + 'english' => "Polish", + 'local' => "Polski", + 'rtl' => false, + 'country' => 'pl', + 'variant' => false, + ], + 'ps' => [ + 'code' => 'ps', + 'english' => "Pashto", + 'local' => "پښتو", + 'rtl' => true, + 'country' => 'pk', + 'variant' => false, + ], + 'pt' => [ + 'code' => 'pt', + 'english' => "Portuguese", + 'local' => "Português", + 'rtl' => false, + 'country' => 'pt', + 'variant' => false, + ], + 'ro' => [ + 'code' => 'ro', + 'english' => "Romanian", + 'local' => "Română", + 'rtl' => false, + 'country' => 'ro', + 'variant' => false, + ], + 'ru' => [ + 'code' => 'ru', + 'english' => "Russian", + 'local' => "Русский", + 'rtl' => false, + 'country' => 'ru', + 'variant' => false, + ], + 'sd' => [ + 'code' => 'sd', + 'english' => "Sindhi", + 'local' => "سنڌي، سندھی, सिन्धी", + 'rtl' => false, + 'country' => 'pk', + 'variant' => false, + ], + 'si' => [ + 'code' => 'si', + 'english' => "Sinhalese", + 'local' => "සිංහල", + 'rtl' => false, + 'country' => 'lk', + 'variant' => false, + ], + 'sk' => [ + 'code' => 'sk', + 'english' => "Slovak", + 'local' => "Slovenčina", + 'rtl' => false, + 'country' => 'sk', + 'variant' => false, + ], + 'sl' => [ + 'code' => 'sl', + 'english' => "Slovenian", + 'local' => "Slovenščina", + 'rtl' => false, + 'country' => 'si', + 'variant' => false, + ], + 'sm' => [ + 'code' => 'sm', + 'english' => "Samoan", + 'local' => "gagana fa'a Samoa", + 'rtl' => false, + 'country' => 'ws', + 'variant' => false, + ], + 'sn' => [ + 'code' => 'sn', + 'english' => "Shona", + 'local' => "chiShona", + 'rtl' => false, + 'country' => 'zw', + 'variant' => false, + ], + 'so' => [ + 'code' => 'so', + 'english' => "Somali", + 'local' => "Soomaaliga", + 'rtl' => false, + 'country' => 'so', + 'variant' => false, + ], + 'sq' => [ + 'code' => 'sq', + 'english' => "Albanian", + 'local' => "Shqip", + 'rtl' => false, + 'country' => 'al', + 'variant' => false, + ], + 'sr' => [ + 'code' => 'sr', + 'english' => "Serbian (Cyrillic)", + 'local' => "Српски", + 'rtl' => false, + 'country' => 'rs', + 'variant' => false, + ], + 'st' => [ + 'code' => 'st', + 'english' => "Southern Sotho", + 'local' => "seSotho", + 'rtl' => false, + 'country' => 'ng', + 'variant' => false, + ], + 'su' => [ + 'code' => 'su', + 'english' => "Sundanese", + 'local' => "Sundanese", + 'rtl' => false, + 'country' => 'sd', + 'variant' => false, + ], + 'sv' => [ + 'code' => 'sv', + 'english' => "Swedish", + 'local' => "Svenska", + 'rtl' => false, + 'country' => 'se', + 'variant' => false, + ], + 'sw' => [ + 'code' => 'sw', + 'english' => "Swahili", + 'local' => "Kiswahili", + 'rtl' => false, + 'country' => 'ke', + 'variant' => false, + ], + 'ta' => [ + 'code' => 'ta', + 'english' => "Tamil", + 'local' => "தமிழ்", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'te' => [ + 'code' => 'te', + 'english' => "Telugu", + 'local' => "తెలుగు", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'tg' => [ + 'code' => 'tg', + 'english' => "Tajik", + 'local' => "Тоҷикӣ", + 'rtl' => false, + 'country' => 'tj', + 'variant' => false, + ], + 'th' => [ + 'code' => 'th', + 'english' => "Thai", + 'local' => "ภาษาไทย", + 'rtl' => false, + 'country' => 'th', + 'variant' => false, + ], + 'tl' => [ + 'code' => 'tl', + 'english' => "Tagalog", + 'local' => "Tagalog", + 'rtl' => false, + 'country' => 'ph', + 'variant' => false, + ], + 'to' => [ + 'code' => 'to', + 'english' => "Tongan", + 'local' => "faka-Tonga", + 'rtl' => false, + 'country' => 'to', + 'variant' => false, + ], + 'tr' => [ + 'code' => 'tr', + 'english' => "Turkish", + 'local' => "Türkçe", + 'rtl' => false, + 'country' => 'tr', + 'variant' => false, + ], + 'tt' => [ + 'code' => 'tt', + 'english' => "Tatar", + 'local' => "Tatar", + 'rtl' => false, + 'country' => 'tr', + 'variant' => false, + ], + 'tw' => [ + 'code' => 'tw', + 'english' => "Traditional Chinese", + 'local' => "中文 (繁體)", + 'rtl' => false, + 'country' => 'tw', + 'variant' => false, + ], + 'ty' => [ + 'code' => 'ty', + 'english' => "Tahitian", + 'local' => "te reo Tahiti, te reo Māʼohi", + 'rtl' => false, + 'country' => 'pf', + 'variant' => false, + ], + 'uk' => [ + 'code' => 'uk', + 'english' => "Ukrainian", + 'local' => "Українська", + 'rtl' => false, + 'country' => 'ua', + 'variant' => false, + ], + 'ur' => [ + 'code' => 'ur', + 'english' => "Urdu", + 'local' => "اردو", + 'rtl' => true, + 'country' => 'pk', + 'variant' => false, + ], + 'uz' => [ + 'code' => 'uz', + 'english' => "Uzbek", + 'local' => "O'zbek", + 'rtl' => false, + 'country' => 'uz', + 'variant' => false, + ], + 'vi' => [ + 'code' => 'vi', + 'english' => "Vietnamese", + 'local' => "Tiếng Việt", + 'rtl' => false, + 'country' => 'vn', + 'variant' => false, + ], + 'xh' => [ + 'code' => 'xh', + 'english' => "Xhosa", + 'local' => "isiXhosa", + 'rtl' => false, + 'country' => 'za', + 'variant' => false, + ], + 'yi' => [ + 'code' => 'yi', + 'english' => "Yiddish", + 'local' => "ייִדיש", + 'rtl' => false, + 'country' => 'il', + 'variant' => false, + ], + 'yo' => [ + 'code' => 'yo', + 'english' => "Yoruba", + 'local' => "Yorùbá", + 'rtl' => false, + 'country' => 'ng', + 'variant' => false, + ], + 'zh' => [ + 'code' => 'zh', + 'english' => "Simplified Chinese", + 'local' => "中文 (简体)", + 'rtl' => false, + 'country' => 'cn', + 'variant' => false, + ], + 'zu' => [ + 'code' => 'zu', + 'english' => "Zulu", + 'local' => "isiZulu", + 'rtl' => false, + 'country' => 'za', + 'variant' => false, + ], + 'hm' => [ + 'code' => 'hm', + 'english' => "Hmong", + 'local' => "Hmoob", + 'rtl' => false, + 'country' => 'hmn', + 'variant' => false, + ], + 'cb' => [ + 'code' => 'cb', + 'english' => "Cebuano", + 'local' => "Sugbuanon", + 'rtl' => false, + 'country' => 'ph', + 'variant' => false, + ], + 'or' => [ + 'code' => 'or', + 'english' => "Odia", + 'local' => "ଓଡ଼ିଆ", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'tk' => [ + 'code' => 'tk', + 'english' => "Turkmen", + 'local' => "Türkmen", + 'rtl' => false, + 'country' => 'tr', + 'variant' => false, + ], + 'ug' => [ + 'code' => 'ug', + 'english' => "Uyghur", + 'local' => "ئۇيغۇر", + 'rtl' => true, + 'country' => 'uig', + 'variant' => false, + ], + 'fc' => [ + 'code' => 'fc', + 'english' => "French (Canada)", + 'local' => "Français (Canada)", + 'rtl' => false, + 'country' => 'ca', + 'variant' => true, + ], + 'as' => [ + 'code' => 'as', + 'english' => "Assamese", + 'local' => "অসমীয়া", + 'rtl' => false, + 'country' => 'in', + 'variant' => false, + ], + 'sa' => [ + 'code' => 'sa', + 'english' => "Serbian (Latin)", + 'local' => "Srpski", + 'rtl' => false, + 'country' => 'rs', + 'variant' => false, + ], + 'om' => [ + 'code' => 'om', + 'english' => "Oromo", + 'local' => "Afaan Oromoo", + 'rtl' => false, + 'country' => 'et', + 'variant' => false, + ], + 'iu' => [ + 'code' => 'iu', + 'english' => "Inuktitut", + 'local' => "ᐃᓄᒃᑎᑐᑦ", + 'rtl' => false, + 'country' => 'ca', + 'variant' => false, + ], + 'ti' => [ + 'code' => 'ti', + 'english' => "Tigrinya", + 'local' => "ቲግሪንያ", + 'rtl' => false, + 'country' => 'er', + 'variant' => false, + ], + 'bm' => [ + 'code' => 'bm', + 'english' => "Bambara", + 'local' => "Bamanankan", + 'rtl' => false, + 'country' => 'ml', + 'variant' => false, + ], + 'bo' => [ + 'code' => 'bo', + 'english' => "Tibetan", + 'local' => "བོད་ཡིག", + 'rtl' => false, + 'country' => 'cn', + 'variant' => false, + ], + 'ak' => [ + 'code' => 'ak', + 'english' => "Akan", + 'local' => "Baoulé", + 'rtl' => false, + 'country' => 'gh', + 'variant' => false, + ], + 'rw' => [ + 'code' => 'rw', + 'english' => "Kinyarwanda", + 'local' => "Kinyarwanda", + 'rtl' => false, + 'country' => 'rw', + 'variant' => false, + ], + 'kb' => [ + 'code' => 'kb', + 'english' => "Kurdish (Sorani)", + 'local' => "سۆرانی", + 'rtl' => true, + 'country' => 'iq', + 'variant' => false, + ], + 'fo' => [ + 'code' => 'fo', + 'english' => "Faroese", + 'local' => "Føroyskt", + 'rtl' => false, + 'country' => 'fo', + 'variant' => false, + ] + ]; +} + +function test(string $code): void +{ + $country = Languages::DATA[$code]['country']; + + if ($country === 'fo' || $country === 'Faroese' || $country === 'Føroyskt') { + // foo + } else { + assertType('(bool|string)', $country); + } +} + From 468f7f85a504b676e2e9925699fd1fee69508b34 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 09:15:36 +0100 Subject: [PATCH 1162/3097] Fix build after merge --- src/Analyser/MutatingScope.php | 18 +++++++++--------- tests/PHPStan/Analyser/nsrt/bug-1021.php | 2 +- ...trictComparisonOfDifferentTypesRuleTest.php | 1 - .../PHPStan/Rules/Variables/EmptyRuleTest.php | 1 - 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 71970153330..78c84ba3be8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4920,7 +4920,7 @@ public function processClosureScope( $prevVariableType = $prevScope->getVariableType($variableName); if (!$variableType->equals($prevVariableType)) { $variableType = TypeCombinator::union($variableType, $prevVariableType); - $variableType = self::generalizeType($variableType, $prevVariableType, 0); + $variableType = $this->generalizeType($variableType, $prevVariableType, 0); } } @@ -5045,7 +5045,7 @@ private function generalizeVariableTypeHolders( $variableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $variableTypeHolder->getExpr(), - self::generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0), + $this->generalizeType($variableTypeHolder->getType(), $otherVariableTypeHolders[$variableExprString]->getType(), 0), $variableTypeHolder->getCertainty(), ); } @@ -5053,7 +5053,7 @@ private function generalizeVariableTypeHolders( return $variableTypeHolders; } - private static function generalizeType(Type $a, Type $b, int $depth): Type + private function generalizeType(Type $a, Type $b, int $depth): Type { if ($a->equals($b)) { return $a; @@ -5146,7 +5146,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { $resultArrayBuilder->setOffsetValueType( $keyType, - self::generalizeType( + $this->generalizeType( $constantArraysA->getOffsetValueType($keyType), $constantArraysB->getOffsetValueType($keyType), $depth + 1, @@ -5158,13 +5158,13 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type $resultTypes[] = $resultArrayBuilder->getArray(); } else { $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)), - TypeCombinator::union(self::generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)), + TypeCombinator::union($this->generalizeType($constantArraysA->getIterableKeyType(), $constantArraysB->getIterableKeyType(), $depth + 1)), + TypeCombinator::union($this->generalizeType($constantArraysA->getIterableValueType(), $constantArraysB->getIterableValueType(), $depth + 1)), ); if ( $constantArraysA->isIterableAtLeastOnce()->yes() && $constantArraysB->isIterableAtLeastOnce()->yes() - && $constantArraysA->getArraySize()->getGreaterOrEqualType()->isSuperTypeOf($constantArraysB->getArraySize())->yes() + && $constantArraysA->getArraySize()->getGreaterOrEqualType($this->phpVersion)->isSuperTypeOf($constantArraysB->getArraySize())->yes() ) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); } @@ -5205,8 +5205,8 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type } $resultType = new ArrayType( - TypeCombinator::union(self::generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)), - TypeCombinator::union(self::generalizeType($aValueType, $bValueType, $depth + 1)), + TypeCombinator::union($this->generalizeType($generalArraysA->getIterableKeyType(), $generalArraysB->getIterableKeyType(), $depth + 1)), + TypeCombinator::union($this->generalizeType($aValueType, $bValueType, $depth + 1)), ); if ($generalArraysA->isIterableAtLeastOnce()->yes() && $generalArraysB->isIterableAtLeastOnce()->yes()) { $resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType()); diff --git a/tests/PHPStan/Analyser/nsrt/bug-1021.php b/tests/PHPStan/Analyser/nsrt/bug-1021.php index 189cba0b5a3..37e2f7244f0 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1021.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1021.php @@ -13,7 +13,7 @@ function foobar() { } } - assertType('array<1|2|3>&list', $x); + assertType('list<1|2|3>', $x); if ($x) { } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 822aaa6e379..fc8ebd021a7 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -738,7 +738,6 @@ public function testLastMatchArm(bool $reportAlwaysTrueInLastCondition, array $e public function testBug8030(): void { - $this->checkAlwaysTrueStrictComparison = true; $this->analyse([__DIR__ . '/data/bug-8030.php'], []); } diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 4aa00a2064a..f45e41b774e 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -202,7 +202,6 @@ public function testBug9403(bool $treatPhpDocTypesAsCertain): void public function testBug12658(): void { $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = false; $this->analyse([__DIR__ . '/data/bug-12658.php'], []); } From 96b43048ac9be8084aaac8825660499abede46c0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 09:21:51 +0100 Subject: [PATCH 1163/3097] Fix --- tests/PHPStan/Analyser/nsrt/bug-1021.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-1021.php b/tests/PHPStan/Analyser/nsrt/bug-1021.php index 189cba0b5a3..c02e342e1e2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-1021.php +++ b/tests/PHPStan/Analyser/nsrt/bug-1021.php @@ -13,7 +13,7 @@ function foobar() { } } - assertType('array<1|2|3>&list', $x); + assertType('array<0|1|2, 1|2|3>&list', $x); if ($x) { } From 7d8742a37c9103067f8750facea0557bb4a0dea8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 09:50:50 +0100 Subject: [PATCH 1164/3097] Fix --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4350b592526..0e39ac8ab6e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5047,7 +5047,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type $constantArraysB = TypeCombinator::union(...$constantArrays['b']); if ( $constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType()) - && $constantArraysA->getArraySize()->equals($constantArraysB->getArraySize()) + && $constantArraysA->getArraySize()->getGreaterOrEqualType()->isSuperTypeOf($constantArraysB->getArraySize())->yes() ) { $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { From 781aefaf6b2bbaca5a02e017984852fc1b9b99f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 09:52:04 +0100 Subject: [PATCH 1165/3097] Fix after merge --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 85fb77ed817..91ca0f68efe 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5140,7 +5140,7 @@ private function generalizeType(Type $a, Type $b, int $depth): Type $constantArraysB = TypeCombinator::union(...$constantArrays['b']); if ( $constantArraysA->getIterableKeyType()->equals($constantArraysB->getIterableKeyType()) - && $constantArraysA->getArraySize()->getGreaterOrEqualType()->isSuperTypeOf($constantArraysB->getArraySize())->yes() + && $constantArraysA->getArraySize()->getGreaterOrEqualType($this->phpVersion)->isSuperTypeOf($constantArraysB->getArraySize())->yes() ) { $resultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); foreach (TypeUtils::flattenTypes($constantArraysA->getIterableKeyType()) as $keyType) { From fe595cba71279d990a8be265d594b470265ea318 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 11:33:35 +0100 Subject: [PATCH 1166/3097] ArrayType - setting new offset with `[]` on array with constant-integer offset will add one to the offset --- src/Type/ArrayType.php | 12 +++++- .../PHPStan/Rules/Variables/IssetRuleTest.php | 8 ++++ .../PHPStan/Rules/Variables/data/bug-9328.php | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-9328.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 74b47dea0b0..38d1e6741b5 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -455,7 +455,17 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni if ($isKeyTypeInteger->no()) { $offsetType = new IntegerType(); } elseif ($isKeyTypeInteger->yes()) { - $offsetType = $this->keyType; + /** @var list $constantScalars */ + $constantScalars = $this->keyType->getConstantScalarTypes(); + if (count($constantScalars) > 0) { + foreach ($constantScalars as $constantScalar) { + $constantScalars[] = ConstantTypeHelper::getTypeFromValue($constantScalar->getValue() + 1); + } + + $offsetType = TypeCombinator::union(...$constantScalars); + } else { + $offsetType = $this->keyType; + } } else { $integerTypes = []; TypeTraverser::map($this->keyType, static function (Type $type, callable $traverse) use (&$integerTypes): Type { diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 2c22dfd35a8..16d92ed7677 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -483,4 +483,12 @@ public function testBug10064(): void $this->analyse([__DIR__ . '/data/bug-10064.php'], []); } + public function testBug9328(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->strictUnnecessaryNullsafePropertyFetch = true; + + $this->analyse([__DIR__ . '/data/bug-9328.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-9328.php b/tests/PHPStan/Rules/Variables/data/bug-9328.php new file mode 100644 index 00000000000..92221f90408 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-9328.php @@ -0,0 +1,37 @@ + 2 + ) { + // flush previously collected section: + if ($lines) { + $sections[] = [ + 'name' => $currentSection, + 'lines' => $lines, + ]; + } + $currentSection = substr($line, 1, -1); + $lines = []; + } + $lines[] = $line; + } + + if (isset($sections[1])) { + echo "We have multiple remaining sections!\n"; + } +}; From fac96bf560a6dc0a6b17f592398807464ea68c2f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 12:57:22 +0100 Subject: [PATCH 1167/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/9850 --- ...isonOperatorsConstantConditionRuleTest.php | 6 +++++ .../Rules/Comparison/data/bug-9850.php | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-9850.php diff --git a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php index 4d9733a493c..9db84af00da 100644 --- a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php @@ -240,4 +240,10 @@ public function testBug6642(): void $this->analyse([__DIR__ . '/data/bug-6642.php'], []); } + public function testBug9850(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-9850.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-9850.php b/tests/PHPStan/Rules/Comparison/data/bug-9850.php new file mode 100644 index 00000000000..2f1ba9e50ac --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-9850.php @@ -0,0 +1,24 @@ += 3) { + // todo + } + } + } + } +} From c9c74c5337df5ac432a1c11fbafe47f0cfde9b5b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 9 Mar 2025 12:59:50 +0100 Subject: [PATCH 1168/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/10650 --- tests/PHPStan/Analyser/nsrt/bug-10650.php | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10650.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10650.php b/tests/PHPStan/Analyser/nsrt/bug-10650.php new file mode 100644 index 00000000000..97ce54a9af0 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10650.php @@ -0,0 +1,33 @@ + $distPoints + */ + public function repro(array $distPoints): void + { + $ranges = []; + $pointPrev = null; + foreach ($distPoints as $distPoint) { + if ($pointPrev !== null) { + $ranges[] = 'x'; + } + $pointPrev = $distPoint; + } + + assertType('list<\'x\'>', $ranges); + + foreach (array_keys($ranges) as $key) { + if (mt_rand() === 0) { + unset($ranges[$key]); + } + } + + assertType('array, \'x\'>', $ranges); + } +} From 215468593c4bb09e3ec6dcb7e13249edf9e9c910 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Mar 2025 13:12:06 +0100 Subject: [PATCH 1169/3097] Fix false positives on non-existing array offsets --- src/Analyser/TypeSpecifier.php | 78 ++++++++++++++++++- ...nexistentOffsetInArrayDimFetchRuleTest.php | 44 +++++++++++ ...rray-dim-after-array-key-first-or-last.php | 75 ++++++++++++++++++ .../data/array-dim-after-array-search.php | 37 +++++++++ .../Arrays/data/array-dim-after-count.php | 45 +++++++++++ 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/array-dim-after-array-key-first-or-last.php create mode 100644 tests/PHPStan/Rules/Arrays/data/array-dim-after-array-search.php create mode 100644 tests/PHPStan/Rules/Arrays/data/array-dim-after-count.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 2f57e11abd9..3c1dc42d5f7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -664,11 +664,85 @@ public function specifyTypesInCondition( if (!$scope instanceof MutatingScope) { throw new ShouldNotHappenException(); } + if ($context->null()) { - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr); + $specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr); + + // infer $arr[$key] after $key = array_key_first/last($arr) + if ( + $expr->expr instanceof FuncCall + && $expr->expr->name instanceof Name + && in_array($expr->expr->name->toLowerString(), ['array_key_first', 'array_key_last'], true) + && count($expr->expr->getArgs()) >= 1 + ) { + $arrayArg = $expr->expr->getArgs()[0]->value; + $arrayType = $scope->getType($arrayArg); + if ( + $arrayType->isArray()->yes() + && $arrayType->isIterableAtLeastOnce()->yes() + ) { + $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); + $iterableValueType = $expr->expr->name->toLowerString() === 'array_key_first' + ? $arrayType->getFirstIterableValueType() + : $arrayType->getLastIterableValueType(); + + return $specifiedTypes->unionWith( + $this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope), + ); + } + } + + // infer $list[$count] after $count = count($list) - 1 + if ( + $expr->expr instanceof Expr\BinaryOp\Minus + && $expr->expr->left instanceof FuncCall + && $expr->expr->left->name instanceof Name + && in_array($expr->expr->left->name->toLowerString(), ['count', 'sizeof'], true) + && count($expr->expr->left->getArgs()) >= 1 + && $expr->expr->right instanceof Node\Scalar\Int_ + && $expr->expr->right->value === 1 + ) { + $arrayArg = $expr->expr->left->getArgs()[0]->value; + $arrayType = $scope->getType($arrayArg); + if ( + $arrayType->isList()->yes() + && $arrayType->isIterableAtLeastOnce()->yes() + ) { + $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); + + return $specifiedTypes->unionWith( + $this->create($dimFetch, $arrayType->getLastIterableValueType(), TypeSpecifierContext::createTrue(), $scope), + ); + } + } + + return $specifiedTypes; } - return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr); + $specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr); + + if ($context->true()) { + // infer $arr[$key] after $key = array_search($needle, $arr) + if ( + $expr->expr instanceof FuncCall + && $expr->expr->name instanceof Name + && $expr->expr->name->toLowerString() === 'array_search' + && count($expr->expr->getArgs()) >= 2 + ) { + $arrayArg = $expr->expr->getArgs()[1]->value; + $arrayType = $scope->getType($arrayArg); + + if ($arrayType->isArray()->yes()) { + $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); + $iterableValueType = $arrayType->getIterableValueType(); + + return $specifiedTypes->unionWith( + $this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope), + ); + } + } + } + return $specifiedTypes; } elseif ( $expr instanceof Expr\Isset_ && count($expr->vars) > 0 diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 79e015fbfea..518fabc0b96 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -785,4 +785,48 @@ public function testBug12122(): void $this->analyse([__DIR__ . '/data/bug-12122.php'], []); } + public function testArrayDimFetchAfterArrayKeyFirstOrLast(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/array-dim-after-array-key-first-or-last.php'], [ + [ + 'Offset null does not exist on array{}.', + 19, + ], + ]); + } + + public function testArrayDimFetchAfterCount(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/array-dim-after-count.php'], [ + [ + 'Offset int<0, max> might not exist on list.', + 26, + ], + [ + 'Offset int<-1, max> might not exist on array.', + 35, + ], + [ + 'Offset int<0, max> might not exist on non-empty-array.', + 42, + ], + ]); + } + + public function testArrayDimFetchAfterArraySearch(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/array-dim-after-array-search.php'], [ + [ + 'Offset int|string might not exist on array.', + 20, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-key-first-or-last.php b/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-key-first-or-last.php new file mode 100644 index 00000000000..e27fcfa1759 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-key-first-or-last.php @@ -0,0 +1,75 @@ += 8.0 + +declare(strict_types = 1); + +namespace ArrayDimAfterArrayKeyFirstOrLast; + +class HelloWorld +{ + /** + * @param list $hellos + */ + public function last(array $hellos): string + { + if ($hellos !== []) { + $last = array_key_last($hellos); + return $hellos[$last]; + } else { + $last = array_key_last($hellos); + return $hellos[$last]; + } + } + + /** + * @param array $hellos + */ + public function lastOnArray(array $hellos): string + { + if ($hellos !== []) { + $last = array_key_last($hellos); + return $hellos[$last]; + } + + return 'nothing'; + } + + /** + * @param list $hellos + */ + public function first(array $hellos): string + { + if ($hellos !== []) { + $first = array_key_first($hellos); + return $hellos[$first]; + } + + return 'nothing'; + } + + /** + * @param array $hellos + */ + public function firstOnArray(array $hellos): string + { + if ($hellos !== []) { + $first = array_key_first($hellos); + return $hellos[$first]; + } + + return 'nothing'; + } + + /** + * @param array{first: int, middle: float, last: bool} $hellos + */ + public function shape(array $hellos): int|bool + { + $first = array_key_first($hellos); + $last = array_key_last($hellos); + + if (rand(0,1)) { + return $hellos[$first]; + } + return $hellos[$last]; + } +} diff --git a/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-search.php b/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-search.php new file mode 100644 index 00000000000..3aa2d4c21b1 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/array-dim-after-array-search.php @@ -0,0 +1,37 @@ += 8.0 + +declare(strict_types = 1); + +namespace ArrayDimAfterArraySeach; + +class HelloWorld +{ + public function doFoo(array $arr, string $needle): string + { + if (($key = array_search($needle, $arr, true)) !== false) { + echo $arr[$key]; + } + } + + public function doBar(array $arr, string $needle): string + { + $key = array_search($needle, $arr, true); + if ($key !== false) { + echo $arr[$key]; + } + } + + public function doFooBar(array $arr, string $needle): string + { + if (($key = array_search($needle, $arr, false)) !== false) { + echo $arr[$key]; + } + } + + public function doBaz(array $arr, string $needle): string + { + if (($key = array_search($needle, $arr)) !== false) { + echo $arr[$key]; + } + } +} diff --git a/tests/PHPStan/Rules/Arrays/data/array-dim-after-count.php b/tests/PHPStan/Rules/Arrays/data/array-dim-after-count.php new file mode 100644 index 00000000000..4f52d30b24d --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/array-dim-after-count.php @@ -0,0 +1,45 @@ + $hellos + */ + public function works(array $hellos): string + { + if ($hellos === []) { + return 'nothing'; + } + + $count = count($hellos) - 1; + return $hellos[$count]; + } + + /** + * @param list $hellos + */ + public function offByOne(array $hellos): string + { + $count = count($hellos); + return $hellos[$count]; + } + + /** + * @param array $hellos + */ + public function maybeInvalid(array $hellos): string + { + $count = count($hellos) - 1; + echo $hellos[$count]; + + if ($hellos === []) { + return 'nothing'; + } + + $count = count($hellos) - 1; + return $hellos[$count]; + } + +} From 9cd58b5a912dd4d014effd431e1754ab145bef8f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Mar 2025 13:13:42 +0100 Subject: [PATCH 1170/3097] More precise `implode()` return type --- .../Php/ImplodeFunctionReturnTypeExtension.php | 9 +++++---- .../Php/StrCaseFunctionsReturnTypeExtension.php | 11 ++++++----- .../Php/StrContainingTypeSpecifyingExtension.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-11201.php | 2 +- tests/PHPStan/Analyser/nsrt/implode.php | 14 +++++++++++++- .../nsrt/non-empty-string-str-containing-fns.php | 10 ++++++++++ tests/PHPStan/Analyser/nsrt/non-empty-string.php | 5 +++-- tests/PHPStan/Analyser/nsrt/non-falsy-string.php | 1 + 8 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index a052a43416c..15d1d86706c 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -81,10 +81,11 @@ private function implode(Type $arrayType, Type $separatorType): Type } $accessoryTypes = []; + $valueTypeAsString = $arrayType->getIterableValueType()->toString(); if ($arrayType->isIterableAtLeastOnce()->yes()) { - if ($arrayType->getIterableValueType()->isNonFalsyString()->yes() || $separatorType->isNonFalsyString()->yes()) { + if ($valueTypeAsString->isNonFalsyString()->yes() || $separatorType->isNonFalsyString()->yes()) { $accessoryTypes[] = new AccessoryNonFalsyStringType(); - } elseif ($arrayType->getIterableValueType()->isNonEmptyString()->yes() || $separatorType->isNonEmptyString()->yes()) { + } elseif ($valueTypeAsString->isNonEmptyString()->yes() || $separatorType->isNonEmptyString()->yes()) { $accessoryTypes[] = new AccessoryNonEmptyStringType(); } } @@ -93,10 +94,10 @@ private function implode(Type $arrayType, Type $separatorType): Type if ($arrayType->getIterableValueType()->isLiteralString()->yes() && $separatorType->isLiteralString()->yes()) { $accessoryTypes[] = new AccessoryLiteralStringType(); } - if ($arrayType->getIterableValueType()->isLowercaseString()->yes() && $separatorType->isLowercaseString()->yes()) { + if ($valueTypeAsString->isLowercaseString()->yes() && $separatorType->isLowercaseString()->yes()) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } - if ($arrayType->getIterableValueType()->isUppercaseString()->yes() && $separatorType->isUppercaseString()->yes()) { + if ($valueTypeAsString->isUppercaseString()->yes() && $separatorType->isUppercaseString()->yes()) { $accessoryTypes[] = new AccessoryUppercaseStringType(); } diff --git a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php index db36561ab67..c6226f5a5f8 100644 --- a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php +++ b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php @@ -142,18 +142,19 @@ public function getTypeFromFunctionCall( } $accessoryTypes = []; - if ($forceLowercase || ($keepLowercase && $argType->isLowercaseString()->yes())) { + $argStringType = $argType->toString(); + if ($forceLowercase || ($keepLowercase && $argStringType->isLowercaseString()->yes())) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } - if ($forceUppercase || ($keepUppercase && $argType->isUppercaseString()->yes())) { + if ($forceUppercase || ($keepUppercase && $argStringType->isUppercaseString()->yes())) { $accessoryTypes[] = new AccessoryUppercaseStringType(); } - if ($argType->isNumericString()->yes()) { + if ($argStringType->isNumericString()->yes()) { $accessoryTypes[] = new AccessoryNumericStringType(); - } elseif ($argType->isNonFalsyString()->yes()) { + } elseif ($argStringType->isNonFalsyString()->yes()) { $accessoryTypes[] = new AccessoryNonFalsyStringType(); - } elseif ($argType->isNonEmptyString()->yes()) { + } elseif ($argStringType->isNonEmptyString()->yes()) { $accessoryTypes[] = new AccessoryNonEmptyStringType(); } diff --git a/src/Type/Php/StrContainingTypeSpecifyingExtension.php b/src/Type/Php/StrContainingTypeSpecifyingExtension.php index 8cf678ae56d..1f1a0e168ea 100644 --- a/src/Type/Php/StrContainingTypeSpecifyingExtension.php +++ b/src/Type/Php/StrContainingTypeSpecifyingExtension.php @@ -66,7 +66,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n [$hackstackArg, $needleArg] = self::STR_CONTAINING_FUNCTIONS[strtolower($functionReflection->getName())]; $haystackType = $scope->getType($args[$hackstackArg]->value); - $needleType = $scope->getType($args[$needleArg]->value); + $needleType = $scope->getType($args[$needleArg]->value)->toString(); if ($needleType->isNonEmptyString()->yes() && $haystackType->isString()->yes()) { $accessories = [ diff --git a/tests/PHPStan/Analyser/nsrt/bug-11201.php b/tests/PHPStan/Analyser/nsrt/bug-11201.php index 202e5b1700a..705e237a854 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11201.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11201.php @@ -41,7 +41,7 @@ function returnsBool(): bool { assertType('string', $s); $s = sprintf("%s", implode(', ', array_map('intval', returnsArray()))); -assertType('string', $s); +assertType('lowercase-string&uppercase-string', $s); $s = sprintf('%2$s', 1234, returnsNonFalsyString()); assertType('non-falsy-string', $s); diff --git a/tests/PHPStan/Analyser/nsrt/implode.php b/tests/PHPStan/Analyser/nsrt/implode.php index 51e121a4c14..5c96b64e674 100644 --- a/tests/PHPStan/Analyser/nsrt/implode.php +++ b/tests/PHPStan/Analyser/nsrt/implode.php @@ -6,6 +6,18 @@ class Foo { + /** + * @param array $arr + */ + public function ints(array $arr, int $i) + { + assertType("lowercase-string&uppercase-string", implode($arr)); + assertType("lowercase-string&non-empty-string&uppercase-string", implode([$i, $i])); + if ($i !== 0) { + assertType("lowercase-string&non-falsy-string&uppercase-string", implode([$i, $i])); + } + } + const X = 'x'; const ONE = 1; @@ -49,6 +61,6 @@ public function constArrays5($constArr) { /** @param array{0: 1, 1: 'a'|'b', 3?: 'c'|'d', 4?: 'e'|'f', 5?: 'g'|'h', 6?: 'x'|'y'} $constArr */ public function constArrays6($constArr) { - assertType("string", implode('', $constArr)); + assertType("lowercase-string&non-falsy-string", implode('', $constArr)); } } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php index 12d34950982..19482b7fd04 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php @@ -14,6 +14,16 @@ class Foo { */ public function strContains(string $s, string $s2, $nonES, $nonFalsy, $numS, $literalS, $nonEAndNumericS, int $i): void { + if (str_contains($i, 0)) { + assertType('int', $i); + } + if (str_contains($s, 0)) { + assertType('non-empty-string', $s); + } + if (str_contains($s, 1)) { + assertType('non-falsy-string', $s); + } + if (str_contains($s, ':')) { assertType('non-falsy-string', $s); } diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string.php b/tests/PHPStan/Analyser/nsrt/non-empty-string.php index cd831db4d82..11adfa5dcb1 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string.php @@ -212,7 +212,8 @@ public function sayHello(int $i): void // coming from issue #5291 $s = array(1, $i); - assertType('non-falsy-string', implode("a", $s)); + assertType('lowercase-string&non-falsy-string', implode("a", $s)); + assertType('non-falsy-string&uppercase-string', implode("A", $s)); } /** @@ -233,7 +234,7 @@ public function sayHello2(int $i): void // coming from issue #5291 $s = array(1, $i); - assertType('non-falsy-string', join("a", $s)); + assertType('lowercase-string&non-falsy-string', join("a", $s)); } /** diff --git a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php index c5fd9fc1d81..598a358927d 100644 --- a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php @@ -87,6 +87,7 @@ function stringFunctions(string $s, $nonFalsey, $arrayOfNonFalsey, $nonEmptyArra assertType('non-falsy-string', escapeshellarg($nonFalsey)); assertType('non-falsy-string', escapeshellcmd($nonFalsey)); + assertType('non-falsy-string&uppercase-string', strtoupper($s ?: 1)); assertType('non-falsy-string&uppercase-string', strtoupper($nonFalsey)); assertType('lowercase-string&non-falsy-string', strtolower($nonFalsey)); assertType('non-falsy-string&uppercase-string', mb_strtoupper($nonFalsey)); From deb091148b63515ff2765d18525090fcba7baff3 Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Sun, 9 Mar 2025 18:46:49 +0100 Subject: [PATCH 1171/3097] Detect accessing static property as non static --- src/Rules/Properties/AccessPropertiesCheck.php | 10 ++++++++++ .../Properties/AccessPropertiesRuleTest.php | 11 +++++++++++ .../PHPStan/Rules/Properties/data/bug-12692.php | 17 +++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12692.php diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index 023cc167568..f1a70365a7a 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -160,6 +160,16 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string } $propertyReflection = $type->getProperty($name, $scope); + if ($propertyReflection->isStatic()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Non-static access to static property %s::$%s.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $name, + ))->identifier('staticProperty.nonStaticAccess')->build(), + ]; + } + if ($write) { if ($scope->canWriteProperty($propertyReflection)) { return []; diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index f1e6d16f503..e6aa299b750 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -344,6 +344,17 @@ public function testAccessPropertiesOnThisOnly(): void ); } + public function testBug12692(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = false; + $this->checkDynamicProperties = false; + $this->analyse([__DIR__ . '/data/bug-12692.php'], [[ + 'Non-static access to static property Bug12692\Foo::$static.', + 14, + ]]); + } + public function testAccessPropertiesAfterIsNullInBooleanOr(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Properties/data/bug-12692.php b/tests/PHPStan/Rules/Properties/data/bug-12692.php new file mode 100644 index 00000000000..237f71e684f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12692.php @@ -0,0 +1,17 @@ +static; + } + +} From 08e38e26faa2ae2a3e498078bf809c39a9b1261b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Mar 2025 07:38:38 +0100 Subject: [PATCH 1172/3097] Add regression test --- ...nexistentOffsetInArrayDimFetchRuleTest.php | 7 ++++++ tests/PHPStan/Rules/Arrays/data/bug-8649.php | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-8649.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 518fabc0b96..55da1ddeb3d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -829,4 +829,11 @@ public function testArrayDimFetchAfterArraySearch(): void ]); } + public function testBug8649(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-8649.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-8649.php b/tests/PHPStan/Rules/Arrays/data/bug-8649.php new file mode 100644 index 00000000000..f23eb8f5162 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-8649.php @@ -0,0 +1,25 @@ + 'test'], + ['b' => 'asdf'], + ]; + + foreach ($test as $property) { + $firstKey = array_key_first($property); + + if ($firstKey === 'b') { + continue; + } + + echo($property[$firstKey]); + } + } +} From 7d4dcb5e48ef063cef058655a61e160ca6725541 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 10 Mar 2025 09:53:50 +0100 Subject: [PATCH 1173/3097] Infer types of variables with dynamic name --- src/Analyser/MutatingScope.php | 15 +++++++++++---- tests/PHPStan/Analyser/nsrt/bug-12398.php | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12398.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 91ca0f68efe..45e956408f9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2000,12 +2000,19 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu ); } - if ($node instanceof Variable && is_string($node->name)) { - if ($this->hasVariableType($node->name)->no()) { - return new ErrorType(); + if ($node instanceof Variable) { + if (is_string($node->name)) { + if ($this->hasVariableType($node->name)->no()) { + return new ErrorType(); + } + + return $this->getVariableType($node->name); } - return $this->getVariableType($node->name); + $nameType = $this->getType($node->name); + if (count($nameType->getConstantStrings()) > 0) { + return TypeCombinator::union(...array_map(fn ($constantString) => $this->getVariableType($constantString->getValue()), $nameType->getConstantStrings())); + } } if ($node instanceof Expr\ArrayDimFetch && $node->dim !== null) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12398.php b/tests/PHPStan/Analyser/nsrt/bug-12398.php new file mode 100644 index 00000000000..ee8450a8e36 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12398.php @@ -0,0 +1,16 @@ + Date: Mon, 10 Mar 2025 21:24:22 +0100 Subject: [PATCH 1174/3097] Update propertyAccesses levels test data --- .../data/propertyAccesses-10-missing.json | 20 +++++++++++++++++ .../Levels/data/propertyAccesses-2.json | 20 +++++++++++++++++ .../data/propertyAccesses-7-missing.json | 22 +++++++++++++++++++ .../data/propertyAccesses-8-missing.json | 20 +++++++++++++++++ .../data/propertyAccesses-9-missing.json | 20 +++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 tests/PHPStan/Levels/data/propertyAccesses-7-missing.json diff --git a/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json index 1a8bc8b4b7d..fd6f669c7c1 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-10-missing.json @@ -1,14 +1,34 @@ [ + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 58, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 61, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 162, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 166, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 170, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", "line": 63, diff --git a/tests/PHPStan/Levels/data/propertyAccesses-2.json b/tests/PHPStan/Levels/data/propertyAccesses-2.json index 95bf5c3c296..1d3376b7811 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-2.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-2.json @@ -9,21 +9,41 @@ "line": 36, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 58, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 61, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 64, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Baz::$foo.", "line": 66, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 162, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 166, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 170, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Baz::$foo.", "line": 173, diff --git a/tests/PHPStan/Levels/data/propertyAccesses-7-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-7-missing.json new file mode 100644 index 00000000000..7d9c064f4b8 --- /dev/null +++ b/tests/PHPStan/Levels/data/propertyAccesses-7-missing.json @@ -0,0 +1,22 @@ +[ + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 58, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 162, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 170, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses-8-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-8-missing.json index 1a8bc8b4b7d..fd6f669c7c1 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-8-missing.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-8-missing.json @@ -1,14 +1,34 @@ [ + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 58, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 61, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 162, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 166, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 170, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", "line": 63, diff --git a/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json index 1a8bc8b4b7d..fd6f669c7c1 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json @@ -1,14 +1,34 @@ [ + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 58, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 61, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 162, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", "line": 166, "ignorable": true }, + { + "message": "Non-static access to static property Levels\\PropertyAccesses\\Bar::$bar.", + "line": 170, + "ignorable": true + }, { "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", "line": 63, From 60b29fab9e1509b5c1c67f9ee5e4851a1b21c8d6 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 10 Mar 2025 21:52:58 +0100 Subject: [PATCH 1175/3097] Improve `count()` narrowing of constant arrays --- src/Analyser/TypeSpecifier.php | 217 ++++++++---------- tests/PHPStan/Analyser/nsrt/bug-4700.php | 8 +- tests/PHPStan/Analyser/nsrt/bug11480.php | 2 +- tests/PHPStan/Analyser/nsrt/count-type.php | 23 ++ .../CallToFunctionParametersRuleTest.php | 5 + .../PHPStan/Rules/Functions/data/bug-3631.php | 27 +++ 6 files changed, 150 insertions(+), 132 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3631.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 3c1dc42d5f7..73a34c21758 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -272,22 +272,21 @@ public function specifyTypesInCondition( ) { $argType = $scope->getType($expr->right->getArgs()[0]->value); - if ($argType instanceof UnionType) { - $sizeType = null; - if ($leftType instanceof ConstantIntegerType) { - if ($orEqual) { - $sizeType = IntegerRangeType::createAllGreaterThanOrEqualTo($leftType->getValue()); - } else { - $sizeType = IntegerRangeType::createAllGreaterThan($leftType->getValue()); - } - } elseif ($leftType instanceof IntegerRangeType) { - $sizeType = $leftType; + if ($leftType instanceof ConstantIntegerType) { + if ($orEqual) { + $sizeType = IntegerRangeType::createAllGreaterThanOrEqualTo($leftType->getValue()); + } else { + $sizeType = IntegerRangeType::createAllGreaterThan($leftType->getValue()); } + } elseif ($leftType instanceof IntegerRangeType) { + $sizeType = $leftType->shift($offset); + } else { + $sizeType = $leftType; + } - $narrowed = $this->narrowUnionByArraySize($expr->right, $argType, $sizeType, $context, $scope, $expr); - if ($narrowed !== null) { - return $narrowed; - } + $specifiedTypes = $this->specifyTypesForCountFuncCall($expr->right, $argType, $sizeType, $context, $scope, $expr); + if ($specifiedTypes !== null) { + $result = $result->unionWith($specifiedTypes); } if ( @@ -1046,115 +1045,95 @@ public function specifyTypesInCondition( return (new SpecifiedTypes([], []))->setRootExpr($expr); } - private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argType, ?Type $sizeType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr): ?SpecifiedTypes + private function specifyTypesForCountFuncCall( + FuncCall $countFuncCall, + Type $type, + Type $sizeType, + TypeSpecifierContext $context, + Scope $scope, + Expr $rootExpr, + ): ?SpecifiedTypes { - if ($sizeType === null) { - return null; - } - if (count($countFuncCall->getArgs()) === 1) { $isNormalCount = TrinaryLogic::createYes(); } else { $mode = $scope->getType($countFuncCall->getArgs()[1]->value); - $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($argType->getIterableValueType()->isArray()->negate()); + $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($type->getIterableValueType()->isArray()->negate()); } + $isList = $type->isList(); if ( - $isNormalCount->yes() - && $argType->isConstantArray()->yes() + !$isNormalCount->yes() + || (!$type->isConstantArray()->yes() && !$isList->yes()) + || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing ) { - $result = []; - foreach ($argType->getTypes() as $innerType) { - $arraySize = $innerType->getArraySize(); - $isSize = $sizeType->isSuperTypeOf($arraySize); - if ($context->truthy()) { - if ($isSize->no()) { - continue; - } - - $constArray = $this->turnListIntoConstantArray($countFuncCall, $innerType, $sizeType, $scope); - if ($constArray !== null) { - $innerType = $constArray; - } - } - if ($context->falsey()) { - if (!$isSize->yes()) { - continue; - } - } - - $result[] = $innerType; - } - - return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$result), $context, $scope)->setRootExpr($rootExpr); + return null; } - return null; - } - - private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type, Type $sizeType, Scope $scope): ?Type - { - $argType = $scope->getType($countFuncCall->getArgs()[0]->value); - - if (count($countFuncCall->getArgs()) === 1) { - $isNormalCount = TrinaryLogic::createYes(); - } else { - $mode = $scope->getType($countFuncCall->getArgs()[1]->value); - $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($argType->getIterableValueType()->isArray()->negate()); - } + $resultTypes = []; + foreach ($type->getArrays() as $arrayType) { + $isSizeSuperTypeOfArraySize = $sizeType->isSuperTypeOf($arrayType->getArraySize()); + if ($isSizeSuperTypeOfArraySize->no()) { + continue; + } - if ( - $isNormalCount->yes() - && $type->isList()->yes() - && $sizeType instanceof ConstantIntegerType - && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT - ) { - // turn optional offsets non-optional - $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); - for ($i = 0; $i < $sizeType->getValue(); $i++) { - $offsetType = new ConstantIntegerType($i); - $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType)); + if ($context->falsey() && $isSizeSuperTypeOfArraySize->maybe()) { + continue; } - return $valueTypesBuilder->getArray(); - } - if ( - $isNormalCount->yes() - && $type->isList()->yes() - && $sizeType instanceof IntegerRangeType - && $sizeType->getMin() !== null - ) { - // turn optional offsets non-optional - $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); - for ($i = 0; $i < $sizeType->getMin(); $i++) { - $offsetType = new ConstantIntegerType($i); - $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType)); - } - if ($sizeType->getMax() !== null) { - for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) { + if ( + $isList->yes() + && $sizeType instanceof ConstantIntegerType + && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT + ) { + // turn optional offsets non-optional + $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); + for ($i = 0; $i < $sizeType->getValue(); $i++) { $offsetType = new ConstantIntegerType($i); - $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), true); + $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType)); } - } elseif ($type->isConstantArray()->yes()) { - for ($i = $sizeType->getMin();; $i++) { + $resultTypes[] = $valueTypesBuilder->getArray(); + continue; + } + + if ( + $isList->yes() + && $sizeType instanceof IntegerRangeType + && $sizeType->getMin() !== null + ) { + // turn optional offsets non-optional + $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); + for ($i = 0; $i < $sizeType->getMin(); $i++) { $offsetType = new ConstantIntegerType($i); - $hasOffset = $type->hasOffsetValueType($offsetType); - if ($hasOffset->no()) { - break; + $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType)); + } + if ($sizeType->getMax() !== null) { + for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) { + $offsetType = new ConstantIntegerType($i); + $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), true); } - $valueTypesBuilder->setOffsetValueType($offsetType, $type->getOffsetValueType($offsetType), !$hasOffset->yes()); + } elseif ($arrayType->isConstantArray()->yes()) { + for ($i = $sizeType->getMin();; $i++) { + $offsetType = new ConstantIntegerType($i); + $hasOffset = $arrayType->hasOffsetValueType($offsetType); + if ($hasOffset->no()) { + break; + } + $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), !$hasOffset->yes()); + } + } else { + $resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + continue; } - } else { - return null; - } - $arrayType = $valueTypesBuilder->getArray(); - if ($arrayType->isIterableAtLeastOnce()->yes()) { - return $arrayType; + $resultTypes[] = $valueTypesBuilder->getArray(); + continue; } + + $resultTypes[] = $arrayType; } - return null; + return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$resultTypes), $context, $scope)->setRootExpr($rootExpr); } private function specifyTypesForConstantBinaryExpression( @@ -2186,36 +2165,20 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty ); } - if ($argType instanceof UnionType) { - $narrowed = $this->narrowUnionByArraySize($unwrappedLeftExpr, $argType, $rightType, $context, $scope, $expr); - if ($narrowed !== null) { - return $narrowed; - } + $specifiedTypes = $this->specifyTypesForCountFuncCall($unwrappedLeftExpr, $argType, $rightType, $context, $scope, $expr); + if ($specifiedTypes !== null) { + return $specifiedTypes; } - if ($context->truthy()) { - if ($argType->isArray()->yes()) { - if ( - $argType->isConstantArray()->yes() - && $rightType->isSuperTypeOf($argType->getArraySize())->no() - ) { - return $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NeverType(), $context, $scope)->setRootExpr($expr); - } - - $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr); - $constArray = $this->turnListIntoConstantArray($unwrappedLeftExpr, $argType, $rightType, $scope); - if ($constArray !== null) { - return $funcTypes->unionWith( - $this->create($unwrappedLeftExpr->getArgs()[0]->value, $constArray, $context, $scope)->setRootExpr($expr), - ); - } elseif (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) { - return $funcTypes->unionWith( - $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope)->setRootExpr($expr), - ); - } - - return $funcTypes; + if ($context->truthy() && $argType->isArray()->yes()) { + $funcTypes = $this->create($unwrappedLeftExpr, $rightType, $context, $scope)->setRootExpr($expr); + if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($rightType)->yes()) { + return $funcTypes->unionWith( + $this->create($unwrappedLeftExpr->getArgs()[0]->value, new NonEmptyArrayType(), $context, $scope)->setRootExpr($expr), + ); } + + return $funcTypes; } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-4700.php b/tests/PHPStan/Analyser/nsrt/bug-4700.php index 078ea41b12a..9d386b0c50e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4700.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4700.php @@ -40,10 +40,10 @@ function(array $array, int $count): void { if (isset($array['d'])) $a[] = $array['d']; if (isset($array['e'])) $a[] = $array['e']; if (count($a) > $count) { - assertType('int<1, 5>', count($a)); - assertType('array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); + assertType('int<2, 5>', count($a)); + assertType('list{0: mixed~null, 1: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); } else { - assertType('0', count($a)); - assertType('array{}', $a); + assertType('int<0, 5>', count($a)); // Could be int<0, 1> + assertType('array{}|array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); // Could be array{}|array{0: mixed~null} } }; diff --git a/tests/PHPStan/Analyser/nsrt/bug11480.php b/tests/PHPStan/Analyser/nsrt/bug11480.php index f4d3898790a..17077d7bfc7 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11480.php +++ b/tests/PHPStan/Analyser/nsrt/bug11480.php @@ -84,7 +84,7 @@ public function intUnionCount(): void if (count($x) >= $count) { assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x); } else { - assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x); + assertType("array{}", $x); } assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x); } diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 54fb89c2c79..859718b615e 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -64,6 +64,29 @@ public function doFooBar( } } + /** @param array{0: string, 1?: string} $arr */ + public function doBar(array $arr): void + { + if (count($arr) <= 1) { + assertType('1', count($arr)); + return; + } + + assertType('2', count($arr)); + assertType('array{string, string}', $arr); + } + + /** @param array{0: string, 1?: string} $arr */ + public function doBaz(array $arr): void + { + if (count($arr) > 1) { + assertType('2', count($arr)); + assertType('array{string, string}', $arr); + } + + assertType('1|2', count($arr)); + } + } /** diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ab04c623f43..bbd10c4ec7c 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -545,6 +545,11 @@ public function testBug3608(): void $this->analyse([__DIR__ . '/data/bug-3608.php'], []); } + public function testBug3631(): void + { + $this->analyse([__DIR__ . '/data/bug-3631.php'], []); + } + public function testBug3920(): void { $this->analyse([__DIR__ . '/data/bug-3920.php'], []); diff --git a/tests/PHPStan/Rules/Functions/data/bug-3631.php b/tests/PHPStan/Rules/Functions/data/bug-3631.php new file mode 100644 index 00000000000..e3cb0d28f6f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3631.php @@ -0,0 +1,27 @@ + + */ +function someFunc(bool $flag): array +{ + $ids = [ + ['fa', 'foo', 'baz'] + ]; + + if ($flag) { + $ids[] = ['foo', 'bar', 'baz']; + + } + + if (count($ids) > 1) { + return array_intersect(...$ids); + } + + return $ids[0]; +} + +var_dump(someFunc(true)); +var_dump(someFunc(false)); From 1b586265ea9932bdc453c89b638f42c607f62e51 Mon Sep 17 00:00:00 2001 From: Watasuke Date: Tue, 11 Mar 2025 17:27:24 +0900 Subject: [PATCH 1176/3097] Constant array with negative keys cannot be a list --- .../Constant/ConstantArrayTypeBuilder.php | 20 +++++++----- .../VarTagChangedExpressionTypeRuleTest.php | 22 +++++++++++++ tests/PHPStan/Rules/PhpDoc/data/bug-12708.php | 31 +++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-12708.php diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 4f1e5582544..a639bf6c0e6 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -165,16 +165,22 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt if ($offsetType instanceof ConstantIntegerType) { $min = min($this->nextAutoIndexes); $max = max($this->nextAutoIndexes); - if ($offsetType->getValue() > $min) { - if ($offsetType->getValue() <= $max) { - $this->isList = $this->isList->and(TrinaryLogic::createMaybe()); - } else { - $this->isList = TrinaryLogic::createNo(); + $offsetValue = $offsetType->getValue(); + if ($offsetValue >= 0) { + if ($offsetValue > $min) { + if ($offsetValue <= $max) { + $this->isList = $this->isList->and(TrinaryLogic::createMaybe()); + } else { + $this->isList = TrinaryLogic::createNo(); + } } + } else { + $this->isList = TrinaryLogic::createNo(); } - if ($offsetType->getValue() >= $max) { + + if ($offsetValue >= $max) { /** @var int|float $newAutoIndex */ - $newAutoIndex = $offsetType->getValue() + 1; + $newAutoIndex = $offsetValue + 1; if (is_float($newAutoIndex)) { $newAutoIndex = $max; } diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index f20482f72f4..d9307d0bc71 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -78,4 +78,26 @@ public function testBug10130(): void ]); } + public function testBug12708(): void + { + $this->analyse([__DIR__ . '/data/bug-12708.php'], [ + [ + "PHPDoc tag @var with type list is not subtype of native type array{1: 'b', 2: 'c'}.", + 12, + ], + [ + "PHPDoc tag @var with type list is not subtype of native type array{0: 'a', 2: 'c'}.", + 18, + ], + [ + "PHPDoc tag @var with type list is not subtype of native type array{-1: 'z', 0: 'a', 1: 'b', 2: 'c'}.", + 24, + ], + [ + "PHPDoc tag @var with type list is not subtype of native type array{0: 'a', -1: 'z', 1: 'b', 2: 'c'}.", + 30, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-12708.php b/tests/PHPStan/Rules/PhpDoc/data/bug-12708.php new file mode 100644 index 00000000000..435359de5e5 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-12708.php @@ -0,0 +1,31 @@ + */ + return [0 => 'a', 1 => 'b', 2 => 'c']; +} + +function do1() +{ + /** @var list */ + return [1 => 'b', 2 => 'c']; +} + +function do2() +{ + /** @var list */ + return [0 => 'a', 2 => 'c']; +} + +function do3() +{ + /** @var list */ + return [-1 => 'z', 0 => 'a', 1 => 'b', 2 => 'c']; +} + +function do4() +{ + /** @var list */ + return [0 => 'a', -1 => 'z', 1 => 'b', 2 => 'c']; +} From 54ca85b5b35237489eb0e12c90aa69679ec34171 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 11 Mar 2025 09:49:24 +0100 Subject: [PATCH 1177/3097] Fix false positives on non-existing-offsets --- .../NonexistentOffsetInArrayDimFetchRule.php | 45 +++++++++++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 20 ++++++++ ...rray-dim-fetch-on-array-key-first-last.php | 50 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/array-dim-fetch-on-array-key-first-last.php diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index d3ef021189d..b0edfcadf92 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -13,6 +13,8 @@ use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function count; +use function in_array; +use function is_string; use function sprintf; /** @@ -96,6 +98,49 @@ public function processNode(Node $node, Scope $scope): array return []; } + if ( + $node->dim instanceof Node\Expr\FuncCall + && $node->dim->name instanceof Node\Name + && in_array($node->dim->name->toLowerString(), ['array_key_first', 'array_key_last'], true) + && count($node->dim->getArgs()) >= 1 + ) { + $arrayArg = $node->dim->getArgs()[0]->value; + $arrayType = $scope->getType($arrayArg); + if ( + $arrayArg instanceof Node\Expr\Variable + && $node->var instanceof Node\Expr\Variable + && is_string($arrayArg->name) + && $arrayArg->name === $node->var->name + && $arrayType->isArray()->yes() + && $arrayType->isIterableAtLeastOnce()->yes() + ) { + return []; + } + } + + if ( + $node->dim instanceof Node\Expr\BinaryOp\Minus + && $node->dim->left instanceof Node\Expr\FuncCall + && $node->dim->left->name instanceof Node\Name + && in_array($node->dim->left->name->toLowerString(), ['count', 'sizeof'], true) + && count($node->dim->left->getArgs()) >= 1 + && $node->dim->right instanceof Node\Scalar\Int_ + && $node->dim->right->value === 1 + ) { + $arrayArg = $node->dim->left->getArgs()[0]->value; + $arrayType = $scope->getType($arrayArg); + if ( + $arrayArg instanceof Node\Expr\Variable + && $node->var instanceof Node\Expr\Variable + && is_string($arrayArg->name) + && $arrayArg->name === $node->var->name + && $arrayType->isList()->yes() + && $arrayType->isIterableAtLeastOnce()->yes() + ) { + return []; + } + } + return $this->nonexistentOffsetInArrayDimFetchCheck->check( $scope, $node->var, diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 55da1ddeb3d..78e8d86ccfd 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -829,6 +829,26 @@ public function testArrayDimFetchAfterArraySearch(): void ]); } + public function testArrayDimFetchOnArrayKeyFirsOrLastOrCount(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/array-dim-fetch-on-array-key-first-last.php'], [ + [ + 'Offset 0|null might not exist on list.', + 12, + ], + [ + 'Offset (int|string) might not exist on non-empty-list.', + 16, + ], + [ + 'Offset int<-1, max> might not exist on non-empty-list.', + 45, + ], + ]); + } + public function testBug8649(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/array-dim-fetch-on-array-key-first-last.php b/tests/PHPStan/Rules/Arrays/data/array-dim-fetch-on-array-key-first-last.php new file mode 100644 index 00000000000..82fac733275 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/array-dim-fetch-on-array-key-first-last.php @@ -0,0 +1,50 @@ + $hellos + */ + public function first(array $hellos, array $anotherArray): string + { + if (rand(0,1)) { + return $hellos[array_key_first($hellos)]; + } + if ($hellos !== []) { + if ($anotherArray !== []) { + return $hellos[array_key_first($anotherArray)]; + } + + return $hellos[array_key_first($hellos)]; + } + return ''; + } + + /** + * @param array $hellos + */ + public function last(array $hellos): string + { + if ($hellos !== []) { + return $hellos[array_key_last($hellos)]; + } + return ''; + } + + /** + * @param list $hellos + */ + public function countOnArray(array $hellos, array $anotherArray): string + { + if ($hellos === []) { + return 'nothing'; + } + + if (rand(0,1)) { + return $hellos[count($anotherArray) - 1]; + } + + return $hellos[count($hellos) - 1]; + } +} From 39c65a95015b83a691ec12cdf0dacc6f7df8bfb0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 11 Mar 2025 16:40:27 +0100 Subject: [PATCH 1178/3097] Fix false positives on existing offsets after assign --- src/Analyser/NodeScopeResolver.php | 3 +- .../Analyser/NodeScopeResolverTest.php | 1 + ...nexistentOffsetInArrayDimFetchRuleTest.php | 14 +++++++ tests/PHPStan/Rules/Arrays/data/bug-11679.php | 39 +++++++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-12406.php | 15 +++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11679.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12406.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 12672c9d53f..ecf824a2400 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5480,8 +5480,7 @@ private function processAssignVar( } if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { - $currentVarType = $scope->getType($originalVar); - if (!$originalValueToWrite->isSuperTypeOf($currentVarType)->yes()) { + if (!$scope->hasExpressionType($originalVar)->yes()) { $scope = $scope->assignExpression( $originalVar, $originalValueToWrite, diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7f3d9638b2b..9dd8161487c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -208,6 +208,7 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Classes/data/bug-11591-property-tag.php'; yield __DIR__ . '/../Rules/Classes/data/mixin-trait-use.php'; + yield __DIR__ . '/../Rules/Arrays/data/bug-11679.php'; yield __DIR__ . '/../Rules/Methods/data/bug-4801.php'; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 78e8d86ccfd..6557f97ed30 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -849,6 +849,20 @@ public function testArrayDimFetchOnArrayKeyFirsOrLastOrCount(): void ]); } + public function testBug12406(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-12406.php'], []); + } + + public function testBug11679(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-11679.php'], []); + } + public function testBug8649(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11679.php b/tests/PHPStan/Rules/Arrays/data/bug-11679.php new file mode 100644 index 00000000000..463362516aa --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11679.php @@ -0,0 +1,39 @@ +arr); + if (!isset($this->arr['foo'])) { + $this->arr['foo'] = true; + assertType('array{foo: true}', $this->arr); + } + assertType('array{foo: bool}', $this->arr); + return $this->arr['foo']; // PHPStan realizes optional 'foo' is set + } +} + +class NonworkingExample +{ + /** @var array */ + private array $arr = []; + + public function sayHello(int $index): bool + { + assertType('array', $this->arr); + if (!isset($this->arr[$index]['foo'])) { + $this->arr[$index]['foo'] = true; + assertType('non-empty-array', $this->arr); + } + assertType('array', $this->arr); + return $this->arr[$index]['foo']; // PHPStan does not realize 'foo' is set + } +} diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12406.php b/tests/PHPStan/Rules/Arrays/data/bug-12406.php new file mode 100644 index 00000000000..5d967399c1e --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12406.php @@ -0,0 +1,15 @@ + */ + protected array $words = []; + + public function sayHello(string $word, int $count): void + { + $this->words[$word] ??= 0; + $this->words[$word] += $count; + } +} From c8833df6ffe3de490c641db572fc68a65bf412f2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 11 Mar 2025 17:07:13 +0100 Subject: [PATCH 1179/3097] Fix false positives on non-existing offsets of superglobals --- src/Analyser/MutatingScope.php | 7 +++---- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 1 + .../NonexistentOffsetInArrayDimFetchRuleTest.php | 7 +++++++ .../Rules/Arrays/data/narrow-superglobal.php | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/narrow-superglobal.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 45e956408f9..81162164992 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -548,16 +548,15 @@ public function getVariableType(string $variableName): Type } } - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); - } - if ($this->hasVariableType($variableName)->no()) { throw new UndefinedVariableException($this, $variableName); } $varExprString = '$' . $variableName; if (!array_key_exists($varExprString, $this->expressionTypes)) { + if ($this->isGlobalVariable($variableName)) { + return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); + } return new MixedType(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9dd8161487c..a2e9ef06192 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -210,6 +210,7 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Arrays/data/bug-11679.php'; yield __DIR__ . '/../Rules/Methods/data/bug-4801.php'; + yield __DIR__ . '/../Rules/Arrays/data/narrow-superglobal.php'; } /** diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 6557f97ed30..a1e7fbd2126 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -870,4 +870,11 @@ public function testBug8649(): void $this->analyse([__DIR__ . '/data/bug-8649.php'], []); } + public function testNarrowSuperglobals(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/narrow-superglobal.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/narrow-superglobal.php b/tests/PHPStan/Rules/Arrays/data/narrow-superglobal.php new file mode 100644 index 00000000000..83edeb9fd50 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/narrow-superglobal.php @@ -0,0 +1,16 @@ + Date: Wed, 12 Mar 2025 14:14:45 +0100 Subject: [PATCH 1180/3097] RuleTestCase - set shouldPolluteScopeWithLoopInitialAssignments to true which is PHPStan's default behaviour --- src/Testing/RuleTestCase.php | 2 +- .../StrictComparisonOfDifferentTypesRuleTest.php | 2 +- .../data/access-properties-after-isnull.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index e48e757bfe7..45e25730e10 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -206,7 +206,7 @@ public function gatherAnalyserErrors(array $files): array protected function shouldPolluteScopeWithLoopInitialAssignments(): bool { - return false; + return true; } protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index fc8ebd021a7..b94df8dae23 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -151,7 +151,7 @@ public function testStrictComparison(): void 335, ], [ - 'Strict comparison using === between int<0, max> and \'string\' will always evaluate to false.', + 'Strict comparison using === between int<1, max> and \'string\' will always evaluate to false.', 343, ], [ diff --git a/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php b/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php index 434d25be5dd..1189bc2232a 100644 --- a/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php +++ b/tests/PHPStan/Rules/Properties/data/access-properties-after-isnull.php @@ -33,22 +33,22 @@ public function doFoo($foo) } while (is_null($foo) && $foo->fooProperty) { - + break; } while (is_null($foo) || $foo->fooProperty) { - + break; } while (!is_null($foo) && $foo->fooProperty) { - + break; } while (!is_null($foo) || $foo->fooProperty) { - + break; } while (is_null($foo) || $foo->barProperty) { - + break; } while (!is_null($foo) && $foo->barProperty) { - + break; } } From 038e8b24719dd1bca68191778a92ea54113858cf Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 11 Mar 2025 11:09:19 +0100 Subject: [PATCH 1181/3097] Add regression test --- .../TypesAssignedToPropertiesRuleTest.php | 5 ++++ .../Rules/Properties/data/bug-1311.php | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100755 tests/PHPStan/Rules/Properties/data/bug-1311.php diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 5e57ee2994a..1c077ad8ad1 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -139,6 +139,11 @@ public function testBug1216(): void ]); } + public function testBug1311(): void + { + $this->analyse([__DIR__ . '/data/bug-1311.php'], []); + } + public function testTypesAssignedToPropertiesExpressionNames(): void { $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ diff --git a/tests/PHPStan/Rules/Properties/data/bug-1311.php b/tests/PHPStan/Rules/Properties/data/bug-1311.php new file mode 100755 index 00000000000..995f2d8216d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-1311.php @@ -0,0 +1,24 @@ + + */ + private $list = []; + + public function convertList(): void + { + $temp = [1, 2, 3]; + + for ($i = 0; $i < count($temp); $i++) { + $temp[$i] = (string) $temp[$i]; + } + + $this->list = $temp; + } +} + +(new HelloWorld())->convertList(); From 54159bbf2557ce4874b9be22a2b689cbb9af21ae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 11 Mar 2025 17:33:24 +0100 Subject: [PATCH 1182/3097] Added regression test --- .../Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php | 7 +++++++ tests/PHPStan/Rules/Arrays/data/bug-11447.php | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11447.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index a1e7fbd2126..3323d3a0dd5 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -870,6 +870,13 @@ public function testBug8649(): void $this->analyse([__DIR__ . '/data/bug-8649.php'], []); } + public function testBug11447(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-11447.php'], []); + } + public function testNarrowSuperglobals(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11447.php b/tests/PHPStan/Rules/Arrays/data/bug-11447.php new file mode 100644 index 00000000000..f59f2bdd6a5 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11447.php @@ -0,0 +1,8 @@ + Date: Wed, 12 Mar 2025 16:38:12 +0100 Subject: [PATCH 1183/3097] ResultCache: allow customization of params not invalidating cache --- conf/config.neon | 14 +++++ conf/parametersSchema.neon | 1 + .../ResultCache/ResultCacheManager.php | 19 +++--- src/Internal/ArrayHelper.php | 27 ++++++++ tests/PHPStan/Internal/ArrayHelperTest.php | 61 +++++++++++++++++++ 5 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 src/Internal/ArrayHelper.php create mode 100644 tests/PHPStan/Internal/ArrayHelperTest.php diff --git a/conf/config.neon b/conf/config.neon index 54f4ff31862..e764d064eb4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -189,6 +189,19 @@ parameters: - '1.1.1.2' tmpDir: %sysGetTempDir%/phpstan-fixer __validate: true + parametersNotInvalidatingCache: + - parameters.editorUrl + - parameters.editorUrlTitle + - parameters.errorFormat + - parameters.ignoreErrors + - parameters.reportUnmatchedIgnoredErrors + - parameters.tipsOfTheDay + - parameters.parallel + - parameters.internalErrorsCountLimit + - parameters.cache + - parameters.memoryLimitFile + - parameters.pro + - parametersSchema extensions: rules: PHPStan\DependencyInjection\RulesExtension @@ -502,6 +515,7 @@ services: scanFiles: %scanFiles% scanDirectories: %scanDirectories% checkDependenciesOfProjectExtensionFiles: %resultCacheChecksProjectExtensionFilesDependencies% + parametersNotInvalidatingCache: %parametersNotInvalidatingCache% - class: PHPStan\Analyser\ResultCache\ResultCacheClearer diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index f7328f7863b..3791c293a1c 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -166,6 +166,7 @@ parametersSchema: ]) env: arrayOf(string(), anyOf(int(), string())) sysGetTempDir: string() + parametersNotInvalidatingCache: listOf(string()) # playground mode sourceLocatorPlaygroundMode: bool() diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 7e997503a34..0db2ffc5463 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -16,6 +16,7 @@ use PHPStan\File\FileFinder; use PHPStan\File\FileHelper; use PHPStan\File\FileWriter; +use PHPStan\Internal\ArrayHelper; use PHPStan\Internal\ComposerHelper; use PHPStan\PhpDoc\StubFilesProvider; use PHPStan\Reflection\ReflectionProvider; @@ -29,6 +30,7 @@ use function array_unique; use function array_values; use function count; +use function explode; use function get_loaded_extensions; use function implode; use function is_array; @@ -65,6 +67,7 @@ final class ResultCacheManager * @param string[] $bootstrapFiles * @param string[] $scanFiles * @param string[] $scanDirectories + * @param list $parametersNotInvalidatingCache */ public function __construct( private Container $container, @@ -82,6 +85,7 @@ public function __construct( private array $scanFiles, private array $scanDirectories, private bool $checkDependenciesOfProjectExtensionFiles, + private array $parametersNotInvalidatingCache, ) { } @@ -887,18 +891,9 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a sort($extensions); if ($projectConfigArray !== null) { - unset($projectConfigArray['parameters']['editorUrl']); - unset($projectConfigArray['parameters']['editorUrlTitle']); - unset($projectConfigArray['parameters']['errorFormat']); - unset($projectConfigArray['parameters']['ignoreErrors']); - unset($projectConfigArray['parameters']['reportUnmatchedIgnoredErrors']); - unset($projectConfigArray['parameters']['tipsOfTheDay']); - unset($projectConfigArray['parameters']['parallel']); - unset($projectConfigArray['parameters']['internalErrorsCountLimit']); - unset($projectConfigArray['parameters']['cache']); - unset($projectConfigArray['parameters']['memoryLimitFile']); - unset($projectConfigArray['parameters']['pro']); - unset($projectConfigArray['parametersSchema']); + foreach ($this->parametersNotInvalidatingCache as $parameterPath) { + ArrayHelper::unsetKeyAtPath($projectConfigArray, explode('.', $parameterPath)); + } ksort($projectConfigArray); } diff --git a/src/Internal/ArrayHelper.php b/src/Internal/ArrayHelper.php new file mode 100644 index 00000000000..2498f61efbb --- /dev/null +++ b/src/Internal/ArrayHelper.php @@ -0,0 +1,27 @@ + $path + */ + public static function unsetKeyAtPath(array &$array, array $path): void + { + [$head, $tail] = [$path[0], array_slice($path, 1)]; + + if (count($tail) === 0) { + unset($array[$head]); + + } elseif (isset($array[$head])) { + self::unsetKeyAtPath($array[$head], $tail); + } + } + +} diff --git a/tests/PHPStan/Internal/ArrayHelperTest.php b/tests/PHPStan/Internal/ArrayHelperTest.php new file mode 100644 index 00000000000..6cf63b46a7a --- /dev/null +++ b/tests/PHPStan/Internal/ArrayHelperTest.php @@ -0,0 +1,61 @@ + [ + 'dep2a' => [ + 'dep3a' => null, + ], + 'dep2b' => null, + ], + 'dep1b' => null, + ]; + + ArrayHelper::unsetKeyAtPath($array, ['dep1a', 'dep2a', 'dep3a']); + + $this->assertSame([ + 'dep1a' => [ + 'dep2a' => [], + 'dep2b' => null, + ], + 'dep1b' => null, + ], $array); + + ArrayHelper::unsetKeyAtPath($array, ['dep1a', 'dep2a']); + + $this->assertSame([ + 'dep1a' => [ + 'dep2b' => null, + ], + 'dep1b' => null, + ], $array); + + ArrayHelper::unsetKeyAtPath($array, ['dep1a']); + + $this->assertSame([ + 'dep1b' => null, + ], $array); + + ArrayHelper::unsetKeyAtPath($array, ['dep1b']); + + $this->assertSame([], $array); + } + + public function testUnsetKeyAtPathEmpty(): void + { + $array = []; + + ArrayHelper::unsetKeyAtPath($array, ['foo', 'bar']); + + $this->assertSame([], $array); + } + +} From 7dd7b1426dd24da14eb788b4a476aad588e86dfb Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 12 Mar 2025 17:04:29 +0100 Subject: [PATCH 1184/3097] Support narrowing a constant array to a list with count --- src/Analyser/TypeSpecifier.php | 17 ++++++++++++----- tests/PHPStan/Analyser/nsrt/count-type.php | 10 ++++++++++ tests/PHPStan/Analyser/nsrt/list-count.php | 3 +-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 73a34c21758..c016b2869e5 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1061,10 +1061,11 @@ private function specifyTypesForCountFuncCall( $isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->result->or($type->getIterableValueType()->isArray()->negate()); } + $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); if ( !$isNormalCount->yes() - || (!$type->isConstantArray()->yes() && !$isList->yes()) + || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing ) { return null; @@ -1082,9 +1083,12 @@ private function specifyTypesForCountFuncCall( } if ( - $isList->yes() - && $sizeType instanceof ConstantIntegerType + $sizeType instanceof ConstantIntegerType && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT + && ( + $isList->yes() + || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getValue() - 1))->yes() + ) ) { // turn optional offsets non-optional $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); @@ -1097,9 +1101,12 @@ private function specifyTypesForCountFuncCall( } if ( - $isList->yes() - && $sizeType instanceof IntegerRangeType + $sizeType instanceof IntegerRangeType && $sizeType->getMin() !== null + && ( + $isList->yes() + || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes() + ) ) { // turn optional offsets non-optional $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); diff --git a/tests/PHPStan/Analyser/nsrt/count-type.php b/tests/PHPStan/Analyser/nsrt/count-type.php index 859718b615e..1deb2e86952 100644 --- a/tests/PHPStan/Analyser/nsrt/count-type.php +++ b/tests/PHPStan/Analyser/nsrt/count-type.php @@ -87,6 +87,16 @@ public function doBaz(array $arr): void assertType('1|2', count($arr)); } + public function constantArrayWhichCanBecomeList(string $h): void + { + preg_match('#^([a-z0-9-]+)\..+$#', $h, $matches); + if (count($matches) !== 2) { + return; + } + + assertType('array{string, non-empty-string}', $matches); + } + } /** diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index c51ea31efcd..6654e463785 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -379,9 +379,8 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t */ protected function testOptionalKeysInUnionArrayWithIntRange($row, $twoOrThree): void { - // doesn't narrow because no list if (count($row) >= $twoOrThree) { - assertType('array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + assertType('array{0: int, 1: string|null, 2?: int|null}', $row); } else { assertType('array{0: int, 1?: string|null, 2?: int|null, 3?: float|null}|array{string}', $row); } From ff6da9e1a1d8b6c5b9f7b3b5c3c326d9b67d44ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 11:27:46 +0100 Subject: [PATCH 1185/3097] Sync MutatingScope::issetCheck with IssetCheck --- src/Analyser/MutatingScope.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 81162164992..1a204fd548c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2415,8 +2415,7 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n return null; } - $nativeType = $propertyReflection->getNativeType(); - if (!$nativeType instanceof MixedType) { + if ($propertyReflection->hasNativeType() && !$propertyReflection->isVirtual()->yes()) { if (!$this->hasExpressionType($expr)->yes()) { if ($expr instanceof Node\Expr\PropertyFetch) { return $this->issetCheckUndefined($expr->var); From 8bb0670cffd471f2f1dfc39cc002cbaf2bbbfeb2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 10 Mar 2025 10:06:29 +0100 Subject: [PATCH 1186/3097] Infer types of property fetch with dynamic name --- src/Analyser/MutatingScope.php | 57 ++++++++++++------- .../Properties/PropertyReflectionFinder.php | 16 ++++-- tests/PHPStan/Analyser/nsrt/bug-12398.php | 10 ++++ .../TypesAssignedToPropertiesRuleTest.php | 4 -- .../PHPStan/Rules/Variables/IssetRuleTest.php | 2 +- .../Rules/Variables/NullCoalesceRuleTest.php | 2 +- 6 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1a204fd548c..a141b5dcdc0 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2126,35 +2126,48 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return $callType; } - if ($node instanceof PropertyFetch && $node->name instanceof Node\Identifier) { - if ($this->nativeTypesPromoted) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); - if ($propertyReflection === null) { - return new ErrorType(); - } + if ($node instanceof PropertyFetch) { + if ($node->name instanceof Node\Identifier) { + if ($this->nativeTypesPromoted) { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); + if ($propertyReflection === null) { + return new ErrorType(); + } - if (!$propertyReflection->hasNativeType()) { - return new MixedType(); + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); + } + + $nativeType = $propertyReflection->getNativeType(); + + return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } - $nativeType = $propertyReflection->getNativeType(); + $typeCallback = function () use ($node): Type { + $returnType = $this->propertyFetchType( + $this->getType($node->var), + $node->name->name, + $node, + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; - return $this->getNullsafeShortCircuitingType($node->var, $nativeType); + return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); } - $typeCallback = function () use ($node): Type { - $returnType = $this->propertyFetchType( - $this->getType($node->var), - $node->name->name, - $node, + $nameType = $this->getType($node->name); + if (count($nameType->getConstantStrings()) > 0) { + return TypeCombinator::union( + ...array_map(fn ($constantString) => $this + ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) + ->getType( + new PropertyFetch($node->var, new Identifier($constantString->getValue())), + ), $nameType->getConstantStrings()), ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + } } if ($node instanceof Expr\NullsafePropertyFetch) { diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index 6cd33e10d58..67b2785fa9c 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -10,6 +10,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; use function array_map; +use function count; final class PropertyReflectionFinder { @@ -86,11 +87,18 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?FoundPropertyReflection { if ($propertyFetch instanceof Node\Expr\PropertyFetch) { - if (!$propertyFetch->name instanceof Node\Identifier) { - return null; - } $propertyHolderType = $scope->getType($propertyFetch->var); - return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + if ($propertyFetch->name instanceof Node\Identifier) { + return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + } + + $nameType = $scope->getType($propertyFetch->name); + $nameTypeConstantStrings = $nameType->getConstantStrings(); + if (count($nameTypeConstantStrings) === 1) { + return $this->findPropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope); + } + + return null; } if (!$propertyFetch->name instanceof Node\Identifier) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12398.php b/tests/PHPStan/Analyser/nsrt/bug-12398.php index ee8450a8e36..b89a699dd37 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12398.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12398.php @@ -7,10 +7,20 @@ class Foo { + public int $test; + public function doFoo(string $foo): void { $bar = 'foo'; assertType('string', $$bar); } + + public function doBar(): void + { + $a = 'test'; + assertType('int', $this->$a); + } + } + diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 1c077ad8ad1..533c53c25d2 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -163,10 +163,6 @@ public function testTypesAssignedToPropertiesExpressionNames(): void 'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.', 69, ], - [ - 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept (float|int).', - 73, - ], [ 'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float.', 83, diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index a3db0d10721..8d2adbdb98e 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -354,7 +354,7 @@ public function testBug7109(): void 67, ], [ - 'Using nullsafe property access "?->(Expression)" in isset() is unnecessary. Use -> instead.', + 'Expression in isset() is not nullable.', 74, ], ]); diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index f876b3d8231..0745db66a63 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -288,7 +288,7 @@ public function testBug7109(): void 66, ], [ - 'Using nullsafe property access "?->(Expression)" on left side of ?? is unnecessary. Use -> instead.', + 'Expression on left side of ?? is not nullable.', 73, ], ]); From 0df0c6f34b38e75daf5bfb53d1fe088c78d30b97 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 11:37:47 +0100 Subject: [PATCH 1187/3097] Filter scope by dynamic variable name for `$$name` --- src/Analyser/MutatingScope.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a141b5dcdc0..1bf6ec4d543 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2010,7 +2010,11 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { - return TypeCombinator::union(...array_map(fn ($constantString) => $this->getVariableType($constantString->getValue()), $nameType->getConstantStrings())); + return TypeCombinator::union( + ...array_map(fn ($constantString) => $this + ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) + ->getVariableType($constantString->getValue()), $nameType->getConstantStrings()), + ); } } From 6037f784d80bc46fa0df8b0e7647744f9ccf1a73 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 11:42:35 +0100 Subject: [PATCH 1188/3097] Regression test Closes https://github.com/phpstan/phpstan/issues/12716 --- ...isonOperatorsConstantConditionRuleTest.php | 6 ++++++ .../Rules/Comparison/data/bug-12716.php | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-12716.php diff --git a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php index 9db84af00da..eb7fe432902 100644 --- a/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/NumberComparisonOperatorsConstantConditionRuleTest.php @@ -246,4 +246,10 @@ public function testBug9850(): void $this->analyse([__DIR__ . '/data/bug-9850.php'], []); } + public function testBug12716(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12716.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12716.php b/tests/PHPStan/Rules/Comparison/data/bug-12716.php new file mode 100644 index 00000000000..a4429d9d43f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12716.php @@ -0,0 +1,19 @@ += 10) { + var_dump(count($items)); + $items = []; + } + }; + $i = 0; + while ($i++ <= 100) { + $a(); + } +}; From 51a867f439b55960de460223af0e3b125414fc5c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 13:14:16 +0100 Subject: [PATCH 1189/3097] Infer types of StaticCall with dynamic name --- src/Analyser/MutatingScope.php | 71 ++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1bf6ec4d543..02370848956 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2077,23 +2077,50 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu ); } - if ($node instanceof Expr\StaticCall && $node->name instanceof Node\Identifier) { - if ($this->nativeTypesPromoted) { + if ($node instanceof Expr\StaticCall) { + if ($node->name instanceof Node\Identifier) { + if ($this->nativeTypesPromoted) { + $typeCallback = function () use ($node): Type { + if ($node->class instanceof Name) { + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); + } else { + $staticMethodCalledOnType = $this->getNativeType($node->class); + } + $methodReflection = $this->getMethodReflection( + $staticMethodCalledOnType, + $node->name->name, + ); + if ($methodReflection === null) { + return new ErrorType(); + } + + return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + }; + + $callType = $typeCallback(); + if ($node->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($node->class, $callType); + } + + return $callType; + } + $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); } else { - $staticMethodCalledOnType = $this->getNativeType($node->class); + $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); } - $methodReflection = $this->getMethodReflection( + + $returnType = $this->methodCallReturnType( $staticMethodCalledOnType, - $node->name->name, + $node->name->toString(), + $node, ); - if ($methodReflection === null) { + if ($returnType === null) { return new ErrorType(); } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + return $returnType; }; $callType = $typeCallback(); @@ -2104,30 +2131,14 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return $callType; } - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); - } else { - $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); - } - - $returnType = $this->methodCallReturnType( - $staticMethodCalledOnType, - $node->name->toString(), - $node, + $nameType = $this->getType($node->name); + if (count($nameType->getConstantStrings()) > 0) { + return TypeCombinator::union( + ...array_map(fn ($constantString) => $this + ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) + ->getType(new Expr\StaticCall($node->class, new Identifier($constantString->getValue()), $node->args)), $nameType->getConstantStrings()), ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - $callType = $typeCallback(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $callType); } - - return $callType; } if ($node instanceof PropertyFetch) { From 244093e4f43a58a6dcbb596ea2eac1e042016ac1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 13:17:51 +0100 Subject: [PATCH 1190/3097] Infer types of MethodCall with dynamic name --- src/Analyser/MutatingScope.php | 49 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 02370848956..b9dde3c62d3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2029,36 +2029,47 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu ); } - if ($node instanceof MethodCall && $node->name instanceof Node\Identifier) { - if ($this->nativeTypesPromoted) { + if ($node instanceof MethodCall) { + if ($node->name instanceof Node\Identifier) { + if ($this->nativeTypesPromoted) { + $typeCallback = function () use ($node): Type { + $methodReflection = $this->getMethodReflection( + $this->getNativeType($node->var), + $node->name->name, + ); + if ($methodReflection === null) { + return new ErrorType(); + } + + return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + }; + + return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + } + $typeCallback = function () use ($node): Type { - $methodReflection = $this->getMethodReflection( - $this->getNativeType($node->var), + $returnType = $this->methodCallReturnType( + $this->getType($node->var), $node->name->name, + $node, ); - if ($methodReflection === null) { + if ($returnType === null) { return new ErrorType(); } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + return $returnType; }; return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); } - $typeCallback = function () use ($node): Type { - $returnType = $this->methodCallReturnType( - $this->getType($node->var), - $node->name->name, - $node, + $nameType = $this->getType($node->name); + if (count($nameType->getConstantStrings()) > 0) { + return TypeCombinator::union( + ...array_map(fn ($constantString) => $this + ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) + ->getType(new MethodCall($node->var, new Identifier($constantString->getValue()), $node->args)), $nameType->getConstantStrings()), ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + } } if ($node instanceof Expr\NullsafeMethodCall) { From 2fe4e0f94e75fe8844a21fdb81799f01f0591dfe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 13 Mar 2025 13:19:52 +0100 Subject: [PATCH 1191/3097] Infer types of StaticPropertyFetch with a dynamic name --- src/Analyser/MutatingScope.php | 80 +++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b9dde3c62d3..ff4f3b9657c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2212,52 +2212,60 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu ); } - if ( - $node instanceof Expr\StaticPropertyFetch - && $node->name instanceof Node\VarLikeIdentifier - ) { - if ($this->nativeTypesPromoted) { - $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); - if ($propertyReflection === null) { - return new ErrorType(); - } - if (!$propertyReflection->hasNativeType()) { - return new MixedType(); - } + if ($node instanceof Expr\StaticPropertyFetch) { + if ($node->name instanceof Node\VarLikeIdentifier) { + if ($this->nativeTypesPromoted) { + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node, $this); + if ($propertyReflection === null) { + return new ErrorType(); + } + if (!$propertyReflection->hasNativeType()) { + return new MixedType(); + } - $nativeType = $propertyReflection->getNativeType(); + $nativeType = $propertyReflection->getNativeType(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $nativeType); + if ($node->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($node->class, $nativeType); + } + + return $nativeType; } - return $nativeType; - } + $typeCallback = function () use ($node): Type { + if ($node->class instanceof Name) { + $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); + } else { + $staticPropertyFetchedOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); + } - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); - } else { - $staticPropertyFetchedOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); - } + $returnType = $this->propertyFetchType( + $staticPropertyFetchedOnType, + $node->name->toString(), + $node, + ); + if ($returnType === null) { + return new ErrorType(); + } + return $returnType; + }; - $returnType = $this->propertyFetchType( - $staticPropertyFetchedOnType, - $node->name->toString(), - $node, - ); - if ($returnType === null) { - return new ErrorType(); + $fetchType = $typeCallback(); + if ($node->class instanceof Expr) { + return $this->getNullsafeShortCircuitingType($node->class, $fetchType); } - return $returnType; - }; - $fetchType = $typeCallback(); - if ($node->class instanceof Expr) { - return $this->getNullsafeShortCircuitingType($node->class, $fetchType); + return $fetchType; } - return $fetchType; + $nameType = $this->getType($node->name); + if (count($nameType->getConstantStrings()) > 0) { + return TypeCombinator::union( + ...array_map(fn ($constantString) => $this + ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) + ->getType(new Expr\StaticPropertyFetch($node->class, new Node\VarLikeIdentifier($constantString->getValue()))), $nameType->getConstantStrings()), + ); + } } if ($node instanceof FuncCall) { From c533a6e853245863f23313132f174151093d8d64 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Mon, 17 Mar 2025 17:53:45 +0900 Subject: [PATCH 1192/3097] Treat `#[Pure(true)]` in PhpStorm stubs as `hasSideEffects => true` --- bin/functionMetadata_original.php | 15 +- bin/generate-function-metadata.php | 74 +++++++-- resources/functionMetadata.php | 257 +++++++++++++++-------------- 3 files changed, 207 insertions(+), 139 deletions(-) diff --git a/bin/functionMetadata_original.php b/bin/functionMetadata_original.php index 838d2ecd5c0..f4c9cd19fc1 100644 --- a/bin/functionMetadata_original.php +++ b/bin/functionMetadata_original.php @@ -70,8 +70,6 @@ 'chown' => ['hasSideEffects' => true], 'copy' => ['hasSideEffects' => true], 'count' => ['hasSideEffects' => false], - 'connection_aborted' => ['hasSideEffects' => true], - 'connection_status' => ['hasSideEffects' => true], 'error_log' => ['hasSideEffects' => true], 'fclose' => ['hasSideEffects' => true], 'fflush' => ['hasSideEffects' => true], @@ -79,7 +77,6 @@ 'fgetcsv' => ['hasSideEffects' => true], 'fgets' => ['hasSideEffects' => true], 'fgetss' => ['hasSideEffects' => true], - 'file_get_contents' => ['hasSideEffects' => true], 'file_put_contents' => ['hasSideEffects' => true], 'flock' => ['hasSideEffects' => true], 'fopen' => ['hasSideEffects' => true], @@ -98,6 +95,18 @@ 'mb_str_pad' => ['hasSideEffects' => false], 'mkdir' => ['hasSideEffects' => true], 'move_uploaded_file' => ['hasSideEffects' => true], + 'ob_clean' => ['hasSideEffects' => true], + 'ob_end_clean' => ['hasSideEffects' => true], + 'ob_end_flush' => ['hasSideEffects' => true], + 'ob_flush' => ['hasSideEffects' => true], + 'ob_get_clean' => ['hasSideEffects' => true], + 'ob_get_contents' => ['hasSideEffects' => true], + 'ob_get_length' => ['hasSideEffects' => true], + 'ob_get_level' => ['hasSideEffects' => true], + 'ob_get_status' => ['hasSideEffects' => true], + 'ob_list_handlers' => ['hasSideEffects' => true], + 'output_add_rewrite_var' => ['hasSideEffects' => true], + 'output_reset_rewrite_vars' => ['hasSideEffects' => true], 'pclose' => ['hasSideEffects' => true], 'popen' => ['hasSideEffects' => true], 'readfile' => ['hasSideEffects' => true], diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index 97737499a5e..80032561d9c 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -25,18 +25,63 @@ /** @var string[] */ public array $functions = []; + /** @var list */ + public array $impureFunctions = []; + /** @var string[] */ public array $methods = []; public function enterNode(Node $node) { if ($node instanceof Node\Stmt\Function_) { + assert(isset($node->namespacedName)); + $functionName = $node->namespacedName->toLowerString(); + foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { - if ($attr->name->toString() === Pure::class) { - $this->functions[] = $node->namespacedName->toLowerString(); + if ($attr->name->toString() !== Pure::class) { + continue; + } + + // The following functions have side effects, but their state is managed within the PHPStan scope: + if (in_array($functionName, [ + 'stat', + 'lstat', + 'file_exists', + 'is_writable', + 'is_writeable', + 'is_readable', + 'is_executable', + 'is_file', + 'is_dir', + 'is_link', + 'filectime', + 'fileatime', + 'filemtime', + 'fileinode', + 'filegroup', + 'fileowner', + 'filesize', + 'filetype', + 'fileperms', + 'ftell', + 'ini_get', + 'function_exists', + 'json_last_error', + 'json_last_error_msg', + ], true)) { + $this->functions[] = $functionName; break 2; } + + // PhpStorm stub's #[Pure(true)] means the function has side effects but its return value is important. + // In PHPStan's criteria, these functions are simply considered as ['hasSideEffect' => true]. + if (isset($attr->args[0]->value->name->name) && $attr->args[0]->value->name->name === 'true') { + $this->impureFunctions[] = $functionName; + } else { + $this->functions[] = $functionName; + } + break 2; } } } @@ -74,26 +119,29 @@ public function enterNode(Node $node) ); } + /** @var array $metadata */ $metadata = require __DIR__ . '/functionMetadata_original.php'; foreach ($visitor->functions as $functionName) { if (array_key_exists($functionName, $metadata)) { if ($metadata[$functionName]['hasSideEffects']) { - if (in_array($functionName, [ - 'mt_rand', - 'rand', - 'random_bytes', - 'random_int', - 'connection_aborted', - 'connection_status', - 'file_get_contents', - ], true)) { - continue; - } throw new ShouldNotHappenException($functionName); } } $metadata[$functionName] = ['hasSideEffects' => false]; } + foreach ($visitor->impureFunctions as $functionName) { + if (array_key_exists($functionName, $metadata)) { + if (in_array($functionName, [ + 'ob_get_contents', + ], true)) { + continue; + } + if ($metadata[$functionName]['hasSideEffects']) { + throw new ShouldNotHappenException($functionName); + } + } + $metadata[$functionName] = ['hasSideEffects' => true]; + } foreach ($visitor->methods as $methodName) { if (array_key_exists($methodName, $metadata)) { diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index 0c5c33759a9..69cbb72a62b 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -766,7 +766,7 @@ 'bzerrstr' => ['hasSideEffects' => false], 'bzopen' => ['hasSideEffects' => false], 'ceil' => ['hasSideEffects' => false], - 'checkdate' => ['hasSideEffects' => false], + 'checkdate' => ['hasSideEffects' => true], 'checkdnsrr' => ['hasSideEffects' => false], 'chgrp' => ['hasSideEffects' => true], 'chmod' => ['hasSideEffects' => true], @@ -776,11 +776,11 @@ 'chunk_split' => ['hasSideEffects' => false], 'class_implements' => ['hasSideEffects' => false], 'class_parents' => ['hasSideEffects' => false], - 'cli_get_process_title' => ['hasSideEffects' => false], + 'cli_get_process_title' => ['hasSideEffects' => true], 'collator_compare' => ['hasSideEffects' => false], 'collator_create' => ['hasSideEffects' => false], 'collator_get_attribute' => ['hasSideEffects' => false], - 'collator_get_error_code' => ['hasSideEffects' => false], + 'collator_get_error_code' => ['hasSideEffects' => true], 'collator_get_error_message' => ['hasSideEffects' => false], 'collator_get_locale' => ['hasSideEffects' => false], 'collator_get_sort_key' => ['hasSideEffects' => false], @@ -788,7 +788,7 @@ 'compact' => ['hasSideEffects' => false], 'connection_aborted' => ['hasSideEffects' => true], 'connection_status' => ['hasSideEffects' => true], - 'constant' => ['hasSideEffects' => false], + 'constant' => ['hasSideEffects' => true], 'convert_cyr_string' => ['hasSideEffects' => false], 'convert_uudecode' => ['hasSideEffects' => false], 'convert_uuencode' => ['hasSideEffects' => false], @@ -811,47 +811,47 @@ 'ctype_upper' => ['hasSideEffects' => false], 'ctype_xdigit' => ['hasSideEffects' => false], 'curl_copy_handle' => ['hasSideEffects' => false], - 'curl_errno' => ['hasSideEffects' => false], - 'curl_error' => ['hasSideEffects' => false], + 'curl_errno' => ['hasSideEffects' => true], + 'curl_error' => ['hasSideEffects' => true], 'curl_escape' => ['hasSideEffects' => false], 'curl_file_create' => ['hasSideEffects' => false], - 'curl_getinfo' => ['hasSideEffects' => false], - 'curl_multi_errno' => ['hasSideEffects' => false], + 'curl_getinfo' => ['hasSideEffects' => true], + 'curl_multi_errno' => ['hasSideEffects' => true], 'curl_multi_getcontent' => ['hasSideEffects' => false], 'curl_multi_info_read' => ['hasSideEffects' => false], - 'curl_share_errno' => ['hasSideEffects' => false], + 'curl_share_errno' => ['hasSideEffects' => true], 'curl_share_strerror' => ['hasSideEffects' => false], 'curl_strerror' => ['hasSideEffects' => false], 'curl_unescape' => ['hasSideEffects' => false], 'curl_version' => ['hasSideEffects' => false], 'current' => ['hasSideEffects' => false], - 'date' => ['hasSideEffects' => false], - 'date_create' => ['hasSideEffects' => false], - 'date_create_from_format' => ['hasSideEffects' => false], - 'date_create_immutable' => ['hasSideEffects' => false], - 'date_create_immutable_from_format' => ['hasSideEffects' => false], + 'date' => ['hasSideEffects' => true], + 'date_create' => ['hasSideEffects' => true], + 'date_create_from_format' => ['hasSideEffects' => true], + 'date_create_immutable' => ['hasSideEffects' => true], + 'date_create_immutable_from_format' => ['hasSideEffects' => true], 'date_default_timezone_get' => ['hasSideEffects' => false], - 'date_diff' => ['hasSideEffects' => false], - 'date_format' => ['hasSideEffects' => false], - 'date_get_last_errors' => ['hasSideEffects' => false], - 'date_interval_create_from_date_string' => ['hasSideEffects' => false], - 'date_interval_format' => ['hasSideEffects' => false], - 'date_offset_get' => ['hasSideEffects' => false], - 'date_parse' => ['hasSideEffects' => false], - 'date_parse_from_format' => ['hasSideEffects' => false], - 'date_sun_info' => ['hasSideEffects' => false], - 'date_sunrise' => ['hasSideEffects' => false], - 'date_sunset' => ['hasSideEffects' => false], - 'date_timestamp_get' => ['hasSideEffects' => false], - 'date_timezone_get' => ['hasSideEffects' => false], + 'date_diff' => ['hasSideEffects' => true], + 'date_format' => ['hasSideEffects' => true], + 'date_get_last_errors' => ['hasSideEffects' => true], + 'date_interval_create_from_date_string' => ['hasSideEffects' => true], + 'date_interval_format' => ['hasSideEffects' => true], + 'date_offset_get' => ['hasSideEffects' => true], + 'date_parse' => ['hasSideEffects' => true], + 'date_parse_from_format' => ['hasSideEffects' => true], + 'date_sun_info' => ['hasSideEffects' => true], + 'date_sunrise' => ['hasSideEffects' => true], + 'date_sunset' => ['hasSideEffects' => true], + 'date_timestamp_get' => ['hasSideEffects' => true], + 'date_timezone_get' => ['hasSideEffects' => true], 'datefmt_create' => ['hasSideEffects' => false], 'datefmt_format' => ['hasSideEffects' => false], 'datefmt_format_object' => ['hasSideEffects' => false], 'datefmt_get_calendar' => ['hasSideEffects' => false], 'datefmt_get_calendar_object' => ['hasSideEffects' => false], 'datefmt_get_datetype' => ['hasSideEffects' => false], - 'datefmt_get_error_code' => ['hasSideEffects' => false], - 'datefmt_get_error_message' => ['hasSideEffects' => false], + 'datefmt_get_error_code' => ['hasSideEffects' => true], + 'datefmt_get_error_message' => ['hasSideEffects' => true], 'datefmt_get_locale' => ['hasSideEffects' => false], 'datefmt_get_pattern' => ['hasSideEffects' => false], 'datefmt_get_timetype' => ['hasSideEffects' => false], @@ -862,16 +862,16 @@ 'decbin' => ['hasSideEffects' => false], 'dechex' => ['hasSideEffects' => false], 'decoct' => ['hasSideEffects' => false], - 'defined' => ['hasSideEffects' => false], + 'defined' => ['hasSideEffects' => true], 'deflate_init' => ['hasSideEffects' => false], 'deg2rad' => ['hasSideEffects' => false], 'dirname' => ['hasSideEffects' => false], - 'disk_free_space' => ['hasSideEffects' => false], - 'disk_total_space' => ['hasSideEffects' => false], - 'diskfreespace' => ['hasSideEffects' => false], + 'disk_free_space' => ['hasSideEffects' => true], + 'disk_total_space' => ['hasSideEffects' => true], + 'diskfreespace' => ['hasSideEffects' => true], 'dngettext' => ['hasSideEffects' => false], 'doubleval' => ['hasSideEffects' => false], - 'error_get_last' => ['hasSideEffects' => false], + 'error_get_last' => ['hasSideEffects' => true], 'error_log' => ['hasSideEffects' => true], 'escapeshellarg' => ['hasSideEffects' => false], 'escapeshellcmd' => ['hasSideEffects' => false], @@ -881,13 +881,13 @@ 'extension_loaded' => ['hasSideEffects' => false], 'fclose' => ['hasSideEffects' => true], 'fdiv' => ['hasSideEffects' => false], - 'feof' => ['hasSideEffects' => false], + 'feof' => ['hasSideEffects' => true], 'fflush' => ['hasSideEffects' => true], 'fgetc' => ['hasSideEffects' => true], 'fgetcsv' => ['hasSideEffects' => true], 'fgets' => ['hasSideEffects' => true], 'fgetss' => ['hasSideEffects' => true], - 'file' => ['hasSideEffects' => false], + 'file' => ['hasSideEffects' => true], 'file_exists' => ['hasSideEffects' => false], 'file_get_contents' => ['hasSideEffects' => true], 'file_put_contents' => ['hasSideEffects' => true], @@ -913,7 +913,7 @@ 'flock' => ['hasSideEffects' => true], 'floor' => ['hasSideEffects' => false], 'fmod' => ['hasSideEffects' => false], - 'fnmatch' => ['hasSideEffects' => false], + 'fnmatch' => ['hasSideEffects' => true], 'fopen' => ['hasSideEffects' => true], 'fpassthru' => ['hasSideEffects' => true], 'fputcsv' => ['hasSideEffects' => true], @@ -921,17 +921,17 @@ 'fread' => ['hasSideEffects' => true], 'fscanf' => ['hasSideEffects' => true], 'fseek' => ['hasSideEffects' => true], - 'fstat' => ['hasSideEffects' => false], + 'fstat' => ['hasSideEffects' => true], 'ftell' => ['hasSideEffects' => false], - 'ftok' => ['hasSideEffects' => false], + 'ftok' => ['hasSideEffects' => true], 'ftruncate' => ['hasSideEffects' => true], 'func_get_arg' => ['hasSideEffects' => false], 'func_get_args' => ['hasSideEffects' => false], 'func_num_args' => ['hasSideEffects' => false], 'function_exists' => ['hasSideEffects' => false], 'fwrite' => ['hasSideEffects' => true], - 'gc_enabled' => ['hasSideEffects' => false], - 'gc_status' => ['hasSideEffects' => false], + 'gc_enabled' => ['hasSideEffects' => true], + 'gc_status' => ['hasSideEffects' => true], 'gd_info' => ['hasSideEffects' => false], 'geoip_continent_code_by_name' => ['hasSideEffects' => false], 'geoip_country_code3_by_name' => ['hasSideEffects' => false], @@ -948,41 +948,41 @@ 'geoip_region_by_name' => ['hasSideEffects' => false], 'geoip_region_name_by_code' => ['hasSideEffects' => false], 'geoip_time_zone_by_country_and_region' => ['hasSideEffects' => false], - 'get_browser' => ['hasSideEffects' => false], + 'get_browser' => ['hasSideEffects' => true], 'get_called_class' => ['hasSideEffects' => false], 'get_cfg_var' => ['hasSideEffects' => false], 'get_class' => ['hasSideEffects' => false], 'get_class_methods' => ['hasSideEffects' => false], 'get_class_vars' => ['hasSideEffects' => false], - 'get_current_user' => ['hasSideEffects' => false], + 'get_current_user' => ['hasSideEffects' => true], 'get_debug_type' => ['hasSideEffects' => false], - 'get_declared_classes' => ['hasSideEffects' => false], - 'get_declared_interfaces' => ['hasSideEffects' => false], - 'get_declared_traits' => ['hasSideEffects' => false], - 'get_defined_constants' => ['hasSideEffects' => false], - 'get_defined_functions' => ['hasSideEffects' => false], - 'get_defined_vars' => ['hasSideEffects' => false], + 'get_declared_classes' => ['hasSideEffects' => true], + 'get_declared_interfaces' => ['hasSideEffects' => true], + 'get_declared_traits' => ['hasSideEffects' => true], + 'get_defined_constants' => ['hasSideEffects' => true], + 'get_defined_functions' => ['hasSideEffects' => true], + 'get_defined_vars' => ['hasSideEffects' => true], 'get_extension_funcs' => ['hasSideEffects' => false], - 'get_headers' => ['hasSideEffects' => false], + 'get_headers' => ['hasSideEffects' => true], 'get_html_translation_table' => ['hasSideEffects' => false], - 'get_include_path' => ['hasSideEffects' => false], - 'get_included_files' => ['hasSideEffects' => false], + 'get_include_path' => ['hasSideEffects' => true], + 'get_included_files' => ['hasSideEffects' => true], 'get_loaded_extensions' => ['hasSideEffects' => false], - 'get_meta_tags' => ['hasSideEffects' => false], + 'get_meta_tags' => ['hasSideEffects' => true], 'get_object_vars' => ['hasSideEffects' => false], 'get_parent_class' => ['hasSideEffects' => false], - 'get_required_files' => ['hasSideEffects' => false], + 'get_required_files' => ['hasSideEffects' => true], 'get_resource_id' => ['hasSideEffects' => false], - 'get_resources' => ['hasSideEffects' => false], + 'get_resources' => ['hasSideEffects' => true], 'getallheaders' => ['hasSideEffects' => false], - 'getcwd' => ['hasSideEffects' => false], - 'getdate' => ['hasSideEffects' => false], - 'getenv' => ['hasSideEffects' => false], + 'getcwd' => ['hasSideEffects' => true], + 'getdate' => ['hasSideEffects' => true], + 'getenv' => ['hasSideEffects' => true], 'gethostbyaddr' => ['hasSideEffects' => false], 'gethostbyname' => ['hasSideEffects' => false], 'gethostbynamel' => ['hasSideEffects' => false], 'gethostname' => ['hasSideEffects' => false], - 'getlastmod' => ['hasSideEffects' => false], + 'getlastmod' => ['hasSideEffects' => true], 'getmygid' => ['hasSideEffects' => false], 'getmyinode' => ['hasSideEffects' => false], 'getmypid' => ['hasSideEffects' => false], @@ -990,15 +990,15 @@ 'getprotobyname' => ['hasSideEffects' => false], 'getprotobynumber' => ['hasSideEffects' => false], 'getrandmax' => ['hasSideEffects' => false], - 'getrusage' => ['hasSideEffects' => false], + 'getrusage' => ['hasSideEffects' => true], 'getservbyname' => ['hasSideEffects' => false], 'getservbyport' => ['hasSideEffects' => false], 'gettext' => ['hasSideEffects' => false], - 'gettimeofday' => ['hasSideEffects' => false], + 'gettimeofday' => ['hasSideEffects' => true], 'gettype' => ['hasSideEffects' => false], - 'glob' => ['hasSideEffects' => false], - 'gmdate' => ['hasSideEffects' => false], - 'gmmktime' => ['hasSideEffects' => false], + 'glob' => ['hasSideEffects' => true], + 'gmdate' => ['hasSideEffects' => true], + 'gmmktime' => ['hasSideEffects' => true], 'gmp_abs' => ['hasSideEffects' => false], 'gmp_add' => ['hasSideEffects' => false], 'gmp_and' => ['hasSideEffects' => false], @@ -1073,7 +1073,7 @@ 'headers_list' => ['hasSideEffects' => false], 'hebrev' => ['hasSideEffects' => false], 'hexdec' => ['hasSideEffects' => false], - 'hrtime' => ['hasSideEffects' => false], + 'hrtime' => ['hasSideEffects' => true], 'html_entity_decode' => ['hasSideEffects' => false], 'htmlentities' => ['hasSideEffects' => false], 'htmlspecialchars' => ['hasSideEffects' => false], @@ -1111,7 +1111,7 @@ 'iconv_strpos' => ['hasSideEffects' => false], 'iconv_strrpos' => ['hasSideEffects' => false], 'iconv_substr' => ['hasSideEffects' => false], - 'idate' => ['hasSideEffects' => false], + 'idate' => ['hasSideEffects' => true], 'image_type_to_extension' => ['hasSideEffects' => false], 'image_type_to_mime_type' => ['hasSideEffects' => false], 'imagecolorat' => ['hasSideEffects' => false], @@ -1146,13 +1146,13 @@ 'inflate_get_status' => ['hasSideEffects' => false], 'inflate_init' => ['hasSideEffects' => false], 'ini_get' => ['hasSideEffects' => false], - 'ini_get_all' => ['hasSideEffects' => false], + 'ini_get_all' => ['hasSideEffects' => true], 'intcal_get_maximum' => ['hasSideEffects' => false], 'intdiv' => ['hasSideEffects' => false], 'intl_error_name' => ['hasSideEffects' => false], 'intl_get' => ['hasSideEffects' => false], - 'intl_get_error_code' => ['hasSideEffects' => false], - 'intl_get_error_message' => ['hasSideEffects' => false], + 'intl_get_error_code' => ['hasSideEffects' => true], + 'intl_get_error_message' => ['hasSideEffects' => true], 'intl_is_failure' => ['hasSideEffects' => false], 'intlcal_after' => ['hasSideEffects' => false], 'intlcal_before' => ['hasSideEffects' => false], @@ -1165,8 +1165,8 @@ 'intlcal_get_actual_minimum' => ['hasSideEffects' => false], 'intlcal_get_available_locales' => ['hasSideEffects' => false], 'intlcal_get_day_of_week_type' => ['hasSideEffects' => false], - 'intlcal_get_error_code' => ['hasSideEffects' => false], - 'intlcal_get_error_message' => ['hasSideEffects' => false], + 'intlcal_get_error_code' => ['hasSideEffects' => true], + 'intlcal_get_error_message' => ['hasSideEffects' => true], 'intlcal_get_first_day_of_week' => ['hasSideEffects' => false], 'intlcal_get_greatest_minimum' => ['hasSideEffects' => false], 'intlcal_get_keyword_values_for_locale' => ['hasSideEffects' => false], @@ -1175,7 +1175,7 @@ 'intlcal_get_maximum' => ['hasSideEffects' => false], 'intlcal_get_minimal_days_in_first_week' => ['hasSideEffects' => false], 'intlcal_get_minimum' => ['hasSideEffects' => false], - 'intlcal_get_now' => ['hasSideEffects' => false], + 'intlcal_get_now' => ['hasSideEffects' => true], 'intlcal_get_repeated_wall_time_option' => ['hasSideEffects' => false], 'intlcal_get_skipped_wall_time_option' => ['hasSideEffects' => false], 'intlcal_get_time' => ['hasSideEffects' => false], @@ -1202,8 +1202,8 @@ 'intltz_get_display_name' => ['hasSideEffects' => false], 'intltz_get_dst_savings' => ['hasSideEffects' => false], 'intltz_get_equivalent_id' => ['hasSideEffects' => false], - 'intltz_get_error_code' => ['hasSideEffects' => false], - 'intltz_get_error_message' => ['hasSideEffects' => false], + 'intltz_get_error_code' => ['hasSideEffects' => true], + 'intltz_get_error_message' => ['hasSideEffects' => true], 'intltz_get_gmt' => ['hasSideEffects' => false], 'intltz_get_id' => ['hasSideEffects' => false], 'intltz_get_offset' => ['hasSideEffects' => false], @@ -1245,7 +1245,7 @@ 'is_scalar' => ['hasSideEffects' => false], 'is_string' => ['hasSideEffects' => false], 'is_subclass_of' => ['hasSideEffects' => false], - 'is_uploaded_file' => ['hasSideEffects' => false], + 'is_uploaded_file' => ['hasSideEffects' => true], 'is_writable' => ['hasSideEffects' => false], 'is_writeable' => ['hasSideEffects' => false], 'iterator_count' => ['hasSideEffects' => false], @@ -1258,10 +1258,10 @@ 'lcfirst' => ['hasSideEffects' => false], 'lchgrp' => ['hasSideEffects' => true], 'lchown' => ['hasSideEffects' => true], - 'libxml_get_errors' => ['hasSideEffects' => false], - 'libxml_get_last_error' => ['hasSideEffects' => false], + 'libxml_get_errors' => ['hasSideEffects' => true], + 'libxml_get_last_error' => ['hasSideEffects' => true], 'link' => ['hasSideEffects' => true], - 'linkinfo' => ['hasSideEffects' => false], + 'linkinfo' => ['hasSideEffects' => true], 'locale_accept_from_http' => ['hasSideEffects' => false], 'locale_canonicalize' => ['hasSideEffects' => false], 'locale_compose' => ['hasSideEffects' => false], @@ -1279,8 +1279,8 @@ 'locale_get_script' => ['hasSideEffects' => false], 'locale_lookup' => ['hasSideEffects' => false], 'locale_parse' => ['hasSideEffects' => false], - 'localeconv' => ['hasSideEffects' => false], - 'localtime' => ['hasSideEffects' => false], + 'localeconv' => ['hasSideEffects' => true], + 'localtime' => ['hasSideEffects' => true], 'log' => ['hasSideEffects' => false], 'log10' => ['hasSideEffects' => false], 'log1p' => ['hasSideEffects' => false], @@ -1336,9 +1336,9 @@ 'mb_substr_count' => ['hasSideEffects' => false], 'mbereg_search_setpos' => ['hasSideEffects' => false], 'md5' => ['hasSideEffects' => false], - 'md5_file' => ['hasSideEffects' => false], - 'memory_get_peak_usage' => ['hasSideEffects' => false], - 'memory_get_usage' => ['hasSideEffects' => false], + 'md5_file' => ['hasSideEffects' => true], + 'memory_get_peak_usage' => ['hasSideEffects' => true], + 'memory_get_usage' => ['hasSideEffects' => true], 'metaphone' => ['hasSideEffects' => false], 'method_exists' => ['hasSideEffects' => false], 'mhash' => ['hasSideEffects' => false], @@ -1346,16 +1346,16 @@ 'mhash_get_block_size' => ['hasSideEffects' => false], 'mhash_get_hash_name' => ['hasSideEffects' => false], 'mhash_keygen_s2k' => ['hasSideEffects' => false], - 'microtime' => ['hasSideEffects' => false], + 'microtime' => ['hasSideEffects' => true], 'min' => ['hasSideEffects' => false], 'mkdir' => ['hasSideEffects' => true], - 'mktime' => ['hasSideEffects' => false], + 'mktime' => ['hasSideEffects' => true], 'move_uploaded_file' => ['hasSideEffects' => true], 'msgfmt_create' => ['hasSideEffects' => false], 'msgfmt_format' => ['hasSideEffects' => false], 'msgfmt_format_message' => ['hasSideEffects' => false], - 'msgfmt_get_error_code' => ['hasSideEffects' => false], - 'msgfmt_get_error_message' => ['hasSideEffects' => false], + 'msgfmt_get_error_code' => ['hasSideEffects' => true], + 'msgfmt_get_error_message' => ['hasSideEffects' => true], 'msgfmt_get_locale' => ['hasSideEffects' => false], 'msgfmt_get_pattern' => ['hasSideEffects' => false], 'msgfmt_parse' => ['hasSideEffects' => false], @@ -1365,7 +1365,7 @@ 'net_get_interfaces' => ['hasSideEffects' => false], 'ngettext' => ['hasSideEffects' => false], 'nl2br' => ['hasSideEffects' => false], - 'nl_langinfo' => ['hasSideEffects' => false], + 'nl_langinfo' => ['hasSideEffects' => true], 'normalizer_get_raw_decomposition' => ['hasSideEffects' => false], 'normalizer_is_normalized' => ['hasSideEffects' => false], 'normalizer_normalize' => ['hasSideEffects' => false], @@ -1374,28 +1374,39 @@ 'numfmt_format' => ['hasSideEffects' => false], 'numfmt_format_currency' => ['hasSideEffects' => false], 'numfmt_get_attribute' => ['hasSideEffects' => false], - 'numfmt_get_error_code' => ['hasSideEffects' => false], - 'numfmt_get_error_message' => ['hasSideEffects' => false], + 'numfmt_get_error_code' => ['hasSideEffects' => true], + 'numfmt_get_error_message' => ['hasSideEffects' => true], 'numfmt_get_locale' => ['hasSideEffects' => false], 'numfmt_get_pattern' => ['hasSideEffects' => false], 'numfmt_get_symbol' => ['hasSideEffects' => false], 'numfmt_get_text_attribute' => ['hasSideEffects' => false], 'numfmt_parse' => ['hasSideEffects' => false], + 'ob_clean' => ['hasSideEffects' => true], + 'ob_end_clean' => ['hasSideEffects' => true], + 'ob_end_flush' => ['hasSideEffects' => true], 'ob_etaghandler' => ['hasSideEffects' => false], - 'ob_get_contents' => ['hasSideEffects' => false], + 'ob_flush' => ['hasSideEffects' => true], + 'ob_get_clean' => ['hasSideEffects' => true], + 'ob_get_contents' => ['hasSideEffects' => true], + 'ob_get_length' => ['hasSideEffects' => true], + 'ob_get_level' => ['hasSideEffects' => true], + 'ob_get_status' => ['hasSideEffects' => true], 'ob_iconv_handler' => ['hasSideEffects' => false], + 'ob_list_handlers' => ['hasSideEffects' => true], 'octdec' => ['hasSideEffects' => false], 'ord' => ['hasSideEffects' => false], + 'output_add_rewrite_var' => ['hasSideEffects' => true], + 'output_reset_rewrite_vars' => ['hasSideEffects' => true], 'pack' => ['hasSideEffects' => false], 'pam_auth' => ['hasSideEffects' => false], 'pam_chpass' => ['hasSideEffects' => false], - 'parse_ini_file' => ['hasSideEffects' => false], + 'parse_ini_file' => ['hasSideEffects' => true], 'parse_ini_string' => ['hasSideEffects' => false], 'parse_url' => ['hasSideEffects' => false], - 'pathinfo' => ['hasSideEffects' => false], + 'pathinfo' => ['hasSideEffects' => true], 'pclose' => ['hasSideEffects' => true], - 'pcntl_errno' => ['hasSideEffects' => false], - 'pcntl_get_last_error' => ['hasSideEffects' => false], + 'pcntl_errno' => ['hasSideEffects' => true], + 'pcntl_get_last_error' => ['hasSideEffects' => true], 'pcntl_getpriority' => ['hasSideEffects' => false], 'pcntl_strerror' => ['hasSideEffects' => false], 'pcntl_wexitstatus' => ['hasSideEffects' => false], @@ -1410,16 +1421,16 @@ 'php_ini_scanned_files' => ['hasSideEffects' => false], 'php_logo_guid' => ['hasSideEffects' => false], 'php_sapi_name' => ['hasSideEffects' => false], - 'php_strip_whitespace' => ['hasSideEffects' => false], - 'php_uname' => ['hasSideEffects' => false], + 'php_strip_whitespace' => ['hasSideEffects' => true], + 'php_uname' => ['hasSideEffects' => true], 'phpversion' => ['hasSideEffects' => false], 'pi' => ['hasSideEffects' => false], 'popen' => ['hasSideEffects' => true], 'pos' => ['hasSideEffects' => false], 'posix_ctermid' => ['hasSideEffects' => false], - 'posix_errno' => ['hasSideEffects' => false], - 'posix_get_last_error' => ['hasSideEffects' => false], - 'posix_getcwd' => ['hasSideEffects' => false], + 'posix_errno' => ['hasSideEffects' => true], + 'posix_get_last_error' => ['hasSideEffects' => true], + 'posix_getcwd' => ['hasSideEffects' => true], 'posix_getegid' => ['hasSideEffects' => false], 'posix_geteuid' => ['hasSideEffects' => false], 'posix_getgid' => ['hasSideEffects' => false], @@ -1444,8 +1455,8 @@ 'posix_uname' => ['hasSideEffects' => false], 'pow' => ['hasSideEffects' => false], 'preg_grep' => ['hasSideEffects' => false], - 'preg_last_error' => ['hasSideEffects' => false], - 'preg_last_error_msg' => ['hasSideEffects' => false], + 'preg_last_error' => ['hasSideEffects' => true], + 'preg_last_error_msg' => ['hasSideEffects' => true], 'preg_quote' => ['hasSideEffects' => false], 'preg_split' => ['hasSideEffects' => false], 'property_exists' => ['hasSideEffects' => false], @@ -1460,23 +1471,23 @@ 'rawurldecode' => ['hasSideEffects' => false], 'rawurlencode' => ['hasSideEffects' => false], 'readfile' => ['hasSideEffects' => true], - 'readlink' => ['hasSideEffects' => false], - 'realpath' => ['hasSideEffects' => false], - 'realpath_cache_get' => ['hasSideEffects' => false], - 'realpath_cache_size' => ['hasSideEffects' => false], + 'readlink' => ['hasSideEffects' => true], + 'realpath' => ['hasSideEffects' => true], + 'realpath_cache_get' => ['hasSideEffects' => true], + 'realpath_cache_size' => ['hasSideEffects' => true], 'rename' => ['hasSideEffects' => true], 'resourcebundle_count' => ['hasSideEffects' => false], 'resourcebundle_create' => ['hasSideEffects' => false], 'resourcebundle_get' => ['hasSideEffects' => false], - 'resourcebundle_get_error_code' => ['hasSideEffects' => false], - 'resourcebundle_get_error_message' => ['hasSideEffects' => false], + 'resourcebundle_get_error_code' => ['hasSideEffects' => true], + 'resourcebundle_get_error_message' => ['hasSideEffects' => true], 'resourcebundle_locales' => ['hasSideEffects' => false], 'rewind' => ['hasSideEffects' => true], 'rmdir' => ['hasSideEffects' => true], 'round' => ['hasSideEffects' => false], 'rtrim' => ['hasSideEffects' => false], 'sha1' => ['hasSideEffects' => false], - 'sha1_file' => ['hasSideEffects' => false], + 'sha1_file' => ['hasSideEffects' => true], 'sin' => ['hasSideEffects' => false], 'sinh' => ['hasSideEffects' => false], 'sizeof' => ['hasSideEffects' => false], @@ -1502,9 +1513,9 @@ 'strcmp' => ['hasSideEffects' => false], 'strcoll' => ['hasSideEffects' => false], 'strcspn' => ['hasSideEffects' => false], - 'stream_get_filters' => ['hasSideEffects' => false], - 'stream_get_transports' => ['hasSideEffects' => false], - 'stream_get_wrappers' => ['hasSideEffects' => false], + 'stream_get_filters' => ['hasSideEffects' => true], + 'stream_get_transports' => ['hasSideEffects' => true], + 'stream_get_wrappers' => ['hasSideEffects' => true], 'stream_is_local' => ['hasSideEffects' => false], 'stream_isatty' => ['hasSideEffects' => false], 'strip_tags' => ['hasSideEffects' => false], @@ -1519,7 +1530,7 @@ 'strncmp' => ['hasSideEffects' => false], 'strpbrk' => ['hasSideEffects' => false], 'strpos' => ['hasSideEffects' => false], - 'strptime' => ['hasSideEffects' => false], + 'strptime' => ['hasSideEffects' => true], 'strrchr' => ['hasSideEffects' => false], 'strrev' => ['hasSideEffects' => false], 'strripos' => ['hasSideEffects' => false], @@ -1527,7 +1538,7 @@ 'strspn' => ['hasSideEffects' => false], 'strstr' => ['hasSideEffects' => false], 'strtolower' => ['hasSideEffects' => false], - 'strtotime' => ['hasSideEffects' => false], + 'strtotime' => ['hasSideEffects' => true], 'strtoupper' => ['hasSideEffects' => false], 'strtr' => ['hasSideEffects' => false], 'strval' => ['hasSideEffects' => false], @@ -1536,18 +1547,18 @@ 'substr_count' => ['hasSideEffects' => false], 'substr_replace' => ['hasSideEffects' => false], 'symlink' => ['hasSideEffects' => true], - 'sys_getloadavg' => ['hasSideEffects' => false], + 'sys_getloadavg' => ['hasSideEffects' => true], 'tan' => ['hasSideEffects' => false], 'tanh' => ['hasSideEffects' => false], 'tempnam' => ['hasSideEffects' => true], 'timezone_abbreviations_list' => ['hasSideEffects' => false], - 'timezone_identifiers_list' => ['hasSideEffects' => false], - 'timezone_location_get' => ['hasSideEffects' => false], - 'timezone_name_from_abbr' => ['hasSideEffects' => false], + 'timezone_identifiers_list' => ['hasSideEffects' => true], + 'timezone_location_get' => ['hasSideEffects' => true], + 'timezone_name_from_abbr' => ['hasSideEffects' => true], 'timezone_name_get' => ['hasSideEffects' => false], - 'timezone_offset_get' => ['hasSideEffects' => false], - 'timezone_open' => ['hasSideEffects' => false], - 'timezone_transitions_get' => ['hasSideEffects' => false], + 'timezone_offset_get' => ['hasSideEffects' => true], + 'timezone_open' => ['hasSideEffects' => true], + 'timezone_transitions_get' => ['hasSideEffects' => true], 'timezone_version_get' => ['hasSideEffects' => false], 'tmpfile' => ['hasSideEffects' => true], 'token_get_all' => ['hasSideEffects' => false], @@ -1556,8 +1567,8 @@ 'transliterator_create' => ['hasSideEffects' => false], 'transliterator_create_from_rules' => ['hasSideEffects' => false], 'transliterator_create_inverse' => ['hasSideEffects' => false], - 'transliterator_get_error_code' => ['hasSideEffects' => false], - 'transliterator_get_error_message' => ['hasSideEffects' => false], + 'transliterator_get_error_code' => ['hasSideEffects' => true], + 'transliterator_get_error_message' => ['hasSideEffects' => true], 'transliterator_list_ids' => ['hasSideEffects' => false], 'transliterator_transliterate' => ['hasSideEffects' => false], 'trim' => ['hasSideEffects' => false], From b5f1cae5fb74014b2cf3f0251141a5957500b2f0 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 18 Mar 2025 10:43:13 +0100 Subject: [PATCH 1193/3097] Do not report constructor unused parameter if implemented interface has a constructor --- .../Classes/UnusedConstructorParametersRule.php | 9 +++++++-- .../UnusedConstructorParametersRuleTest.php | 5 +++++ tests/PHPStan/Rules/Classes/data/bug-11454.php | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-11454.php diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index d87581f69cc..b29ca65bd4b 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -16,7 +16,6 @@ use function array_values; use function count; use function sprintf; -use function strtolower; /** * @implements Rule @@ -37,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array { $method = $node->getMethodReflection(); $originalNode = $node->getOriginalNode(); - if (strtolower($method->getName()) !== '__construct' || $originalNode->stmts === null) { + if (!$method->isConstructor() || $originalNode->stmts === null) { return []; } @@ -48,6 +47,12 @@ public function processNode(Node $node, Scope $scope): array return []; } + foreach ($node->getClassReflection()->getInterfaces() as $interface) { + if ($interface->hasConstructor()) { + return []; + } + } + $message = sprintf( 'Constructor of class %s has an unused parameter $%%s.', SprintfHelper::escapeFormatString($node->getClassReflection()->getDisplayName()), diff --git a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php index cf547b909c4..542ba55e5eb 100644 --- a/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php +++ b/tests/PHPStan/Rules/Classes/UnusedConstructorParametersRuleTest.php @@ -71,4 +71,9 @@ public function testBug10865(): void $this->analyse([__DIR__ . '/data/bug-10865.php'], []); } + public function testBug11454(): void + { + $this->analyse([__DIR__ . '/data/bug-11454.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-11454.php b/tests/PHPStan/Rules/Classes/data/bug-11454.php new file mode 100644 index 00000000000..1a7fe447d16 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11454.php @@ -0,0 +1,14 @@ + Date: Tue, 18 Mar 2025 10:52:06 +0100 Subject: [PATCH 1194/3097] Fix build --- Makefile | 1 + build/collision-detector.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d50e2497e1c..9e007ae2cc1 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/final-properties.php \ --exclude tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php \ --exclude tests/PHPStan/Rules/Constants/data/final-private-const.php \ + --exclude tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php \ src tests cs: diff --git a/build/collision-detector.json b/build/collision-detector.json index 03c717dfff6..c3d69c08a7c 100644 --- a/build/collision-detector.json +++ b/build/collision-detector.json @@ -13,6 +13,7 @@ "../tests/PHPStan/Rules/Names/data/no-namespace.php", "../tests/notAutoloaded", "../tests/PHPStan/Rules/Functions/data/define-bug-3349.php", - "../tests/PHPStan/Levels/data/stubs/function.php" + "../tests/PHPStan/Levels/data/stubs/function.php", + "../tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php" ] } From 7ce62274a77312b4eea6d3b226c2d9cd692a254f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 18 Mar 2025 11:13:45 +0100 Subject: [PATCH 1195/3097] Fix build --- build/collision-detector.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/collision-detector.json b/build/collision-detector.json index c3d69c08a7c..a687cd3ea49 100644 --- a/build/collision-detector.json +++ b/build/collision-detector.json @@ -14,6 +14,7 @@ "../tests/notAutoloaded", "../tests/PHPStan/Rules/Functions/data/define-bug-3349.php", "../tests/PHPStan/Levels/data/stubs/function.php", - "../tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php" + "../tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php", + "../tests/PHPStan/Rules/Properties/data/final-property-hooks.php" ] } From 3b421cd54162890aee9be21394dfa7af8e708bae Mon Sep 17 00:00:00 2001 From: Shyim Date: Mon, 27 Jan 2025 13:46:26 +0100 Subject: [PATCH 1196/3097] fix: json error formatter when files are empty --- src/Command/ErrorFormatter/JsonErrorFormatter.php | 13 +++++++------ .../ErrorFormatter/JsonErrorFormatterTest.php | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Command/ErrorFormatter/JsonErrorFormatter.php b/src/Command/ErrorFormatter/JsonErrorFormatter.php index a46396f12ca..0a4174d4e02 100644 --- a/src/Command/ErrorFormatter/JsonErrorFormatter.php +++ b/src/Command/ErrorFormatter/JsonErrorFormatter.php @@ -5,9 +5,10 @@ use Nette\Utils\Json; use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; +use stdClass; use Symfony\Component\Console\Formatter\OutputFormatter; -use function array_key_exists; use function count; +use function property_exists; final class JsonErrorFormatter implements ErrorFormatter { @@ -23,7 +24,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in 'errors' => count($analysisResult->getNotFileSpecificErrors()), 'file_errors' => count($analysisResult->getFileSpecificErrors()), ], - 'files' => [], + 'files' => new stdClass(), 'errors' => [], ]; @@ -31,13 +32,13 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { $file = $fileSpecificError->getFile(); - if (!array_key_exists($file, $errorsArray['files'])) { - $errorsArray['files'][$file] = [ + if (!property_exists($errorsArray['files'], $file)) { + $errorsArray['files']->$file = [ 'errors' => 0, 'messages' => [], ]; } - $errorsArray['files'][$file]['errors']++; + $errorsArray['files']->$file['errors']++; $message = [ 'message' => $fileSpecificError->getMessage(), @@ -53,7 +54,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in $message['identifier'] = $fileSpecificError->getIdentifier(); } - $errorsArray['files'][$file]['messages'][] = $message; + $errorsArray['files']->$file['messages'][] = $message; } foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { diff --git a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php index 9a1eca01883..ff1626d7d29 100644 --- a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php @@ -24,7 +24,7 @@ public function dataFormatterOutputProvider(): iterable "errors":0, "file_errors":0 }, - "files":[], + "files":{}, "errors": [] }', ]; @@ -67,7 +67,7 @@ public function dataFormatterOutputProvider(): iterable "errors":1, "file_errors":0 }, - "files":[], + "files":{}, "errors": [ "first generic error" ] @@ -133,7 +133,7 @@ public function dataFormatterOutputProvider(): iterable "errors":2, "file_errors":0 }, - "files":[], + "files":{}, "errors": [ "first generic error", "second generic" From ef6cc0a1d6e2b60c0328cb6a47db55b4c615056b Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 18 Mar 2025 13:38:32 +0100 Subject: [PATCH 1197/3097] Fix readonly property assign with ArrayAccess offset --- .../ReadOnlyByPhpDocPropertyAssignRule.php | 12 +++++++ .../Properties/ReadOnlyPropertyAssignRule.php | 12 +++++++ ...ReadOnlyByPhpDocPropertyAssignRuleTest.php | 4 +++ .../ReadOnlyPropertyAssignRuleTest.php | 23 ++++++++++++++ .../Rules/Properties/data/bug-12537.php | 31 +++++++++++++++++++ .../Rules/Properties/data/bug-8929.php | 19 ++++++++++++ .../data/readonly-assign-phpdoc.php | 18 +++++++++++ .../Rules/Properties/data/readonly-assign.php | 17 ++++++++++ 8 files changed, 136 insertions(+) create mode 100755 tests/PHPStan/Rules/Properties/data/bug-12537.php create mode 100755 tests/PHPStan/Rules/Properties/data/bug-8929.php diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index 70f18bcbccb..4c475096ef7 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -2,8 +2,11 @@ namespace PHPStan\Rules\Properties; +use ArrayAccess; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\Expr\SetOffsetValueTypeExpr; +use PHPStan\Node\Expr\UnsetOffsetExpr; use PHPStan\Node\PropertyAssignNode; use PHPStan\Reflection\ConstructorsHelper; use PHPStan\Reflection\MethodReflection; @@ -11,6 +14,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\ObjectType; use PHPStan\Type\TypeUtils; use function in_array; use function sprintf; @@ -106,6 +110,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + $assignedExpr = $node->getAssignedExpr(); + if ( + ($assignedExpr instanceof SetOffsetValueTypeExpr || $assignedExpr instanceof UnsetOffsetExpr) + && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($assignedExpr->getVar()))->yes() + ) { + continue; + } + $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) ->identifier('property.readOnlyByPhpDocAssignNotInConstructor') ->build(); diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php index 4e9673070fc..eac07303a2a 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php @@ -2,14 +2,18 @@ namespace PHPStan\Rules\Properties; +use ArrayAccess; use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\Expr\SetOffsetValueTypeExpr; +use PHPStan\Node\Expr\UnsetOffsetExpr; use PHPStan\Node\PropertyAssignNode; use PHPStan\Reflection\ConstructorsHelper; use PHPStan\Reflection\MethodReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\ObjectType; use PHPStan\Type\TypeUtils; use function in_array; use function sprintf; @@ -89,6 +93,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + $assignedExpr = $node->getAssignedExpr(); + if ( + ($assignedExpr instanceof SetOffsetValueTypeExpr || $assignedExpr instanceof UnsetOffsetExpr) + && (new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($assignedExpr->getVar()))->yes() + ) { + continue; + } + $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) ->identifier('property.readOnlyAssignNotInConstructor') ->build(); diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php index c7ec6ed0ad8..0aecf4c09ca 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRuleTest.php @@ -125,6 +125,10 @@ public function testRule(): void '@readonly property ReadonlyPropertyAssignPhpDoc\C::$c is assigned outside of the constructor.', 293, ], + [ + '@readonly property ReadonlyPropertyAssignPhpDoc\ArrayAccessPropertyFetch::$storage is assigned outside of the constructor.', + 311, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php index 966f8e9e415..d54ae3a02f5 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyAssignRuleTest.php @@ -123,6 +123,11 @@ public function testRule(): void ]; } + $errors[] = [ + 'Readonly property ReadonlyPropertyAssign\ArrayAccessPropertyFetch::$storage is assigned outside of the constructor.', + 212, + ]; + $this->analyse([__DIR__ . '/data/readonly-assign.php'], $errors); } @@ -168,4 +173,22 @@ public function testBug6773(): void ]); } + public function testBug8929(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-8929.php'], []); + } + + public function testBug12537(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/bug-12537.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12537.php b/tests/PHPStan/Rules/Properties/data/bug-12537.php new file mode 100755 index 00000000000..85ae54496e2 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12537.php @@ -0,0 +1,31 @@ += 8.1 + +namespace Bug12537; + +use WeakMap; + +class Metadata { + /** + * @var WeakMap + */ + private readonly WeakMap $storage; + + public function __construct() { + $this->storage = new WeakMap(); + } + + public function set(stdClass $class, int $value): void { + $this->storage[$class] = $value; + } + + public function get(stdClass $class): mixed { + return $this->storage[$class] ?? null; + } +} + +$class = new stdClass(); +$meta = new Metadata(); + +$meta->set($class, 123); + +var_dump($meta->get($class)); diff --git a/tests/PHPStan/Rules/Properties/data/bug-8929.php b/tests/PHPStan/Rules/Properties/data/bug-8929.php new file mode 100755 index 00000000000..4138ce73c9d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-8929.php @@ -0,0 +1,19 @@ += 8.1 + +namespace Bug8929; + +class Test +{ + /** @var \WeakMap */ + protected readonly \WeakMap $cache; + + public function __construct() + { + $this->cache = new \WeakMap(); + } + + public function add(object $key, mixed $value): void + { + $this->cache[$key] = $value; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/readonly-assign-phpdoc.php b/tests/PHPStan/Rules/Properties/data/readonly-assign-phpdoc.php index 55af82fe305..c390bbb6da0 100644 --- a/tests/PHPStan/Rules/Properties/data/readonly-assign-phpdoc.php +++ b/tests/PHPStan/Rules/Properties/data/readonly-assign-phpdoc.php @@ -294,3 +294,21 @@ public function mod() } } + +class ArrayAccessPropertyFetch +{ + + /** @readonly */ + private \ArrayObject $storage; + + public function __construct() { + $this->storage = new \ArrayObject(); + } + + public function set(\stdClass $class, int $value): void { + $this->storage[$class] = $value; + unset($this->storage[$class]); + $this->storage = new \WeakMap(); // invalid + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/readonly-assign.php b/tests/PHPStan/Rules/Properties/data/readonly-assign.php index fe4af364668..e23655217c7 100644 --- a/tests/PHPStan/Rules/Properties/data/readonly-assign.php +++ b/tests/PHPStan/Rules/Properties/data/readonly-assign.php @@ -196,3 +196,20 @@ protected function setUp(): void } } + +class ArrayAccessPropertyFetch +{ + + private readonly \ArrayObject $storage; + + public function __construct() { + $this->storage = new \ArrayObject(); + } + + public function set(\stdClass $class, int $value): void { + $this->storage[$class] = $value; + unset($this->storage[$class]); + $this->storage = new \WeakMap(); // invalid + } + +} From 19df9a2d8ae0a6a6ce87010eac24d6ad0c18c6a1 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 18 Mar 2025 16:51:43 +0100 Subject: [PATCH 1198/3097] Collected data: reduce memory consumption & result cache size --- src/Analyser/Analyser.php | 5 +++- src/Analyser/AnalyserResult.php | 5 ++-- src/Analyser/FileAnalyser.php | 11 ++++---- src/Analyser/FileAnalyserResult.php | 5 ++-- src/Analyser/ResultCache/ResultCache.php | 5 ++-- .../ResultCache/ResultCacheManager.php | 25 ++++++++----------- src/Collectors/CollectedData.php | 2 ++ src/Command/AnalyseApplication.php | 22 +++++++++++++++- src/Command/WorkerCommand.php | 8 ++++-- src/Node/CollectedDataNode.php | 15 +++++------ src/Parallel/ParallelAnalyser.php | 9 ++++--- 11 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index 4ab99fc5d37..31599aaee45 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -12,6 +12,9 @@ use function count; use function memory_get_peak_usage; +/** + * @phpstan-import-type CollectorData from CollectedData + */ final class Analyser { @@ -59,7 +62,7 @@ public function analyse( $linesToIgnore = []; $unmatchedLineIgnores = []; - /** @var list $collectedData */ + /** @var CollectorData $collectedData */ $collectedData = []; $internalErrorsCount = 0; diff --git a/src/Analyser/AnalyserResult.php b/src/Analyser/AnalyserResult.php index 212fdcf422e..4226e76fbd7 100644 --- a/src/Analyser/AnalyserResult.php +++ b/src/Analyser/AnalyserResult.php @@ -8,6 +8,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult + * @phpstan-import-type CollectorData from CollectedData */ final class AnalyserResult { @@ -22,7 +23,7 @@ final class AnalyserResult * @param list $locallyIgnoredErrors * @param array $linesToIgnore * @param array $unmatchedLineIgnores - * @param list $collectedData + * @param CollectorData $collectedData * @param list $internalErrors * @param array>|null $dependencies * @param array> $exportedNodes @@ -125,7 +126,7 @@ public function getInternalErrors(): array } /** - * @return list + * @return CollectorData */ public function getCollectedData(): array { diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 969154c3c89..80724ea18ad 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -37,6 +37,9 @@ use const E_USER_WARNING; use const E_WARNING; +/** + * @phpstan-import-type CollectorData from CollectedData + */ final class FileAnalyser { @@ -76,7 +79,7 @@ public function analyseFile( /** @var list $locallyIgnoredErrors */ $locallyIgnoredErrors = []; - /** @var list $fileCollectedData */ + /** @var CollectorData $fileCollectedData */ $fileCollectedData = []; $fileDependencies = []; @@ -195,11 +198,7 @@ public function analyseFile( continue; } - $fileCollectedData[] = new CollectedData( - $collectedData, - $scope->getFile(), - get_class($collector), - ); + $fileCollectedData[$scope->getFile()][get_class($collector)][] = $collectedData; } try { diff --git a/src/Analyser/FileAnalyserResult.php b/src/Analyser/FileAnalyserResult.php index d1727f5824c..2aba60730f4 100644 --- a/src/Analyser/FileAnalyserResult.php +++ b/src/Analyser/FileAnalyserResult.php @@ -7,6 +7,7 @@ /** * @phpstan-type LinesToIgnore = array|null>> + * @phpstan-import-type CollectorData from CollectedData */ final class FileAnalyserResult { @@ -16,7 +17,7 @@ final class FileAnalyserResult * @param list $filteredPhpErrors * @param list $allPhpErrors * @param list $locallyIgnoredErrors - * @param list $collectedData + * @param CollectorData $collectedData * @param list $dependencies * @param list $exportedNodes * @param LinesToIgnore $linesToIgnore @@ -69,7 +70,7 @@ public function getLocallyIgnoredErrors(): array } /** - * @return list + * @return CollectorData */ public function getCollectedData(): array { diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index f409f9c0627..1708a1f53b9 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -9,6 +9,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult + * @phpstan-import-type CollectorData from CollectedData */ final class ResultCache { @@ -20,7 +21,7 @@ final class ResultCache * @param array> $locallyIgnoredErrors * @param array $linesToIgnore * @param array $unmatchedLineIgnores - * @param array> $collectedData + * @param CollectorData $collectedData * @param array> $dependencies * @param array> $exportedNodes * @param array $projectExtensionFiles @@ -101,7 +102,7 @@ public function getUnmatchedLineIgnores(): array } /** - * @return array> + * @return CollectorData */ public function getCollectedData(): array { diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 0db2ffc5463..80ea03ac06c 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -49,6 +49,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult + * @phpstan-import-type CollectorData from CollectedData */ final class ResultCacheManager { @@ -406,10 +407,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $freshLocallyIgnoredErrorsByFile[$error->getFilePath()][] = $error; } - $freshCollectedDataByFile = []; - foreach ($analyserResult->getCollectedData() as $collectedData) { - $freshCollectedDataByFile[$collectedData->getFilePath()][] = $collectedData; - } + $freshCollectedDataByFile = $analyserResult->getCollectedData(); $meta = $resultCache->getMeta(); $projectConfigArray = $meta['projectConfig']; @@ -524,13 +522,6 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } } - $flatCollectedData = []; - foreach ($collectedDataByFile as $fileCollectedData) { - foreach ($fileCollectedData as $collectedData) { - $flatCollectedData[] = $collectedData; - } - } - return new ResultCacheProcessResult(new AnalyserResult( $flatErrors, $analyserResult->getFilteredPhpErrors(), @@ -539,7 +530,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $linesToIgnore, $unmatchedLineIgnores, $internalErrors, - $flatCollectedData, + $collectedDataByFile, $dependencies, $exportedNodes, $analyserResult->hasReachedInternalErrorsCountLimit(), @@ -584,8 +575,8 @@ private function mergeLocallyIgnoredErrors(ResultCache $resultCache, array $fres } /** - * @param array> $freshCollectedDataByFile - * @return array> + * @param CollectorData $freshCollectedDataByFile + * @return CollectorData */ private function mergeCollectedData(ResultCache $resultCache, array $freshCollectedDataByFile): array { @@ -704,7 +695,7 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres * @param array> $locallyIgnoredErrors * @param array $linesToIgnore * @param array $unmatchedLineIgnores - * @param array> $collectedData + * @param array>> $collectedData * @param array> $dependencies * @param array> $exportedNodes * @param array $projectExtensionFiles @@ -760,6 +751,10 @@ private function save( ksort($collectedData); ksort($invertedDependencies); + foreach ($collectedData as & $collectedDataPerFile) { + ksort($collectedDataPerFile); + } + foreach ($invertedDependencies as $file => $fileData) { $dependentFiles = $fileData['dependentFiles']; sort($dependentFiles); diff --git a/src/Collectors/CollectedData.php b/src/Collectors/CollectedData.php index 1ae0078880a..f6057f8cf83 100644 --- a/src/Collectors/CollectedData.php +++ b/src/Collectors/CollectedData.php @@ -8,6 +8,8 @@ /** * @api + * + * @phpstan-type CollectorData = array>, list>> */ final class CollectedData implements JsonSerializable { diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 88589db6cc6..81793c6ba05 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Ignore\IgnoredErrorHelper; use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory; +use PHPStan\Collectors\CollectedData; use PHPStan\Internal\BytesHelper; use PHPStan\PhpDoc\StubFilesProvider; use PHPStan\PhpDoc\StubValidator; @@ -19,6 +20,9 @@ use function sha1_file; use function sprintf; +/** + * @phpstan-import-type CollectorData from CollectedData + */ final class AnalyseApplication { @@ -150,7 +154,7 @@ public function analyse( $notFileSpecificErrors, $internalErrors, [], - $collectedData, + $this->mapCollectedData($collectedData), $defaultLevelUsed, $projectConfigFile, $savedResultCache, @@ -160,6 +164,22 @@ public function analyse( ); } + /** + * @param CollectorData $collectedData + * + * @return list + */ + private function mapCollectedData(array $collectedData): array + { + $result = []; + foreach ($collectedData as $file => $dataPerCollector) { + foreach ($dataPerCollector as $collectorType => $rawData) { + $result[] = new CollectedData($rawData, $file, $collectorType); + } + } + return $result; + } + /** * @param string[] $files * @param string[] $allAnalysedFiles diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 6e44a112177..27fdb1ce802 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -226,8 +226,12 @@ private function runWorker( foreach ($fileAnalyserResult->getLocallyIgnoredErrors() as $locallyIgnoredError) { $locallyIgnoredErrors[] = $locallyIgnoredError; } - foreach ($fileAnalyserResult->getCollectedData() as $data) { - $collectedData[] = $data; + foreach ($fileAnalyserResult->getCollectedData() as $collectedFile => $dataPerCollector) { + foreach ($dataPerCollector as $collectorType => $collectorData) { + foreach ($collectorData as $data) { + $collectedData[$collectedFile][$collectorType][] = $data; + } + } } } catch (Throwable $t) { $internalErrorsCount++; diff --git a/src/Node/CollectedDataNode.php b/src/Node/CollectedDataNode.php index 8c0f52dc3b8..acc4f24f2d9 100644 --- a/src/Node/CollectedDataNode.php +++ b/src/Node/CollectedDataNode.php @@ -6,16 +6,16 @@ use PhpParser\NodeAbstract; use PHPStan\Collectors\CollectedData; use PHPStan\Collectors\Collector; -use function array_key_exists; /** * @api + * @phpstan-import-type CollectorData from CollectedData */ final class CollectedDataNode extends NodeAbstract implements VirtualNode { /** - * @param CollectedData[] $collectedData + * @param CollectorData $collectedData */ public function __construct(private array $collectedData, private bool $onlyFiles) { @@ -31,17 +31,14 @@ public function __construct(private array $collectedData, private bool $onlyFile public function get(string $collectorType): array { $result = []; - foreach ($this->collectedData as $collectedData) { - if ($collectedData->getCollectorType() !== $collectorType) { + foreach ($this->collectedData as $filePath => $collectedDataPerCollector) { + if (!isset($collectedDataPerCollector[$collectorType])) { continue; } - $filePath = $collectedData->getFilePath(); - if (!array_key_exists($filePath, $result)) { - $result[$filePath] = []; + foreach ($collectedDataPerCollector[$collectorType] as $rawData) { + $result[$filePath][] = $rawData; } - - $result[$filePath][] = $collectedData->getData(); } return $result; diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 4c31b630505..96dcd368204 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -9,7 +9,6 @@ use PHPStan\Analyser\AnalyserResult; use PHPStan\Analyser\Error; use PHPStan\Analyser\InternalError; -use PHPStan\Collectors\CollectedData; use PHPStan\Dependency\RootExportedNode; use PHPStan\Process\ProcessHelper; use React\EventLoop\LoopInterface; @@ -211,8 +210,12 @@ public function analyse( $locallyIgnoredErrors[] = $locallyIgnoredFileError; } - foreach ($json['collectedData'] as $jsonData) { - $collectedData[] = CollectedData::decode($jsonData); + foreach ($json['collectedData'] as $file => $jsonDataByCollector) { + foreach ($jsonDataByCollector as $collectorType => $listOfCollectedData) { + foreach ($listOfCollectedData as $rawCollectedData) { + $collectedData[$file][$collectorType][] = $rawCollectedData; + } + } } /** From 791e708efd0e4f2b845d6d9a34494f54c493ca81 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 18 Mar 2025 20:07:40 +0100 Subject: [PATCH 1199/3097] Optimize scalar values in oversized constant arrays --- src/Type/TypeCombinator.php | 4 + .../Analyser/AnalyserIntegrationTest.php | 6 + tests/PHPStan/Analyser/data/bug-12671.php | 1198 +++++++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12671.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index cd776efa5fd..3f3a053368f 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -843,6 +843,10 @@ private static function optimizeConstantArrays(array $types): array return TypeCombinator::intersect($type, new OversizedArrayType()); } + if ($type instanceof ConstantScalarType) { + return $type->generalize(GeneralizePrecision::moreSpecific()); + } + return $traverse($type); }); $valueTypes[$generalizedValueType->describe(VerbosityLevel::precise())] = $generalizedValueType; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index a73ff8ec727..fe7bacfed49 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -913,6 +913,12 @@ public function testBug7637(): void $this->assertSame(57, $errors[2]->getLine()); } + public function testBug12671(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12671.php'); + $this->assertNoErrors($errors); + } + public function testBug7737(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7737.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12671.php b/tests/PHPStan/Analyser/data/bug-12671.php new file mode 100644 index 00000000000..80663164001 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12671.php @@ -0,0 +1,1198 @@ + [], + // Angola. + 'AO' => [], + // Argentina. + 'AR' => [ + 'C' => ['Ciudad Autónoma de Buenos Aires', 'Ciudad Autónoma de Buenos Aires', NULL], + 'B' => ['Buenos Aires', 'Buenos Aires', NULL], + 'K' => ['Catamarca', 'Catamarca', NULL], + 'H' => ['Chaco', 'Chaco', NULL], + 'U' => ['Chubut', 'Chubut', NULL], + 'X' => ['Córdoba', 'Córdoba', NULL], + 'W' => ['Corrientes', 'Corrientes', NULL], + 'E' => ['Entre Ríos', 'Entre Ríos', NULL], + 'P' => ['Formosa', 'Formosa', NULL], + 'Y' => ['Jujuy', 'Jujuy', NULL], + 'L' => ['La Pampa', 'La Pampa', NULL], + 'F' => ['La Rioja', 'La Rioja', NULL], + 'M' => ['Mendoza', 'Mendoza', NULL], + 'N' => ['Misiones', 'Misiones', NULL], + 'Q' => ['Neuquén', 'Neuquén', NULL], + 'R' => ['Río Negro', 'Río Negro', NULL], + 'A' => ['Salta', 'Salta', NULL], + 'J' => ['San Juan', 'San Juan', NULL], + 'D' => ['San Luis', 'San Luis', NULL], + 'Z' => ['Santa Cruz', 'Santa Cruz', NULL], + 'S' => ['Santa Fe', 'Santa Fe', NULL], + 'G' => ['Santiago del Estero', 'Santiago del Estero', NULL], + 'V' => ['Tierra del Fuego', 'Tierra del Fuego', NULL], + 'T' => ['Tucumán', 'Tucumán', NULL], + ], + // Austria. + 'AT' => [], + // Australia. + 'AU' => [ + 'ACT' => ['ACT', 'Australian Capital Territory', NULL], + 'NSW' => ['NSW', 'New South Wales', NULL], + 'NT' => ['NT', 'Northern Territory', NULL], + 'QLD' => ['QLD', 'Queensland', NULL], + 'SA' => ['SA', 'South Australia', NULL], + 'TAS' => ['TAS', 'Tasmania', NULL], + 'VIC' => ['VIC', 'Victoria', NULL], + 'WA' => ['WA', 'Western Australia', NULL], + // [ 'JBT', 'Jervis Bay Territory', NULL ], + ], + // Aland Islands. + 'AX' => [], + // Bangladesh. + 'BD' => [], + // Belgium. + 'BE' => [], + // Bulgaria. + 'BG' => [], + // Bahrain. + 'BH' => [], + // Burundi. + 'BI' => [], + // Benin. + 'BJ' => [], + // Bolivia. + 'BO' => [], + // Brazil. + 'BR' => [ + 'AC' => ['AC', 'Acre', NULL], + 'AL' => ['AL', 'Alagoas', NULL], + 'AP' => ['AP', 'Amapá', NULL], + 'AM' => ['AM', 'Amazonas', NULL], + 'BA' => ['BA', 'Bahia', NULL], + 'CE' => ['CE', 'Ceará', NULL], + 'DF' => ['DF', 'Distrito Federal', NULL], + 'ES' => ['ES', 'Espírito Santo', NULL], + 'GO' => ['GO', 'Goiás', NULL], + 'MA' => ['MA', 'Maranhão', NULL], + 'MT' => ['MT', 'Mato Grosso', NULL], + 'MS' => ['MS', 'Mato Grosso do Sul', NULL], + 'MG' => ['MG', 'Minas Gerais', NULL], + 'PA' => ['PA', 'Pará', NULL], + 'PB' => ['PB', 'Paraíba', NULL], + 'PR' => ['PR', 'Paraná', NULL], + 'PE' => ['PE', 'Pernambuco', NULL], + 'PI' => ['PI', 'Piauí', NULL], + 'RJ' => ['RJ', 'Rio de Janeiro', NULL], + 'RN' => ['RN', 'Rio Grande do Norte', NULL], + 'RS' => ['RS', 'Rio Grande do Sul', NULL], + 'RO' => ['RO', 'Rondônia', NULL], + 'RR' => ['RR', 'Roraima', NULL], + 'SC' => ['SC', 'Santa Catarina', NULL], + 'SP' => ['SP', 'São Paulo', NULL], + 'SE' => ['SE', 'Sergipe', NULL], + 'TO' => ['TO', 'Tocantins', NULL], + ], + // Canada. + 'CA' => [ + 'AB' => ['AB', 'Alberta', 'Alberta'], + 'BC' => ['BC', 'British Columbia', 'Colombie-Britannique'], + 'MB' => ['MB', 'Manitoba', 'Manitoba'], + 'NB' => ['NB', 'New Brunswick', 'Nouveau-Brunswick'], + 'NL' => ['NL', 'Newfoundland and Labrador', 'Terre-Neuve-et-Labrador'], + 'NT' => ['NT', 'Northwest Territories', 'Territoires du Nord-Ouest'], + 'NS' => ['NS', 'Nova Scotia', 'Nouvelle-Écosse'], + 'NU' => ['NU', 'Nunavut', 'Nunavut'], + 'ON' => ['ON', 'Ontario', 'Ontario'], + 'PE' => ['PE', 'Prince Edward Island', 'Île-du-Prince-Édouard'], + 'QC' => ['QC', 'Quebec', 'Québec'], + 'SK' => ['SK', 'Saskatchewan', 'Saskatchewan'], + 'YT' => ['YT', 'Yukon', 'Yukon'], + ], + // Switzerland. + 'CH' => [], + // China. + 'CN' => [ + 'CN1' => ['Yunnan Sheng', 'Yunnan Sheng', '云南省'], + 'CN2' => ['Beijing Shi', 'Beijing Shi', '北京市'], + 'CN3' => ['Tianjin Shi', 'Tianjin Shi', '天津市'], + 'CN4' => ['Hebei Sheng', 'Hebei Sheng', '河北省'], + 'CN5' => ['Shanxi Sheng', 'Shanxi Sheng', '山西省'], + 'CN6' => ['Neimenggu Zizhiqu', 'Neimenggu Zizhiqu', '内蒙古'], + 'CN7' => ['Liaoning Sheng', 'Liaoning Sheng', '辽宁省'], + 'CN8' => ['Jilin Sheng', 'Jilin Sheng', '吉林省'], + 'CN9' => ['Heilongjiang Sheng', 'Heilongjiang Sheng', '黑龙江省'], + 'CN10' => ['Shanghai Shi', 'Shanghai Shi', '上海市'], + 'CN11' => ['Jiangsu Sheng', 'Jiangsu Sheng', '江苏省'], + 'CN12' => ['Zhejiang Sheng', 'Zhejiang Sheng', '浙江省'], + 'CN13' => ['Anhui Sheng', 'Anhui Sheng', '安徽省'], + 'CN14' => ['Fujian Sheng', 'Fujian Sheng', '福建省'], + 'CN15' => ['Jiangxi Sheng', 'Jiangxi Sheng', '江西省'], + 'CN16' => ['Shandong Sheng', 'Shandong Sheng', '山东省'], + 'CN17' => ['Henan Sheng', 'Henan Sheng', '河南省'], + 'CN18' => ['Hubei Sheng', 'Hubei Sheng', '湖北省'], + 'CN19' => ['Hunan Sheng', 'Hunan Sheng', '湖南省'], + 'CN20' => ['Guangdong Sheng', 'Guangdong Sheng', '广东省'], + 'CN21' => ['Guangxi Zhuangzuzizhiqu', 'Guangxi Zhuangzuzizhiqu', '广西'], + 'CN22' => ['Hainan Sheng', 'Hainan Sheng', '海南省'], + 'CN23' => ['Chongqing Shi', 'Chongqing Shi', '重庆市'], + 'CN24' => ['Sichuan Sheng', 'Sichuan Sheng', '四川省'], + 'CN25' => ['Guizhou Sheng', 'Guizhou Sheng', '贵州省'], + 'CN26' => ['Shaanxi Sheng', 'Shaanxi Sheng', '陕西省'], + 'CN27' => ['Gansu Sheng', 'Gansu Sheng', '甘肃省'], + 'CN28' => ['Qinghai Sheng', 'Qinghai Sheng', '青海省'], + 'CN29' => ['Ningxia Huizuzizhiqu', 'Ningxia Huizuzizhiqu', '宁夏'], + 'CN30' => ['Macau', 'Macau', '澳门'], + 'CN31' => ['Xizang Zizhiqu', 'Xizang Zizhiqu', '西藏'], + 'CN32' => ['Xinjiang Weiwuerzizhiqu', 'Xinjiang Weiwuerzizhiqu', '新疆'], + // [ 'Taiwan', 'Taiwan', '台湾' ], + // [ 'Hong Kong', 'Hong Kong', '香港' ], + ], + // Czech Republic. + 'CZ' => [], + // Germany. + 'DE' => [], + // Denmark. + 'DK' => [], + // Dominican Republic. + 'DO' => [], + // Algeria. + 'DZ' => [], + // Estonia. + 'EE' => [], + // Egypt. + 'EG' => [ + 'EGALX' => ['Alexandria Governorate', 'Alexandria Governorate', 'الإسكندرية'], + 'EGASN' => ['Aswan Governorate', 'Aswan Governorate', 'أسوان'], + 'EGAST' => ['Asyut Governorate', 'Asyut Governorate', 'أسيوط'], + 'EGBA' => ['Red Sea Governorate', 'Red Sea Governorate', 'البحر الأحمر'], + 'EGBH' => ['El Beheira Governorate', 'El Beheira Governorate', 'البحيرة'], + 'EGBNS' => ['Beni Suef Governorate', 'Beni Suef Governorate', 'بني سويف'], + 'EGC' => ['Cairo Governorate', 'Cairo Governorate', 'القاهرة'], + 'EGDK' => ['Dakahlia Governorate', 'Dakahlia Governorate', 'الدقهلية'], + 'EGDT' => ['Damietta Governorate', 'Damietta Governorate', 'دمياط'], + 'EGFYM' => ['Faiyum Governorate', 'Faiyum Governorate', 'الفيوم'], + 'EGGH' => ['Gharbia Governorate', 'Gharbia Governorate', 'الغربية'], + 'EGGZ' => ['Giza Governorate', 'Giza Governorate', 'الجيزة'], + 'EGIS' => ['Ismailia Governorate', 'Ismailia Governorate', 'الإسماعيلية'], + 'EGJS' => ['South Sinai Governorate', 'South Sinai Governorate', 'جنوب سيناء'], + 'EGKB' => ['Qalyubia Governorate', 'Qalyubia Governorate', 'القليوبية'], + 'EGKFS' => ['Kafr El Sheikh Governorate', 'Kafr El Sheikh Governorate', 'كفر الشيخ'], + 'EGKN' => ['Qena Governorate', 'Qena Governorate', 'قنا'], + 'EGLX' => ['Luxor Governorate', 'Luxor Governorate', 'الأقصر'], + 'EGMN' => ['Menia Governorate', 'Menia Governorate', 'المنيا'], + 'EGMNF' => ['Menofia Governorate', 'Menofia Governorate', 'المنوفية'], + 'EGMT' => ['Matrouh Governorate', 'Matrouh Governorate', 'مطروح'], + 'EGPTS' => ['Port Said Governorate', 'Port Said Governorate', 'بورسعيد'], + 'EGSHG' => ['Sohag Governorate', 'Sohag Governorate', 'سوهاج'], + 'EGSHR' => ['Ash Sharqia Governorate', 'Ash Sharqia Governorate', 'الشرقية'], + 'EGSIN' => ['North Sinai Governorate', 'North Sinai Governorate', 'شمال سيناء'], + 'EGSUZ' => ['Suez Governorate', 'Suez Governorate', 'السويس'], + 'EGWAD' => ['New Valley Governorate', 'New Valley Governorate', 'الوادي الجديد'], + ], + // Spain. + 'ES' => [ + 'C' => ['A Coruña', 'A Coruña', NULL], + 'VI' => ['Álava', 'Álava', NULL], + 'AB' => ['Albacete', 'Albacete', NULL], + 'A' => ['Alicante', 'Alicante', NULL], + 'AL' => ['Almería', 'Almería', NULL], + 'O' => ['Asturias', 'Asturias', NULL], + 'AV' => ['Ávila', 'Ávila', NULL], + 'BA' => ['Badajoz', 'Badajoz', NULL], + 'PM' => ['Balears', 'Balears', NULL], + 'B' => ['Barcelona', 'Barcelona', NULL], + 'BU' => ['Burgos', 'Burgos', NULL], + 'CC' => ['Cáceres', 'Cáceres', NULL], + 'CA' => ['Cádiz', 'Cádiz', NULL], + 'S' => ['Cantabria', 'Cantabria', NULL], + 'CS' => ['Castellón', 'Castellón', NULL], + 'CE' => ['Ceuta', 'Ceuta', NULL], + 'CR' => ['Ciudad Real', 'Ciudad Real', NULL], + 'CO' => ['Córdoba', 'Córdoba', NULL], + 'CU' => ['Cuenca', 'Cuenca', NULL], + 'GI' => ['Girona', 'Girona', NULL], + 'GR' => ['Granada', 'Granada', NULL], + 'GU' => ['Guadalajara', 'Guadalajara', NULL], + 'SS' => ['Guipúzcoa', 'Guipúzcoa', NULL], + 'H' => ['Huelva', 'Huelva', NULL], + 'HU' => ['Huesca', 'Huesca', NULL], + 'J' => ['Jaén', 'Jaén', NULL], + 'LO' => ['La Rioja', 'La Rioja', NULL], + 'GC' => ['Las Palmas', 'Las Palmas', NULL], + 'LE' => ['León', 'León', NULL], + 'L' => ['Lleida', 'Lleida', NULL], + 'LU' => ['Lugo', 'Lugo', NULL], + 'M' => ['Madrid', 'Madrid', NULL], + 'MA' => ['Málaga', 'Málaga', NULL], + 'ML' => ['Melilla', 'Melilla', NULL], + 'MU' => ['Murcia', 'Murcia', NULL], + 'NA' => ['Navarra', 'Navarra', NULL], + 'OR' => ['Ourense', 'Ourense', NULL], + 'P' => ['Palencia', 'Palencia', NULL], + 'PO' => ['Pontevedra', 'Pontevedra', NULL], + 'SA' => ['Salamanca', 'Salamanca', NULL], + 'TF' => ['Santa Cruz de Tenerife', 'Santa Cruz de Tenerife', NULL], + 'SG' => ['Segovia', 'Segovia', NULL], + 'SE' => ['Sevilla', 'Sevilla', NULL], + 'SO' => ['Soria', 'Soria', NULL], + 'T' => ['Tarragona', 'Tarragona', NULL], + 'TE' => ['Teruel', 'Teruel', NULL], + 'TO' => ['Toledo', 'Toledo', NULL], + 'V' => ['Valencia', 'Valencia', NULL], + 'VA' => ['Valladolid', 'Valladolid', NULL], + 'BI' => ['Vizcaya', 'Vizcaya', NULL], + 'ZA' => ['Zamora', 'Zamora', NULL], + 'Z' => ['Zaragoza', 'Zaragoza', NULL], + ], + // Finland. + 'FI' => [], + // France. + 'FR' => [], + // French Guiana. + 'GF' => [], + // Ghana. + 'GH' => [], + // Guadeloupe. + 'GP' => [], + // Greece. + 'GR' => [], + // Guatemala. + 'GT' => [], + // Hong Kong. + 'HK' => [ + 'HONG KONG' => ['Hong Kong Island', 'Hong Kong Island', '香港島'], + 'KOWLOON' => ['Kowloon', 'Kowloon', '九龍'], + 'NEW TERRITORIES' => ['New Territories', 'New Territories', '新界'], + ], + // Hungary. + 'HU' => [], + // Indonesia. + 'ID' => [ + 'AC' => ['Aceh', 'Aceh', NULL], + 'SU' => ['Sumatera Utara', 'Sumatera Utara', NULL], + 'SB' => ['Sumatera Barat', 'Sumatera Barat', NULL], + 'RI' => ['Riau', 'Riau', NULL], + 'KR' => ['Kepulauan Riau', 'Kepulauan Riau', NULL], + 'JA' => ['Jambi', 'Jambi', NULL], + 'SS' => ['Sumatera Selatan', 'Sumatera Selatan', NULL], + 'BB' => ['Kepulauan Bangka Belitung', 'Kepulauan Bangka Belitung', NULL], + 'BE' => ['Bengkulu', 'Bengkulu', NULL], + 'LA' => ['Lampung', 'Lampung', NULL], + 'JK' => ['DKI Jakarta', 'DKI Jakarta', NULL], + 'JB' => ['Jawa Barat', 'Jawa Barat', NULL], + 'BT' => ['Banten', 'Banten', NULL], + 'JT' => ['Jawa Tengah', 'Jawa Tengah', NULL], + 'JI' => ['Jawa Timur', 'Jawa Timur', NULL], + 'YO' => ['Daerah Istimewa Yogyakarta', 'Daerah Istimewa Yogyakarta', NULL], + 'BA' => ['Bali', 'Bali', NULL], + 'NB' => ['Nusa Tenggara Barat', 'Nusa Tenggara Barat', NULL], + 'NT' => ['Nusa Tenggara Timur', 'Nusa Tenggara Timur', NULL], + 'KB' => ['Kalimantan Barat', 'Kalimantan Barat', NULL], + 'KT' => ['Kalimantan Tengah', 'Kalimantan Tengah', NULL], + 'KI' => ['Kalimantan Timur', 'Kalimantan Timur', NULL], + 'KS' => ['Kalimantan Selatan', 'Kalimantan Selatan', NULL], + 'KU' => ['Kalimantan Utara', 'Kalimantan Utara', NULL], + 'SA' => ['Sulawesi Utara', 'Sulawesi Utara', NULL], + 'ST' => ['Sulawesi Tengah', 'Sulawesi Tengah', NULL], + 'SG' => ['Sulawesi Tenggara', 'Sulawesi Tenggara', NULL], + 'SR' => ['Sulawesi Barat', 'Sulawesi Barat', NULL], + 'SN' => ['Sulawesi Selatan', 'Sulawesi Selatan', NULL], + 'GO' => ['Gorontalo', 'Gorontalo', NULL], + 'MA' => ['Maluku', 'Maluku', NULL], + 'MU' => ['Maluku Utara', 'Maluku Utara', NULL], + 'PA' => ['Papua', 'Papua', NULL], + 'PB' => ['Papua Barat', 'Papua Barat', NULL], + // [ 'Kalimantan Tengah', 'Kalimantan Tengah', NULL ], + // [ 'Kalimantan Timur', 'Kalimantan Timur', NULL ], + ], + // Ireland. + 'IE' => [ + 'CW' => ['Co. Carlow', 'Co. Carlow', NULL], + 'CN' => ['Co. Cavan', 'Co. Cavan', NULL], + 'CE' => ['Co. Clare', 'Co. Clare', NULL], + 'CO' => ['Co. Cork', 'Co. Cork', NULL], + 'DL' => ['Co. Donegal', 'Co. Donegal', NULL], + 'D' => ['Co. Dublin', 'Co. Dublin', NULL], + 'G' => ['Co. Galway', 'Co. Galway', NULL], + 'KY' => ['Co. Kerry', 'Co. Kerry', NULL], + 'KE' => ['Co. Kildare', 'Co. Kildare', NULL], + 'KK' => ['Co. Kilkenny', 'Co. Kilkenny', NULL], + 'LS' => ['Co. Laois', 'Co. Laois', NULL], + 'LM' => ['Co. Leitrim', 'Co. Leitrim', NULL], + 'LK' => ['Co. Limerick', 'Co. Limerick', NULL], + 'LD' => ['Co. Longford', 'Co. Longford', NULL], + 'LH' => ['Co. Louth', 'Co. Louth', NULL], + 'MO' => ['Co. Mayo', 'Co. Mayo', NULL], + 'MH' => ['Co. Meath', 'Co. Meath', NULL], + 'MN' => ['Co. Monaghan', 'Co. Monaghan', NULL], + 'OY' => ['Co. Offaly', 'Co. Offaly', NULL], + 'RN' => ['Co. Roscommon', 'Co. Roscommon', NULL], + 'SO' => ['Co. Sligo', 'Co. Sligo', NULL], + 'TA' => ['Co. Tipperary', 'Co. Tipperary', NULL], + 'WD' => ['Co. Waterford', 'Co. Waterford', NULL], + 'WH' => ['Co. Westmeath', 'Co. Westmeath', NULL], + 'WX' => ['Co. Wexford', 'Co. Wexford', NULL], + 'WW' => ['Co. Wicklow', 'Co. Wicklow', NULL], + ], + // Israel. + 'IL' => [], + // Isle of Man. + 'IM' => [], + // India. + 'IN' => [ + 'AP' => ['Andhra Pradesh', 'Andhra Pradesh', NULL], + 'AR' => ['Arunachal Pradesh', 'Arunachal Pradesh', NULL], + 'AS' => ['Assam', 'Assam', NULL], + 'BR' => ['Bihar', 'Bihar', NULL], + 'CT' => ['Chhattisgarh', 'Chhattisgarh', NULL], + 'GA' => ['Goa', 'Goa', NULL], + 'GJ' => ['Gujarat', 'Gujarat', NULL], + 'HR' => ['Haryana', 'Haryana', NULL], + 'HP' => ['Himachal Pradesh', 'Himachal Pradesh', NULL], + 'JK' => ['Jammu and Kashmir', 'Jammu & Kashmir', NULL], + 'JH' => ['Jharkhand', 'Jharkhand', NULL], + 'KA' => ['Karnataka', 'Karnataka', NULL], + 'KL' => ['Kerala', 'Kerala', NULL], + // 'LA' => __( 'Ladakh', 'woocommerce' ), + 'MP' => ['Madhya Pradesh', 'Madhya Pradesh', NULL], + 'MH' => ['Maharashtra', 'Maharashtra', NULL], + 'MN' => ['Manipur', 'Manipur', NULL], + 'ML' => ['Meghalaya', 'Meghalaya', NULL], + 'MZ' => ['Mizoram', 'Mizoram', NULL], + 'NL' => ['Nagaland', 'Nagaland', NULL], + 'OR' => ['Odisha', 'Odisha', NULL], + 'PB' => ['Punjab', 'Punjab', NULL], + 'RJ' => ['Rajasthan', 'Rajasthan', NULL], + 'SK' => ['Sikkim', 'Sikkim', NULL], + 'TN' => ['Tamil Nadu', 'Tamil Nadu', NULL], + 'TS' => ['Telangana', 'Telangana', NULL], + 'TR' => ['Tripura', 'Tripura', NULL], + 'UK' => ['Uttarakhand', 'Uttarakhand', NULL], + 'UP' => ['Uttar Pradesh', 'Uttar Pradesh', NULL], + 'WB' => ['West Bengal', 'West Bengal', NULL], + 'AN' => ['Andaman and Nicobar Islands', 'Andaman & Nicobar', NULL], + 'CH' => ['Chandigarh', 'Chandigarh', NULL], + 'DN' => ['Dadra and Nagar Haveli', 'Dadra & Nagar Haveli', NULL], + 'DD' => ['Daman and Diu', 'Daman & Diu', NULL], + 'DL' => ['Delhi', 'Delhi', NULL], + 'LD' => ['Lakshadweep', 'Lakshadweep', NULL], + 'PY' => ['Puducherry', 'Puducherry', NULL], + ], + // Iran. + 'IR' => [ + 'KHZ' => ['Khuzestan Province', 'Khuzestan Province', 'استان خوزستان'], + 'THR' => ['Tehran Province', 'Tehran Province', 'استان تهران'], + 'ILM' => ['Ilam Province', 'Ilam Province', 'استان ایلام'], + 'BHR' => ['Bushehr Province', 'Bushehr Province', 'استان بوشهر'], + 'ADL' => ['Ardabil Province', 'Ardabil Province', 'استان اردبیل'], + 'ESF' => ['Isfahan Province', 'Isfahan Province', 'استان اصفهان'], + 'YZD' => ['Yazd Province', 'Yazd Province', 'استان یزد'], + 'KRH' => ['Kermanshah Province', 'Kermanshah Province', 'استان کرمانشاه'], + 'KRN' => ['Kerman Province', 'Kerman Province', 'استان کرمان'], + 'HDN' => ['Hamadan Province', 'Hamadan Province', 'استان همدان'], + 'GZN' => ['Qazvin Province', 'Qazvin Province', 'استان قزوین'], + 'ZJN' => ['Zanjan Province', 'Zanjan Province', 'استان زنجان'], + 'LRS' => ['Lorestan Province', 'Lorestan Province', 'استان لرستان'], + 'ABZ' => ['Alborz Province', 'Alborz Province', 'استان البرز'], + 'EAZ' => ['East Azerbaijan Province', 'East Azerbaijan Province', 'استان آذربایجان شرقی'], + 'WAZ' => ['West Azerbaijan Province', 'West Azerbaijan Province', 'استان آذربایجان غربی'], + 'CHB' => ['Chaharmahal and Bakhtiari Province', 'Chaharmahal and Bakhtiari Province', 'استان چهارمحال و بختیاری'], + 'SKH' => ['South Khorasan Province', 'South Khorasan Province', 'استان خراسان جنوبی'], + 'RKH' => ['Razavi Khorasan Province', 'Razavi Khorasan Province', 'استان خراسان رضوی'], + 'NKH' => ['North Khorasan Province', 'North Khorasan Province', 'استان خراسان شمالی'], + 'SMN' => ['Semnan Province', 'Semnan Province', 'استان سمنان'], + 'FRS' => ['Fars Province', 'Fars Province', 'استان فارس'], + 'QHM' => ['Qom Province', 'Qom Province', 'استان قم'], + 'KRD' => ['Kurdistan Province', 'Kurdistan Province', 'استان کردستان'], + 'KBD' => ['Kohgiluyeh and Boyer-Ahmad Province', 'Kohgiluyeh and Boyer-Ahmad Province', 'استان کهگیلویه و بویراحمد'], + 'GLS' => ['Golestan Province', 'Golestan Province', 'استان گلستان'], + 'GIL' => ['Gilan Province', 'Gilan Province', 'استان گیلان'], + 'MZN' => ['Mazandaran Province', 'Mazandaran Province', 'استان مازندران'], + 'MKZ' => ['Markazi Province', 'Markazi Province', 'استان مرکزی'], + 'HRZ' => ['Hormozgan Province', 'Hormozgan Province', 'استان هرمزگان'], + 'SBN' => ['Sistan and Baluchestan Province', 'Sistan and Baluchestan Province', 'استان سیستان و بلوچستان'], + ], + // Iceland. + 'IS' => [], + // Italy. + 'IT' => [ + 'AG' => ['AG', 'Agrigento', NULL], + 'AL' => ['AL', 'Alessandria', NULL], + 'AN' => ['AN', 'Ancona', NULL], + 'AO' => ['AO', 'Aosta', NULL], + 'AR' => ['AR', 'Arezzo', NULL], + 'AP' => ['AP', 'Ascoli Piceno', NULL], + 'AT' => ['AT', 'Asti', NULL], + 'AV' => ['AV', 'Avellino', NULL], + 'BA' => ['BA', 'Bari', NULL], + 'BT' => ['BT', 'Barletta-Andria-Trani', NULL], + 'BL' => ['BL', 'Belluno', NULL], + 'BN' => ['BN', 'Benevento', NULL], + 'BG' => ['BG', 'Bergamo', NULL], + 'BI' => ['BI', 'Biella', NULL], + 'BO' => ['BO', 'Bologna', NULL], + 'BZ' => ['BZ', 'Bolzano', NULL], + 'BS' => ['BS', 'Brescia', NULL], + 'BR' => ['BR', 'Brindisi', NULL], + 'CA' => ['CA', 'Cagliari', NULL], + 'CL' => ['CL', 'Caltanissetta', NULL], + 'CB' => ['CB', 'Campobasso', NULL], + 'CE' => ['CE', 'Caserta', NULL], + 'CT' => ['CT', 'Catania', NULL], + 'CZ' => ['CZ', 'Catanzaro', NULL], + 'CH' => ['CH', 'Chieti', NULL], + 'CO' => ['CO', 'Como', NULL], + 'CS' => ['CS', 'Cosenza', NULL], + 'CR' => ['CR', 'Cremona', NULL], + 'KR' => ['KR', 'Crotone', NULL], + 'CN' => ['CN', 'Cuneo', NULL], + 'EN' => ['EN', 'Enna', NULL], + 'FM' => ['FM', 'Fermo', NULL], + 'FE' => ['FE', 'Ferrara', NULL], + 'FI' => ['FI', 'Firenze', NULL], + 'FG' => ['FG', 'Foggia', NULL], + 'FC' => ['FC', 'Forlì-Cesena', NULL], + 'FR' => ['FR', 'Frosinone', NULL], + 'GE' => ['GE', 'Genova', NULL], + 'GO' => ['GO', 'Gorizia', NULL], + 'GR' => ['GR', 'Grosseto', NULL], + 'IM' => ['IM', 'Imperia', NULL], + 'IS' => ['IS', 'Isernia', NULL], + 'SP' => ['SP', 'La Spezia', NULL], + 'AQ' => ['AQ', "L'Aquila", NULL], + 'LT' => ['LT', 'Latina', NULL], + 'LE' => ['LE', 'Lecce', NULL], + 'LC' => ['LC', 'Lecco', NULL], + 'LI' => ['LI', 'Livorno', NULL], + 'LO' => ['LO', 'Lodi', NULL], + 'LU' => ['LU', 'Lucca', NULL], + 'MC' => ['MC', 'Macerata', NULL], + 'MN' => ['MN', 'Mantova', NULL], + 'MS' => ['MS', 'Massa-Carrara', NULL], + 'MT' => ['MT', 'Matera', NULL], + 'ME' => ['ME', 'Messina', NULL], + 'MI' => ['MI', 'Milano', NULL], + 'MO' => ['MO', 'Modena', NULL], + 'MB' => ['MB', 'Monza e Brianza', NULL], + 'NA' => ['NA', 'Napoli', NULL], + 'NO' => ['NO', 'Novara', NULL], + 'NU' => ['NU', 'Nuoro', NULL], + 'OR' => ['OR', 'Oristano', NULL], + 'PD' => ['PD', 'Padova', NULL], + 'PA' => ['PA', 'Palermo', NULL], + 'PR' => ['PR', 'Parma', NULL], + 'PV' => ['PV', 'Pavia', NULL], + 'PG' => ['PG', 'Perugia', NULL], + 'PU' => ['PU', 'Pesaro e Urbino', NULL], + 'PE' => ['PE', 'Pescara', NULL], + 'PC' => ['PC', 'Piacenza', NULL], + 'PI' => ['PI', 'Pisa', NULL], + 'PT' => ['PT', 'Pistoia', NULL], + 'PN' => ['PN', 'Pordenone', NULL], + 'PZ' => ['PZ', 'Potenza', NULL], + 'PO' => ['PO', 'Prato', NULL], + 'RG' => ['RG', 'Ragusa', NULL], + 'RA' => ['RA', 'Ravenna', NULL], + 'RC' => ['RC', 'Reggio Calabria', NULL], + 'RE' => ['RE', 'Reggio Emilia', NULL], + 'RI' => ['RI', 'Rieti', NULL], + 'RN' => ['RN', 'Rimini', NULL], + 'RM' => ['RM', 'Roma', NULL], + 'RO' => ['RO', 'Rovigo', NULL], + 'SA' => ['SA', 'Salerno', NULL], + 'SS' => ['SS', 'Sassari', NULL], + 'SV' => ['SV', 'Savona', NULL], + 'SI' => ['SI', 'Siena', NULL], + 'SR' => ['SR', 'Siracusa', NULL], + 'SO' => ['SO', 'Sondrio', NULL], + 'SU' => ['SU', 'Sud Sardegna', NULL], + 'TA' => ['TA', 'Taranto', NULL], + 'TE' => ['TE', 'Teramo', NULL], + 'TR' => ['TR', 'Terni', NULL], + 'TO' => ['TO', 'Torino', NULL], + 'TP' => ['TP', 'Trapani', NULL], + 'TN' => ['TN', 'Trento', NULL], + 'TV' => ['TV', 'Treviso', NULL], + 'TS' => ['TS', 'Trieste', NULL], + 'UD' => ['UD', 'Udine', NULL], + 'VA' => ['VA', 'Varese', NULL], + 'VE' => ['VE', 'Venezia', NULL], + 'VB' => ['VB', 'Verbano-Cusio-Ossola', NULL], + 'VC' => ['VC', 'Vercelli', NULL], + 'VR' => ['VR', 'Verona', NULL], + 'VV' => ['VV', 'Vibo Valentia', NULL], + 'VI' => ['VI', 'Vicenza', NULL], + 'VT' => ['VT', 'Viterbo', NULL], + ], + // Jamaica. + 'JM' => [ + 'JM-01' => ['Kingston', 'Kingston', NULL], + 'JM-02' => ['St. Andrew', 'St. Andrew', NULL], + 'JM-03' => ['St. Thomas', 'St. Thomas', NULL], + 'JM-04' => ['Portland', 'Portland', NULL], + 'JM-05' => ['St. Mary', 'St. Mary', NULL], + 'JM-06' => ['St. Ann', 'St. Ann', NULL], + 'JM-07' => ['Trelawny', 'Trelawny', NULL], + 'JM-08' => ['St. James', 'St. James', NULL], + 'JM-09' => ['Hanover', 'Hanover', NULL], + 'JM-10' => ['Westmoreland', 'Westmoreland', NULL], + 'JM-11' => ['St. Elizabeth', 'St. Elizabeth', NULL], + 'JM-12' => ['Manchester', 'Manchester', NULL], + 'JM-13' => ['Clarendon', 'Clarendon', NULL], + 'JM-14' => ['St. Catherine', 'St. Catherine', NULL], + ], + // Japan. + 'JP' => [ + 'JP01' => ['Hokkaido', 'Hokkaido', '北海道'], + 'JP02' => ['Aomori', 'Aomori', '青森県'], + 'JP03' => ['Iwate', 'Iwate', '岩手県'], + 'JP04' => ['Miyagi', 'Miyagi', '宮城県'], + 'JP05' => ['Akita', 'Akita', '秋田県'], + 'JP06' => ['Yamagata', 'Yamagata', '山形県'], + 'JP07' => ['Fukushima', 'Fukushima', '福島県'], + 'JP08' => ['Ibaraki', 'Ibaraki', '茨城県'], + 'JP09' => ['Tochigi', 'Tochigi', '栃木県'], + 'JP10' => ['Gunma', 'Gunma', '群馬県'], + 'JP11' => ['Saitama', 'Saitama', '埼玉県'], + 'JP12' => ['Chiba', 'Chiba', '千葉県'], + 'JP13' => ['Tokyo', 'Tokyo', '東京都'], + 'JP14' => ['Kanagawa', 'Kanagawa', '神奈川県'], + 'JP15' => ['Niigata', 'Niigata', '新潟県'], + 'JP16' => ['Toyama', 'Toyama', '富山県'], + 'JP17' => ['Ishikawa', 'Ishikawa', '石川県'], + 'JP18' => ['Fukui', 'Fukui', '福井県'], + 'JP19' => ['Yamanashi', 'Yamanashi', '山梨県'], + 'JP20' => ['Nagano', 'Nagano', '長野県'], + 'JP21' => ['Gifu', 'Gifu', '岐阜県'], + 'JP22' => ['Shizuoka', 'Shizuoka', '静岡県'], + 'JP23' => ['Aichi', 'Aichi', '愛知県'], + 'JP24' => ['Mie', 'Mie', '三重県'], + 'JP25' => ['Shiga', 'Shiga', '滋賀県'], + 'JP26' => ['Kyoto', 'Kyoto', '京都府'], + 'JP27' => ['Osaka', 'Osaka', '大阪府'], + 'JP28' => ['Hyogo', 'Hyogo', '兵庫県'], + 'JP29' => ['Nara', 'Nara', '奈良県'], + 'JP30' => ['Wakayama', 'Wakayama', '和歌山県'], + 'JP31' => ['Tottori', 'Tottori', '鳥取県'], + 'JP32' => ['Shimane', 'Shimane', '島根県'], + 'JP33' => ['Okayama', 'Okayama', '岡山県'], + 'JP34' => ['Hiroshima', 'Hiroshima', '広島県'], + 'JP35' => ['Yamaguchi', 'Yamaguchi', '山口県'], + 'JP36' => ['Tokushima', 'Tokushima', '徳島県'], + 'JP37' => ['Kagawa', 'Kagawa', '香川県'], + 'JP38' => ['Ehime', 'Ehime', '愛媛県'], + 'JP39' => ['Kochi', 'Kochi', '高知県'], + 'JP40' => ['Fukuoka', 'Fukuoka', '福岡県'], + 'JP41' => ['Saga', 'Saga', '佐賀県'], + 'JP42' => ['Nagasaki', 'Nagasaki', '長崎県'], + 'JP43' => ['Kumamoto', 'Kumamoto', '熊本県'], + 'JP44' => ['Oita', 'Oita', '大分県'], + 'JP45' => ['Miyazaki', 'Miyazaki', '宮崎県'], + 'JP46' => ['Kagoshima', 'Kagoshima', '鹿児島県'], + 'JP47' => ['Okinawa', 'Okinawa', '沖縄県'], + ], + // Kenya. + 'KE' => [], + // South Korea. + 'KR' => [], + // Kuwait. + 'KW' => [], + // Laos. + 'LA' => [], + // Lebanon. + 'LB' => [], + // Sri Lanka. + 'LK' => [], + // Liberia. + 'LR' => [], + // Luxembourg. + 'LU' => [], + // Moldova. + 'MD' => [], + // Martinique. + 'MQ' => [], + // Malta. + 'MT' => [], + // Mexico. + 'MX' => [ + 'DF' => ['CDMX', 'Ciudad de México', NULL], + 'JA' => ['Jal.', 'Jalisco', NULL], + 'NL' => ['N.L.', 'Nuevo León', NULL], + 'AG' => ['Ags.', 'Aguascalientes', NULL], + 'BC' => ['B.C.', 'Baja California', NULL], + 'BS' => ['B.C.S.', 'Baja California Sur', NULL], + 'CM' => ['Camp.', 'Campeche', NULL], + 'CS' => ['Chis.', 'Chiapas', NULL], + 'CH' => ['Chih.', 'Chihuahua', NULL], + 'CO' => ['Coah.', 'Coahuila de Zaragoza', NULL], + 'CL' => ['Col.', 'Colima', NULL], + 'DG' => ['Dgo.', 'Durango', NULL], + 'GT' => ['Gto.', 'Guanajuato', NULL], + 'GR' => ['Gro.', 'Guerrero', NULL], + 'HG' => ['Hgo.', 'Hidalgo', NULL], + 'MX' => ['Méx.', 'Estado de México', NULL], + 'MI' => ['Mich.', 'Michoacán', NULL], + 'MO' => ['Mor.', 'Morelos', NULL], + 'NA' => ['Nay.', 'Nayarit', NULL], + 'OA' => ['Oax.', 'Oaxaca', NULL], + 'PU' => ['Pue.', 'Puebla', NULL], + 'QT' => ['Qro.', 'Querétaro', NULL], + 'QR' => ['Q.R.', 'Quintana Roo', NULL], + 'SL' => ['S.L.P.', 'San Luis Potosí', NULL], + 'SI' => ['Sin.', 'Sinaloa', NULL], + 'SO' => ['Son.', 'Sonora', NULL], + 'TB' => ['Tab.', 'Tabasco', NULL], + 'TM' => ['Tamps.', 'Tamaulipas', NULL], + 'TL' => ['Tlax.', 'Tlaxcala', NULL], + 'VE' => ['Ver.', 'Veracruz', NULL], + 'YU' => ['Yuc.', 'Yucatán', NULL], + 'ZA' => ['Zac.', 'Zacatecas', NULL], + ], + // Malaysia. + 'MY' => [ + 'JHR' => ['Johor', 'Johor', NULL], + 'KDH' => ['Kedah', 'Kedah', NULL], + 'KTN' => ['Kelantan', 'Kelantan', NULL], + 'LBN' => ['Labuan', 'Labuan', NULL], + 'MLK' => ['Melaka', 'Melaka', NULL], + 'NSN' => ['Negeri Sembilan', 'Negeri Sembilan', NULL], + 'PHG' => ['Pahang', 'Pahang', NULL], + 'PNG' => ['Pulau Pinang', 'Pulau Pinang', NULL], + 'PRK' => ['Perak', 'Perak', NULL], + 'PLS' => ['Perlis', 'Perlis', NULL], + 'SBH' => ['Sabah', 'Sabah', NULL], + 'SWK' => ['Sarawak', 'Sarawak', NULL], + 'SGR' => ['Selangor', 'Selangor', NULL], + 'TRG' => ['Terengganu', 'Terengganu', NULL], + 'PJY' => ['Putrajaya', 'Putrajaya', NULL], + 'KUL' => ['Kuala Lumpur', 'Kuala Lumpur', NULL], + ], + // Mozambique. + 'MZ' => [ + 'MZP' => ['Cabo Delgado', 'Cabo Delgado', NULL], + 'MZG' => ['Gaza', 'Gaza', NULL], + 'MZI' => ['Inhambane', 'Inhambane', NULL], + 'MZB' => ['Manica', 'Manica', NULL], + 'MZL' => ['Maputo', 'Maputo', NULL], + 'MZMPM' => ['Cidade de Maputo', 'Cidade de Maputo', NULL], + 'MZN' => ['Nampula', 'Nampula', NULL], + 'MZA' => ['Niassa', 'Niassa', NULL], + 'MZS' => ['Sofala', 'Sofala', NULL], + 'MZT' => ['Tete', 'Tete', NULL], + 'MZQ' => ['Zambezia', 'Zambezia', NULL], + ], + // Namibia. + 'NA' => [], + // Nigeria. + 'NG' => [ + 'AB' => ['Abia', 'Abia', NULL], + 'FC' => ['Federal Capital Territory', 'Federal Capital Territory', NULL], + 'AD' => ['Adamawa', 'Adamawa', NULL], + 'AK' => ['Akwa Ibom', 'Akwa Ibom', NULL], + 'AN' => ['Anambra', 'Anambra', NULL], + 'BA' => ['Bauchi', 'Bauchi', NULL], + 'BY' => ['Bayelsa', 'Bayelsa', NULL], + 'BE' => ['Benue', 'Benue', NULL], + 'BO' => ['Borno', 'Borno', NULL], + 'CR' => ['Cross River', 'Cross River', NULL], + 'DE' => ['Delta', 'Delta', NULL], + 'EB' => ['Ebonyi', 'Ebonyi', NULL], + 'ED' => ['Edo', 'Edo', NULL], + 'EK' => ['Ekiti', 'Ekiti', NULL], + 'EN' => ['Enugu', 'Enugu', NULL], + 'GO' => ['Gombe', 'Gombe', NULL], + 'IM' => ['Imo', 'Imo', NULL], + 'JI' => ['Jigawa', 'Jigawa', NULL], + 'KD' => ['Kaduna', 'Kaduna', NULL], + 'KN' => ['Kano', 'Kano', NULL], + 'KT' => ['Katsina', 'Katsina', NULL], + 'KE' => ['Kebbi', 'Kebbi', NULL], + 'KO' => ['Kogi', 'Kogi', NULL], + 'KW' => ['Kwara', 'Kwara', NULL], + 'LA' => ['Lagos', 'Lagos', NULL], + 'NA' => ['Nasarawa', 'Nasarawa', NULL], + 'NI' => ['Niger', 'Niger', NULL], + 'OG' => ['Ogun State', 'Ogun State', NULL], + 'ON' => ['Ondo', 'Ondo', NULL], + 'OS' => ['Osun', 'Osun', NULL], + 'OY' => ['Oyo', 'Oyo', NULL], + 'PL' => ['Plateau', 'Plateau', NULL], + 'RI' => ['Rivers', 'Rivers', NULL], + 'SO' => ['Sokoto', 'Sokoto', NULL], + 'TA' => ['Taraba', 'Taraba', NULL], + 'YO' => ['Yobe', 'Yobe', NULL], + 'ZA' => ['Zamfara', 'Zamfara', NULL], + ], + // Netherlands. + 'NL' => [], + // Norway. + 'NO' => [], + // Nepal. + 'NP' => [], + // New Zealand. + 'NZ' => [], + // Peru. + 'PE' => [ + 'CAL' => ['Callao', 'Callao', NULL], + 'LMA' => ['Municipalidad Metropolitana de Lima', 'Municipalidad Metropolitana de Lima', NULL], + 'AMA' => ['Amazonas', 'Amazonas', NULL], + 'ANC' => ['Áncash', 'Áncash', NULL], + 'APU' => ['Apurímac', 'Apurímac', NULL], + 'ARE' => ['Arequipa', 'Arequipa', NULL], + 'AYA' => ['Ayacucho', 'Ayacucho', NULL], + 'CAJ' => ['Cajamarca', 'Cajamarca', NULL], + 'CUS' => ['Cuzco', 'Cuzco', NULL], + 'HUV' => ['Huancavelica', 'Huancavelica', NULL], + 'HUC' => ['Huánuco', 'Huánuco', NULL], + 'ICA' => ['Ica', 'Ica', NULL], + 'JUN' => ['Junín', 'Junín', NULL], + 'LAL' => ['La Libertad', 'La Libertad', NULL], + 'LAM' => ['Lambayeque', 'Lambayeque', NULL], + 'LIM' => ['Gobierno Regional de Lima', 'Gobierno Regional de Lima', NULL], + 'LOR' => ['Loreto', 'Loreto', NULL], + 'MDD' => ['Madre de Dios', 'Madre de Dios', NULL], + 'MOQ' => ['Moquegua', 'Moquegua', NULL], + 'PAS' => ['Pasco', 'Pasco', NULL], + 'PIU' => ['Piura', 'Piura', NULL], + 'PUN' => ['Puno', 'Puno', NULL], + 'SAM' => ['San Martín', 'San Martín', NULL], + 'TAC' => ['Tacna', 'Tacna', NULL], + 'TUM' => ['Tumbes', 'Tumbes', NULL], + 'UCA' => ['Ucayali', 'Ucayali', NULL], + ], + // Philippines. + 'PH' => [ + 'ABR' => ['Abra', 'Abra', NULL], + 'AGN' => ['Agusan del Norte', 'Agusan del Norte', NULL], + 'AGS' => ['Agusan del Sur', 'Agusan del Sur', NULL], + 'AKL' => ['Aklan', 'Aklan', NULL], + 'ALB' => ['Albay', 'Albay', NULL], + 'ANT' => ['Antique', 'Antique', NULL], + 'APA' => ['Apayao', 'Apayao', NULL], + 'AUR' => ['Aurora', 'Aurora', NULL], + 'BAS' => ['Basilan', 'Basilan', NULL], + 'BAN' => ['Bataan', 'Bataan', NULL], + 'BTN' => ['Batanes', 'Batanes', NULL], + 'BTG' => ['Batangas', 'Batangas', NULL], + 'BEN' => ['Benguet', 'Benguet', NULL], + 'BIL' => ['Biliran', 'Biliran', NULL], + 'BOH' => ['Bohol', 'Bohol', NULL], + 'BUK' => ['Bukidnon', 'Bukidnon', NULL], + 'BUL' => ['Bulacan', 'Bulacan', NULL], + 'CAG' => ['Cagayan', 'Cagayan', NULL], + 'CAN' => ['Camarines Norte', 'Camarines Norte', NULL], + 'CAS' => ['Camarines Sur', 'Camarines Sur', NULL], + 'CAM' => ['Camiguin', 'Camiguin', NULL], + 'CAP' => ['Capiz', 'Capiz', NULL], + 'CAT' => ['Catanduanes', 'Catanduanes', NULL], + 'CAV' => ['Cavite', 'Cavite', NULL], + 'CEB' => ['Cebu', 'Cebu', NULL], + 'COM' => ['Compostela Valley', 'Compostela Valley', NULL], + 'NCO' => ['Cotabato', 'Cotabato', NULL], + 'DAV' => ['Davao del Norte', 'Davao del Norte', NULL], + 'DAS' => ['Davao del Sur', 'Davao del Sur', NULL], + 'DAC' => ['Davao Occidental', 'Davao Occidental', NULL], + 'DAO' => ['Davao Oriental', 'Davao Oriental', NULL], + 'DIN' => ['Dinagat Islands', 'Dinagat Islands', NULL], + 'EAS' => ['Eastern Samar', 'Eastern Samar', NULL], + 'GUI' => ['Guimaras', 'Guimaras', NULL], + 'IFU' => ['Ifugao', 'Ifugao', NULL], + 'ILN' => ['Ilocos Norte', 'Ilocos Norte', NULL], + 'ILS' => ['Ilocos Sur', 'Ilocos Sur', NULL], + 'ILI' => ['Iloilo', 'Iloilo', NULL], + 'ISA' => ['Isabela', 'Isabela', NULL], + 'KAL' => ['Kalinga', 'Kalinga', NULL], + 'LUN' => ['La Union', 'La Union', NULL], + 'LAG' => ['Laguna', 'Laguna', NULL], + 'LAN' => ['Lanao del Norte', 'Lanao del Norte', NULL], + 'LAS' => ['Lanao del Sur', 'Lanao del Sur', NULL], + 'LEY' => ['Leyte', 'Leyte', NULL], + 'MAG' => ['Maguindanao', 'Maguindanao', NULL], + 'MAD' => ['Marinduque', 'Marinduque', NULL], + 'MAS' => ['Masbate', 'Masbate', NULL], + 'MSC' => ['Misamis Occidental', 'Misamis Occidental', NULL], + 'MSR' => ['Misamis Oriental', 'Misamis Oriental', NULL], + 'MOU' => ['Mountain Province', 'Mountain Province', NULL], + 'NEC' => ['Negros Occidental', 'Negros Occidental', NULL], + 'NER' => ['Negros Oriental', 'Negros Oriental', NULL], + 'NSA' => ['Northern Samar', 'Northern Samar', NULL], + 'NUE' => ['Nueva Ecija', 'Nueva Ecija', NULL], + 'NUV' => ['Nueva Vizcaya', 'Nueva Vizcaya', NULL], + 'MDC' => ['Mindoro Occidental', 'Mindoro Occidental', NULL], + 'MDR' => ['Mindoro Oriental', 'Mindoro Oriental', NULL], + 'PLW' => ['Palawan', 'Palawan', NULL], + 'PAM' => ['Pampanga', 'Pampanga', NULL], + 'PAN' => ['Pangasinan', 'Pangasinan', NULL], + 'QUE' => ['Quezon Province', 'Quezon Province', NULL], + 'QUI' => ['Quirino', 'Quirino', NULL], + 'RIZ' => ['Rizal', 'Rizal', NULL], + 'ROM' => ['Romblon', 'Romblon', NULL], + 'WSA' => ['Samar', 'Samar', NULL], + 'SAR' => ['Sarangani', 'Sarangani', NULL], + 'SIQ' => ['Siquijor', 'Siquijor', NULL], + 'SOR' => ['Sorsogon', 'Sorsogon', NULL], + 'SCO' => ['South Cotabato', 'South Cotabato', NULL], + 'SLE' => ['Southern Leyte', 'Southern Leyte', NULL], + 'SUK' => ['Sultan Kudarat', 'Sultan Kudarat', NULL], + 'SLU' => ['Sulu', 'Sulu', NULL], + 'SUN' => ['Surigao del Norte', 'Surigao del Norte', NULL], + 'SUR' => ['Surigao del Sur', 'Surigao del Sur', NULL], + 'TAR' => ['Tarlac', 'Tarlac', NULL], + 'TAW' => ['Tawi-Tawi', 'Tawi-Tawi', NULL], + 'ZMB' => ['Zambales', 'Zambales', NULL], + 'ZAN' => ['Zamboanga del Norte', 'Zamboanga del Norte', NULL], + 'ZAS' => ['Zamboanga del Sur', 'Zamboanga del Sur', NULL], + 'ZSI' => ['Zamboanga Sibuguey', 'Zamboanga Sibuguey', NULL], + '00' => ['Metro Manila', 'Metro Manila', NULL], + ], + // Pakistan. + 'PK' => [], + // Poland. + 'PL' => [], + // Puerto Rico. + 'PR' => [], + // Portugal. + 'PT' => [], + // Paraguay. + 'PY' => [], + // Reunion. + 'RE' => [], + // Romania. + 'RO' => [], + // Serbia. + 'RS' => [], + // Sweden. + 'SE' => [], + // Singapore. + 'SG' => [], + // Slovenia. + 'SI' => [], + // Slovakia. + 'SK' => [], + // Thailand. + 'TH' => [ + 'TH-37' => ['Amnat Charoen', 'Amnat Charoen', 'อำนาจเจริญ'], + 'TH-15' => ['Ang Thong', 'Ang Thong', 'อ่างทอง'], + 'TH-14' => ['Phra Nakhon Si Ayutthaya', 'Phra Nakhon Si Ayutthaya', 'พระนครศรีอยุธยา'], + 'TH-10' => ['Bangkok', 'Bangkok', 'กรุงเทพมหานคร'], + 'TH-38' => ['Bueng Kan', 'Bueng Kan', 'จังหวัด บึงกาฬ'], + 'TH-31' => ['Buri Ram', 'Buri Ram', 'บุรีรัมย์'], + 'TH-24' => ['Chachoengsao', 'Chachoengsao', 'ฉะเชิงเทรา'], + 'TH-18' => ['Chai Nat', 'Chai Nat', 'ชัยนาท'], + 'TH-36' => ['Chaiyaphum', 'Chaiyaphum', 'ชัยภูมิ'], + 'TH-22' => ['Chanthaburi', 'Chanthaburi', 'จันทบุรี'], + 'TH-50' => ['Chiang Rai', 'Chiang Rai', 'เชียงราย'], + 'TH-57' => ['Chiang Mai', 'Chiang Mai', 'เชียงใหม่'], + 'TH-20' => ['Chon Buri', 'Chon Buri', 'ชลบุรี'], + 'TH-86' => ['Chumpon', 'Chumpon', 'ชุมพร'], + 'TH-46' => ['Kalasin', 'Kalasin', 'กาฬสินธุ์'], + 'TH-62' => ['Kamphaeng Phet', 'Kamphaeng Phet', 'กำแพงเพชร'], + 'TH-71' => ['Kanchanaburi', 'Kanchanaburi', 'กาญจนบุรี'], + 'TH-40' => ['Khon Kaen', 'Khon Kaen', 'ขอนแก่น'], + 'TH-81' => ['Krabi', 'Krabi', 'กระบี่'], + 'TH-52' => ['Lampang', 'Lampang', 'ลำปาง'], + 'TH-51' => ['Lamphun', 'Lamphun', 'ลำพูน'], + 'TH-42' => ['Loei', 'Loei', 'เลย'], + 'TH-16' => ['Lop Buri', 'Lop Buri', 'ลพบุรี'], + 'TH-58' => ['Mae Hong Son', 'Mae Hong Son', 'แม่ฮ่องสอน'], + 'TH-44' => ['Maha Sarakham', 'Maha Sarakham', 'มหาสารคาม'], + 'TH-49' => ['Mukdahan', 'Mukdahan', 'มุกดาหาร'], + 'TH-26' => ['Nakhon Nayok', 'Nakhon Nayok', 'นครนายก'], + 'TH-73' => ['Nakhon Pathom', 'Nakhon Pathom', 'นครปฐม'], + 'TH-48' => ['Nakhon Phanom', 'Nakhon Phanom', 'นครพนม'], + 'TH-30' => ['Nakhon Ratchasima', 'Nakhon Ratchasima', 'นครราชสีมา'], + 'TH-60' => ['Nakhon Sawan', 'Nakhon Sawan', 'นครสวรรค์'], + 'TH-80' => ['Nakhon Si Thammarat', 'Nakhon Si Thammarat', 'นครศรีธรรมราช'], + 'TH-55' => ['Nan', 'Nan', 'น่าน'], + 'TH-96' => ['Narathiwat', 'Narathiwat', 'นราธิวาส'], + 'TH-39' => ['Nong Bua Lam Phu', 'Nong Bua Lam Phu', 'หนองบัวลำภู'], + 'TH-43' => ['Nong Khai', 'Nong Khai', 'หนองคาย'], + 'TH-12' => ['Nonthaburi', 'Nonthaburi', 'นนทบุรี'], + 'TH-13' => ['Pathum Thani', 'Pathum Thani', 'ปทุมธานี'], + 'TH-94' => ['Pattani', 'Pattani', 'ปัตตานี'], + 'TH-82' => ['Phang Nga', 'Phang Nga', 'พังงา'], + 'TH-93' => ['Phattalung', 'Phattalung', 'พัทลุง'], + 'TH-56' => ['Phayao', 'Phayao', 'พะเยา'], + 'TH-67' => ['Phetchabun', 'Phetchabun', 'เพชรบูรณ์'], + 'TH-76' => ['Phetchaburi', 'Phetchaburi', 'เพชรบุรี'], + 'TH-66' => ['Phichit', 'Phichit', 'พิจิตร'], + 'TH-65' => ['Phitsanulok', 'Phitsanulok', 'พิษณุโลก'], + 'TH-54' => ['Phrae', 'Phrae', 'แพร่'], + 'TH-83' => ['Phuket', 'Phuket', 'ภูเก็ต'], + 'TH-25' => ['Prachin Buri', 'Prachin Buri', 'ปราจีนบุรี'], + 'TH-77' => ['Prachuap Khiri Khan', 'Prachuap Khiri Khan', 'ประจวบคีรีขันธ์'], + 'TH-85' => ['Ranong', 'Ranong', 'ระนอง'], + 'TH-70' => ['Ratchaburi', 'Ratchaburi', 'ราชบุรี'], + 'TH-21' => ['Rayong', 'Rayong', 'ระยอง'], + 'TH-45' => ['Roi Et', 'Roi Et', 'ร้อยเอ็ด'], + 'TH-27' => ['Sa Kaeo', 'Sa Kaeo', 'สระแก้ว'], + 'TH-47' => ['Sakon Nakhon', 'Sakon Nakhon', 'สกลนคร'], + 'TH-11' => ['Samut Prakan', 'Samut Prakan', 'สมุทรปราการ'], + 'TH-74' => ['Samut Sakhon', 'Samut Sakhon', 'สมุทรสาคร'], + 'TH-75' => ['Samut Songkhram', 'Samut Songkhram', 'สมุทรสงคราม'], + 'TH-19' => ['Saraburi', 'Saraburi', 'สระบุรี'], + 'TH-91' => ['Satun', 'Satun', 'สตูล'], + 'TH-17' => ['Sing Buri', 'Sing Buri', 'สิงห์บุรี'], + 'TH-33' => ['Si Sa Ket', 'Si Sa Ket', 'ศรีสะเกษ'], + 'TH-90' => ['Songkhla', 'Songkhla', 'สงขลา'], + 'TH-64' => ['Sukhothai', 'Sukhothai', 'สุโขทัย'], + 'TH-72' => ['Suphanburi', 'Suphanburi', 'สุพรรณบุรี'], + 'TH-84' => ['Surat Thani', 'Surat Thani', 'สุราษฎร์ธานี'], + 'TH-32' => ['Surin', 'Surin', 'สุรินทร์'], + 'TH-63' => ['Tak', 'Tak', 'ตาก'], + 'TH-92' => ['Trang', 'Trang', 'ตรัง'], + 'TH-23' => ['Trat', 'Trat', 'ตราด'], + 'TH-34' => ['Ubon Ratchathani', 'Ubon Ratchathani', 'อุบลราชธานี'], + 'TH-41' => ['Udon Thani', 'Udon Thani', 'อุดรธานี'], + 'TH-61' => ['Uthai Thani', 'Uthai Thani', 'อุทัยธานี'], + 'TH-53' => ['Uttaradit', 'Uttaradit', 'อุตรดิตถ์'], + 'TH-95' => ['Yala', 'Yala', 'ยะลา'], + 'TH-35' => ['Yasothon', 'Yasothon', 'ยโสธร'], + ], + // Turkey. + 'TR' => [ + 'TR01' => ['Adana', 'Adana', NULL], + 'TR02' => ['Adıyaman', 'Adıyaman', NULL], + 'TR03' => ['Afyon', 'Afyon', NULL], + 'TR04' => ['Ağrı', 'Ağrı', NULL], + 'TR05' => ['Amasya', 'Amasya', NULL], + 'TR06' => ['Ankara', 'Ankara', NULL], + 'TR07' => ['Antalya', 'Antalya', NULL], + 'TR08' => ['Artvin', 'Artvin', NULL], + 'TR09' => ['Aydın', 'Aydın', NULL], + 'TR10' => ['Balıkesir', 'Balıkesir', NULL], + 'TR11' => ['Bilecik', 'Bilecik', NULL], + 'TR12' => ['Bingöl', 'Bingöl', NULL], + 'TR13' => ['Bitlis', 'Bitlis', NULL], + 'TR14' => ['Bolu', 'Bolu', NULL], + 'TR15' => ['Burdur', 'Burdur', NULL], + 'TR16' => ['Bursa', 'Bursa', NULL], + 'TR17' => ['Çanakkale', 'Çanakkale', NULL], + 'TR18' => ['Çankırı', 'Çankırı', NULL], + 'TR19' => ['Çorum', 'Çorum', NULL], + 'TR20' => ['Denizli', 'Denizli', NULL], + 'TR21' => ['Diyarbakır', 'Diyarbakır', NULL], + 'TR22' => ['Edirne', 'Edirne', NULL], + 'TR23' => ['Elazığ', 'Elazığ', NULL], + 'TR24' => ['Erzincan', 'Erzincan', NULL], + 'TR25' => ['Erzurum', 'Erzurum', NULL], + 'TR26' => ['Eskişehir', 'Eskişehir', NULL], + 'TR27' => ['Gaziantep', 'Gaziantep', NULL], + 'TR28' => ['Giresun', 'Giresun', NULL], + 'TR29' => ['Gümüşhane', 'Gümüşhane', NULL], + 'TR30' => ['Hakkari', 'Hakkari', NULL], + 'TR31' => ['Hatay', 'Hatay', NULL], + 'TR32' => ['Isparta', 'Isparta', NULL], + 'TR33' => ['Mersin', 'Mersin', NULL], + 'TR34' => ['İstanbul', 'İstanbul', NULL], + 'TR35' => ['İzmir', 'İzmir', NULL], + 'TR36' => ['Kars', 'Kars', NULL], + 'TR37' => ['Kastamonu', 'Kastamonu', NULL], + 'TR38' => ['Kayseri', 'Kayseri', NULL], + 'TR39' => ['Kırklareli', 'Kırklareli', NULL], + 'TR40' => ['Kırşehir', 'Kırşehir', NULL], + 'TR41' => ['Kocaeli', 'Kocaeli', NULL], + 'TR42' => ['Konya', 'Konya', NULL], + 'TR43' => ['Kütahya', 'Kütahya', NULL], + 'TR44' => ['Malatya', 'Malatya', NULL], + 'TR45' => ['Manisa', 'Manisa', NULL], + 'TR46' => ['Kahramanmaraş', 'Kahramanmaraş', NULL], + 'TR47' => ['Mardin', 'Mardin', NULL], + 'TR48' => ['Muğla', 'Muğla', NULL], + 'TR49' => ['Muş', 'Muş', NULL], + 'TR50' => ['Nevşehir', 'Nevşehir', NULL], + 'TR51' => ['Niğde', 'Niğde', NULL], + 'TR52' => ['Ordu', 'Ordu', NULL], + 'TR53' => ['Rize', 'Rize', NULL], + 'TR54' => ['Sakarya', 'Sakarya', NULL], + 'TR55' => ['Samsun', 'Samsun', NULL], + 'TR56' => ['Siirt', 'Siirt', NULL], + 'TR57' => ['Sinop', 'Sinop', NULL], + 'TR58' => ['Sivas', 'Sivas', NULL], + 'TR59' => ['Tekirdağ', 'Tekirdağ', NULL], + 'TR60' => ['Tokat', 'Tokat', NULL], + 'TR61' => ['Trabzon', 'Trabzon', NULL], + 'TR62' => ['Tunceli', 'Tunceli', NULL], + 'TR63' => ['Şanlıurfa', 'Şanlıurfa', NULL], + 'TR64' => ['Uşak', 'Uşak', NULL], + 'TR65' => ['Van', 'Van', NULL], + 'TR66' => ['Yozgat', 'Yozgat', NULL], + 'TR67' => ['Zonguldak', 'Zonguldak', NULL], + 'TR68' => ['Aksaray', 'Aksaray', NULL], + 'TR69' => ['Bayburt', 'Bayburt', NULL], + 'TR70' => ['Karaman', 'Karaman', NULL], + 'TR71' => ['Kırıkkale', 'Kırıkkale', NULL], + 'TR72' => ['Batman', 'Batman', NULL], + 'TR73' => ['Şırnak', 'Şırnak', NULL], + 'TR74' => ['Bartın', 'Bartın', NULL], + 'TR75' => ['Ardahan', 'Ardahan', NULL], + 'TR76' => ['Iğdır', 'Iğdır', NULL], + 'TR77' => ['Yalova', 'Yalova', NULL], + 'TR78' => ['Karabük', 'Karabük', NULL], + 'TR79' => ['Kilis', 'Kilis', NULL], + 'TR80' => ['Osmaniye', 'Osmaniye', NULL], + 'TR81' => ['Düzce', 'Düzce', NULL], + ], + // Tanzania. + 'TZ' => [], + // Uganda. + 'UG' => [], + // United States Minor Outlying Islands. + 'UM' => [], + // United States. + 'US' => [ + 'AL' => ['AL', 'Alabama', NULL], + 'AK' => ['AK', 'Alaska', NULL], + 'AZ' => ['AZ', 'Arizona', NULL], + 'AR' => ['AR', 'Arkansas', NULL], + 'CA' => ['CA', 'California', NULL], + 'CO' => ['CO', 'Colorado', NULL], + 'CT' => ['CT', 'Connecticut', NULL], + 'DE' => ['DE', 'Delaware', NULL], + 'DC' => ['DC', 'District of Columbia', NULL], + 'FL' => ['FL', 'Florida', NULL], + 'GA' => ['GA', 'Georgia', NULL], + 'HI' => ['HI', 'Hawaii', NULL], + 'ID' => ['ID', 'Idaho', NULL], + 'IL' => ['IL', 'Illinois', NULL], + 'IN' => ['IN', 'Indiana', NULL], + 'IA' => ['IA', 'Iowa', NULL], + 'KS' => ['KS', 'Kansas', NULL], + 'KY' => ['KY', 'Kentucky', NULL], + 'LA' => ['LA', 'Louisiana', NULL], + 'ME' => ['ME', 'Maine', NULL], + 'MD' => ['MD', 'Maryland', NULL], + 'MA' => ['MA', 'Massachusetts', NULL], + 'MI' => ['MI', 'Michigan', NULL], + 'MN' => ['MN', 'Minnesota', NULL], + 'MS' => ['MS', 'Mississippi', NULL], + 'MO' => ['MO', 'Missouri', NULL], + 'MT' => ['MT', 'Montana', NULL], + 'NE' => ['NE', 'Nebraska', NULL], + 'NV' => ['NV', 'Nevada', NULL], + 'NH' => ['NH', 'New Hampshire', NULL], + 'NJ' => ['NJ', 'New Jersey', NULL], + 'NM' => ['NM', 'New Mexico', NULL], + 'NY' => ['NY', 'New York', NULL], + 'NC' => ['NC', 'North Carolina', NULL], + 'ND' => ['ND', 'North Dakota', NULL], + 'OH' => ['OH', 'Ohio', NULL], + 'OK' => ['OK', 'Oklahoma', NULL], + 'OR' => ['OR', 'Oregon', NULL], + 'PA' => ['PA', 'Pennsylvania', NULL], + 'RI' => ['RI', 'Rhode Island', NULL], + 'SC' => ['SC', 'South Carolina', NULL], + 'SD' => ['SD', 'South Dakota', NULL], + 'TN' => ['TN', 'Tennessee', NULL], + 'TX' => ['TX', 'Texas', NULL], + 'UT' => ['UT', 'Utah', NULL], + 'VT' => ['VT', 'Vermont', NULL], + 'VA' => ['VA', 'Virginia', NULL], + 'WA' => ['WA', 'Washington', NULL], + 'WV' => ['WV', 'West Virginia', NULL], + 'WI' => ['WI', 'Wisconsin', NULL], + 'WY' => ['WY', 'Wyoming', NULL], + 'AA' => ['AA', 'Armed Forces (AA)', NULL], + 'AE' => ['AE', 'Armed Forces (AE)', NULL], + 'AP' => ['AP', 'Armed Forces (AP)', NULL], + //[ 'AS', 'American Samoa', NULL ], + //[ 'GU', 'Guam', NULL ], + //[ 'MH', 'Marshall Islands', NULL ], + //[ 'FM', 'Micronesia', NULL ], + //[ 'MP', 'Northern Mariana Islands', NULL ], + //[ 'PW', 'Palau', NULL ], + //[ 'PR', 'Puerto Rico', NULL ], + //[ 'VI', 'Virgin Islands', NULL ], + ], + // Vietnam. + 'VN' => [], + // Mayotte. + 'YT' => [], + // South Africa. + 'ZA' => [], + // Zambia. + 'ZM' => [], + ]; + // phpcs:enable +} + +/** + * WC_Stripe_Express_Checkout_Helper class. + */ +class WC_Stripe_Express_Checkout_Helper { + + /** + * Sanitize string for comparison. + * + * @param string $string String to be sanitized. + * + * @return string The sanitized string. + */ + public function sanitize_string( $string ) { + return trim( strtolower( $string ) ); + } + + /** + * Get normalized state from express checkout API dropdown list of states. + * + * @param string $state Full state name or state code. + * @param string $country Two-letter country code. + * + * @return string Normalized state or original state input value. + */ + public function get_normalized_state_from_pr_states( $state, $country ) { + // Include Payment Request API State list for compatibility with WC countries/states. + $pr_states = WC_Stripe_Payment_Request_Button_States::STATES; + + if ( ! isset( $pr_states[ $country ] ) ) { + return $state; + } + + foreach ( $pr_states[ $country ] as $wc_state_abbr => $pr_state ) { + $sanitized_state_string = $this->sanitize_string( $state ); + // Checks if input state matches with Payment Request state code (0), name (1) or localName (2). + if ( + ( ! empty( $pr_state[0] ) && $sanitized_state_string === $this->sanitize_string( $pr_state[0] ) ) || + ( ! empty( $pr_state[1] ) && $sanitized_state_string === $this->sanitize_string( $pr_state[1] ) ) || + ( ! empty( $pr_state[2] ) && $sanitized_state_string === $this->sanitize_string( $pr_state[2] ) ) + ) { + return $wc_state_abbr; + } + } + + return $state; + } +} From 09992c738e2dfdb731b90f1f2593736d09f293c2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 18 Mar 2025 20:16:34 +0100 Subject: [PATCH 1200/3097] Fix build after merge --- tests/PHPStan/Analyser/nsrt/bug-10717.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10717.php b/tests/PHPStan/Analyser/nsrt/bug-10717.php index fb0d0a1f9e5..a7752842476 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10717.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10717.php @@ -1046,7 +1046,7 @@ function test(string $code): void if ($country === 'fo' || $country === 'Faroese' || $country === 'Føroyskt') { // foo } else { - assertType('(bool|string)', $country); + assertType('(bool|(literal-string&non-falsy-string))', $country); } } From 75debf6314c2cd007efc364e11ad21bc11c43a9f Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 19 Mar 2025 16:51:45 +0900 Subject: [PATCH 1201/3097] Support dynamic Expr name expressions in rules --- src/Rules/Classes/ClassConstantRule.php | 28 ++++++- src/Rules/Methods/CallMethodsRule.php | 27 ++++++- src/Rules/Methods/CallStaticMethodsRule.php | 28 ++++++- src/Rules/Variables/DefinedVariableRule.php | 38 +++++++-- .../Rules/Classes/ClassConstantRuleTest.php | 44 +++++++++++ .../Classes/data/dynamic-constant-access.php | 48 ++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 35 +++++++++ .../Methods/CallStaticMethodsRuleTest.php | 34 ++++++++ .../Rules/Methods/data/dynamic-call.php | 62 +++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 78 +++++++++++++++++++ .../Rules/Variables/data/dynamic-access.php | 54 +++++++++++++ 11 files changed, 460 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/dynamic-constant-access.php create mode 100644 tests/PHPStan/Rules/Methods/data/dynamic-call.php create mode 100644 tests/PHPStan/Rules/Variables/data/dynamic-access.php diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 968b3d75f3c..23c3aafdaa1 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -3,7 +3,9 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node; +use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; @@ -11,6 +13,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -47,11 +50,30 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->name instanceof Node\Identifier) { - return []; + $errors = []; + if ($node->name instanceof Node\Identifier) { + $constantNameScopes = [$node->name->name => $scope]; + } else { + $nameType = $scope->getType($node->name); + $constantNameScopes = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $name = $constantString->getValue(); + $constantNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); + } + } + + foreach ($constantNameScopes as $constantName => $constantScope) { + $errors = array_merge($errors, $this->processSingleClassConstFetch($constantScope, $node, $constantName)); } - $constantName = $node->name->name; + return $errors; + } + + /** + * @return list + */ + private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $node, string $constantName): array + { $class = $node->class; $messages = []; if ($class instanceof Node\Name) { diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 19bc52fe80b..68957aa2e59 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -3,11 +3,14 @@ namespace PHPStan\Rules\Methods; use PhpParser\Node; +use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionCallParametersCheck; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use function array_merge; @@ -31,12 +34,30 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->name instanceof Node\Identifier) { - return []; + $errors = []; + if ($node->name instanceof Node\Identifier) { + $methodNameScopes = [$node->name->name => $scope]; + } else { + $nameType = $scope->getType($node->name); + $methodNameScopes = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $name = $constantString->getValue(); + $methodNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); + } } - $methodName = $node->name->name; + foreach ($methodNameScopes as $methodName => $methodScope) { + $errors = array_merge($errors, $this->processSingleMethodCall($methodScope, $node, $methodName)); + } + + return $errors; + } + /** + * @return list + */ + private function processSingleMethodCall(Scope $scope, MethodCall $node, string $methodName): array + { [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodName, $node->var); if ($methodReflection === null) { return $errors; diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index e6b2c9dbb57..954c3a8669e 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -3,11 +3,14 @@ namespace PHPStan\Rules\Methods; use PhpParser\Node; +use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionCallParametersCheck; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use function array_merge; use function sprintf; @@ -32,11 +35,30 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!$node->name instanceof Node\Identifier) { - return []; + $errors = []; + if ($node->name instanceof Node\Identifier) { + $methodNameScopes = [$node->name->name => $scope]; + } else { + $nameType = $scope->getType($node->name); + $methodNameScopes = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $name = $constantString->getValue(); + $methodNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); + } } - $methodName = $node->name->name; + foreach ($methodNameScopes as $methodName => $methodScope) { + $errors = array_merge($errors, $this->processSingleMethodCall($methodScope, $node, $methodName)); + } + + return $errors; + } + + /** + * @return list + */ + private function processSingleMethodCall(Scope $scope, StaticCall $node, string $methodName): array + { [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class); if ($method === null) { return $errors; diff --git a/src/Rules/Variables/DefinedVariableRule.php b/src/Rules/Variables/DefinedVariableRule.php index fc665ed9011..2c7163434c6 100644 --- a/src/Rules/Variables/DefinedVariableRule.php +++ b/src/Rules/Variables/DefinedVariableRule.php @@ -3,10 +3,14 @@ namespace PHPStan\Rules\Variables; use PhpParser\Node; +use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use function array_merge; use function in_array; use function is_string; use function sprintf; @@ -31,11 +35,31 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (!is_string($node->name)) { - return []; + $errors = []; + if (is_string($node->name)) { + $variableNameScopes = [$node->name => $scope]; + } else { + $nameType = $scope->getType($node->name); + $variableNameScopes = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $name = $constantString->getValue(); + $variableNameScopes[$name] = $scope->filterByTruthyValue(new Identical($node->name, new String_($name))); + } + } + + foreach ($variableNameScopes as $name => $variableScope) { + $errors = array_merge($errors, $this->processSingleVariable($variableScope, $node, $name)); } - if ($this->cliArgumentsVariablesRegistered && in_array($node->name, [ + return $errors; + } + + /** + * @return list + */ + private function processSingleVariable(Scope $scope, Variable $node, string $variableName): array + { + if ($this->cliArgumentsVariablesRegistered && in_array($variableName, [ 'argc', 'argv', ], true)) { @@ -49,18 +73,18 @@ public function processNode(Node $node, Scope $scope): array return []; } - if ($scope->hasVariableType($node->name)->no()) { + if ($scope->hasVariableType($variableName)->no()) { return [ - RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $node->name)) + RuleErrorBuilder::message(sprintf('Undefined variable: $%s', $variableName)) ->identifier('variable.undefined') ->build(), ]; } elseif ( $this->checkMaybeUndefinedVariables - && !$scope->hasVariableType($node->name)->yes() + && !$scope->hasVariableType($variableName)->yes() ) { return [ - RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $node->name)) + RuleErrorBuilder::message(sprintf('Variable $%s might not be defined.', $variableName)) ->identifier('variable.undefined') ->build(), ]; diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 32086476beb..838201ffded 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -435,4 +435,48 @@ public function testClassConstantAccessedOnTrait(): void ]); } + public function testDynamicAccess(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->phpVersion = PHP_VERSION_ID; + + $this->analyse([__DIR__ . '/data/dynamic-constant-access.php'], [ + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::FOO.', + 20, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::BUZ.', + 20, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::FOO.', + 37, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::BUZ.', + 39, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::QUX.', + 41, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::QUX.', + 44, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::BUZ.', + 44, + ], + [ + 'Access to undefined constant ClassConstantDynamicAccess\Foo::FOO.', + 44, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/dynamic-constant-access.php b/tests/PHPStan/Rules/Classes/data/dynamic-constant-access.php new file mode 100644 index 00000000000..10809e566a2 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/dynamic-constant-access.php @@ -0,0 +1,48 @@ += 8.3 + +namespace ClassConstantDynamicAccess; + +final class Foo +{ + + private const BAR = 'BAR'; + + /** @var 'FOO'|'BAR'|'BUZ' */ + public $name; + + public function test(string $string, object $obj): void + { + $bar = 'FOO'; + + echo self::{$foo}; + echo self::{$string}; + echo self::{$obj}; + echo self::{$this->name}; + } + + public function testScope(): void + { + $name1 = 'FOO'; + $rand = rand(); + if ($rand === 1) { + $foo = 1; + $name = $name1; + } elseif ($rand === 2) { + $name = 'BUZ'; + } else { + $name = 'QUX'; + } + + if ($name === 'FOO') { + echo self::{$name}; + } elseif ($name === 'BUZ') { + echo self::{$name}; + } else { + echo self::{$name}; + } + + echo self::{$name}; + } + + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index acbbd53dd3b..e544eeeb5bc 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3552,4 +3552,39 @@ public function testBug6828(): void $this->analyse([__DIR__ . '/data/bug-6828.php'], []); } + public function testDynamicCall(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/dynamic-call.php'], [ + [ + 'Call to an undefined method MethodsDynamicCall\Foo::bar().', + 23, + ], + [ + 'Call to an undefined method MethodsDynamicCall\Foo::doBar().', + 26, + ], + [ + 'Call to an undefined method MethodsDynamicCall\Foo::doBuz().', + 26, + ], + [ + 'Parameter #1 $n of method MethodsDynamicCall\Foo::doFoo() expects int, int|string given.', + 53, + ], + [ + 'Parameter #1 $s of method MethodsDynamicCall\Foo::doQux() expects string, int given.', + 54, + ], + [ + 'Parameter #1 $n of method MethodsDynamicCall\Foo::doFoo() expects int, string given.', + 55, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 51210125cf6..2cfc7891b57 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -862,4 +862,38 @@ public function testBug12015(): void $this->analyse([__DIR__ . '/data/bug-12015.php'], []); } + public function testDynamicCall(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/dynamic-call.php'], [ + [ + 'Call to an undefined static method MethodsDynamicCall\Foo::bar().', + 33, + ], + [ + 'Call to an undefined static method MethodsDynamicCall\Foo::doBar().', + 36, + ], + [ + 'Call to an undefined static method MethodsDynamicCall\Foo::doBuz().', + 36, + ], + [ + 'Parameter #1 $n of method MethodsDynamicCall\Foo::doFoo() expects int, int|string given.', + 58, + ], + [ + 'Parameter #1 $s of static method MethodsDynamicCall\Foo::doQux() expects string, int given.', + 59, + ], + [ + 'Parameter #1 $n of method MethodsDynamicCall\Foo::doFoo() expects int, string given.', + 60, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/dynamic-call.php b/tests/PHPStan/Rules/Methods/data/dynamic-call.php new file mode 100644 index 00000000000..3a917c0c812 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/dynamic-call.php @@ -0,0 +1,62 @@ +$foo(); + echo $this->$string(); + echo $this->$obj(); + echo $this->{self::$name}(); + } + + public function testStaticCall(string $string, object $obj): void + { + $foo = 'bar'; + + echo self::$foo(); + echo self::$string(); + echo self::$obj(); + echo self::{self::$name}(); + } + + public function testScope(): void + { + $param1 = 1; + $param2 = 'str'; + $name1 = 'doFoo'; + if (rand(0, 1)) { + $name = $name1; + $param = $param1; + } else { + $name = 'doQux'; + $param = $param2; + } + + $this->$name($param); // ok + $this->$name1($param); + $this->$name($param1); + $this->$name($param2); + + self::$name($param); // ok + self::$name1($param); + self::$name($param1); + self::$name($param2); + } +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 17aa83b245d..e6b29e28095 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1098,4 +1098,82 @@ public function testPropertyHooks(): void ]); } + public function testDynamicAccess(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/dynamic-access.php'], [ + [ + 'Undefined variable: $bar', + 15, + ], + [ + 'Undefined variable: $bar', + 18, + ], + [ + 'Undefined variable: $buz', + 18, + ], + [ + 'Variable $foo might not be defined.', + 36, + ], + [ + 'Variable $foo might not be defined.', + 37, + ], + [ + 'Variable $bar might not be defined.', + 38, + ], + [ + 'Variable $bar might not be defined.', + 40, + ], + [ + 'Variable $foo might not be defined.', + 41, + ], + [ + 'Variable $bar might not be defined.', + 42, + ], + [ + 'Undefined variable: $buz', + 44, + ], + [ + 'Undefined variable: $foo', + 45, + ], + [ + 'Undefined variable: $bar', + 46, + ], + [ + 'Undefined variable: $buz', + 49, + ], + [ + 'Variable $bar might not be defined.', + 49, + ], + [ + 'Variable $foo might not be defined.', + 49, + ], + [ + 'Variable $foo might not be defined.', + 50, + ], + [ + 'Variable $bar might not be defined.', + 51, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/dynamic-access.php b/tests/PHPStan/Rules/Variables/data/dynamic-access.php new file mode 100644 index 00000000000..a1109c5858b --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/dynamic-access.php @@ -0,0 +1,54 @@ +name}; + } + + public function testScope(): void + { + $name1 = 'foo'; + $rand = rand(); + if ($rand === 1) { + $foo = 1; + $name = $name1; + } elseif ($rand === 2) { + $name = 'bar'; + $bar = 'str'; + } else { + $name = 'buz'; + } + + if ($name === 'foo') { + echo $$name; // ok + echo $foo; // ok + echo $bar; + } elseif ($name === 'bar') { + echo $$name; // ok + echo $foo; + echo $bar; // ok + } else { + echo $$name; // ok + echo $foo; + echo $bar; + } + + echo $$name; // ok + echo $foo; + echo $bar; + } + +} From 85c709d11114dd5e26e29f74cdcaf13f92697875 Mon Sep 17 00:00:00 2001 From: Emanuele Panzeri Date: Wed, 19 Mar 2025 11:46:23 +0100 Subject: [PATCH 1202/3097] excludePaths: include example for optional path --- src/Command/CommandHelper.php | 28 ++++++++- .../InvalidExcludePathsException.php | 11 +++- .../ValidateExcludePathsExtension.php | 58 +++++++++---------- .../ValidateIgnoredErrorsExtension.php | 2 +- 4 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 499ebb48e8a..a142ecdca87 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -25,6 +25,7 @@ use PHPStan\File\FileExcluder; use PHPStan\File\FileFinder; use PHPStan\File\FileHelper; +use PHPStan\File\ParentDirectoryRelativePathHelper; use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Internal\ComposerHelper; use PHPStan\Internal\DirectoryCreator; @@ -379,9 +380,30 @@ public static function begin( $errorOutput->writeLineFormatted(''); } - $errorOutput->writeLineFormatted('If the excluded path can sometimes exist, append (?)'); - $errorOutput->writeLineFormatted('to its config entry to mark it as optional.'); - $errorOutput->writeLineFormatted(''); + $suggestOptional = $e->getSuggestOptional(); + if (count($suggestOptional) > 0) { + $baselinePathHelper = null; + if ($projectConfigFile !== null) { + $baselinePathHelper = new ParentDirectoryRelativePathHelper(dirname($projectConfigFile)); + } + $errorOutput->writeLineFormatted('If the excluded path can sometimes exist, append (?)'); + $errorOutput->writeLineFormatted('to its config entry to mark it as optional. Example:'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('parameters:'); + $errorOutput->writeLineFormatted("\texcludePaths:"); + foreach ($suggestOptional as $key => $suggestOptionalPaths) { + $errorOutput->writeLineFormatted(sprintf("\t\t%s:", $key)); + foreach ($suggestOptionalPaths as $suggestOptionalPath) { + if ($baselinePathHelper === null) { + $errorOutput->writeLineFormatted(sprintf("\t\t\t- %s (?)", $suggestOptionalPath)); + continue; + } + + $errorOutput->writeLineFormatted(sprintf("\t\t\t- %s (?)", $baselinePathHelper->getRelativePath($suggestOptionalPath))); + } + } + $errorOutput->writeLineFormatted(''); + } throw new InceptionNotSuccessfulException(); } catch (ValidationException $e) { diff --git a/src/DependencyInjection/InvalidExcludePathsException.php b/src/DependencyInjection/InvalidExcludePathsException.php index 24ecfb9565b..b2ae0307827 100644 --- a/src/DependencyInjection/InvalidExcludePathsException.php +++ b/src/DependencyInjection/InvalidExcludePathsException.php @@ -10,8 +10,9 @@ final class InvalidExcludePathsException extends Exception /** * @param string[] $errors + * @param array{analyse?: list, analyseAndScan?: list} $suggestOptional */ - public function __construct(private array $errors) + public function __construct(private array $errors, private array $suggestOptional) { parent::__construct(implode("\n", $this->errors)); } @@ -24,4 +25,12 @@ public function getErrors(): array return $this->errors; } + /** + * @return array{analyse?: list, analyseAndScan?: list} + */ + public function getSuggestOptional(): array + { + return $this->suggestOptional; + } + } diff --git a/src/DependencyInjection/ValidateExcludePathsExtension.php b/src/DependencyInjection/ValidateExcludePathsExtension.php index 6d099d1c105..dcb8fa89824 100644 --- a/src/DependencyInjection/ValidateExcludePathsExtension.php +++ b/src/DependencyInjection/ValidateExcludePathsExtension.php @@ -7,7 +7,6 @@ use PHPStan\File\FileExcluder; use function array_key_exists; use function array_map; -use function array_merge; use function count; use function is_dir; use function is_file; @@ -27,41 +26,42 @@ public function loadConfiguration(): void return; } + $newExcludePaths = []; + if (array_key_exists('analyseAndScan', $excludePaths)) { + $newExcludePaths['analyseAndScan'] = $excludePaths['analyseAndScan']; + } + if (array_key_exists('analyse', $excludePaths)) { + $newExcludePaths['analyse'] = $excludePaths['analyse']; + } + $errors = []; + $suggestOptional = []; if ($builder->parameters['__validate']) { - $paths = []; - if (array_key_exists('analyse', $excludePaths)) { - $paths = $excludePaths['analyse']; - } - if (array_key_exists('analyseAndScan', $excludePaths)) { - $paths = array_merge($paths, $excludePaths['analyseAndScan']); - } - foreach ($paths as $path) { - if ($path instanceof OptionalPath) { - continue; - } - if (FileExcluder::isAbsolutePath($path)) { - if (is_dir($path)) { + foreach ($newExcludePaths as $key => $paths) { + foreach ($paths as $path) { + if ($path instanceof OptionalPath) { continue; } - if (is_file($path)) { + if (FileExcluder::isAbsolutePath($path)) { + if (is_dir($path)) { + continue; + } + if (is_file($path)) { + continue; + } + } + if (FileExcluder::isFnmatchPattern($path)) { continue; } - } - if (FileExcluder::isFnmatchPattern($path)) { - continue; - } - $errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path); + $suggestOptional[$key][] = $path; + $errors[] = sprintf('Path "%s" is neither a directory, nor a file path, nor a fnmatch pattern.', $path); + } } } - $newExcludePaths = []; - if (array_key_exists('analyseAndScan', $excludePaths)) { - $newExcludePaths['analyseAndScan'] = $excludePaths['analyseAndScan']; - } - if (array_key_exists('analyse', $excludePaths)) { - $newExcludePaths['analyse'] = $excludePaths['analyse']; + if (count($errors) !== 0) { + throw new InvalidExcludePathsException($errors, $suggestOptional); } foreach ($newExcludePaths as $key => $p) { @@ -72,12 +72,6 @@ public function loadConfiguration(): void } $builder->parameters['excludePaths'] = $newExcludePaths; - - if (count($errors) === 0) { - return; - } - - throw new InvalidExcludePathsException($errors); } } diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 4560fde44cd..68a41d3e9f7 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -168,7 +168,7 @@ public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry continue; } - $errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $ignorePath); + $errors[] = sprintf('Path "%s" is neither a directory, nor a file path, nor a fnmatch pattern.', $ignorePath); } } } From 610984b11a3c1cd8ffa71784eddf867cafd9dd7a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:46:58 +0100 Subject: [PATCH 1203/3097] Fix E2E tests --- .github/workflows/e2e-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5e574eb6d2f..d2b87d307a3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -190,25 +190,25 @@ jobs: OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c ignore.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in ignoreErrors' "$OUTPUT" - ../bashunit -a contains 'tests is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains '"tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon.php") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in ignoreErrors' "$OUTPUT" - ../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains '"src/test.php" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c excludePaths.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" - ../bashunit -a contains 'tests is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains '"tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon2.php") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" - ../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains '"src/test.php" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon) From 11ef303e5820540bd0af2ecc9a1277edce85dcbb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:49:10 +0100 Subject: [PATCH 1204/3097] Fix --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d2b87d307a3..e02de443a59 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -190,7 +190,7 @@ jobs: OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c ignore.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in ignoreErrors' "$OUTPUT" - ../bashunit -a contains '"tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains 'tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon.php") @@ -202,7 +202,7 @@ jobs: OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c excludePaths.neon") echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" - ../bashunit -a contains '"tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + ../bashunit -a contains 'tests" is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" - script: | cd e2e/bad-exclude-paths OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c phpneon2.php") From 17d4a030613e197fc2e42b5e39ed854abe5dd2fd Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 20 Mar 2025 09:55:44 +0100 Subject: [PATCH 1205/3097] Narrow type on setting offsets of properties Co-authored-by: Ondrej Mirtes --- src/Analyser/NodeScopeResolver.php | 28 ++++++++--- .../TypesAssignedToPropertiesRuleTest.php | 22 ++++++++ .../Rules/Properties/data/bug-12565.php | 50 +++++++++++++++++++ .../Rules/Properties/data/bug-6398.php | 32 ++++++++++++ .../Rules/Properties/data/bug-6571.php | 31 ++++++++++++ 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100755 tests/PHPStan/Rules/Properties/data/bug-12565.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-6398.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-6571.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ecf824a2400..e31c6c4c3c9 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5344,9 +5344,17 @@ private function processAssignVar( $originalVar = $var; $assignedPropertyExpr = $assignedExpr; while ($var instanceof ArrayDimFetch) { - $varForSetOffsetValue = $var->var; - if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + if ( + $var->var instanceof PropertyFetch + || $var->var instanceof StaticPropertyFetch + ) { + if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->var))->yes())) { + $varForSetOffsetValue = $var->var; + } else { + $varForSetOffsetValue = new OriginalPropertyTypeExpr($var->var); + } + } else { + $varForSetOffsetValue = $var->var; } $assignedPropertyExpr = new SetOffsetValueTypeExpr( $varForSetOffsetValue, @@ -5682,9 +5690,17 @@ static function (): void { $dimFetchStack = []; $assignedPropertyExpr = $assignedExpr; while ($var instanceof ExistingArrayDimFetch) { - $varForSetOffsetValue = $var->getVar(); - if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + if ( + $var->getVar() instanceof PropertyFetch + || $var->getVar() instanceof StaticPropertyFetch + ) { + if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->getVar()))->yes())) { + $varForSetOffsetValue = $var->getVar(); + } else { + $varForSetOffsetValue = new OriginalPropertyTypeExpr($var->getVar()); + } + } else { + $varForSetOffsetValue = $var->getVar(); } $assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( $varForSetOffsetValue, diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 533c53c25d2..7c80c91cd60 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -689,6 +689,28 @@ public function testBug12131(): void ]); } + public function testBug6398(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-6398.php'], []); + } + + public function testBug6571(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-6571.php'], []); + } + + public function testBug12565(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12565.php'], []); + } + public function testShortBodySetHook(): void { if (PHP_VERSION_ID < 80400) { diff --git a/tests/PHPStan/Rules/Properties/data/bug-12565.php b/tests/PHPStan/Rules/Properties/data/bug-12565.php new file mode 100755 index 00000000000..12fafa74695 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12565.php @@ -0,0 +1,50 @@ + + */ +class ArrayLike implements \ArrayAccess { + + /** @var EntryType[] */ + private array $values = []; + public function offsetExists(mixed $offset): bool + { + return isset($this->values[$offset]); + } + + public function offsetGet(mixed $offset): EntryType + { + return $this->values[$offset] ?? new EntryType(); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->values[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->values[$offset]); + } +} + +class Wrapper { + public ?ArrayLike $myArrayLike; + + public function __construct() + { + $this->myArrayLike = new ArrayLike(); + + } +} + +$baz = new Wrapper(); +$baz->myArrayLike = new ArrayLike(); +$baz->myArrayLike[1] = new EntryType(); +$baz->myArrayLike[1]->title = "Test"; diff --git a/tests/PHPStan/Rules/Properties/data/bug-6398.php b/tests/PHPStan/Rules/Properties/data/bug-6398.php new file mode 100644 index 00000000000..b1b824d541c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-6398.php @@ -0,0 +1,32 @@ +>|null + */ + private static $threadLocalStorage = null; + + /** + * @param mixed $complexData the data to store + */ + protected function storeLocal(string $key, $complexData) : void{ + if(self::$threadLocalStorage === null){ + self::$threadLocalStorage = new \ArrayObject(); + } + self::$threadLocalStorage[spl_object_id($this)][$key] = $complexData; + } + + /** + * @return mixed + */ + protected function fetchLocal(string $key){ + $id = spl_object_id($this); + if(self::$threadLocalStorage === null or !isset(self::$threadLocalStorage[$id][$key])){ + throw new \InvalidArgumentException("No matching thread-local data found on this thread"); + } + + return self::$threadLocalStorage[$id][$key]; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-6571.php b/tests/PHPStan/Rules/Properties/data/bug-6571.php new file mode 100644 index 00000000000..3ce06cc10d2 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-6571.php @@ -0,0 +1,31 @@ += 7.4 + +namespace Bug6571; + +interface ClassLoader{} + +class HelloWorld +{ + /** @var \Threaded|\ClassLoader[]|null */ + private ?\Threaded $classLoaders = null; + + /** + * @param \ClassLoader[] $autoloaders + */ + public function setClassLoaders(?array $autoloaders = null) : void{ + if($autoloaders === null){ + $autoloaders = []; + } + + if($this->classLoaders === null){ + $this->classLoaders = new \Threaded(); + }else{ + foreach($this->classLoaders as $k => $autoloader){ + unset($this->classLoaders[$k]); + } + } + foreach($autoloaders as $autoloader){ + $this->classLoaders[] = $autoloader; + } + } +} From c6e045060f11ec49c58327f34cbff370baff30d2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 20 Mar 2025 13:53:02 +0100 Subject: [PATCH 1206/3097] Fix `count()` regression --- src/Analyser/TypeSpecifier.php | 2 + tests/PHPStan/Analyser/nsrt/list-count.php | 45 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c016b2869e5..105204cee71 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1063,10 +1063,12 @@ private function specifyTypesForCountFuncCall( $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); + $zeroOrMore = IntegerRangeType::fromInterval(0, null); if ( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing + || !$zeroOrMore->isSuperTypeOf($sizeType)->yes() ) { return null; } diff --git a/tests/PHPStan/Analyser/nsrt/list-count.php b/tests/PHPStan/Analyser/nsrt/list-count.php index 6654e463785..b5496d324dc 100644 --- a/tests/PHPStan/Analyser/nsrt/list-count.php +++ b/tests/PHPStan/Analyser/nsrt/list-count.php @@ -369,7 +369,7 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t if (count($row) >= $maxThree) { assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } else { - assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } } @@ -386,3 +386,46 @@ protected function testOptionalKeysInUnionArrayWithIntRange($row, $twoOrThree): } } } + +class FooBug +{ + public int $totalExpectedRows = 0; + + /** @var list<\stdClass> */ + public array $importedDaySummaryRows = []; + + public function sayHello(): void + { + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + if ($this->totalExpectedRows !== count($this->importedDaySummaryRows)) { + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } + assertType('int', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } +} + +class FooBugPositiveInt +{ + /** + * @var positive-int + */ + public int $totalExpectedRows = 1; + + /** @var list<\stdClass> */ + public array $importedDaySummaryRows = []; + + public function sayHello(): void + { + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + if ($this->totalExpectedRows !== count($this->importedDaySummaryRows)) { + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } + assertType('int<1, max>', $this->totalExpectedRows); + assertType('list', $this->importedDaySummaryRows); + } +} From 4111d0f5951338d3fa9735edd8ae38de90d15456 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Mar 2025 13:04:12 +0100 Subject: [PATCH 1207/3097] StaticPropertyFetch is an impure point --- src/Analyser/ImpurePoint.php | 2 +- src/Analyser/NodeScopeResolver.php | 10 +++- .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 12 +++++ tests/PHPStan/Rules/Pure/data/pure-method.php | 46 +++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/Analyser/ImpurePoint.php b/src/Analyser/ImpurePoint.php index d4dc6fe133f..f76b230f2e4 100644 --- a/src/Analyser/ImpurePoint.php +++ b/src/Analyser/ImpurePoint.php @@ -6,7 +6,7 @@ use PHPStan\Node\VirtualNode; /** - * @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyAssignByRef'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags' + * @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyAssignByRef'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags'|'staticPropertyAccess' * @api * @final */ diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c0617856f7f..8599b4bd6e7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2908,7 +2908,15 @@ static function (): void { } elseif ($expr instanceof StaticPropertyFetch) { $hasYield = false; $throwPoints = []; - $impurePoints = []; + $impurePoints = [ + new ImpurePoint( + $scope, + $expr, + 'staticPropertyAccess', + 'static property access', + true, + ), + ]; if ($expr->class instanceof Expr) { $result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep()); $hasYield = $result->hasYield(); diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 4ec5028172d..584e644809b 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -140,6 +140,14 @@ public function testRule(): void 'Possibly impure call to a callable in pure method PureMethod\MaybeCallableFromUnion::doFoo().', 330, ], + [ + 'Impure static property access in pure method PureMethod\StaticMethodAccessingStaticProperty::getA().', + 388, + ], + [ + 'Impure property assignment in pure method PureMethod\StaticMethodAssigningStaticProperty::getA().', + 409, + ], ]); } @@ -151,6 +159,10 @@ public function testPureConstructor(): void $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/pure-constructor.php'], [ + [ + 'Impure static property access in pure method PureConstructor\Foo::__construct().', + 19, + ], [ 'Impure property assignment in pure method PureConstructor\Foo::__construct().', 19, diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index bcaa939b76e..c45f048e958 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -375,3 +375,49 @@ public function assertSth($value): void } } + +class StaticMethodAccessingStaticProperty +{ + public static int $a = 0; + + /** + * @phpstan-pure + */ + public static function getA(): int + { + return self::$a; + } + + /** + * @phpstan-impure + */ + public static function getB(): int + { + return self::$a; + } +} + +class StaticMethodAssigningStaticProperty +{ + public static int $a = 0; + + /** + * @phpstan-pure + */ + public static function getA(): int + { + self::$a = 1; + + return 1; + } + + /** + * @phpstan-impure + */ + public static function getB(): int + { + self::$a = 1; + + return 1; + } +} From 2252516fc7b0b48a3f0a5f48db733c345094a2fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Mar 2025 13:16:42 +0100 Subject: [PATCH 1208/3097] Fix build --- tests/PHPStan/Rules/Pure/data/pure-method.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index c45f048e958..aca12422314 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -378,8 +378,8 @@ public function assertSth($value): void class StaticMethodAccessingStaticProperty { - public static int $a = 0; - + /** @var int */ + public static $a = 0; /** * @phpstan-pure */ From 21923d32a908355238a0367b32e838aafe8f8789 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 21 Mar 2025 13:26:24 +0100 Subject: [PATCH 1209/3097] Fix build --- tests/PHPStan/Rules/Pure/data/pure-method.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index aca12422314..efa83c9191e 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -399,8 +399,8 @@ public static function getB(): int class StaticMethodAssigningStaticProperty { - public static int $a = 0; - + /** @var int */ + public static $a = 0; /** * @phpstan-pure */ From 12a0b4e4a5d464c22ea66ff44c67be3583f0c4bd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 21 Mar 2025 13:37:25 +0100 Subject: [PATCH 1210/3097] RegexArrayShapeMatcher - turn more details immutable --- src/Type/Php/RegexArrayShapeMatcher.php | 102 +++------------ src/Type/Regex/RegexCapturingGroup.php | 51 ++++++-- src/Type/Regex/RegexGroupList.php | 166 ++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 97 deletions(-) create mode 100644 src/Type/Regex/RegexGroupList.php diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index cd91c0aa802..da6a44e7355 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -15,14 +15,13 @@ use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\NullType; -use PHPStan\Type\Regex\RegexAlternation; use PHPStan\Type\Regex\RegexCapturingGroup; use PHPStan\Type\Regex\RegexExpressionHelper; +use PHPStan\Type\Regex\RegexGroupList; use PHPStan\Type\Regex\RegexGroupParser; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use function array_reverse; use function count; use function in_array; use function is_string; @@ -115,16 +114,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched } [$groupList, $markVerbs] = $parseResult; - $trailingOptionals = 0; - foreach (array_reverse($groupList) as $captureGroup) { - if (!$captureGroup->isOptional()) { - break; - } - $trailingOptionals++; - } - - $onlyOptionalTopLevelGroup = $this->getOnlyOptionalTopLevelGroup($groupList); - $onlyTopLevelAlternation = $this->getOnlyTopLevelAlternation($groupList); + $regexGroupList = new RegexGroupList($groupList); + $trailingOptionals = $regexGroupList->countTrailingOptionals(); + $onlyOptionalTopLevelGroup = $regexGroupList->getOnlyOptionalTopLevelGroup(); + $onlyTopLevelAlternation = $regexGroupList->getOnlyTopLevelAlternation(); $flags ??= 0; if ( @@ -134,11 +127,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ) { // if only one top level capturing optional group exists // we build a more precise tagged union of a empty-match and a match with the group - - $onlyOptionalTopLevelGroup->forceNonOptional(); + $regexGroupList = $regexGroupList->forceGroupNonOptional($onlyOptionalTopLevelGroup); $combiType = $this->buildArrayType( - $groupList, + $regexGroupList, $wasMatched, $trailingOptionals, $flags, @@ -154,8 +146,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ); } - $onlyOptionalTopLevelGroup->clearOverrides(); - return $combiType; } elseif ( !$matchesAll @@ -168,24 +158,24 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $combiTypes = []; $isOptionalAlternation = false; foreach ($onlyTopLevelAlternation->getGroupCombinations() as $groupCombo) { - $comboList = $groupList; + $comboList = new RegexGroupList($groupList); $beforeCurrentCombo = true; - foreach ($comboList as $groupId => $group) { - if (in_array($groupId, $groupCombo, true)) { + foreach ($comboList as $group) { + if (in_array($group->getId(), $groupCombo, true)) { $isOptionalAlternation = $group->inOptionalAlternation(); - $group->forceNonOptional(); + $comboList = $comboList->forceGroupNonOptional($group); $beforeCurrentCombo = false; } elseif ($beforeCurrentCombo && !$group->resetsGroupCounter()) { - $group->forceNonOptional(); - $group->forceType( + $comboList = $comboList->forceGroupTypeAndNonOptional( + $group, $this->containsUnmatchedAsNull($flags, $matchesAll) ? new NullType() : new ConstantStringType(''), ); } elseif ( $group->getAlternationId() === $onlyTopLevelAlternation->getId() && !$this->containsUnmatchedAsNull($flags, $matchesAll) ) { - unset($comboList[$groupId]); + $comboList = $comboList->removeGroup($group); } } @@ -199,11 +189,6 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ); $combiTypes[] = $combiType; - - foreach ($groupCombo as $groupId) { - $group = $comboList[$groupId]; - $group->clearOverrides(); - } } if ( @@ -223,7 +208,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched // the general case, which should work in all cases but does not yield the most // precise result possible in some cases return $this->buildArrayType( - $groupList, + $regexGroupList, $wasMatched, $trailingOptionals, $flags, @@ -233,65 +218,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched } /** - * @param array $captureGroups - */ - private function getOnlyOptionalTopLevelGroup(array $captureGroups): ?RegexCapturingGroup - { - $group = null; - foreach ($captureGroups as $captureGroup) { - if (!$captureGroup->isTopLevel()) { - continue; - } - - if (!$captureGroup->isOptional()) { - return null; - } - - if ($group !== null) { - return null; - } - - $group = $captureGroup; - } - - return $group; - } - - /** - * @param array $captureGroups - */ - private function getOnlyTopLevelAlternation(array $captureGroups): ?RegexAlternation - { - $alternation = null; - foreach ($captureGroups as $captureGroup) { - if (!$captureGroup->isTopLevel()) { - continue; - } - - if (!$captureGroup->inAlternation()) { - return null; - } - - if ($captureGroup->inOptionalQuantification()) { - return null; - } - - if ($alternation === null) { - $alternation = $captureGroup->getAlternation(); - } elseif ($alternation->getId() !== $captureGroup->getAlternation()->getId()) { - return null; - } - } - - return $alternation; - } - - /** - * @param array $captureGroups * @param list $markVerbs */ private function buildArrayType( - array $captureGroups, + RegexGroupList $captureGroups, TrinaryLogic $wasMatched, int $trailingOptionals, int $flags, diff --git a/src/Type/Regex/RegexCapturingGroup.php b/src/Type/Regex/RegexCapturingGroup.php index 51a1fc9d85e..3cc16fa182e 100644 --- a/src/Type/Regex/RegexCapturingGroup.php +++ b/src/Type/Regex/RegexCapturingGroup.php @@ -7,10 +7,6 @@ final class RegexCapturingGroup { - private bool $forceNonOptional = false; - - private ?Type $forceType = null; - public function __construct( private readonly int $id, private readonly ?string $name, @@ -18,6 +14,8 @@ public function __construct( private readonly bool $inOptionalQuantification, private readonly RegexCapturingGroup|RegexNonCapturingGroup|null $parent, private readonly Type $type, + private readonly bool $forceNonOptional = false, + private readonly ?Type $forceType = null, ) { } @@ -27,20 +25,46 @@ public function getId(): int return $this->id; } - public function forceNonOptional(): void + public function forceNonOptional(): self { - $this->forceNonOptional = true; + return new self( + $this->id, + $this->name, + $this->alternation, + $this->inOptionalQuantification, + $this->parent, + $this->type, + true, + $this->forceType, + ); } - public function forceType(Type $type): void + public function forceType(Type $type): self { - $this->forceType = $type; + return new self( + $this->id, + $this->name, + $this->alternation, + $this->inOptionalQuantification, + $this->parent, + $type, + $this->forceNonOptional, + $this->forceType, + ); } - public function clearOverrides(): void + public function withParent(RegexCapturingGroup|RegexNonCapturingGroup $parent): self { - $this->forceNonOptional = false; - $this->forceType = null; + return new self( + $this->id, + $this->name, + $this->alternation, + $this->inOptionalQuantification, + $parent, + $this->type, + $this->forceNonOptional, + $this->forceType, + ); } public function resetsGroupCounter(): bool @@ -128,4 +152,9 @@ public function getType(): Type return $this->type; } + public function getParent(): RegexCapturingGroup|RegexNonCapturingGroup|null + { + return $this->parent; + } + } diff --git a/src/Type/Regex/RegexGroupList.php b/src/Type/Regex/RegexGroupList.php new file mode 100644 index 00000000000..d5f624f5df3 --- /dev/null +++ b/src/Type/Regex/RegexGroupList.php @@ -0,0 +1,166 @@ + + */ +final class RegexGroupList implements Countable, IteratorAggregate +{ + + /** + * @param array $groups + */ + public function __construct( + private readonly array $groups, + ) + { + } + + public function countTrailingOptionals(): int + { + $trailingOptionals = 0; + foreach (array_reverse($this->groups) as $captureGroup) { + if (!$captureGroup->isOptional()) { + break; + } + $trailingOptionals++; + } + return $trailingOptionals; + } + + public function forceGroupNonOptional(RegexCapturingGroup $group): self + { + return $this->cloneAndReParentList($group); + } + + public function forceGroupTypeAndNonOptional(RegexCapturingGroup $group, Type $type): self + { + return $this->cloneAndReParentList($group, $type); + } + + private function cloneAndReParentList(RegexCapturingGroup $target, ?Type $type = null): self + { + $groups = []; + $forcedGroup = null; + foreach ($this->groups as $i => $group) { + if ($group->getId() === $target->getId()) { + $forcedGroup = $group->forceNonOptional(); + if ($type !== null) { + $forcedGroup = $forcedGroup->forceType($type); + } + $groups[$i] = $forcedGroup; + + continue; + } + + $groups[$i] = $group; + } + + if ($forcedGroup === null) { + throw new ShouldNotHappenException(); + } + + foreach ($groups as $i => $group) { + $parent = $group->getParent(); + + while ($parent !== null) { + if ($parent instanceof RegexNonCapturingGroup) { + $parent = $parent->getParent(); + continue; + } + + if ($parent->getId() === $target->getId()) { + $groups[$i] = $groups[$i]->withParent($forcedGroup); + } + $parent = $parent->getParent(); + } + } + + return new self($groups); + } + + public function removeGroup(RegexCapturingGroup $remove): self + { + $groups = []; + foreach ($this->groups as $i => $group) { + if ($group->getId() === $remove->getId()) { + continue; + } + + $groups[$i] = $group; + } + + return new self($groups); + } + + public function getOnlyOptionalTopLevelGroup(): ?RegexCapturingGroup + { + $group = null; + foreach ($this->groups as $captureGroup) { + if (!$captureGroup->isTopLevel()) { + continue; + } + + if (!$captureGroup->isOptional()) { + return null; + } + + if ($group !== null) { + return null; + } + + $group = $captureGroup; + } + + return $group; + } + + public function getOnlyTopLevelAlternation(): ?RegexAlternation + { + $alternation = null; + foreach ($this->groups as $captureGroup) { + if (!$captureGroup->isTopLevel()) { + continue; + } + + if (!$captureGroup->inAlternation()) { + return null; + } + + if ($captureGroup->inOptionalQuantification()) { + return null; + } + + if ($alternation === null) { + $alternation = $captureGroup->getAlternation(); + } elseif ($alternation->getId() !== $captureGroup->getAlternation()->getId()) { + return null; + } + } + + return $alternation; + } + + public function count(): int + { + return count($this->groups); + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->groups); + } + +} From 327ac3e54bd04d85aec3af9f893f8e3e8f38af7d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 22 Mar 2025 08:45:41 +0100 Subject: [PATCH 1211/3097] Fix false positives on existing-offsets after assign --- src/Analyser/NodeScopeResolver.php | 7 +++- ...nexistentOffsetInArrayDimFetchRuleTest.php | 16 +++++++++ .../PHPStan/Rules/Arrays/data/bug-12406b.php | 35 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12406b.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 73f330cb12f..50a57278b0e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5496,7 +5496,12 @@ private function processAssignVar( } if ($originalVar->dim instanceof Variable || $originalVar->dim instanceof Node\Scalar) { - if (!$scope->hasExpressionType($originalVar)->yes()) { + $currentVarType = $scope->getType($originalVar); + $currentVarNativeType = $scope->getNativeType($originalVar); + if ( + !$originalValueToWrite->isSuperTypeOf($currentVarType)->yes() + || !$originalNativeValueToWrite->isSuperTypeOf($currentVarNativeType)->yes() + ) { $scope = $scope->assignExpression( $originalVar, $originalValueToWrite, diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 3323d3a0dd5..9a1f34bf73b 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -856,6 +856,22 @@ public function testBug12406(): void $this->analyse([__DIR__ . '/data/bug-12406.php'], []); } + public function testBug12406b(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-12406b.php'], [ + [ + 'Offset int<0, max> might not exist on non-empty-list.', + 22, + ], + [ + 'Offset int<0, max> might not exist on non-empty-list.', + 23, + ], + ]); + } + public function testBug11679(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12406b.php b/tests/PHPStan/Rules/Arrays/data/bug-12406b.php new file mode 100644 index 00000000000..c0012503d05 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12406b.php @@ -0,0 +1,35 @@ +]+>\\n + AuthorDate:[^\\n]+\\n + Commit:[^\\n]+\\n + CommitDate:[^\\n]+\\n\\n + (\s+(?:[^\n]+\n)+)\n + [ ](\\d+)[ ]files?[ ]changed,(?:[ ](\\d+)[ ]insertions?\\(\\+\\),?)?(?:[ ](\\d+)[ ]deletions?\\(-\\))? + ~mx', $s, $matches, PREG_SET_ORDER); + + for ($i = 0; $i < count($matches); $i++) { + $author = $matches[$i][1]; + $files = (int) $matches[$i][3]; + $insertions = (int) ($matches[$i][4] ?? 0); + $deletions = (int) ($matches[$i][5] ?? 0); + + $stats[$author]['commits'] = ($stats[$author]['commits'] ?? 0) + 1; + $stats[$author]['files'] = ($stats[$author]['files'] ?? 0) + $files; + $stats[$author]['insertions'] = ($stats[$author]['insertions'] ?? 0) + $insertions; + $stats[$author]['deletions'] = ($stats[$author]['deletions'] ?? 0) + $deletions; + $stats[$author]['diff'] = ($stats[$author]['diff'] ?? 0) + $insertions - $deletions; + } + } + +} From 815f31740fc17db9dcc90fa67b04886febc9a905 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 22 Mar 2025 10:21:24 +0100 Subject: [PATCH 1212/3097] Fix `count()` regression --- src/Analyser/TypeSpecifier.php | 4 +- .../Analyser/nsrt/count-const-array.php | 78 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/count-const-array.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 105204cee71..33d3c34a7ed 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1063,12 +1063,12 @@ private function specifyTypesForCountFuncCall( $isConstantArray = $type->isConstantArray(); $isList = $type->isList(); - $zeroOrMore = IntegerRangeType::fromInterval(0, null); + $oneOrMore = IntegerRangeType::fromInterval(1, null); if ( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing - || !$zeroOrMore->isSuperTypeOf($sizeType)->yes() + || !$oneOrMore->isSuperTypeOf($sizeType)->yes() ) { return null; } diff --git a/tests/PHPStan/Analyser/nsrt/count-const-array.php b/tests/PHPStan/Analyser/nsrt/count-const-array.php new file mode 100644 index 00000000000..dfcb626150b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-const-array.php @@ -0,0 +1,78 @@ + [ + '17:00', + 'evening', + ], + '2019-01-05' => [ + '07:00', + 'morning', + ], + '2019-01-06' => [ + '12:00', + 'afternoon', + ], + '2019-01-07' => [ + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + 'morning', + 'afternoon', + 'evening', + ], + '2019-01-08' => [ + '07:00', + '08:00', + '13:00', + '19:00', + 'morning', + 'afternoon', + 'evening', + ], + 'anyDay' => [ + '07:00', + '08:00', + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + '19:00', + 'morning', + 'afternoon', + 'evening', + ], + ]; + $actualEnabledDays = $this->getEnabledDays(); + assert(count($expectedDaysResult) === count($actualEnabledDays)); + assertType("array{2019-01-04: array{'17:00', 'evening'}, 2019-01-05: array{'07:00', 'morning'}, 2019-01-06: array{'12:00', 'afternoon'}, 2019-01-07: array{'10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', 'morning', 'afternoon', 'evening'}, 2019-01-08: array{'07:00', '08:00', '13:00', '19:00', 'morning', 'afternoon', 'evening'}, anyDay: array{'07:00', '08:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '19:00', 'morning', 'afternoon', 'evening'}}", $expectedDaysResult); + } + + /** + * @return array> + */ + private function getEnabledDays(): array + { + return []; + } +} From 40d71799414df876f4591b14b051ebea93fdfdc7 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sat, 22 Mar 2025 10:32:17 +0100 Subject: [PATCH 1213/3097] Remove count() narrowing handling of empty array Is already covered by the adaption of https://github.com/phpstan/phpstan-src/pull/3895 --- src/Analyser/TypeSpecifier.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 33d3c34a7ed..485d8277cf9 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1067,7 +1067,6 @@ private function specifyTypesForCountFuncCall( if ( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) - || $type->isIterableAtLeastOnce()->no() // array{} cannot be used for further narrowing || !$oneOrMore->isSuperTypeOf($sizeType)->yes() ) { return null; From 69db4ae92b1e10d4cb84d4938129433b2718e245 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sat, 22 Mar 2025 21:07:16 +0100 Subject: [PATCH 1214/3097] Avoid falsely specifying never types via `count()` --- src/Analyser/TypeSpecifier.php | 1 + .../Analyser/nsrt/count-const-array-2.php | 35 +++++++++++++++++++ .../TypesAssignedToPropertiesRuleTest.php | 5 --- .../Rules/Properties/data/bug-1311.php | 24 ------------- 4 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/count-const-array-2.php delete mode 100755 tests/PHPStan/Rules/Properties/data/bug-1311.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 485d8277cf9..52b3d76d457 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1068,6 +1068,7 @@ private function specifyTypesForCountFuncCall( !$isNormalCount->yes() || (!$isConstantArray->yes() && !$isList->yes()) || !$oneOrMore->isSuperTypeOf($sizeType)->yes() + || $sizeType->isSuperTypeOf($type->getArraySize())->yes() ) { return null; } diff --git a/tests/PHPStan/Analyser/nsrt/count-const-array-2.php b/tests/PHPStan/Analyser/nsrt/count-const-array-2.php new file mode 100644 index 00000000000..f83d7d8b5f3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/count-const-array-2.php @@ -0,0 +1,35 @@ + $limit + * @return list<\stdClass> + */ + public function searchRecommendedMinPrices(int $limit): array + { + $bestMinPrice = new \stdClass(); + $limit--; + if ($limit === 0) { + return [$bestMinPrice]; + } + + $otherMinPrices = [new \stdClass()]; + while (count($otherMinPrices) < $limit) { + $otherMinPrice = new \stdClass(); + if (rand(0, 1)) { + $otherMinPrice = null; + } + if ($otherMinPrice === null) { + break; + } + array_unshift($otherMinPrices, $otherMinPrice); + } + assertType('non-empty-list', $otherMinPrices); + return [$bestMinPrice, ...$otherMinPrices]; + } +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 7c80c91cd60..2ba73fdc96d 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -139,11 +139,6 @@ public function testBug1216(): void ]); } - public function testBug1311(): void - { - $this->analyse([__DIR__ . '/data/bug-1311.php'], []); - } - public function testTypesAssignedToPropertiesExpressionNames(): void { $this->analyse([__DIR__ . '/data/properties-from-array-into-object.php'], [ diff --git a/tests/PHPStan/Rules/Properties/data/bug-1311.php b/tests/PHPStan/Rules/Properties/data/bug-1311.php deleted file mode 100755 index 995f2d8216d..00000000000 --- a/tests/PHPStan/Rules/Properties/data/bug-1311.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - private $list = []; - - public function convertList(): void - { - $temp = [1, 2, 3]; - - for ($i = 0; $i < count($temp); $i++) { - $temp[$i] = (string) $temp[$i]; - } - - $this->list = $temp; - } -} - -(new HelloWorld())->convertList(); From 443751763f95ca7d658add7d649937aec026e329 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 23 Mar 2025 14:10:37 +0100 Subject: [PATCH 1215/3097] JIT in GitHub Actions when running tests --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d7c4673b404..348be4eb68d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=2G + ini-values: memory_limit=2G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -87,7 +87,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=1G + ini-values: memory_limit=1G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -147,7 +147,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=1G + ini-values: memory_limit=1G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M - name: "Install dependencies" run: "composer install --no-interaction --no-progress" From cb5443abfccbdd5740c0054beab5503525e844ca Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 23 Mar 2025 14:11:59 +0100 Subject: [PATCH 1216/3097] JIT in GitHub Actions when running PHPStan --- .github/workflows/static-analysis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 602152e12ff..5e8232b803f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -47,6 +47,7 @@ jobs: php-version: "${{ matrix.php-version }}" ini-file: development extensions: mbstring + ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -84,6 +85,7 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-file: development + ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M extensions: mbstring - name: "Install dependencies" @@ -121,6 +123,7 @@ jobs: with: coverage: "none" php-version: "8.1" + ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M ini-file: development - name: "Install dependencies" @@ -147,6 +150,7 @@ jobs: with: coverage: "none" php-version: "8.1" + ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M ini-file: development - name: "Install dependencies" From 4dcd38d46e66dcbcbde8e5df1d1f8b0985d08498 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 23 Mar 2025 14:20:49 +0100 Subject: [PATCH 1217/3097] Revert "JIT in GitHub Actions when running PHPStan" This reverts commit cb5443abfccbdd5740c0054beab5503525e844ca. --- .github/workflows/static-analysis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5e8232b803f..602152e12ff 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -47,7 +47,6 @@ jobs: php-version: "${{ matrix.php-version }}" ini-file: development extensions: mbstring - ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -85,7 +84,6 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" ini-file: development - ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M extensions: mbstring - name: "Install dependencies" @@ -123,7 +121,6 @@ jobs: with: coverage: "none" php-version: "8.1" - ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M ini-file: development - name: "Install dependencies" @@ -150,7 +147,6 @@ jobs: with: coverage: "none" php-version: "8.1" - ini-values: opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M ini-file: development - name: "Install dependencies" From 9ab24a9961523b7d1a768734b9018e1f573da28d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 23 Mar 2025 14:20:50 +0100 Subject: [PATCH 1218/3097] Revert "JIT in GitHub Actions when running tests" This reverts commit 443751763f95ca7d658add7d649937aec026e329. --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 348be4eb68d..d7c4673b404 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=2G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M + ini-values: memory_limit=2G - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -87,7 +87,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=1G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M + ini-values: memory_limit=1G - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -147,7 +147,7 @@ jobs: tools: pecl extensions: ds,mbstring ini-file: development - ini-values: memory_limit=1G, opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M + ini-values: memory_limit=1G - name: "Install dependencies" run: "composer install --no-interaction --no-progress" From ce0aaf2bffcb61273675aea8c8c5251fe02bd58d Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sun, 23 Mar 2025 14:31:48 +0100 Subject: [PATCH 1219/3097] Fix condition of fall-through case not used for exhaustive checks --- src/Analyser/NodeScopeResolver.php | 8 ++- tests/PHPStan/Analyser/nsrt/bug-11064.php | 30 +++++++++++ .../Rules/Missing/MissingReturnRuleTest.php | 21 ++++++++ .../PHPStan/Rules/Missing/data/bug-12722.php | 32 ++++++++++++ .../PHPStan/Rules/Missing/data/bug-3488-2.php | 52 +++++++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 9 ++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 4 +- .../PHPStan/Rules/Variables/data/bug-8719.php | 50 ++++++++++++++++++ 8 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11064.php create mode 100644 tests/PHPStan/Rules/Missing/data/bug-12722.php create mode 100644 tests/PHPStan/Rules/Missing/data/bug-3488-2.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-8719.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8599b4bd6e7..7beb64aed15 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1449,9 +1449,11 @@ private function processStmtNode( $exitPointsForOuterLoop = []; $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); + $fullCondExpr = null; foreach ($stmt->cases as $caseNode) { if ($caseNode->cond !== null) { $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); + $fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr); $caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); $scopeForBranches = $caseResult->getScope(); $hasYield = $hasYield || $caseResult->hasYield(); @@ -1460,6 +1462,7 @@ private function processStmtNode( $branchScope = $caseResult->getTruthyScope()->filterByTruthyValue($condExpr); } else { $hasDefaultCase = true; + $fullCondExpr = null; $branchScope = $scopeForBranches; } @@ -1481,8 +1484,9 @@ private function processStmtNode( if ($branchScopeResult->isAlwaysTerminating()) { $alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating(); $prevScope = null; - if (isset($condExpr)) { - $scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr); + if (isset($fullCondExpr)) { + $scopeForBranches = $scopeForBranches->filterByFalseyValue($fullCondExpr); + $fullCondExpr = null; } if (!$branchFinalScopeResult->isAlwaysTerminating()) { $finalScope = $branchScope->mergeWith($finalScope); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11064.php b/tests/PHPStan/Analyser/nsrt/bug-11064.php new file mode 100644 index 00000000000..8f0876667a0 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11064.php @@ -0,0 +1,30 @@ +analyse([__DIR__ . '/data/bug-9374.php'], []); } + public function testBug3488Two(): void + { + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-3488-2.php'], [ + [ + 'Method Bug3488\C::invalidCase() should return int but return statement is missing.', + 30, + ], + ]); + } + + public function testBug12722(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->checkExplicitMixedMissingReturn = true; + $this->analyse([__DIR__ . '/data/bug-12722.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Missing/data/bug-12722.php b/tests/PHPStan/Rules/Missing/data/bug-12722.php new file mode 100644 index 00000000000..6c42edbd198 --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-12722.php @@ -0,0 +1,32 @@ += 8.1 + +namespace Bug12722; + +enum states { + case state1; + case statealmost1; + case state3; +} + +class HelloWorld +{ + public function intentional_fallthrough(states $state): int + { + switch($state) { + + case states::state1: //intentional fall-trough this case... + case states::statealmost1: return 1; + case states::state3: return 3; + } + } + + public function no_fallthrough(states $state): int + { + switch($state) { + + case states::state1: return 1; + case states::statealmost1: return 1; + case states::state3: return 3; + } + } +} diff --git a/tests/PHPStan/Rules/Missing/data/bug-3488-2.php b/tests/PHPStan/Rules/Missing/data/bug-3488-2.php new file mode 100644 index 00000000000..87d3466236e --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/bug-3488-2.php @@ -0,0 +1,52 @@ +analyse([__DIR__ . '/data/bug-10228.php'], []); } + public function testBug8719(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-8719.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 16d92ed7677..f96185de697 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -277,10 +277,12 @@ public function testVariableCertaintyInIsset(): void 112, ], [ - 'Variable $variableInFirstCase in isset() always exists and is not nullable.', + // could be Variable $variableInFirstCase in isset() always exists and is not nullable. + 'Variable $variableInFirstCase in isset() is never defined.', 116, ], [ + // could be Variable $variableInSecondCase in isset() always exists and is not nullable. 'Variable $variableInSecondCase in isset() is never defined.', 117, ], diff --git a/tests/PHPStan/Rules/Variables/data/bug-8719.php b/tests/PHPStan/Rules/Variables/data/bug-8719.php new file mode 100644 index 00000000000..d1d33371111 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-8719.php @@ -0,0 +1,50 @@ +getCase()) { + case self::CASE_1: + $foo = 'bar'; + break; + case self::CASE_2: + $foo = 'baz'; + break; + case self::CASE_3: + $foo = 'barbaz'; + break; + } + + return $foo; + } + + public function not_ok(): string + { + switch($this->getCase()) { + case self::CASE_1: + $foo = 'bar'; + break; + case self::CASE_2: + case self::CASE_3: + $foo = 'barbaz'; + break; + } + + return $foo; + } +} From 72d2f3b5b10faf9f388ad68b8271ece94bb53bc5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 23 Mar 2025 15:40:41 +0100 Subject: [PATCH 1220/3097] Fix calling getVariableType without checking hasVariableType first --- src/Analyser/MutatingScope.php | 20 ++++++++++++++----- .../Analyser/AnalyserIntegrationTest.php | 10 ++++++++++ tests/PHPStan/Analyser/data/bug-12767.php | 19 ++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12767.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index ff4f3b9657c..e3bc5fd2e83 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2010,11 +2010,21 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { - return TypeCombinator::union( - ...array_map(fn ($constantString) => $this - ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) - ->getVariableType($constantString->getValue()), $nameType->getConstantStrings()), - ); + $types = []; + foreach ($nameType->getConstantStrings() as $constantString) { + $variableScope = $this + ->filterByTruthyValue( + new BinaryOp\Identical($node->name, new String_($constantString->getValue())), + ); + if ($variableScope->hasVariableType($constantString->getValue())->no()) { + $types[] = new ErrorType(); + continue; + } + + $types[] = $variableScope->getVariableType($constantString->getValue()); + } + + return TypeCombinator::union(...$types); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 0c10a1c43ba..6456765aae1 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -896,6 +896,16 @@ public function testBug7500(): void $this->assertNoErrors($errors); } + public function testBug12767(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12767.php'); + $this->assertCount(3, $errors); + + $this->assertSame('Expected type int, actual: *ERROR*', $errors[0]->getMessage()); + $this->assertSame('Undefined variable: $field1', $errors[1]->getMessage()); + $this->assertSame('Undefined variable: $field2', $errors[2]->getMessage()); + } + public function testBug7554(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7554.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12767.php b/tests/PHPStan/Analyser/data/bug-12767.php new file mode 100644 index 00000000000..8ba79bff668 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12767.php @@ -0,0 +1,19 @@ + ['dd1' => 1, 'dd2' => 2]]; + + for ($i=1; $i <= 2; $i++) { + ${'field'.$i} = $employee->data['dd'.$i]; + + assertType('int', ${'field'.$i}); + } + } +} From 02eb0a833897bfa5c0ea5d4d2566ef71f3d13ae2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 24 Mar 2025 11:37:55 +0100 Subject: [PATCH 1221/3097] Fix narrowing of superglobals --- src/Analyser/MutatingScope.php | 12 ++++ tests/PHPStan/Analyser/nsrt/superglobals.php | 69 +++++++++++++++++++ ...rictComparisonOfDifferentTypesRuleTest.php | 5 ++ .../Rules/Comparison/data/bug-12772.php | 17 +++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 7 ++ .../Rules/Variables/data/bug-12771.php | 25 +++++++ 6 files changed, 135 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/superglobals.php create mode 100755 tests/PHPStan/Rules/Comparison/data/bug-12772.php create mode 100755 tests/PHPStan/Rules/Variables/data/bug-12771.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index e3bc5fd2e83..eba5f2e8536 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4833,6 +4833,8 @@ private function createConditionalExpressions( private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array { $intersectedVariableTypeHolders = []; + $globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name); + $nodeFinder = new NodeFinder(); foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) { if (isset($theirVariableTypeHolders[$exprString])) { if ($variableTypeHolder === $theirVariableTypeHolders[$exprString]) { @@ -4842,6 +4844,11 @@ private function mergeVariableHolders(array $ourVariableTypeHolders, array $thei $intersectedVariableTypeHolders[$exprString] = $variableTypeHolder->and($theirVariableTypeHolders[$exprString]); } else { + $expr = $variableTypeHolder->getExpr(); + if ($nodeFinder->findFirst($expr, $globalVariableCallback) !== null) { + continue; + } + $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); } } @@ -4851,6 +4858,11 @@ private function mergeVariableHolders(array $ourVariableTypeHolders, array $thei continue; } + $expr = $variableTypeHolder->getExpr(); + if ($nodeFinder->findFirst($expr, $globalVariableCallback) !== null) { + continue; + } + $intersectedVariableTypeHolders[$exprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType()); } diff --git a/tests/PHPStan/Analyser/nsrt/superglobals.php b/tests/PHPStan/Analyser/nsrt/superglobals.php new file mode 100644 index 00000000000..ee7aadb6860 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/superglobals.php @@ -0,0 +1,69 @@ +', $GLOBALS); + assertType('array', $_SERVER); + assertType('array', $_GET); + assertType('array', $_POST); + assertType('array', $_FILES); + assertType('array', $_COOKIE); + assertType('array', $_SESSION); + assertType('array', $_REQUEST); + assertType('array', $_ENV); + } + + public function canBeOverwritten(): void + { + $GLOBALS = []; + assertType('array{}', $GLOBALS); + assertNativeType('array{}', $GLOBALS); + } + + public function canBePartlyOverwritten(): void + { + $GLOBALS['foo'] = 'foo'; + assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); + assertNativeType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); + } + + public function canBeNarrowed(): void + { + if (isset($GLOBALS['foo'])) { + assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $GLOBALS); + assertNativeType("non-empty-array&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395 + } else { + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); + } + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); + } + +} + +function functionScope() { + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); +} + +assertType('array', $GLOBALS); +assertNativeType('array', $GLOBALS); + +function badNarrowing() { + if (empty($_GET['id'])) { + echo "b"; + } else { + echo "b"; + } + assertType('array', $_GET); + assertType('mixed', $_GET['id']); +}; diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index b94df8dae23..4797cc4dba0 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1001,4 +1001,9 @@ public function testHashing(): void ]); } + public function testBug12772(): void + { + $this->analyse([__DIR__ . '/data/bug-12772.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12772.php b/tests/PHPStan/Rules/Comparison/data/bug-12772.php new file mode 100755 index 00000000000..3a01127243e --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12772.php @@ -0,0 +1,17 @@ +analyse([__DIR__ . '/data/bug-9328.php'], []); } + public function testBug12771(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-12771.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-12771.php b/tests/PHPStan/Rules/Variables/data/bug-12771.php new file mode 100755 index 00000000000..30fb66f1a7b --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12771.php @@ -0,0 +1,25 @@ += 3 + && ($_SESSION['prev_error_subm_time'] - time()) <= 3000 + ) { + $_SESSION['error_subm_count'] = 0; + $_SESSION['prev_errors'] = ''; + } else { + $_SESSION['prev_error_subm_time'] = time(); + $_SESSION['error_subm_count'] = isset($_SESSION['error_subm_count']) + ? $_SESSION['error_subm_count'] + 1 + : 0; + } + + } +} From a3039ef58efe8b303e9618272e50dfe85e74ff53 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 24 Mar 2025 11:48:43 +0100 Subject: [PATCH 1222/3097] String value passed to Identifier cannot be empty --- build/phpstan.neon | 1 + build/stubs/Identifier.stub | 15 +++++++++++++++ src/Analyser/MutatingScope.php | 12 ++++++++---- src/Analyser/NodeScopeResolver.php | 2 +- src/Broker/AnonymousClassNameHelper.php | 3 +++ src/Node/ClassPropertyNode.php | 4 ++++ src/Node/ClassStatementsGatherer.php | 6 ++++++ src/Reflection/ClassReflection.php | 3 +++ src/Type/Php/ConstantHelper.php | 2 +- .../Php/PropertyExistsTypeSpecifyingExtension.php | 4 ++++ .../PHPStan/Analyser/AnalyserIntegrationTest.php | 6 ++++++ .../PHPStan/Analyser/ArgumentsNormalizerTest.php | 8 ++++---- tests/PHPStan/Analyser/data/bug-12778.php | 13 +++++++++++++ 13 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 build/stubs/Identifier.stub create mode 100644 tests/PHPStan/Analyser/data/bug-12778.php diff --git a/build/phpstan.neon b/build/phpstan.neon index 1b1bf800f9c..b285f56e127 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -97,6 +97,7 @@ parameters: - stubs/ReactStreams.stub - stubs/NetteDIContainer.stub - stubs/PhpParserName.stub + - stubs/Identifier.stub rules: - PHPStan\Build\FinalClassRule diff --git a/build/stubs/Identifier.stub b/build/stubs/Identifier.stub new file mode 100644 index 00000000000..301d034b2db --- /dev/null +++ b/build/stubs/Identifier.stub @@ -0,0 +1,15 @@ + $attributes + */ + public function __construct(string $name, array $attributes = []) { } + +} diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index eba5f2e8536..32b72333440 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2075,7 +2075,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { return TypeCombinator::union( - ...array_map(fn ($constantString) => $this + ...array_map(fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $this ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) ->getType(new MethodCall($node->var, new Identifier($constantString->getValue()), $node->args)), $nameType->getConstantStrings()), ); @@ -2155,7 +2155,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { return TypeCombinator::union( - ...array_map(fn ($constantString) => $this + ...array_map(fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $this ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) ->getType(new Expr\StaticCall($node->class, new Identifier($constantString->getValue()), $node->args)), $nameType->getConstantStrings()), ); @@ -2197,7 +2197,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { return TypeCombinator::union( - ...array_map(fn ($constantString) => $this + ...array_map(fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $this ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) ->getType( new PropertyFetch($node->var, new Identifier($constantString->getValue())), @@ -2271,7 +2271,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $nameType = $this->getType($node->name); if (count($nameType->getConstantStrings()) > 0) { return TypeCombinator::union( - ...array_map(fn ($constantString) => $this + ...array_map(fn ($constantString) => $constantString->getValue() === '' ? new ErrorType() : $this ->filterByTruthyValue(new BinaryOp\Identical($node->name, new String_($constantString->getValue()))) ->getType(new Expr\StaticPropertyFetch($node->class, new Node\VarLikeIdentifier($constantString->getValue()))), $nameType->getConstantStrings()), ); @@ -5695,6 +5695,10 @@ private function exactInstantiation(New_ $node, string $className): ?Type $constructorMethod = new DummyConstructorReflection($classReflection); } + if ($constructorMethod->getName() === '') { + throw new ShouldNotHappenException(); + } + $resolvedTypes = []; $methodCall = new Expr\StaticCall( new Name($resolvedClassName), diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 62c18fce6db..0135b2dab77 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -686,7 +686,7 @@ private function processStmtNode( continue; } - if (!$param->var instanceof Variable || !is_string($param->var->name)) { + if (!$param->var instanceof Variable || !is_string($param->var->name) || $param->var->name === '') { throw new ShouldNotHappenException(); } $phpDoc = null; diff --git a/src/Broker/AnonymousClassNameHelper.php b/src/Broker/AnonymousClassNameHelper.php index 06ed10355cf..0f9e82cb216 100644 --- a/src/Broker/AnonymousClassNameHelper.php +++ b/src/Broker/AnonymousClassNameHelper.php @@ -20,6 +20,9 @@ public function __construct( { } + /** + * @return non-empty-string + */ public function getAnonymousClassName( Node\Stmt\Class_ $classNode, string $filename, diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index 2dfc3cee7a9..a3eb0567ea0 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -15,6 +15,9 @@ final class ClassPropertyNode extends NodeAbstract implements VirtualNode { + /** + * @param non-empty-string $name + */ public function __construct( private string $name, private int $flags, @@ -35,6 +38,7 @@ public function __construct( parent::__construct($originalNode->getAttributes()); } + /** @return non-empty-string */ public function getName(): string { return $this->name; diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 7fb27f83519..a2bb6889d59 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -259,6 +259,9 @@ private function tryToApplyPropertyReads(Expr\FuncCall $node, Scope $scope): voi if ($property->isStatic()) { continue; } + if ($property->getName() === '') { + throw new ShouldNotHappenException(); + } $this->propertyUsages[] = new PropertyRead( new PropertyFetch(new Expr\Variable('this'), new Identifier($property->getName())), $scope, @@ -282,6 +285,9 @@ private function tryToApplyPropertyWritesFromAncestorConstructor(StaticCall $anc if (!$property->isPromoted() || $property->getDeclaringClass()->getName() !== $classReflection->getName()) { continue; } + if ($property->getName() === '') { + throw new ShouldNotHappenException(); + } $this->propertyUsages[] = new PropertyWrite( new PropertyFetch(new Expr\Variable('this'), new Identifier($property->getName()), $ancestorConstructorCall->getAttributes()), $scope, diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 4c4e4fd5d8f..05a9f9b6a6e 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1332,6 +1332,9 @@ private function findAttributeFlags(): ?int $attributeClass = $this->reflectionProvider->getClass(Attribute::class); $arguments = []; foreach ($nativeAttributes[0]->getArgumentsExpressions() as $i => $expression) { + if ($i === '') { + throw new ShouldNotHappenException(); + } $arguments[] = new Arg($expression, false, false, [], is_int($i) ? null : new Identifier($i)); } diff --git a/src/Type/Php/ConstantHelper.php b/src/Type/Php/ConstantHelper.php index edcbbf7b7fd..a3e8c5224f9 100644 --- a/src/Type/Php/ConstantHelper.php +++ b/src/Type/Php/ConstantHelper.php @@ -24,7 +24,7 @@ public function createExprFromConstantName(string $constantName): ?Expr $classConstParts = explode('::', $constantName); if (count($classConstParts) >= 2) { $fqcn = ltrim($classConstParts[0], '\\'); - if ($fqcn === '') { + if ($fqcn === '' || $classConstParts[1] === '') { return null; } diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 38592e632e7..407233ae0b4 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -56,6 +56,10 @@ public function specifyTypes( return new SpecifiedTypes([], []); } + if ($propertyNameType->getValue() === '') { + return new SpecifiedTypes([], []); + } + $objectType = $scope->getType($node->getArgs()[0]->value); if ($objectType instanceof ConstantStringType) { return new SpecifiedTypes([], []); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 6456765aae1..e7678b1449e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -600,6 +600,12 @@ public function testBug6649(): void $this->assertNoErrors($errors); } + public function testBug12778(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12778.php'); + $this->assertNoErrors($errors); + } + public function testBug6842(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-6842.php'); diff --git a/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php b/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php index 3d28594e7e7..6ee268c32ab 100644 --- a/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php +++ b/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php @@ -246,8 +246,8 @@ public function dataReorderValid(): iterable /** * @dataProvider dataReorderValid - * @param array $parameterSettings - * @param array $argumentSettings + * @param array $parameterSettings + * @param array $argumentSettings * @param array $expectedArgumentTypes */ public function testReorderValid( @@ -326,8 +326,8 @@ public function dataReorderInvalid(): iterable /** * @dataProvider dataReorderInvalid - * @param array $parameterSettings - * @param array $argumentSettings + * @param array $parameterSettings + * @param array $argumentSettings */ public function testReorderInvalid( array $parameterSettings, diff --git a/tests/PHPStan/Analyser/data/bug-12778.php b/tests/PHPStan/Analyser/data/bug-12778.php new file mode 100644 index 00000000000..23e4039715e --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12778.php @@ -0,0 +1,13 @@ +{''}; + } +} From a3ce38e6f1489aab4ddc475802d70a9d5af231d6 Mon Sep 17 00:00:00 2001 From: sayuprc Date: Tue, 18 Mar 2025 21:20:26 +0900 Subject: [PATCH 1223/3097] Fix `SessionHandlerInterface::read` return type --- resources/functionMap.php | 2 +- ...rictComparisonOfDifferentTypesRuleTest.php | 5 ++ .../Rules/Comparison/data/bug-12748.php | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-12748.php diff --git a/resources/functionMap.php b/resources/functionMap.php index 9d23945857a..40b1848c3aa 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10421,7 +10421,7 @@ 'SessionHandlerInterface::destroy' => ['bool', 'session_id'=>'string'], 'SessionHandlerInterface::gc' => ['int|false', 'maxlifetime'=>'int'], 'SessionHandlerInterface::open' => ['bool', 'save_path'=>'string', 'name'=>'string'], -'SessionHandlerInterface::read' => ['string', 'session_id'=>'string'], +'SessionHandlerInterface::read' => ['string|false', 'session_id'=>'string'], 'SessionHandlerInterface::write' => ['bool', 'session_id'=>'string', 'session_data'=>'string'], 'SessionIdInterface::create_sid' => ['string'], 'SessionUpdateTimestampHandler::updateTimestamp' => ['bool', 'id'=>'string', 'data'=>'string'], diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4797cc4dba0..4c72f04c610 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1006,4 +1006,9 @@ public function testBug12772(): void $this->analyse([__DIR__ . '/data/bug-12772.php'], []); } + public function testBug12748(): void + { + $this->analyse([__DIR__ . '/data/bug-12748.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12748.php b/tests/PHPStan/Rules/Comparison/data/bug-12748.php new file mode 100644 index 00000000000..bcf15355af7 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12748.php @@ -0,0 +1,54 @@ += 8.0 + +namespace Bug12748; + +use SessionHandlerInterface; + +class HelloWorld +{ + public function getHandler(): SessionHandlerInterface + { + return new SessHandler; + } +} + +class SessHandler implements SessionHandlerInterface +{ + + public function close(): bool + { + return true; + } + + public function destroy(string $id): bool + { + return true; + } + + public function gc(int $max_lifetime): int|false + { + return false; + } + + public function open(string $path, string $name): bool + { + return true; + } + + public function read(string $id): string|false + { + return false; + } + + public function write(string $id, string $data): bool + { + return true; + } +} + +$sessionHandler = (new HelloWorld)->getHandler(); +$session = $sessionHandler->read('123'); + +if ($session === false) { + return null; +} From f2f2ddf44425cc58b5b1537ddce7cd06a9bba074 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 24 Mar 2025 14:20:56 +0100 Subject: [PATCH 1224/3097] RegexArrayShapeMatcher - more precise subject types --- src/Type/Php/RegexArrayShapeMatcher.php | 35 +- src/Type/Regex/RegexAstWalkResult.php | 25 ++ src/Type/Regex/RegexGroupParser.php | 28 +- src/Type/Regex/RegexGroupWalkResult.php | 14 + tests/PHPStan/Analyser/nsrt/bug-11293.php | 12 +- tests/PHPStan/Analyser/nsrt/bug-11311.php | 52 +-- tests/PHPStan/Analyser/nsrt/bug-11580.php | 6 +- tests/PHPStan/Analyser/nsrt/bug-12210.php | 8 +- tests/PHPStan/Analyser/nsrt/bug-12211.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-12242.php | 2 +- tests/PHPStan/Analyser/nsrt/bug11384.php | 2 +- .../Analyser/nsrt/preg_match_shapes.php | 386 ++++++++++-------- .../Analyser/nsrt/preg_match_shapes_php80.php | 8 +- .../Analyser/nsrt/preg_match_shapes_php82.php | 20 +- .../preg_replace_callback_shapes-php72.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- 16 files changed, 364 insertions(+), 240 deletions(-) diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index da6a44e7355..64c2f0c496b 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -107,12 +107,17 @@ private function matchPatternType(Type $patternType, ?Type $flagsType, TrinaryLo */ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched, bool $matchesAll): ?Type { - $parseResult = $this->regexGroupParser->parseGroups($regex); - if ($parseResult === null) { + $astWalkResult = $this->regexGroupParser->parseGroups($regex); + if ($astWalkResult === null) { // regex could not be parsed by Hoa/Regex return null; } - [$groupList, $markVerbs] = $parseResult; + $groupList = $astWalkResult->getCapturingGroups(); + $markVerbs = $astWalkResult->getMarkVerbs(); + $subjectBaseType = new StringType(); + if ($wasMatched->yes()) { + $subjectBaseType = $astWalkResult->getSubjectBaseType(); + } $regexGroupList = new RegexGroupList($groupList); $trailingOptionals = $regexGroupList->countTrailingOptionals(); @@ -130,6 +135,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched $regexGroupList = $regexGroupList->forceGroupNonOptional($onlyOptionalTopLevelGroup); $combiType = $this->buildArrayType( + $subjectBaseType, $regexGroupList, $wasMatched, $trailingOptionals, @@ -141,7 +147,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched if (!$this->containsUnmatchedAsNull($flags, $matchesAll)) { // positive match has a subject but not any capturing group $combiType = TypeCombinator::union( - new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], TrinaryLogic::createYes()), + new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)], [1], [], TrinaryLogic::createYes()), $combiType, ); } @@ -180,6 +186,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched } $combiType = $this->buildArrayType( + $subjectBaseType, $comboList, $wasMatched, $trailingOptionals, @@ -199,7 +206,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ) ) { // positive match has a subject but not any capturing group - $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($flags, $matchesAll)], [1], [], TrinaryLogic::createYes()); + $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)], [1], [], TrinaryLogic::createYes()); } return TypeCombinator::union(...$combiTypes); @@ -208,6 +215,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched // the general case, which should work in all cases but does not yield the most // precise result possible in some cases return $this->buildArrayType( + $subjectBaseType, $regexGroupList, $wasMatched, $trailingOptionals, @@ -221,6 +229,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched * @param list $markVerbs */ private function buildArrayType( + Type $subjectBaseType, RegexGroupList $captureGroups, TrinaryLogic $wasMatched, int $trailingOptionals, @@ -234,7 +243,7 @@ private function buildArrayType( // first item in matches contains the overall match. $builder->setOffsetValueType( $this->getKeyType(0), - $this->createSubjectValueType($flags, $matchesAll), + $this->createSubjectValueType($subjectBaseType, $flags, $matchesAll), $this->isSubjectOptional($wasMatched, $matchesAll), ); @@ -298,13 +307,21 @@ private function isSubjectOptional(TrinaryLogic $wasMatched, bool $matchesAll): return !$wasMatched->yes(); } - private function createSubjectValueType(int $flags, bool $matchesAll): Type + /** + * @param Type $baseType A string type (or string variant) representing the subject of the match + */ + private function createSubjectValueType(Type $baseType, int $flags, bool $matchesAll): Type { - $subjectValueType = TypeCombinator::removeNull($this->getValueType(new StringType(), $flags, $matchesAll)); + $subjectValueType = TypeCombinator::removeNull($this->getValueType($baseType, $flags, $matchesAll)); if ($matchesAll) { + $subjectValueType = TypeCombinator::removeNull($this->getValueType(new StringType(), $flags, $matchesAll)); + if ($this->containsPatternOrder($flags)) { - $subjectValueType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $subjectValueType), new AccessoryArrayListType()); + $subjectValueType = TypeCombinator::intersect( + new ArrayType(new IntegerType(), $subjectValueType), + new AccessoryArrayListType(), + ); } } diff --git a/src/Type/Regex/RegexAstWalkResult.php b/src/Type/Regex/RegexAstWalkResult.php index 32e017a254f..ff234b6092c 100644 --- a/src/Type/Regex/RegexAstWalkResult.php +++ b/src/Type/Regex/RegexAstWalkResult.php @@ -2,6 +2,9 @@ namespace PHPStan\Type\Regex; +use PHPStan\Type\StringType; +use PHPStan\Type\Type; + /** @immutable */ final class RegexAstWalkResult { @@ -15,6 +18,7 @@ public function __construct( private int $captureGroupId, private array $capturingGroups, private array $markVerbs, + private Type $subjectBaseType, ) { } @@ -27,6 +31,7 @@ public static function createEmpty(): self 100, [], [], + new StringType(), ); } @@ -37,6 +42,7 @@ public function nextAlternationId(): self $this->captureGroupId, $this->capturingGroups, $this->markVerbs, + $this->subjectBaseType, ); } @@ -47,6 +53,7 @@ public function nextCaptureGroupId(): self $this->captureGroupId + 1, $this->capturingGroups, $this->markVerbs, + $this->subjectBaseType, ); } @@ -60,6 +67,7 @@ public function addCapturingGroup(RegexCapturingGroup $group): self $this->captureGroupId, $capturingGroups, $this->markVerbs, + $this->subjectBaseType, ); } @@ -73,6 +81,18 @@ public function markVerb(string $markVerb): self $this->captureGroupId, $this->capturingGroups, $verbs, + $this->subjectBaseType, + ); + } + + public function withSubjectBaseType(Type $subjectBaseType): self + { + return new self( + $this->alternationId, + $this->captureGroupId, + $this->capturingGroups, + $this->markVerbs, + $subjectBaseType, ); } @@ -102,4 +122,9 @@ public function getMarkVerbs(): array return $this->markVerbs; } + public function getSubjectBaseType(): Type + { + return $this->subjectBaseType; + } + } diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 69eb455eaf3..0383ea4c5ae 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -49,10 +49,7 @@ public function __construct( { } - /** - * @return array{array, list}|null - */ - public function parseGroups(string $regex): ?array + public function parseGroups(string $regex): ?RegexAstWalkResult { if (self::$parser === null) { /** @throws void */ @@ -105,7 +102,28 @@ public function parseGroups(string $regex): ?array RegexAstWalkResult::createEmpty(), ); - return [$astWalkResult->getCapturingGroups(), $astWalkResult->getMarkVerbs()]; + $subjectAsGroupResult = $this->walkGroupAst( + $ast, + false, + false, + $modifiers, + RegexGroupWalkResult::createEmpty(), + ); + + if (!$subjectAsGroupResult->mightContainEmptyStringLiteral()) { + // we could handle numeric-string, in case we know the regex is delimited by ^ and $ + if ($subjectAsGroupResult->isNonFalsy()->yes()) { + $astWalkResult = $astWalkResult->withSubjectBaseType( + TypeCombinator::intersect(new StringType(), new AccessoryNonFalsyStringType()), + ); + } elseif ($subjectAsGroupResult->isNonEmpty()->yes()) { + $astWalkResult = $astWalkResult->withSubjectBaseType( + TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()), + ); + } + } + + return $astWalkResult; } private function createEmptyTokenTreeNode(TreeNode $parentAst): TreeNode diff --git a/src/Type/Regex/RegexGroupWalkResult.php b/src/Type/Regex/RegexGroupWalkResult.php index 65e7fd16916..9169af89ba5 100644 --- a/src/Type/Regex/RegexGroupWalkResult.php +++ b/src/Type/Regex/RegexGroupWalkResult.php @@ -103,6 +103,20 @@ public function getOnlyLiterals(): ?array return $this->onlyLiterals; } + public function mightContainEmptyStringLiteral(): bool + { + if ($this->onlyLiterals === null) { + return false; + } + foreach ($this->onlyLiterals as $onlyLiteral) { + if ($onlyLiteral === '') { + return true; + } + } + + return false; + } + public function isNonEmpty(): TrinaryLogic { return $this->isNonEmpty; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11293.php b/tests/PHPStan/Analyser/nsrt/bug-11293.php index 0c190b23fc4..19a9a1eb5c2 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11293.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -9,21 +9,21 @@ class HelloWorld public function sayHello(string $s): void { if (preg_match('/data-(\d{6})\.json$/', $s, $matches) > 0) { - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } } public function sayHello2(string $s): void { if (preg_match('/data-(\d{6})\.json$/', $s, $matches) === 1) { - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } } public function sayHello3(string $s): void { if (preg_match('/data-(\d{6})\.json$/', $s, $matches) >= 1) { - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } } @@ -35,7 +35,7 @@ public function sayHello4(string $s): void return; } - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } public function sayHello5(string $s): void @@ -46,7 +46,7 @@ public function sayHello5(string $s): void return; } - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } public function sayHello6(string $s): void @@ -57,6 +57,6 @@ public function sayHello6(string $s): void return; } - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index a30f261fae2..ff99e4699ce 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -8,7 +8,7 @@ function doFoo(string $s) { if (1 === preg_match('/(?\d+)\.(?\d+)(?:\.(?\d+))?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, major: numeric-string, 1: numeric-string, minor: numeric-string, 2: numeric-string, patch: numeric-string|null, 3: numeric-string|null}', $matches); + assertType('array{0: non-falsy-string, major: numeric-string, 1: numeric-string, minor: numeric-string, 2: numeric-string, patch: numeric-string|null, 3: numeric-string|null}', $matches); } } @@ -23,11 +23,11 @@ function doUnmatchedAsNull(string $s): void { function unmatchedAsNullWithOptionalGroup(string $s): void { if (preg_match('/Price: (£|€)?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { // with PREG_UNMATCHED_AS_NULL the offset 1 will always exist. It is correct that it's nullable because it's optional though - assertType("array{string, '£'|'€'|null}", $matches); + assertType("array{non-falsy-string, '£'|'€'|null}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{string, '£'|'€'|null}", $matches); + assertType("array{}|array{non-falsy-string, '£'|'€'|null}", $matches); } function bug11331a(string $url):void { @@ -37,7 +37,7 @@ function bug11331a(string $url):void { (?.+) )? (?.+)}mix', $url, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, a: non-empty-string|null, 1: non-empty-string|null, b: non-empty-string, 2: non-empty-string}', $matches); + assertType('array{0: non-empty-string, a: non-empty-string|null, 1: non-empty-string|null, b: non-empty-string, 2: non-empty-string}', $matches); } } @@ -63,20 +63,20 @@ function bug11331c(string $url):void { ([^/]+?) (?:\.git|/)? $}x', $url, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, non-empty-string|null, non-empty-string|null, non-empty-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string|null, non-empty-string|null, non-empty-string, non-empty-string}', $matches); } } class UnmatchedAsNullWithTopLevelAlternation { function doFoo(string $s): void { if (preg_match('/Price: (?:(£)|(€))\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{string, '£'|null, '€'|null}", $matches); // could be tagged union + assertType("array{non-falsy-string, '£'|null, '€'|null}", $matches); // could be tagged union } } function doBar(string $s): void { if (preg_match('/Price: (?:(£)|(€))?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{string, '£'|null, '€'|null}", $matches); // could be tagged union + assertType("array{non-falsy-string, '£'|null, '€'|null}", $matches); // could be tagged union } } } @@ -85,101 +85,101 @@ function (string $size): void { if (preg_match('/ab(\d){2,4}xx([0-9])?e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, numeric-string, numeric-string|null}', $matches); + assertType('array{non-falsy-string, numeric-string, numeric-string|null}', $matches); }; function (string $size): void { if (preg_match('/a(\dAB){2}b(\d){2,4}([1-5])([1-5a-z])e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string, numeric-string, numeric-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string, numeric-string, non-empty-string}', $matches); }; function (string $size): void { if (preg_match('/ab(ab(\d)){2,4}xx([0-9][a-c])?e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string, numeric-string, non-falsy-string|null}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string, non-falsy-string|null}', $matches); }; function (string $size): void { if (preg_match('/ab(\d+)e(\d?)/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType("array{string, numeric-string, ''|numeric-string}", $matches); + assertType("array{non-falsy-string, numeric-string, ''|numeric-string}", $matches); }; function (string $size): void { if (preg_match('/ab(?P\d+)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); }; function (string $size): void { if (preg_match('/ab(\d\d)/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); }; function (string $size): void { if (preg_match('/ab(\d+\s)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string}', $matches); }; function (string $size): void { if (preg_match('/ab(\s)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); }; function (string $size): void { if (preg_match('/ab(\S)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); }; function (string $size): void { if (preg_match('/ab(\S?)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); }; function (string $size): void { if (preg_match('/ab(\S)?e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-empty-string|null}', $matches); + assertType('array{non-falsy-string, non-empty-string|null}', $matches); }; function (string $size): void { if (preg_match('/ab(\d+\d?)e?/', $size, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, numeric-string}', $matches); + assertType('array{non-falsy-string, numeric-string}', $matches); }; function (string $s): void { if (preg_match('/Price: ([2-5])/i', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-falsy-string, numeric-string}', $matches); } }; function (string $s): void { if (preg_match('/Price: ([2-5A-Z])/i', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); } }; function (string $s): void { if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $s, $matches, PREG_UNMATCHED_AS_NULL) === 1) { - assertType("array{string, non-falsy-string|null, 'b'|'d'|'E'|'e'|'F'|'f'|'G'|'g'|'H'|'h'|'o'|'s'|'u'|'X'|'x'}", $matches); + assertType("array{non-falsy-string, non-falsy-string|null, 'b'|'d'|'E'|'e'|'F'|'f'|'G'|'g'|'H'|'h'|'o'|'s'|'u'|'X'|'x'}", $matches); } }; @@ -201,22 +201,22 @@ function (string $s): void { function (string $s): void { if (preg_match('~a|(\d)|(\s)~', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{string, numeric-string|null, non-empty-string|null}", $matches); + assertType("array{non-empty-string, numeric-string|null, non-empty-string|null}", $matches); } else { assertType("array{}", $matches); } - assertType("array{}|array{string, numeric-string|null, non-empty-string|null}", $matches); + assertType("array{}|array{non-empty-string, numeric-string|null, non-empty-string|null}", $matches); }; function (string $s): void { if (preg_match('~a|(\d)|(\s)~', $s, $matches, PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE) === 1) { - assertType("array{array{string|null, int<-1, max>}, array{numeric-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}}", $matches); + assertType("array{array{non-empty-string|null, int<-1, max>}, array{numeric-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}}", $matches); } }; function (string $s): void { if (preg_match('~a|((u)x)|((v)y)~', $s, $matches, PREG_UNMATCHED_AS_NULL) === 1) { - assertType("array{string, 'ux'|null, 'u'|null, 'vy'|null, 'v'|null}", $matches); + assertType("array{non-empty-string, 'ux'|null, 'u'|null, 'vy'|null, 'v'|null}", $matches); } }; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11580.php b/tests/PHPStan/Analyser/nsrt/bug-11580.php index ebb4220372c..2081bb06240 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11580.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11580.php @@ -10,7 +10,7 @@ public function bad(string $in): void { $matches = []; if (preg_match('~^/xxx/([\w\-]+)/?([\w\-]+)?/?$~', $in, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2?: non-empty-string}', $matches); + assertType('array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches); } } @@ -19,7 +19,7 @@ public function bad2(string $in): void $matches = []; $result = preg_match('~^/xxx/([\w\-]+)/?([\w\-]+)?/?$~', $in, $matches); if ($result) { - assertType('array{0: string, 1: non-empty-string, 2?: non-empty-string}', $matches); + assertType('array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches); } } @@ -28,7 +28,7 @@ public function bad3(string $in): void $result = preg_match('~^/xxx/([\w\-]+)/?([\w\-]+)?/?$~', $in, $matches); assertType('array{0?: string, 1?: non-empty-string, 2?: non-empty-string}', $matches); if ($result) { - assertType('array{0: string, 1: non-empty-string, 2?: non-empty-string}', $matches); + assertType('array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12210.php b/tests/PHPStan/Analyser/nsrt/bug-12210.php index 165b61b63e6..13cf62ed269 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12210.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12210.php @@ -8,20 +8,20 @@ function bug12210a(string $text): void { assert(preg_match('(((sum|min|max)))', $text, $match) === 1); - assertType("array{string, 'max'|'min'|'sum', 'max'|'min'|'sum'}", $match); + assertType("array{non-empty-string, 'max'|'min'|'sum', 'max'|'min'|'sum'}", $match); } function bug12210b(string $text): void { assert(preg_match('(((sum|min|ma.)))', $text, $match) === 1); - assertType("array{string, non-empty-string, non-falsy-string}", $match); + assertType("array{non-empty-string, non-empty-string, non-falsy-string}", $match); } function bug12210c(string $text): void { assert(preg_match('(((su.|min|max)))', $text, $match) === 1); - assertType("array{string, non-empty-string, non-falsy-string}", $match); + assertType("array{non-empty-string, non-empty-string, non-falsy-string}", $match); } function bug12210d(string $text): void { assert(preg_match('(((sum|mi.|max)))', $text, $match) === 1); - assertType("array{string, non-empty-string, non-falsy-string}", $match); + assertType("array{non-empty-string, non-empty-string, non-falsy-string}", $match); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12211.php b/tests/PHPStan/Analyser/nsrt/bug-12211.php index 72a268c5060..33131edfe87 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12211.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12211.php @@ -10,7 +10,7 @@ function foo(string $text): void { assert(preg_match(REGEX, $text, $match) === 1); - assertType('array{string, non-falsy-string}', $match); + assertType('array{non-falsy-string, non-falsy-string}', $match); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12242.php b/tests/PHPStan/Analyser/nsrt/bug-12242.php index 4d065367a2b..d9335610d38 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12242.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12242.php @@ -27,7 +27,7 @@ function bar(string $str): void (\w*) # extra description (UNSIGNED, CHARACTER SET, ...) [3] $/x'; if (preg_match($regexp, $str, $matches)) { - assertType('array{string, non-empty-string, string, string}', $matches); + assertType('array{non-falsy-string, non-empty-string, string, string}', $matches); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug11384.php b/tests/PHPStan/Analyser/nsrt/bug11384.php index 12020de0b9b..709f2986351 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11384.php +++ b/tests/PHPStan/Analyser/nsrt/bug11384.php @@ -14,7 +14,7 @@ class HelloWorld public function sayHello(string $s): void { if (preg_match('{(' . Bar::VAL . ')}', $s, $m)) { - assertType("array{string, '3'}", $m); + assertType("array{non-empty-string, '3'}", $m); } } } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 8861e9f0362..88bbe9fad6c 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -7,117 +7,117 @@ function doMatch(string $s): void { if (preg_match('/Price: /i', $s, $matches)) { - assertType('array{string}', $matches); + assertType('array{non-falsy-string}', $matches); } - assertType('array{}|array{string}', $matches); + assertType('array{}|array{non-falsy-string}', $matches); if (preg_match('/Price: (£|€)\d+/', $s, $matches)) { - assertType("array{string, '£'|'€'}", $matches); + assertType("array{non-falsy-string, '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{string, '£'|'€'}", $matches); + assertType("array{}|array{non-falsy-string, '£'|'€'}", $matches); if (preg_match('/Price: (£|€)(\d+)/i', $s, $matches)) { - assertType('array{string, non-empty-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-empty-string, numeric-string}', $matches); } - assertType('array{}|array{string, non-empty-string, numeric-string}', $matches); + assertType('array{}|array{non-falsy-string, non-empty-string, numeric-string}', $matches); if (preg_match(' /Price: (£|€)\d+/ i u', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); } - assertType('array{}|array{string, non-empty-string}', $matches); + assertType('array{}|array{non-falsy-string, non-empty-string}', $matches); if (preg_match('(Price: (£|€))i', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); } - assertType('array{}|array{string, non-empty-string}', $matches); + assertType('array{}|array{non-falsy-string, non-empty-string}', $matches); if (preg_match('_foo(.)\_i_i', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string}', $matches); } - assertType('array{}|array{string, non-empty-string}', $matches); + assertType('array{}|array{non-falsy-string, non-empty-string}', $matches); if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { - assertType("array{0: string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); + assertType("array{0: non-falsy-string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); } - assertType("array{}|array{0: string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); if (preg_match('/(a)(?b)*(c)(d)*/', $s, $matches)) { - assertType("array{0: string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); + assertType("array{0: non-falsy-string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); } - assertType("array{}|array{0: string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); if (preg_match('/(a)(b)*(c)(?d)*/', $s, $matches)) { - assertType("array{0: string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); + assertType("array{0: non-falsy-string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); } - assertType("array{}|array{0: string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { - assertType("array{0: string, 1?: 'a'|'b'}", $matches); + assertType("array{0: non-empty-string, 1?: 'a'|'b'}", $matches); } - assertType("array{}|array{0: string, 1?: 'a'|'b'}", $matches); + assertType("array{}|array{0: non-empty-string, 1?: 'a'|'b'}", $matches); if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { - assertType("array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); } - assertType("array{}|array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{}|array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); if (preg_match('/(foo)(bar)(baz)*/', $s, $matches)) { - assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); + assertType("array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); } - assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); if (preg_match('/(foo)(bar)(baz)?/', $s, $matches)) { - assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); + assertType("array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); } - assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); if (preg_match('/(foo)(bar)(baz){0,3}/', $s, $matches)) { - assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); + assertType("array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); } - assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'foo', 2: 'bar', 3?: non-falsy-string}", $matches); if (preg_match('/(foo)(bar)(baz){2,3}/', $s, $matches)) { - assertType("array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); } - assertType("array{}|array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{}|array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); if (preg_match('/(foo)(bar)(baz){2}/', $s, $matches)) { - assertType("array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); } - assertType("array{}|array{string, 'foo', 'bar', non-falsy-string}", $matches); + assertType("array{}|array{non-falsy-string, 'foo', 'bar', non-falsy-string}", $matches); } function doNonCapturingGroup(string $s): void { if (preg_match('/Price: (?:£|€)(\d+)/', $s, $matches)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-falsy-string, numeric-string}', $matches); } - assertType('array{}|array{string, numeric-string}', $matches); + assertType('array{}|array{non-falsy-string, numeric-string}', $matches); } function doNamedSubpattern(string $s): void { if (preg_match('/\w-(?P\d+)-(\w)/', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string, 2: non-empty-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string, 2: non-empty-string}', $matches); } - assertType('array{}|array{0: string, num: numeric-string, 1: numeric-string, 2: non-empty-string}', $matches); + assertType('array{}|array{0: non-falsy-string, num: numeric-string, 1: numeric-string, 2: non-empty-string}', $matches); if (preg_match('/^(?\S+::\S+)/', $s, $matches)) { - assertType('array{0: string, name: non-falsy-string, 1: non-falsy-string}', $matches); + assertType('array{0: non-falsy-string, name: non-falsy-string, 1: non-falsy-string}', $matches); } - assertType('array{}|array{0: string, name: non-falsy-string, 1: non-falsy-string}', $matches); + assertType('array{}|array{0: non-falsy-string, name: non-falsy-string, 1: non-falsy-string}', $matches); if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $s, $matches)) { - assertType('array{0: string, name: non-falsy-string, 1: non-falsy-string, dataname?: non-falsy-string, 2?: non-falsy-string}', $matches); + assertType('array{0: non-falsy-string, name: non-falsy-string, 1: non-falsy-string, dataname?: non-falsy-string, 2?: non-falsy-string}', $matches); } - assertType('array{}|array{0: string, name: non-falsy-string, 1: non-falsy-string, dataname?: non-falsy-string, 2?: non-falsy-string}', $matches); + assertType('array{}|array{0: non-falsy-string, name: non-falsy-string, 1: non-falsy-string, dataname?: non-falsy-string, 2?: non-falsy-string}', $matches); } function doOffsetCapture(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { - assertType("array{array{string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, max>}}", $matches); + assertType("array{array{non-falsy-string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, max>}}", $matches); } - assertType("array{}|array{array{string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, max>}}", $matches); + assertType("array{}|array{array{non-falsy-string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, max>}}", $matches); } function doUnknownFlags(string $s, int $flags): void { @@ -129,91 +129,91 @@ function doUnknownFlags(string $s, int $flags): void { function doMultipleAlternativeCaptureGroupsWithSameNameWithModifier(string $s): void { if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { - assertType("array{0: string, Foo: non-empty-string, 1: non-empty-string}|array{0: string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); + assertType("array{0: non-empty-string, Foo: non-empty-string, 1: non-empty-string}|array{0: non-empty-string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); } - assertType("array{}|array{0: string, Foo: non-empty-string, 1: non-empty-string}|array{0: string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); + assertType("array{}|array{0: non-empty-string, Foo: non-empty-string, 1: non-empty-string}|array{0: non-empty-string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); } function doMultipleConsecutiveCaptureGroupsWithSameNameWithModifier(string $s): void { if (preg_match('/(?J)(?[a-z]+)|(?[0-9]+)/', $s, $matches)) { - assertType("array{0: string, Foo: non-empty-string, 1: non-empty-string}|array{0: string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); + assertType("array{0: non-empty-string, Foo: non-empty-string, 1: non-empty-string}|array{0: non-empty-string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); } - assertType("array{}|array{0: string, Foo: non-empty-string, 1: non-empty-string}|array{0: string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); + assertType("array{}|array{0: non-empty-string, Foo: non-empty-string, 1: non-empty-string}|array{0: non-empty-string, Foo: numeric-string, 1: '', 2: numeric-string}", $matches); } // https://github.com/hoaproject/Regex/issues/31 function hoaBug31(string $s): void { if (preg_match('/([\w-])/', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-empty-string, non-empty-string}', $matches); } - assertType('array{}|array{string, non-empty-string}', $matches); + assertType('array{}|array{non-empty-string, non-empty-string}', $matches); if (preg_match('/\w-(\d+)-(\w)/', $s, $matches)) { - assertType('array{string, numeric-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, numeric-string, non-empty-string}', $matches); } - assertType('array{}|array{string, numeric-string, non-empty-string}', $matches); + assertType('array{}|array{non-falsy-string, numeric-string, non-empty-string}', $matches); } // https://github.com/phpstan/phpstan/issues/10855#issuecomment-2044323638 function testHoaUnsupportedRegexSyntax(string $s): void { if (preg_match('#\QPHPDoc type array of property App\Log::$fillable is not covariant with PHPDoc type array of overridden property Illuminate\Database\E\\\\\QEloquent\Model::$fillable.\E#', $s, $matches)) { - assertType('array{string}', $matches); + assertType('array{non-falsy-string}', $matches); } - assertType('array{}|array{string}', $matches); + assertType('array{}|array{non-falsy-string}', $matches); } function testPregMatchSimpleCondition(string $value): void { if (preg_match('/%env\((.*)\:.*\)%/U', $value, $matches)) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchIdenticalToOne(string $value): void { if (preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchIdenticalToOneFalseyContext(string $value): void { if (!(preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) !== 1)) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchIdenticalToOneInverted(string $value): void { if (1 === preg_match('/%env\((.*)\:.*\)%/U', $value, $matches)) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchIdenticalToOneFalseyContextInverted(string $value): void { if (!(1 !== preg_match('/%env\((.*)\:.*\)%/U', $value, $matches))) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchEqualToOne(string $value): void { if (preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) == 1) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchEqualToOneFalseyContext(string $value): void { if (!(preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) != 1)) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchEqualToOneInverted(string $value): void { if (1 == preg_match('/%env\((.*)\:.*\)%/U', $value, $matches)) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } function testPregMatchEqualToOneFalseyContextInverted(string $value): void { if (!(1 != preg_match('/%env\((.*)\:.*\)%/U', $value, $matches))) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); } } @@ -225,18 +225,18 @@ function testUnionPattern(string $s): void $pattern = '/Price: (\d+)(\d+)(\d+)/'; } if (preg_match($pattern, $s, $matches)) { - assertType('array{string, numeric-string, numeric-string, numeric-string}|array{string, numeric-string}', $matches); + assertType('array{non-falsy-string, numeric-string, numeric-string, numeric-string}|array{non-falsy-string, numeric-string}', $matches); } - assertType('array{}|array{string, numeric-string, numeric-string, numeric-string}|array{string, numeric-string}', $matches); + assertType('array{}|array{non-falsy-string, numeric-string, numeric-string, numeric-string}|array{non-falsy-string, numeric-string}', $matches); } function doFoo(string $row): void { if (preg_match('~^(a(b))$~', $row, $matches) === 1) { - assertType("array{string, 'ab', 'b'}", $matches); + assertType("array{non-falsy-string, 'ab', 'b'}", $matches); } if (preg_match('~^(a(b)?)$~', $row, $matches) === 1) { - assertType("array{0: string, 1: non-falsy-string, 2?: 'b'}", $matches); + assertType("array{0: non-falsy-string, 1: non-falsy-string, 2?: 'b'}", $matches); } if (preg_match('~^(a(b)?)?$~', $row, $matches) === 1) { assertType("array{0: string, 1?: non-falsy-string, 2?: 'b'}", $matches); @@ -249,7 +249,7 @@ function doFoo2(string $row): void return; } - assertType("array{0: string, 1: string, branchCode: ''|numeric-string, 2: ''|numeric-string, accountNumber: numeric-string, 3: numeric-string, bankCode: non-falsy-string&numeric-string, 4: non-falsy-string&numeric-string}", $matches); + assertType("array{0: non-falsy-string, 1: string, branchCode: ''|numeric-string, 2: ''|numeric-string, accountNumber: numeric-string, 3: numeric-string, bankCode: non-falsy-string&numeric-string, 4: non-falsy-string&numeric-string}", $matches); } function doFoo3(string $row): void @@ -258,56 +258,56 @@ function doFoo3(string $row): void return; } - assertType('array{string, non-falsy-string, non-falsy-string, numeric-string, numeric-string, numeric-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, non-falsy-string, numeric-string, numeric-string, numeric-string, numeric-string}', $matches); } function (string $size): void { if (preg_match('~^a\.b(c(\d+)(\d+)(\s+))?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string, numeric-string, numeric-string, non-empty-string}|array{string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string, numeric-string, non-empty-string}|array{non-falsy-string}', $matches); }; function (string $size): void { if (preg_match('~^a\.b(c(\d+))?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string, numeric-string}|array{string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string}|array{non-falsy-string}', $matches); }; function (string $size): void { if (preg_match('~^a\.b(c(\d+)?)d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{0: string, 1: non-falsy-string, 2?: numeric-string}', $matches); + assertType('array{0: non-falsy-string, 1: non-falsy-string, 2?: numeric-string}', $matches); }; function (string $size): void { if (preg_match('~^a\.b(c(\d+)?)?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{0: string, 1?: non-falsy-string, 2?: numeric-string}', $matches); + assertType('array{0: non-falsy-string, 1?: non-falsy-string, 2?: numeric-string}', $matches); }; function (string $size): void { if (preg_match('~^a\.b(c(\d+))d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{string, non-falsy-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string}', $matches); }; function (string $size): void { if (preg_match('~^a\.(b)?(c)?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType("array{0: string, 1?: ''|'b', 2?: 'c'}", $matches); + assertType("array{0: non-falsy-string, 1?: ''|'b', 2?: 'c'}", $matches); }; function (string $size): void { if (preg_match('~^(?:(\\d+)x(\\d+)|(\\d+)|x(\\d+))$~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType("array{string, '', '', '', numeric-string}|array{string, '', '', numeric-string}|array{string, numeric-string, numeric-string}", $matches); + assertType("array{non-empty-string, '', '', '', numeric-string}|array{non-empty-string, '', '', numeric-string}|array{non-empty-string, numeric-string, numeric-string}", $matches); }; function (string $size): void { @@ -321,16 +321,16 @@ function (string $size): void { if (preg_match('~\{(?:(include)\\s+(?:[$]?\\w+(?£|€)\d+/', $s, $matches)) { - assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{}|array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function bug11323b(string $s): void { if (preg_match('/Price: (?£|€)\d+/', $s, $matches)) { - assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{}|array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function unmatchedAsNullWithMandatoryGroup(string $s): void { if (preg_match('/Price: (?£|€)\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{0: string, currency: '£'|'€', 1: '£'|'€'}", $matches); + assertType("array{}|array{0: non-falsy-string, currency: '£'|'€', 1: '£'|'€'}", $matches); } function (string $s): void { if (preg_match('{' . preg_quote('xxx') . '(z)}', $s, $matches)) { - assertType("array{string, 'z'}", $matches); + assertType("array{non-falsy-string, 'z'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{string, 'z'}", $matches); + assertType("array{}|array{non-falsy-string, 'z'}", $matches); }; function (string $s): void { if (preg_match('{' . preg_quote($s) . '(z)}', $s, $matches)) { - assertType("array{string, 'z'}", $matches); + assertType("array{non-falsy-string, 'z'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{string, 'z'}", $matches); + assertType("array{}|array{non-falsy-string, 'z'}", $matches); }; function (string $s): void { if (preg_match('/' . preg_quote($s, '/') . '(\d)/', $s, $matches)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-empty-string, numeric-string}', $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{string, numeric-string}', $matches); + assertType('array{}|array{non-empty-string, numeric-string}', $matches); }; function (string $s): void { if (preg_match('{' . preg_quote($s) . '(z)' . preg_quote($s) . '(?:abc)(def)?}', $s, $matches)) { - assertType("array{0: string, 1: 'z', 2?: 'def'}", $matches); + assertType("array{0: non-falsy-string, 1: 'z', 2?: 'def'}", $matches); } else { assertType('array{}', $matches); } - assertType("array{}|array{0: string, 1: 'z', 2?: 'def'}", $matches); + assertType("array{}|array{0: non-falsy-string, 1: 'z', 2?: 'def'}", $matches); }; function (string $s, $mixed): void { @@ -429,97 +429,97 @@ function (string $s, $mixed): void { function (string $s): void { if (preg_match('/^%([0-9]*\$)?[0-9]*\.?[0-9]*([sbdeEfFgGhHouxX])$/', $s, $matches) === 1) { - assertType("array{string, string, 'b'|'d'|'E'|'e'|'F'|'f'|'G'|'g'|'H'|'h'|'o'|'s'|'u'|'X'|'x'}", $matches); + assertType("array{non-falsy-string, string, 'b'|'d'|'E'|'e'|'F'|'f'|'G'|'g'|'H'|'h'|'o'|'s'|'u'|'X'|'x'}", $matches); } }; function (string $s): void { if (preg_match('~^((\\d{1,6})-)$~', $s, $matches) === 1) { - assertType("array{string, non-falsy-string, numeric-string}", $matches); + assertType("array{non-falsy-string, non-falsy-string, numeric-string}", $matches); } }; function (string $s): void { if (preg_match('~^((\\d{1,6}).)$~', $s, $matches) === 1) { - assertType("array{string, non-falsy-string, numeric-string}", $matches); + assertType("array{non-falsy-string, non-falsy-string, numeric-string}", $matches); } }; function (string $s): void { if (preg_match('~^([157])$~', $s, $matches) === 1) { - assertType("array{string, '1'|'5'|'7'}", $matches); + assertType("array{non-falsy-string, '1'|'5'|'7'}", $matches); } }; function (string $s): void { if (preg_match('~^([157XY])$~', $s, $matches) === 1) { - assertType("array{string, '1'|'5'|'7'|'X'|'Y'}", $matches); + assertType("array{non-falsy-string, '1'|'5'|'7'|'X'|'Y'}", $matches); } }; function bug11323(string $s): void { if (preg_match('/([*|+?{}()]+)([^*|+[:digit:]?{}()]+)/', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string, non-empty-string}', $matches); } if (preg_match('/\p{L}[[\]]+([-*|+?{}(?:)]+)([^*|+[:digit:]?{a-z}(\p{L})\a-]+)/', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string, non-empty-string}', $matches); } if (preg_match('{([-\p{L}[\]*|\x03\a\b+?{}(?:)-]+[^[:digit:]?{}a-z0-9#-k]+)(a-z)}', $s, $matches)) { - assertType("array{string, non-falsy-string, 'a-z'}", $matches); + assertType("array{non-falsy-string, non-falsy-string, 'a-z'}", $matches); } if (preg_match('{(\d+)(?i)insensitive((?xs-i)case SENSITIVE here.+and dot matches new lines)}', $s, $matches)) { - assertType('array{string, numeric-string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, numeric-string, non-falsy-string}', $matches); } if (preg_match('{(\d+)(?i)insensitive((?x-i)case SENSITIVE here(?i:insensitive non-capturing group))}', $s, $matches)) { - assertType('array{string, numeric-string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, numeric-string, non-falsy-string}', $matches); } if (preg_match('{([]] [^]])}', $s, $matches)) { - assertType('array{string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string}', $matches); } if (preg_match('{([[:digit:]])}', $s, $matches)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-empty-string, numeric-string}', $matches); } if (preg_match('{([\d])(\d)}', $s, $matches)) { - assertType('array{string, numeric-string, numeric-string}', $matches); + assertType('array{non-falsy-string, numeric-string, numeric-string}', $matches); } if (preg_match('{([0-9])}', $s, $matches)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-empty-string, numeric-string}', $matches); } if (preg_match('{(\p{L})(\p{P})(\p{Po})}', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType('array{non-falsy-string, non-empty-string, non-empty-string, non-empty-string}', $matches); } if (preg_match('{(a)??(b)*+(c++)(d)+?}', $s, $matches)) { - assertType("array{string, ''|'a', string, non-empty-string, non-empty-string}", $matches); + assertType("array{non-falsy-string, ''|'a', string, non-empty-string, non-empty-string}", $matches); } if (preg_match('{(.\d)}', $s, $matches)) { - assertType('array{string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string}', $matches); } if (preg_match('{(\d.)}', $s, $matches)) { - assertType('array{string, non-falsy-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string}', $matches); } if (preg_match('{(\d\d)}', $s, $matches)) { - assertType('array{string, non-falsy-string&numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string}', $matches); } if (preg_match('{(.(\d))}', $s, $matches)) { - assertType('array{string, non-falsy-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string}', $matches); } if (preg_match('{((\d).)}', $s, $matches)) { - assertType('array{string, non-falsy-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string}', $matches); } if (preg_match('{(\d([1-4])\d)}', $s, $matches)) { - assertType('array{string, non-falsy-string&numeric-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string&numeric-string, numeric-string}', $matches); } if (preg_match('{(x?([1-4])\d)}', $s, $matches)) { - assertType('array{string, non-falsy-string, numeric-string}', $matches); + assertType('array{non-falsy-string, non-falsy-string, numeric-string}', $matches); } if (preg_match('{([^1-4])}', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-empty-string, non-empty-string}', $matches); } if (preg_match("{([\r\n]+)(\n)([\n])}", $s, $matches)) { - assertType('array{string, non-empty-string, "\n", "\n"}', $matches); + assertType('array{non-falsy-string, non-empty-string, "\n", "\n"}', $matches); } if (preg_match('/foo(*:first)|bar(*:second)([x])/', $s, $matches)) { - assertType("array{0: string, 1?: 'x', MARK?: 'first'|'second'}", $matches); + assertType("array{0: non-empty-string, 1?: 'x', MARK?: 'first'|'second'}", $matches); } } @@ -539,7 +539,7 @@ public function test(string $str): void public function test2(string $str): void { if (preg_match('~^(?:(\w+)::)?(\w+)$~', $str, $matches) === 1) { - assertType('array{string, string, non-empty-string}', $matches); + assertType('array{non-empty-string, string, non-empty-string}', $matches); } } } @@ -552,7 +552,7 @@ function (string $s): void { } if (preg_match($p, $s, $matches)) { - assertType("array{string, '£', 'abc'}|array{string, numeric-string, 'b'}", $matches); + assertType("array{non-falsy-string, '£', 'abc'}|array{non-falsy-string, numeric-string, 'b'}", $matches); } }; @@ -564,97 +564,97 @@ function (string $s): void { } if (preg_match($p, $s, $matches)) { - assertType("array{0: string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); + assertType("array{0: non-falsy-string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); } }; function (string $s): void { if (preg_match('/Price: ([a-z])/i', $s, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{non-falsy-string, non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: ([0-9])/i', $s, $matches)) { - assertType("array{string, numeric-string}", $matches); + assertType("array{non-falsy-string, numeric-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: ([xXa])/i', $s, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{non-falsy-string, non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: ([xXa])/', $s, $matches)) { - assertType("array{string, 'a'|'X'|'x'}", $matches); + assertType("array{non-falsy-string, 'a'|'X'|'x'}", $matches); } }; function (string $s): void { if (preg_match('/Price: (ba[rz])/', $s, $matches)) { - assertType("array{string, 'bar'|'baz'}", $matches); + assertType("array{non-falsy-string, 'bar'|'baz'}", $matches); } }; function (string $s): void { if (preg_match('/Price: (b[ao][mn])/', $s, $matches)) { - assertType("array{string, 'bam'|'ban'|'bom'|'bon'}", $matches); + assertType("array{non-falsy-string, 'bam'|'ban'|'bom'|'bon'}", $matches); } }; function (string $s): void { if (preg_match('/Price: (\s{3}|0)/', $s, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{non-falsy-string, non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (a|bc?)/', $s, $matches)) { - assertType("array{string, non-falsy-string}", $matches); + assertType("array{non-falsy-string, non-falsy-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (?a|bc?)/', $s, $matches)) { - assertType("array{0: string, named: non-falsy-string, 1: non-falsy-string}", $matches); + assertType("array{0: non-falsy-string, named: non-falsy-string, 1: non-falsy-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (a|0c?)/', $s, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{non-falsy-string, non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (a|\d)/', $s, $matches)) { - assertType("array{string, 'a'|numeric-string}", $matches); + assertType("array{non-falsy-string, 'a'|numeric-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (?a|\d)/', $s, $matches)) { - assertType("array{0: string, named: 'a'|numeric-string, 1: 'a'|numeric-string}", $matches); + assertType("array{0: non-falsy-string, named: 'a'|numeric-string, 1: 'a'|numeric-string}", $matches); } }; function (string $s): void { if (preg_match('/Price: (a|0)/', $s, $matches)) { - assertType("array{string, '0'|'a'}", $matches); + assertType("array{non-falsy-string, '0'|'a'}", $matches); } }; function (string $s): void { if (preg_match('/Price: (aa|0)/', $s, $matches)) { - assertType("array{string, '0'|'aa'}", $matches); + assertType("array{non-falsy-string, '0'|'aa'}", $matches); } }; function (string $s): void { if (preg_match('/( \d+ )/x', $s, $matches)) { - assertType('array{string, numeric-string}', $matches); + assertType('array{non-empty-string, numeric-string}', $matches); } }; @@ -672,7 +672,7 @@ function (string $s): void { function (string $s): void { if (preg_match('/( .+ )/x', $s, $matches)) { - assertType('array{string, non-empty-string}', $matches); + assertType('array{non-empty-string, non-empty-string}', $matches); } }; @@ -712,19 +712,19 @@ static public function sayHello(string $source): void function (string $s): void { if (preg_match('~a|(\d)|(\s)~', $s, $matches) === 1) { - assertType("array{0: string, 1?: numeric-string}|array{string, '', non-empty-string}", $matches); + assertType("array{0: non-empty-string, 1?: numeric-string}|array{non-empty-string, '', non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('~a|((u)x)|((v)y)~', $s, $matches) === 1) { - assertType("array{string, '', '', 'vy', 'v'}|array{string, 'ux', 'u'}|array{string}", $matches); + assertType("array{non-empty-string, '', '', 'vy', 'v'}|array{non-empty-string, 'ux', 'u'}|array{non-empty-string}", $matches); } }; function (string $s): void { if (preg_match('~a|(\d)|(\s)~', $s, $matches, PREG_OFFSET_CAPTURE) === 1) { - assertType("array{0: array{string, int<-1, max>}, 1?: array{numeric-string, int<-1, max>}}|array{array{string, int<-1, max>}, array{'', int<-1, max>}, array{non-empty-string, int<-1, max>}}", $matches); + assertType("array{0: array{non-empty-string, int<-1, max>}, 1?: array{numeric-string, int<-1, max>}}|array{array{non-empty-string, int<-1, max>}, array{'', int<-1, max>}, array{non-empty-string, int<-1, max>}}", $matches); } }; @@ -737,7 +737,7 @@ function bug11490 (string $expression): void { $matches = []; if (preg_match('/([-+])?([\d]+)%/', $expression, $matches) === 1) { - assertType("array{string, ''|'+'|'-', numeric-string}", $matches); + assertType("array{non-falsy-string, ''|'+'|'-', numeric-string}", $matches); } } @@ -745,7 +745,7 @@ function bug11490b (string $expression): void { $matches = []; if (preg_match('/([\\[+])?([\d]+)%/', $expression, $matches) === 1) { - assertType("array{string, ''|'+'|'[', numeric-string}", $matches); + assertType("array{non-falsy-string, ''|'+'|'[', numeric-string}", $matches); } } @@ -753,7 +753,7 @@ function bug11622 (string $expression): void { $matches = []; if (preg_match('/^abc(def|$)/', $expression, $matches) === 1) { - assertType("array{string, string}", $matches); + assertType("array{non-falsy-string, string}", $matches); } } @@ -762,23 +762,23 @@ function bug11604 (string $string): void { return; } - assertType("array{0: string, 1?: ''|'XX', 2?: 'YY'}", $matches); + assertType("array{0: non-empty-string, 1?: ''|'XX', 2?: 'YY'}", $matches); // could be array{string, '', 'YY'}|array{string, 'XX'}|array{string} } function bug11604b (string $string): void { if (preg_match('/(XX)|(YY)?(ZZ)/', $string, $matches)) { - assertType("array{0: string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); + assertType("array{0: non-empty-string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); } } function testLtrimDelimiter (string $string): void { if (preg_match(' /(x)/', $string, $matches)) { - assertType("array{string, 'x'}", $matches); + assertType("array{non-empty-string, 'x'}", $matches); } if (preg_match(' /(x)/', $string, $matches)) { - assertType("array{string, 'x'}", $matches); + assertType("array{non-empty-string, 'x'}", $matches); } } @@ -786,31 +786,31 @@ function testUnescapeBackslash (string $string): void { if (preg_match(<<<'EOD' ~(\[)~ EOD, $string, $matches)) { - assertType("array{string, '['}", $matches); + assertType("array{non-empty-string, '['}", $matches); } if (preg_match(<<<'EOD' ~(\d)~ EOD, $string, $matches)) { - assertType("array{string, numeric-string}", $matches); + assertType("array{non-empty-string, numeric-string}", $matches); } if (preg_match(<<<'EOD' ~(\\d)~ EOD, $string, $matches)) { - assertType("array{string, '\\\d'}", $matches); + assertType("array{non-falsy-string, '\\\d'}", $matches); } if (preg_match(<<<'EOD' ~(\\\d)~ EOD, $string, $matches)) { - assertType("array{string, non-falsy-string}", $matches); + assertType("array{non-falsy-string, non-falsy-string}", $matches); } if (preg_match(<<<'EOD' ~(\\\\d)~ EOD, $string, $matches)) { - assertType("array{string, '\\\\\\\d'}", $matches); + assertType("array{non-falsy-string, '\\\\\\\d'}", $matches); } } @@ -818,86 +818,86 @@ function testEscapedDelimiter (string $string): void { if (preg_match(<<<'EOD' /(\/)/ EOD, $string, $matches)) { - assertType("array{string, '/'}", $matches); + assertType("array{non-empty-string, '/'}", $matches); } if (preg_match(<<<'EOD' ~(\~)~ EOD, $string, $matches)) { - assertType("array{string, '~'}", $matches); + assertType("array{non-empty-string, '~'}", $matches); } if (preg_match(<<<'EOD' ~(\[2])~ EOD, $string, $matches)) { - assertType("array{string, '[2]'}", $matches); + assertType("array{non-falsy-string, '[2]'}", $matches); } if (preg_match(<<<'EOD' [(\[2\])] EOD, $string, $matches)) { - assertType("array{string, '[2]'}", $matches); + assertType("array{non-falsy-string, '[2]'}", $matches); } if (preg_match(<<<'EOD' ~(\{2})~ EOD, $string, $matches)) { - assertType("array{string, '{2}'}", $matches); + assertType("array{non-falsy-string, '{2}'}", $matches); } if (preg_match(<<<'EOD' {(\{2\})} EOD, $string, $matches)) { - assertType("array{string, '{2}'}", $matches); + assertType("array{non-falsy-string, '{2}'}", $matches); } if (preg_match(<<<'EOD' ~([a\]])~ EOD, $string, $matches)) { - assertType("array{string, ']'|'a'}", $matches); + assertType("array{non-empty-string, ']'|'a'}", $matches); } if (preg_match(<<<'EOD' ~([a[])~ EOD, $string, $matches)) { - assertType("array{string, '['|'a'}", $matches); + assertType("array{non-empty-string, '['|'a'}", $matches); } if (preg_match(<<<'EOD' ~([a\]b])~ EOD, $string, $matches)) { - assertType("array{string, ']'|'a'|'b'}", $matches); + assertType("array{non-empty-string, ']'|'a'|'b'}", $matches); } if (preg_match(<<<'EOD' ~([a[b])~ EOD, $string, $matches)) { - assertType("array{string, '['|'a'|'b'}", $matches); + assertType("array{non-empty-string, '['|'a'|'b'}", $matches); } if (preg_match(<<<'EOD' ~([a\[b])~ EOD, $string, $matches)) { - assertType("array{string, '['|'a'|'b'}", $matches); + assertType("array{non-empty-string, '['|'a'|'b'}", $matches); } if (preg_match(<<<'EOD' [([a\[b])] EOD, $string, $matches)) { - assertType("array{string, '['|'a'|'b'}", $matches); + assertType("array{non-empty-string, '['|'a'|'b'}", $matches); } if (preg_match(<<<'EOD' {(x\\\{)|(y\\\\\})} EOD, $string, $matches)) { - assertType("array{string, '', 'y\\\\\\\}'}|array{string, 'x\\\{'}", $matches); + assertType("array{non-empty-string, '', 'y\\\\\\\}'}|array{non-empty-string, 'x\\\{'}", $matches); } } function bugUnescapedDashAfterRange (string $string): void { if (preg_match('/([0-1-y])/', $string, $matches)) { - assertType("array{string, non-empty-string}", $matches); + assertType("array{non-empty-string, non-empty-string}", $matches); } } @@ -958,5 +958,55 @@ function bug11744(string $string): void if (!preg_match('~^((/[a-z]+)?.+)~', $string, $matches)) { return; } - assertType('array{0: string, 1: non-empty-string, 2?: non-falsy-string}', $matches); + assertType('array{0: non-empty-string, 1: non-empty-string, 2?: non-falsy-string}', $matches); +} + +function bug12749(string $str): void +{ + if (preg_match('/[A-Z]/', $str, $match)) { + assertType('array{non-empty-string}', $match); // could be non-falsy-string + } +} + +function bug12749a(string $str): void +{ + if (preg_match('/[A-Z]{2,}/', $str, $match)) { + assertType('array{non-falsy-string}', $match); + } +} + +function bug12749b(string $str): void +{ + if (preg_match('/[0-9][A-Z]/', $str, $match)) { + assertType('array{non-falsy-string}', $match); + } +} + +function bug12749c(string $str): void +{ + if (preg_match('/[0-9][A-Z]?/', $str, $match)) { + assertType('array{non-empty-string}', $match); + } +} + +function bug12749d(string $str): void +{ + if (preg_match('/[0-9]?[A-Z]/', $str, $match)) { + assertType('array{non-falsy-string}', $match); + } +} + +function bug12749e(string $str): void +{ + // no ^ $ delims, therefore can be anything which contains a number + if (preg_match('/[0-9]/', $str, $match)) { + assertType('array{non-empty-string}', $match); + } +} + +function bug12749f(string $str): void +{ + if (preg_match('/^[0-9]$/', $str, $match)) { + assertType('array{non-empty-string}', $match); // could be numeric-string + } } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php index 34b1b72756e..4620565210c 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php80.php @@ -7,15 +7,15 @@ function doOffsetCaptureWithUnmatchedNull(string $s): void { // see https://3v4l.org/07rBO#v8.2.9 if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) { - assertType("array{array{string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); + assertType("array{array{non-falsy-string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); } - assertType("array{}|array{array{string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); + assertType("array{}|array{array{non-falsy-string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); } function doNonAutoCapturingModifier(string $s): void { if (preg_match('/(?n)(\d+)/', $s, $matches)) { // should be assertType('array{string}', $matches); - assertType('array{string, numeric-string}', $matches); + assertType('array{non-empty-string, numeric-string}', $matches); } - assertType('array{}|array{string, numeric-string}', $matches); + assertType('array{}|array{non-empty-string, numeric-string}', $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php index dfbcab477e4..1b5dc597b7f 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php @@ -8,39 +8,39 @@ // https://php.watch/versions/8.2/preg-n-no-capture-modifier function doNonAutoCapturingFlag(string $s): void { if (preg_match('/(\d+)/n', $s, $matches)) { - assertType('array{string}', $matches); + assertType('array{non-empty-string}', $matches); } - assertType('array{}|array{string}', $matches); + assertType('array{}|array{non-empty-string}', $matches); if (preg_match('/(\d+)(?P\d+)/n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } - assertType('array{}|array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{}|array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); if (preg_match('/(\w)-(?P\d+)-(\w)/n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } - assertType('array{}|array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{}|array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } // delimiter variants, see https://www.php.net/manual/en/regexp.reference.delimiters.php function (string $s): void { if (preg_match('{(\d+)(?P\d+)}n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } }; function (string $s): void { if (preg_match('<(\d+)(?P\d+)>n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } }; function (string $s): void { if (preg_match('((\d+)(?P\d+))n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } }; function (string $s): void { if (preg_match('[(\d+)(?P\d+)]n', $s, $matches)) { - assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches); + assertType('array{0: non-falsy-string, num: numeric-string, 1: numeric-string}', $matches); } }; diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php index f7230851e4e..d5e650e708f 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes-php72.php @@ -19,7 +19,7 @@ function (string $s): void { preg_replace_callback( '|
2tx13)X zlO#&y;@nyEuZ%&SB*Ws*-r@MT#~STC3{k}tL}9BnK5I~g;rvir$?6oBZ%x;mwsv+? z^q1YDsQ(2o#HyJ2<*I+mhoc7^-uKn+m)lkTBqQ*;NwIS49bQj})qA-)dwv@g2>%B< zGynvkS+r{;(DnS(aywmJZ^zU=LA2V&0Hyt~lIc?mDKeyPsZ|@0hxuih7MtGp3uN&M z(6Gc1MacA`VuhJpasWX$f+@bTiPp=&lR*b68z|vc>?WrS0}gTW6<_1C9k_9i#fWy` zpl=v-F~47a1`91tEz(U)Pt7Z_QqbaZ$t+IH$;mIb;sOB8lfg^FFcik`{uLp3P!A3B zD6Jsc!9Znp^XiDrTNh}P@RB;q*#B;lRkrC6cM!bw`@+lb%X@uksnuLoEpjKdx;`N! zwVid)BujXm0KNeR)7uD3lnyp3=(Yy?sd`NmZy=+i#%OdPND;gNCDv&(MqPrnQdJps z*aF?w`s#+eazR?GGx~CtT%5C0N@S%p!Y#M{$~mtsP{Q_#e0Cv2Zo6yy11<$7IRNO(I_zRvL=BK<29&r&+fQSG} zu>abC$AAr2p!`96HCq)|gY_OFWud3PY4PQ;E&lh-J(4q&HT>25;&0ziNh$&FiCLH* z2DMTBm0oQ3CS~7uE2h}pzz+ie#Qk}j+eX$Wh<=q!lqwB~K_)5Lt}-Zyk1R{J%Cclj z>8+S_zDN?xgvA7C0L;ZnzEAfo-95cd&(hO9``+vHbkE#7)A#oF$6NJ9?iaXU;hx`# z6(BODs_vQVDkTGfK&&TDoO9wVyYp2xtFHk zl4@0CJ4pAE%Vl0v zWi%KB8-HAzp)9!eR%NnNRz*6kJYK3I%KQ&#s_AF&Q6TbYtGz0JP35x_3j;CcLw3&_ z=Ujg0QBlNK6EBLQTy-hfm=8+(!A`bXEOsMIq$fY6Z_rLYr`a)ItXi6Qs$SmI%2du9 z0jF)_JWppkeWU}**Ddav7Wel2DjHXlKwwb8JTH9AN*47+5mmiKlATni!|XvZ%sQPQ zk&U8yQH`^S$V3$klLgJG{xaQ3X_&x zPm0im&bHN%#Tv6kb*Rs2vz1Tt;_Q>Sirea1Wpek->~!hkv;5TV< z@s1TcU+wKV552TREu-x$sxRctc&xtCDYs+JXXu-R1 zmrEWJ7AwEjBW(G2M}j0R3V%iOmfW_Ic)z`bPt(P%U*Ubr!Y}gU+rGm4mbTOf;Yv4AZOrRCEtzG@;`8(*uJU5N_Fg6Nw0aR&>3Kq3iOef(;n`}6v9)#fBF$#`Mf6s)Tlf)&Y75%TMts^@_T(m(W#VNR!QbUu{=dofhnC}=2b$->>Bth%BYVz z+v)tM@DuLcCJ;0ywBuUgsOlBTauH7xfB%QW^6tLyyufBr&K=I$Y&N2PUNYVuI)}Ec z@-!_selVj6iQwJEJivyTF$Q9q|@z^41p>p+2M>*F zy+($E@J(LhQDQLj2ux{<5(1=$5s`qst)q-zs6&R|H&7AY`!N%BZ(FG0^fWHYq>5J6 zy!&e!Ol7DnKM1&Td78A>`D2-9TUGq#^~)DMd5)#?D-bhbwVAjxTrTshOx!7EVrl#E z3$RhDh-+M;6`TZSA8ZUQOSPwGNxrJWzPK!l`Qto)lO`|Xvn2Gsd;RKZ_u%E{PhNQZ z?z1GGC53v|b>E)FmuhLBGhzz613^15ozclhsJ&XX9UME=!{}bWPlv;U`~5y~6|3GU zw?OpLG(S6|F>AU-PJ;EhLPa$c4?9GxzQYD`7h#8>2 zsMwnbw-cr{A{_~JZ-UzQ%e=&Q%N75{yxsPB(c?qG&jTPvgFsfBK%T;cJNyrW;4S#1 zo#7$CvCN7m2+=H7U~{M?w?CHb{bSEKqL#UJ!(-aKZx|KDIA>(n zdNkg%v~;*J!>{cQ^l9cC3kjk51e|64b2??pd~u%mL9aSZGT;4zDiU;9Z)hDh;UC%z zZPNp}QCjPygOh5tsN3b1z$UfwKi3F5O`TJnx$&GVB51~pw zPv&$?WDW+wB$+A5^oWtxRPtM3R}Ix~7LyGUwp8V4_5;FghL}Ox4F25VC?f9??I$CT zdmDNf0D+BsM9)UvtMud)uRhMJDnIk|bTjSrbdkpuDC*<9pd;VwWF7C@bXI{T81#cq zd!eFMh>_MwB{GRn=d_)oG82G-5A3iNm~3H4*eA=aiqau{T|$K)342DVfzup83J3_P$e$y)y!s zH1j&X_gTVoiDPBtNm${D4sI)_<~>vk5lq6q1IkB=$*!W>VIz|0Cd@)0N!{Gr+d3Z; zy_K#{x&83U^h8LijA#m4Hss6oQ`s9{!sCXBSK>fdyg^v6IxX^xoh-T70Tz5x6hxeP zkK-)Ms~w#5JMqqR5trpoO#e6Z263w5!UYUR#=vbkqRnCUATgrNPDk&oWSmWgMbFB^ zBllswCUS)_mXV%=w(*#x^wMmnAjm6v7e!jBcY(M`3SaczBv%Atn@y8ys)HUKffb!@ zMv990EjW(~iDW}ey||2;bg?F0SQ0zB zX)V$d!c}b)bwV_2@?cB$lIU5_9K4xKhDKzAzGv?yMaG0N&kmsvZe=WkX1Dc$b6!98 zuQvpi*9Ai`gxvG0Q}q~d#W8QC5De*(G9{RAoZa-Kb}vK_Ogn`cMj~^1v|&EZiA^9p z`2Tcc@BWM`s+QnDv9m%B>Y4j0N&_qVl~M%Sn? zl=TW~CFHi$E>#tG)oZnc(PWa^xr75;H$tuNQJH=1kBIr`_n>p9?y%0oIUwr8uJ0rF?4z~CQKyG*2~DDx00=ZUPY5Z}EP%47F|P1LM29`p z%AP#a2$%C{aGr^ITM6KKTVxp>_}5r4o|A6Qf2V?%Q%))6@y}eXrW&PQ#?$~W=4#oK zX9snISW*3iCh2jP5KSS+JgfK2e8hb^Z+nS4@OHT5E=JStjOtels-C!?(Y$+3X$ckT z!FFm^@6@?k9iPZjOD4udztcLz4x13W#J|66oG=X>qY1Zrt%TdX$=;s(#Je=nTQ-#s za?8@fgC_|M-zyPDRNT!)Wwx$-l3P0EwA#r0u<@7FMOtYKe6KC_!P{5l0#x-}EXBDv zi5MRG5(fGkB7`I_!9?SEnp{9~0yr&#Jo3}%EV!LkB(Qx%SWD*2?SM&A@!>=YSvjF;jS=A$-}5dG=xl+<1)V^nzzEc419Id#T}kzL8gu zJY!diNE|AXOR}3hCnL*RTt(nkY3?m|L9lc6HrhG}uOd&5P}cZ1KC&1}s^^h^CC=nB zX%6Pfv}_MfB2^5?Zt=$Ld@*&~o2Qq2tJCCn3D2rgZv3+#IEjw!JY7UbOgmgCBtfV7 zg+w326A8)3yqk}mB{@-W9bI<#C1ps^K9>9Y*j=O(3F^_~j(lrK9sN-zPV}jFqH#iI z+TgU^7jN{wz$FX%pKtH!4_f#12QD-A0TISnh3lXYY(s4l?vH#=_}xkMbr`Xw6{m3VVN%oO~p`h_>;4I zmd?|pFeUzECoOko$+Ad@bxLMp2bV@pvFuNFy}(FP^;cj~=Gm|!{vWLGY;Uh5prOMy zQCq3(E%PNYp+v$ovxqS_IYly0hya-;wQ?qxibml1AvXs#Vn0;(nDGXX3U|c`u&Lo}A+vHZ7}!>W6kj znf?q&bc>7YB_tl_QXrz(?1dv`cFDOq&0;7}hmiy#N}6<}Gm2ywZ}$wf*192bPi6tus0x?)*AKf)oaA=t$`G4Gd7xu#uJmJXH}JJOzZ%Bp`F^#oJ&Vf z>UHj><%{@*?+VWY!cWlW$kHmQ-G+WFk%>zUCM}57+%S0FP#vG@(9;K<hyScI_Vh6R(qW=r8R|q%)+4wPPmkcsu$8!SPZgKdsUtne7~3?RcHM z_LlG&TG^@cor}D9BX*9HopMzq$Vjv4Vl_*4(u#6vJremf9Y`7lrYtu9iIxH^O1H%I z8_hTKEApqCWHY%i=B->L)cd zAS`$;zlpA&K7aOQ=*<@?P1ED&FCTyY?X%ZULT|drr*AIOlF-HDmtVg)c=F19NxPb; zEnCKsEq~q++C<~N=uh5Cgccw|HE*)-A9mJ<-C+MlHm&MGV-PABM5@N7mLL7NWv6td z-b2aiS^mCQAC~)2-WbM2fbQ)v|BIw$frS?|iK&;8T-hlTLNg$q0N)pX^GICujvS zcM^z3-e5A(rF9J@fkiM0i4NCyp;O`z>$7Ut${(;KXEzV?V4K{J9nBUT2(#5RX=Qmm zmX41rLk;mP^YV9SZo96wLNM}cB_j|4beL8_7&l&Z@g_x(|L*j*oXg3;mZ?qLsq1HX5dfRBK-lVa1wQ98aiVa%9Yxd=Uim9Ws~{n86$ z{iPPXDHWDjX|DW|IIJ?tq~elPP5GtLWcg*`g^JRlB83&lm$;eUAN2co^9_qOPcggk zdhMOF;4KccQnPw9GUCXaZZzLTN*TUuEw_k91KI7Np=Dv@Aa6ji|5_*wz);n48e`!; zlT1fS)6t2tyI_VH%nAYxmOckut&qpWSJ5@I;4FGHXuL*R;66{1H7~PIlCmoDE0_S~ z*<;rAaP`KCi_Rn|R?pQ(TNz%Lrzylye87b?Zjsnl=2?rHSk*HfWaXn8i_aQJbxBPM zENQ@3u}|PD97>QNRWV+K7uA+CM;MO68r#1jTvOxnr^sk9^kNZR^RX6Q;;L{Za$@9( z!pmlEMkgmez8lk@U^A?md?8;l#$r*wkfcfFVtUf<-7;yykp;`ISVU%LusJ7c7UJ6U z(8g*^^Gfej)vHzXncvBR(YTY*u@!z!pvSikfjY5RaN*}tSPQY5Q1?#sFqi~dVsuln zjxMIOXtl|5tC}3agU-WwZ9R|g5mw8QeD&YeE%Ki`544| zT%<{9)MSY0=xGH7&EXH7EX(3caeNj5(6kKX_4e1I?PKBrmc-?y*$E5~+>S(rHYw~a zi!?9N>WWso32(IO*Yx@UZ8rld z78#`Rd`3fvj~$71{-JKh&r6&7fmp4@x8E%+4Lyn>IG11f3d4Eu!f* zojymVqb(sPS4WTh43Bmf|=%>9&pcs(Yk&<*_{=41$iSd);$!96&o5SAPUY zTFg~AgXI^Ie@UkdA<(7Z3JPF6|Co_?CT6g3gR&c;&qQOu@nx4koe=s1^RD;ohi}=l zv?K1^uvwXQcXuaLlzXUrDJ4Y)nEAe#&^S$Jy!cY**0*)`$DH;pG)GXU^BVgojh;bt zkl$2fFd;q(oa=a^c`<4uIgQWh7?Moc&Nn@lfl!%3q!WiXqc>LQ5l25$OO3Zp$p{w` zyM0|wGjwreTh;V$ynP6lV#O;|Knk#4$BOlZG6S*kW_sT~AyV7hQ4}+j(cHSL^k$JlfSxh46%5 zNO5yKCfFhTdJGOC(%<{j2ofIILTFo}LjWbhDKiyjzZV@pXYGI-+5y|RS}Kt?VeLgL z0@#K=rzMaf(eri$RS7Akl+@)$=nbW%m$y=kMj>n?7|&^Z`70M+4*obs-5%vB@t10# zc~sQ+^4pK{EMR^;vH-Ppi{MguadLLoKj&tn;5u$}effMH%ip+G=fa7|?7$D&@v!P{^@BfP+kH4LxAIuwF}j zp@#~0;F+=?YV3e#e(^xz93CyZY3N~Wb{z7V1(v~V&VARmjkx7SKNWxN}McL zu)bX+1>?Y>x#!56LUv1dH@^9koqWmH3y{{$a9#8#?s)&i-Z915Z090fEOuZa!PiSm z?0fnsg*3RrZPU3leK%RE36%Ip4pk$Jk zMyV6oL)K5#SqA-~S~j0rO2w%5LZ4ew7peA2o_`xU@10XB(;7XR(_A{Th{B){Me5I@ zSH5S=!#y0m%E0JcOUR(K;5M)rbxS2@=uB1j!YS)Qo;pn}QH-$?m6DyGN-e#3Z*LZ? z#xvR%-0~u>lKV5?>zsDH<)zs1I_EOc4~S1Df;#Hc&ys(7SmVd1Fvp9Wep{W+lSWqu z;ed#NTDPFE%+;RXJ&_fk-aV^Vz36!8$8sapX|HJ~8dU0q4qkP1zwE}9=i*guGn=bl z^>$^R4WIDZdEMX?>P7e%Y=9^B`K4X-E4{+R+ne2~(Zfe?myw7}5A5UC(_D8w+2+n! zb;b@{xL593yDrl%TdIw#$tZzA&G(Nu6{pkHTroz=Q#S00?d3$cwp zJ7;cc({0yhxPaZR9vX%>R!J|{YkKF@JW6Y?X(f4!q3gEGwH?+vs@qhoK^Q>Cx~;dg z&2LqwRvIhaL~E=xx^{#9lB2swHSo?3Sp9{QzDukd8s2nIz)F!p#!Q6-JY`vyN3}Vq z-7*e|%Hg-N2Q>|<(s#kJq4;D}|1N0!V^&M|WUHQ##){ip$jM>Zr>)z8bZr28tr2#} z`&6QGHe<7hkRJ^W$CF|+`KDAJP3*|Ag_I}!=;)<6Tcfd&>I}-8Iy_mV+R8fKSm_OW z?IuKgTWO7xMT^j@#vE?3RWJNn2j9Z`Zhdb^$jBmg$sgAs-eOp_(h7-yE zg(Oj!;)FlU?KgIk+ujm6+xlW$rzcHonRi;Cn-I}@?SM=4g@4OF6V3hJP-6|+p{Q-bZtn8aS{=ALfIGKOdtX=SqKsPo zkV);7k^Bj(peMOL{z80ew_q9lM34HEjJ^^^hdTOnV;wLJ+8i)3(KOt3jn^AqnYLY= zwz)mkKA%R``%_|6Z$_whw;yatVyt$K<1#?G3;6LX&>&hd6Mh2ZM9@!lN>9c)j-fhH z?Ir|4v3ZyI#OdMa>ZC26Wpn8J#!+UvDGUv9l2l)6JBycdcACPvYcMZST3*E2=B&0Y z=9Ew}F=A-xIGROE&Ek){S$FP#kyx@cVApa`(1w=cYmG)l>fNBrKE1l(DV+VJX!4m zL?zu1uMX^?M?MmFchtj@SzUkY=TRDC?j8|}|CV8deGU4!500;tGZ#qHXo9!gr-G_} z*rz|;f%Klc!Gus_#uz8MmQ;1<-S6G&4Lmq$AXr=B5;!~(vVS2RX1R2gyNyCtE(&2U z&n1PAkbI<^yYZJ!r*Swq93P~b z51K$k-CjiEgSO`N{7?d==kXq&uK8$xm$pdb<(?mYu5OW0{h$Z;h(z{mdRl z1Yy~OWHjc5%O(?I@KC+$-?TAGvnuQL_5#X<4;KQAOPDkNB5+Ya)0X#SVk2m|*oSJw zdt9ZR_LRwplrlh_#$mAgl2I$80dz(hOgZ7o-2poc@s9I+k;IO#)fk2*QCzF6RF!o) zf!I2N;`JIk_o?nz3{R*rVM<(5u*)iXk}N~F8w|?_t05{fuD1QE){=CjEgi4~Sp*d! z5fg@hMhB&V7^ZuBa2SJDzl0-3wP~=9n;Bs7_aSA(GgrBd_rB63$Q@ zZ?&HgHU^cLmapi%xPr(n!3kZ1=;rB-U7h9mvaWaPIAU&*!y7zIt7VWhK9b1zCS<&4;kB2NTo6v{@Y>ej7v(1f!*w+JA2iO8W^wf{p| zeuATBrhOg)ly+gPjWm^Y=0SqCpAj{naGU4sUr%~ow;A-TD1|J5A1&_Zle1-YbyGiw z4v?!3Yi0S}{zjnv+YXtk<9XpWG1xYUoocikn=wPdL46il2jnM<{5W1bAriXQ=oQ1k zG(JlfIQDP%xO@Sl$gbmW`BFR%wvHl0FtG_F;Q){iyyG+LGQW5Lasa|z<=-Cui3rEx zCty=a>o>oUbEvP#T0RJ*a)r73y3u>Io?Mz*9}{eca?D<%>9~B8G-w3(UTnP{8wE|X zL1=)+9d9twM|viZ9PAP< z;D`n6cMz_n+0YVs289+)62;-2aN<$*tgGX~krR3$e^3l_YD%3mX@n;82_f$cn?IH< zrVcda>ezYP2gWC&Bw)a-fHGXAvv44Y^1N&X5ggO4N{do&7{?oKMnn~5fvBCSmAMNq zB1K@DGgI6P1a-BA4pF^JBn2WJu)zqXSp&SB1U>W~F0t$n4tD%@IwHlfauFh;K#vka z-IIoAbjr8joYz-p$N7TK=y^7lPrbCl#X?>{1)-UR@iJ}deJlx*9T`DkN3vqZf2}m_ z=g0;-uac7|mrI4(<@u)?_O#N)ju$jMy)Vb+Oki8Z2^^Hy)g6^#@{{clE|$`QyG}6` z3wAFw6Q^)7B+kT@I3fnjE7PBUUL;;;a2F>tI!pYP<++LF>Jy{Y7Y7loMj)QZpGWfN zbvp-!lNN9?XYY7)M!bO^-v;~o61(n`=q2Pn;u(Iy1;5aVA1NW3<>%2&c%e0T33U5l ze2T+a4m)nRtkvBa}rf-tjx@7fQO2p+=HeJKaa!$i8IYrp?Y7KqbB43p28H7S> zCC-_xPt(~f$<}E}TYkMD8fJaAT2$$Bk*sNS*_yEUEYB8KYi%;RE~oTj=80F4H$FVP zyw^WGtPT&0!^76(cmH%UiUO503g7c>jX&V`y-9F4cyE2^HD4Thcys8jRlUJl z)pB@9U3(hQA!8amJoJ7453f2{H{bXHjdL;N&!g*SpMS7D>wF9Px9>nC4-^0N7YI{EmsXU`j*34MlYW_j0OYS&qH ziho@^1YMui$JhD13*@T6Ma>T+uj_m^qxBkh2)qOT5C)0a+Cr>)p`Ph=hrq#1CWD5T zdQc+C7mbl(C)}ZFxTA_>Nir+h-~MIQ92=^W3me*AB|oiCXiRcMc7XLU&;#wrSuom9#!yRaKq^clJ~9DU~}toFVx=#9=>N zPdbO!hvnVFamHaZ=T@OZ+C!M%?@6odY~`+LRugAecDgSaHAqJ8Qy zVZY14{P6Nu^U3;$?uh1{e(or|sNQrBS5N6bOak$sS#^U*aO55Km8H`E_h0`@&+(Ac z9T0LQG}Wot849}ghzMX~n;Y1W6@w3{=ngS6Tzn&n%B5tM?Bc$PuBw)+VWnZG4-j_e zhT9d8aKz>;;^SoDNtvLyo~1?TSs|-Ozx{(21UMPy`W%Q#YkuG|?`S3*< zi2BpZvHPq=d&UL1bvODVy5?%bm#WaHKA+FD7)+>Ear1E_-Cj9l`;ZQ0MWfTt5Vm!S zK*zD{xT?x48RpI?qY(Wfsh3kwEmAk3=0}BS6!?VEqX|D^gHiw|Q5c$l-E}xmu-jnS zF8B66l0O4}G=!^k6+x@yCrzg<>va-dfDf2KvzuM}5Yajl=Yb4Fpz0;s=eu5VnXt2b z&?I>@headGd=#NAY5OxJitZ%QhQ}XYJ)8NtHTL0-OnXG-Mv<}4R@}!l5QVV?x?)M# zRZCTSdq>1&O=0!_mU_^k`S_l3(4!#?PHJtv<;PbC@yQEz=R!;$_mBs7AM&oOUh**< zI7_#S$qjGb+L|ee(NI?*jO+bW5*xPrXk1}K!VG5@YUC#}Rsm*Q2Zgu{2x7!pR1Ni1 z+2DANd!9N~En(PS$hC2wd{6> z!6|%&dB5;F3k;xhR{6*YhG)@f@5emN*ii!k`)~A!_V&)%MYQ$$Lx706IAf>8Ta$QI zw;+2&TlI{m&w3V~8bC&`0cEf#`bo&M8R!&@zEidK9 z%w^5KXEO~ZDZr#)EyK^^YzBuq<8(s!(}^-t)nqz*d$1hO2$>lV&NAG@z<8pP&v2!) zDy9j$cR2V22%P%bSK<;Ov_K43~2Y;9Kvw8NDsw;cg;AMC#Z+wE2j zp%Q&BPwm-FB#-;gL`wl z|CTtxEkB2~Dcq)u*j*b3m`k%+y#ZyOYojAJ=v3Om7Hv03Slf?mG|q6Dz1alezxhaH zb9K~VMkF!!cAgxm6U8d)u`buYm9a~CZR-qxf8c89%*}7;Rp9nU-_(tB8Ehksk`^eb zq<7CeN)=P~$F-mcL|z#^2M3QnjoNo*gQHY)**ZVYM>nz{Au|K?ev96%^rd%`LDiRf z=GN!N%J~N5E(;Fic{YTTm6YcS$9m>qWW`Qp&W(`q4y9*>C3wwwahRlc1DpCc=O#S; zO?(rp&tSAC_#?R|i=&$J#&^vPw6u+n*rgVod6D-Z-XZMtp4U0KD_Ud}$dUZe0w-e7=!JNqD z@E{qVL{=ycA@w1POIs_|J+W1-*SabzejkOr4c1cTcvD^J>`x;({fxhq1rW7S`Z-rW#u9{>Tchrp!N4T!9JtEsF%fyv;60l+`O%1*~kQTX+U3wS()Ypf!KIlK2kBCD!~^n_Gw)1 zVxRNK&eX_uBTqa9RG&mAX2Ki+_=%FJtU=kGMQ3eg&fGGFfIGb@Q{<~ysd-yICTZgN&a-k>gtuiUx%XFb+t5Df2 zG>j1eE{aMh`vgR{8Xy=^gLofb4&Vq2Z5qYJNUHhdGUqrCuZt1-{ za6d7qb89KuKu}*7RD4}vn3J!C7rl390J=qNA!VZMSj3$e5%(D_%$Y(%6^PAf2249~liO<3_rO{FA?7n4| zj+BK!F72!vRowoua#2>3oi>Dk9JF9>?~R-#x1?xZ1&mU@j@muf+{CcSvA~ytE7G7f zIUhARWZEBo-oj+cg@hV)GKFo}Y?Sv@xJC$$F@EVWG1}(B8;XDOCPID!{i50zTb47g zuV`^Dcete3S!G2s%}=uQ=VZ2%TrP{Ggd2%)$LpvnS6NCV#_POjlVUln6k`EGKm=|{ zMWFV{N(QA*O0kF(LVe9~CRq+GFJakp0L)@v2V$-mEn?!wIWt;(!VsIuqASH^<$*#5 zEGDm%8aj&%#lSdXT}Z$C)dnspdGZfn*V3XZ1w*((X#i@#w_G<^y?nj?Nd64?kzZ_U zMzZA$>m4|5!`w{nHjFD-$AAgLVKqdCBVG@(PLzUg)0bBFWz+Aw=vDUg8$ip(Xa(}T zNoJnwNUXGCBj|u+iKIj@WpwRaxr86|g{05FER)qN56en0MhZU{c6+dyEOD~AvjXFMlM(8n;A5yVF5Z=4%d(dZ*mEC9k z>Df#_!G8di=Wl;Cq3-{Bt3Ze!LPw1jH(E!iZR6~sdD_cxBd!Med5`BnU0tG%5%yuo z0~(=ZRG;yXsM4QRoOzDE6UBZ8V?g=fL~{;2bBoYTg04=>z_5MP+qZKF^J_GgIXUiX z-G}k7m@5E+_S9P?iLG}@NuBntnQL21xck4jid>hj$B7l>c z){p;#QCp*;Qq>3QNtn}v_OKg?T!I#)Hnrj==V86RL44X2ohorXLKQ+eoi<5hx7M`8 zO}6m~uBZUNaE!^ME_pfv`-jO2cQFTP2bkQGStC%RS*S~l(p`T1K|K=CK`SS9JduUo zUHj5~@sMap?~eOU0+EG4+0>t^B2ee;qy~0A0}5p2p`cmmT7JQY8{b8yHpGRhyEEY0 zJMhZ}h;3q+rt=m?C^9gFfpkPUfj*m?-+dqY37*o{<8J8L9Y|+@JU^ptP}$ve843r; z(UXE2##G#j(!sgcuY9FI<% zBR)LoVfHyafqSSc*|{U!+4rD`hmx}g!_6hRm07*c$ zzwWavP)(rd1dn|Ys~%0EXM~@76R`wQeB$VSjb{^@jZO!&GX0-sg(^LYW>G(Yyk?m% z39SW+=2<%8?M~;2$}^jJy`VOu@g*(ok@70Ksc9miK6g;bzokJ5zDqxnvIzfe!@!|% z%X07Xa_`obn@D#WCsl<}$nG!yh^}L9j;Oo1Q>7zkXp|0)x=|WPiO$h4iwLHgNBhOY zh#ePg9kTYWg6Uo+%SxV^0wPdJ`A|lKw{b`Yk9cR}B~Df#X?p8&@pv){uHpEYw9rB`x?weeED; zw~@|>P0RCECjgM_of7#AiYmj*GVVj|?>_ba$cg^9@x*mqIg#6oiBGrU(~E2 zJa3ph$c9O$6BHdbrj=4(t2$z8x~l4yIMCB1UJnNdfhwpfA_w7rhydP^u0c>p#K~iO zJ(5TSWVtzqxWUC9U#BR-KXEP@VXo8`(k07pQff&YMNLYB z$G8>jiBDK5@9AxA#N6fKN~gsJXKRyUQ4cml`LbE<~VQ>j@trH*V+#m!$+?dj+_ zEzdj%m&hX40FfKUQ}hq<_0TybNI2t>Lg#sOrZ0C+g|0dX&j=Dmssk}i(kckG?F&8D zw8by$s2C`$?&tw77v)XHn8n8+1_1dn_-ykE(pC;>H3<7ON3B+V((s)qOUSOp{Dwl$ zITAb=BUO;V(`A}%D9>FuIKtiSZWK+iry%1EER-!&@o=sZ*436|9n;NZxO$KeSIUV| zeXJ`r)_^)dT_w!3L^Pg>#di2W%Ez7JYM$lQ8gkXebpR=koar>`bXIVmfgS8NnW{S2 zt%mL_ZQ~W91j`O^66dFpUaq<}0PwZE9i`RaTZ>iqo~&_18!+^|jpLR`+b9vreL!gA z(pI@53`GEh?jhF*L{09Q^x(79q>;W*D_M{0vmtt-1^qZnjHc(PA1=)0DHAPaR1tJyC!z!TT5MPjgKOYrT9I$1Ibs>G z>nQ@PGAi}B0rqNZt=A>3fx_KXA@ud(j*4pd$<)9;KvHd)C8P&Ky*8P23B;8;Yq8_W zn8vzMBRQ7?h~dse&tiZMe?E>UPM@drC`W=6){4gQ)?UOyL1&oW)H+VgOnTngm)}3fmUZ_;ZW?)q!?uDPj79sk zU^IFX5u|dcb?mayT%qD1Jk_Zom3c%h(*&Ku>@X*EYD7N-8>xL*G_1T4z*P1ZMz(b5 z(CK#-d3{eDLDo$@K1R1MqN#WcX~zoM&RWeI3exnsIEXGgk>s$TW>fPOwqExJ;z{&a zkBLtTJ`%Jp)Ox)LhF65=p3u1`UE0i}$K$IB(ZYy z$estolPDdZYQ^>we=gWkHw%JmMV!o4II9gha~T~38-y`}{=x|wBSg^ciVCjQRPlgK zE<4ecpn~Y`nduR=maEoI{3CJ91O-PL`S@z71r~JMz&rb^Iso7(I?8$4FTja z4!`4IV^6eWI^_xWsVB!~6{&Nb+|Y-7YipG>s2-yPA%{kY;UW-61hJ5DE`o4Dw3yKa zh$@5CHt% z2Euyg+~XS4S@`Goo_(UCJRw+Ypg8-F8WxV{08n%6toY8IPdaIIokYGd)$phK{BrS? zlzrktKwd%w)|JeX@1$!YJz{v~4e8^Of-f)$Q`WSizT* zdv=1#lVgw^)I5?yP{dUl&2JltvLjw;`lj8USlLalG{u#RK_GT%1vT#AlW}G3(=~(w zFhr)Wf|RUuNk<8R%`6D~lr^XbesD>9yG=9u5p(r~qGN3#>jhd%NNMY`(b2Kk)-{TD zcL_B8K-4ujZ?Lu++@cSAi^*_{BE4bT+VtsvA(iJn;r&8NrK~`(A&x?MoR)OxRZ=*$ zW^t+vNwe0$FdMv!JjdECv=ZT&JKj2zOQ`yuG@-K^Y+EYYt>a=5+&)`pI3ua+=JO`- z*f*s2mO;y+X4LA-)O2_4tRw{loslkiCdihs9xb zIG@}N#`WjJ{n0~z6h5G`gNJK&HS~o@ssP(UIaNl$XiS zhi6xt1wVd&NZn1x3UD8->$|~i5-xF_+c^SqFU)enZqUhs-Ix?+>cf?_ZroX88l|BZ z&RS5TaRF1S%@1|TPp)%F>s!ZQ-G*CkY*jV$ z&h-g%Jw!Pt4+@rrQXE*q82s+wSsqRmp8FJm{-yiV+hUnqFvIZ{-d0 zNAX;`3p*_D`VYp37l+?YIuC>K4-Y4I*S~N)h7En=k-r^)r4P{E+Oit@{l}I*nuzA! z11X^C6VL8Flxgc!Yhu0aKaB2=#!{BV+7FZPx$&E{UhfuT6gG-j2`=bWP+Xb>Cr+OdWip?0%<-F>77K~g6ietU4vSuK;f7lXkU$NyEEXnPb%g;0 z`SB>03NT8uwP_!J!)eC8ql4?*+(Op`aSbeL5Fn_^?(dJSIda zpbxZPYKg2hrU4oY6^dburV_5t9<|`a-1q@URDcn##>{YtB;whuv3xfimhQ-Efi-B4 z``R`NcABX2<7Pk(w^DlV8$ayzP!D0_SpzFa#4;|9uUnK(Oz7w>W7R}ik-3;s88WH; z+}2YFE#0f@z?9})f0LSc^E37%o;8(5RAKD?n!0YVkvQU}vQ5=wRW~i_jYDXu{-(v?BeB2x@Z@Gumu%!w+js2gyP8;<$GLNJ zBFH3`aP)Y7Ys&K<$SCw#APSCXUq>$m%j68ZQSgnEgQ6j^^WeP|d2wn4$iq%&`apxrFksd9ay-@7uee?# z?uN-s1S`&$FSDodqD)wP>@aJ@9}Fwn8fSTN#X)DFdRgE~FXl_Y6iNpiGh!ZH*Ul*G z^$oy`tK3@MG{}{XGGNti;p7oJj3+h|^(|HdhXVUtg8ts#9IB)o7N^uIU||8VnC6Ww zEu+J;Ae0II5_#mr2N0^G3%zAvs0gQwE#p$XwBZSEkQEJmMCnq&Er_=+afd`S4y@Ca zs62zHJbMsL_x7gU?nXD=BpeMrVymk}$@G%J4E-%7(%M1*&UE!kKG{+-j#g5CC)F_? zH&)+vtN1RxUF0qy(X0qsW&}O5KHWS%X{~NIW_`)z+q7y%RMFM)W#_mOG8i2VN$z=s z$L0wov@p@|l@P$L*UpNf?zWJRJ>6(CPM7mtYk%{N33EPoVMJ>eiE-;9#jhJ2@k$(n082`YmSB4vIqr{rHhOBs%&|e%QM2Ri##i~)`^jRtLX)hRp-fIYmv9xd#bN%whq;)X@son%7t#u|2swL6}iz`jd_a(Dx>?-IRm$*w$sN zYw3_Su&p8Xca45?EeZRUiioXO?xw|wEM3aLX-)(;ItUwOGBIV|)2|-nL10vjVi55v#GBMB!*@U z*uRk6jqgvG&5dC&TVKbN)-il4vfAv6gtqN1A62v$!>3&C-fiWGdMU!sf}p;aRPvTM zbR%=PR0JYyz?LIaYn>!<<@Z_|8%G&TI!pTbU@}a{8SqaOql3^!;Y7_iAtFqgjPG}= z34NzskFjAHo^f~+h)Nj&28zQm5cwGP43nq@dGnhZl7W`F5&?$oCvN>C>a9MZp?+;< zE)N6;VCCJE3wq2q;^H)EGcwr<5Sc~rK%yfgigYXL6xHTHoevAdwhJEzunKU85{n|$ zZ1Uk?wADtKiEfuDncUsJ%=gg_f!O&$1eOo{l|BV=bE!*EvIt?YN zI&{;9q@#xZN~X%(c<1trR1Q*Dee7c{EzqKA$jCZ#2Y?kv`7MO7h_SbRM*MY=QQ}pc zoh0j5Iym-PI*?hj4*u7#o&}7X@9Yn6!ZnS1SoT&%Bj?LimF!)_1+3fl_P#Y%N3b|* z7SVB^O-nnbCAJYHW~VIU6NI2=yu+S^j-*`ri7r_Kfg0j*ajasGgAGs1Pc<;IXXDpC zG|D8K(eRV}$;&U4q2%*Cp22b7*T8eFWu=ScuLIpzUj_)44{ad(lU!+Hz2za`7HN(b z*W!9pTKd)m>>LO#{%xFAp-R{Go-ef;M%}Kem`eg6xdP~7ng_Ta+hwm1dwaF=5Bux& zPqqCKi}BG!OB}fz4=Iu(LnO?EabyyvA-o%oLg}#Spr0i2K{}jZM$5r$xS4 zjA{$5TBM^ZZt-w^!^&+J)XOHf&d*JiIVB&^L{f`A1@s@Bz!Z_;aG|M~LQudOzPL2X zf}gos;y}kGE)uzFV|-r=@`^8;XBLOMWm`*@*!%Y0UI#0n*_aYSO|~us70gW?sd>i! zB-N#sF$qc7@)=sCbV3$7$Ylv@1^vn~14-$)osM)rf^aoT*p&;UEp!Nc(+2nTi)`tb z$$j4KIM>{En|{ta8X=cugj=b=fmG-79>)&c@NYB_pSv-+*-KKJ2z>0;*6_u0O?_t> zp6|MTtbY+Dp%T8!IP})8Uby9iQfW>gL7k^3tAd$+z77u%L%U3>Ezz$MP04E^9)b2} zycN+gNXJJ#6FQZ+?Fj*7J!)yQOcB(hZKlmw<6Ews*)2+~Ght&kz+2Tan!O^%ayli{ zev_EnHbhi(i%d4rp#pF-H8M9U=xZo1Sjt3O+X;KIIm$vDAWbP!?EBp`D2ZOjz>lj9F={xN{IdNq#g=CShG>X`;*6GhaneDaOzmpKN@0c7PR}n7@7z zyaiAdji>5tyaju58ke8MRs8O|#Lfp{>}G!w5KZ{}E4G{XTzsm2zgLP4*HWwD?%`&A zIM(S2f}njD?T;UIe>-s{;J;~6YOs*d)awa2dXzV#;dH%@_15^E@Va;Qyr#c_CYW(_ z;F=mCe5x|LmPKAwtoSe^?3{5RXNYV)6??%00`}PF4m-?(abhR!c;2X!hRWG2F*Uz= z{qlv>Pw>G(I5P6hrw$0qQ$hQ#v-rjhNNHsqMi}dvcHJ zKB#T;N%NE7`cp*&DNm0_x7GSpv(}>I8yUKdt6@XBa4Vrvj1 zwOpLb96oG6Wn_{|ovz%u_}=|Wz7d|9L_kLA1uD!`W=s<0zKxl#M>_EQu>yK z9=4W!M=F4f^G(t1@`a3#wj!lUtNr=9-v&Y*EjeFh33EZ{%>GeN4MzKhMW7ZUba|`t zjR^}EAck@J*&hE~O*@zHh6c;X1;`lVnQ6|%wY-nHSybK>y*Nl{oR}rfCNhvw6DhT= zknHC@ef`{`$m7fK+n8CAVV;}N{vaEfbyaxHgv2n^Sl9~a4XDstYuJDQxeJ6^+)AqD z>m>#0#L6WVgxaTuN>r(z>uno(hQ5HY4qFnBE{- z2fo+&Euun0E&Z31yC(Q1A&{SrkjfZ1aKCm+qaw>ZSyDBvwmLx|8Atc}1arl`dmN;uZOvc2bYY9tgwh0uV~)s1+38b_Bt!vf>lyU z*EAvRXcv2X3#yvU)vu?~biKx-(B^)NsIywH2XM%>*pG5JVDNe$;Chnhg}b`wOtt4M z_>A;(^m>1#bTxBGl&ozd&Pl3tv0kHMXhA|KK3a5q{B{PxU5L!X4hzm96Dgo@P(@SN zW>+yS{en`2gd;ZnW=o5_;x#w-Ud@P&qHs;LrN@N6m|Hg-iwE=J085Hcb+XH9j@QJKu zWrbiwk#~&S3Vg#(^;WoeB63HT+I8km<_JmgI#Vb`PSQa|->i_llEH`Uu5U4E%q+Xn zxIrbjJk2lK)Fl)p$|UA#I&0OT7DvOrQVd4}I^8@m zGt~-g1@Nms)Q|GpjX4*}O7hm)f6|&gDzV|vieA-?kLucH0hO8J^E>OqeX8Qe_&i=G z)3Z1zr$xG~==FkjmGs3Hjt9ci5rocGiz;0%lIT61dJhJYHg*s!K$3VSKVo}O`CcU_ zVwx|)@jdhM0X?4-`D!VPnJ4Fqs;GTX0fJSfoOFv)!}ps-A5?`ZUOa61=1AEahT}dw zXnNkn!6tJzWUFDx8)4|4Y(+o2AsnIfI}?P?=gl}kqn6M>`ZGdZ4Uw5NR~s2k%8JFX zSv}}#!x0&zUxtBwPz*ELSaSbWxO4JO7Wr|!z-5xh=D!lXCCzt1uvyUAy{ve>BAKQp z337To$H=PYIicqT^gb7+90GvhLTAC+h4GBPIOWF~L-wo^r~H4EJlE zyW0I9X&)Sf+Y^P;G)jD0#3zj00_|IlAuyDH8EiYFU(vh~ilu|Q z>S;(8v19iOWBSff@3cBwd|4z)y)_RyD;%*4w#t{De`3frw9n(A9ba6FOizQDyIR7o zYf(Dcqzp^>VbOzwjVfEt>L?KN##dO6VID2VGwuALV9gt)ahDTu^{_h#SW`zisWOv1 zjT~Y!Z=8fV-yK(eebRAWNkr%I#mW-y7laI)yx^Y1;|2F*s#j2ZJoEx%T$%XLWBFOMySvqrt=qK< zXDo4s32VmAy~3&XCb`-dN9uSy%U5NxUgjw-#@AD6@JnS^>lv*O{mGZXdb&ubZ}!D8 ze>47}N4yc(t)AcO1jJiea$x(-LBqEiYm$hv ze&J|tg08oi)r{qga6%>YIThIZ$RyzUORgWDjz#VeOt2E-`^`c+b%9_T@*#+rbX}o( z0U=TI!X`uMf=cVM>e1>X>?hcO}v{Li$&cVmdfJNGfLqir$t_HL|ZGLLre#) zm^KBzP0%~R{b0;L?BkA~kNAfWKB7x(OD^EcvtssKnH8ioS-3mhsCUt|JO~k0rD-Kp zbM5aQO4_x)IMI^BKsv!^p>pq(8NcW3fIK&G&?_n%Mr;wkH<~>L@PjGC(AJx2fOmnTA&CkS*-!396R%S<5Gm+DT za2@CJ2Br{dMZ-*BO+vXsLa%L@3fCz?$SZw=#{zw~B!I?ga;Hgns+_F!JeIWze@TZs zQ!>6vLJ{4J?s2{%Kqf{Q=fY}dATsI2CaKGl-@d1+>4mR4ZmwIG`Ho1?CyQ zx~4@S78;^8gw``6IUV5ZM@y}yzpVGS(-D2DDpUlOUPjvqE z?jAzih)WM2DMX{eRa>-lydMpN1rcs=enjwkP9U`CX~3s52-Ff*mGkAYmxuu(;djw9 z;7c8Z`3RTO8v;kn-Mf$%Afl$t2S_h#K9AgQ8X(i{5I^2NV6q$hW_eQIGFOt)eVFDB>Pg|k?=OO&T(frh*yDLOLOxu>i{SiObLdE%{%rL zL!>F~$wqc0Kzg{-*J!rgCxkJ3(hbkkOPrl6mKRG(yLlzSSXpfYc5;+;4(=GEx#kVXiOtY_jDc&b=SQg1AYQzK{0|p%@ z&oK-x4Fjh8s1Q%-77|nGlLF@XW9km92MAKr5^6s23H$GVUFAe6Lz$3|?GnOV$i!6F z(jIm7Ou*eYo#ClzW}G?IJv%!~W+?)<5qH=4rD}!11TLg|IBSSv zz>igmL8w)~xj?zusS{}|Dl39ctm=56jo0M3Rva||lo_>JBqh<~5z8y3VKVGLC6!J% ztK^G8XNbO|L`Dmhmz7?ry}kN#ZRLb+iIrC5bl$8HVD0*nIvOBAMrZ@9(op&)oVr*3 zhPh$Z52Jxnn43ak1CJWhXj(J0WdWV?d*oKDYu&Xz^I6YwYT`;Oz*P4C zgvzC-_!UcKjIbvjr&vLDCuuO94l_zGC-6e!PzR$UU%HP_S_uA>E!LEi75Pd<0iBwY zicmY%gu7Yo(mgj=QQ7!i0}8^aU59*QMEg@^PVyjm>sQ)jY{d>nnS=(MG*-1;)9$Fp zh}Nq6iAr1(H+Pn3CA+559V@yu-6J{lzE%yvUPv=Yw-Or)GHH8PySusd^X)g|YSPgE zyK$)M8uu-uyC~91ryjT^twGq|YK;Hnysv2I{3rzaa_>52J{+T2J*dqco%X%Sa#s_$s9O(S0E zdiA!eQhtUoK{aPSvZ(de=>j7Z7pSwmDt<6FHa7Bazn?f9D7=XX4NIM)aZw*{w#~IOVMOz ziqxLfWs;l%kw}(Nnk%vFLb)NR(zE1s6`w65Iiva!^lOG|Ch1YdVg^{yi!EduT{{k& zLiJi~PdDbNjc$_pw-7+G!0YsE#bbn>L!GkNHXhxEi{VW`a8bc*X;-~At#H7g2K_>a zuui`GAq{Vj>$ax1=p@;8+bsPdow#L|-`1P9PZfx?^wzj#xQ-Xq=gAdLqGLWZ*u~Qn zdwpS!uT$71%?|PvJNZUVRV>t3v!sgC1yZ~iUlKFUADvOYDnjih-|58nNU(C6dV8KG z7pj#)|31d1qx2hDgno36ii>1YvO9~En*PovxsiN6r$hTYo8(+2Rem;e9{8|9x28ps zWZ&5&e<8QNGx{o5!eIGe@-I%)HbySeRH?a(RDMvo{NCPGr7f}8Yw$=JG4}LCjBJt+ zk(mqgy{qMY)TnE)m%#Zf{kZ2NV7L< zV8k^M74cc%->m3ySNf0)4)-7KpQPdu8ju&C>j$hkvwncu_EYhiUa8wVIH4Sc;~%2Q zI-<0`)ayZMS399FZmK1 z-V{x)VltZQn`qI!3aqq-%9fmRx zMr=N`pfDT5O|5_75kK6M9dvp^4j@nbb^`S07iA41tNK2-dS_q##g3}9)@ej0^@tc zqLNsyj9AJE%d|P7ZCOQ!XH{UW&K@j>a|m3cRLp5d%=mWWNM64CE5QcA0lXAwJ&;&X zbt_vPdw)=-qVox@&74P9DkX||tc_&4gXREb?_%)+(Q4tcv8bRZMU+Cbe3(OhalHm&D;&#LOnDM#yo2U4Vp>rPWs10B&IpUf zTtfP~toT!BZEAJYR5hKDQtqjA@xRS4FxkP2{k>^8t zat)-+81e8biTz_vngQm`f&B>E1R7GzE{XXnicg@033V=K$N?fJa19is#O{1=kJTX^ zI9o`Yj%>>+%MIUFu!TmACg&aET8>p_lhF9Sk%she{hW=zvSzECrB60%>a25MJvV9) zS{!YyenrQcFZzmZ=r*7zx=l9z)G>1Q-A7IIZIk3Pj#<*SckUPpyZvf^Er!hdi-x#e0=?0QDCVX5JcWtvxYnfU)@&DA1#py@3)ipKpmU(Bi_RSLb} z51cQJn;}5_#+h2N-+%DaHJ#Hs+t+f6IRP(}6a`VT66FDeNd8bso0-;XGv;Aps%Gbk zbM_r~wG#J8LqOK@P`caJs;zG1ycMTjGbdITv(=ph1c8sVs0XChq8c0JJS?db`Cn`_URj201E!{KsOGpLf$fY_x>ZeopfVfNM^ z#t))=7=zl4Vs#GXN>6(E64zYpE+zwjQfD9M zXGG8?v)9rfE^ur|o>r~Iur^23gclpzeZk}?GzLHm5;4Xzp|66O{_~;j0q4d+UCg*e zH?#JpW1y-XHg@QksICvH%`RNEfWykAv14i5`7%N|E9qh%%pmuBDcLym{| zQa;#xCs%(L#Rze(>d<+G`%NmGuV5N($ia9NcRVk2;!S^66Bk|AJlDvZ#mWQ_GoJUO zB)Gw?r}m1X3KA{1>z+Bf)k@aA3a(em&TLwiuw{t6$S5?;f2RpmqZSvXkIXYmYX&jmR1#k5z_L>Y<*3Y}Ql=s>_*%?^(!cpw|I$N%o9`Kokm&oJpq)u7q zE&Ap1nI-F$!FB0IvH=qO%WWG6wtoP})%s$%GS-;7Pq62QEfGz@B3(wFaw(4$z--m+ z2iiY@?E%}OGh@lKRj&tzjIH0YT2liQcp>|0&3;XbD8v3y7FM%AT+kec-H+%$XR-SV z8oIpVO9y!3WA=}lAU?Uf)GNnx#hJL~{j9?twvcPk!AgjF%r9RvpuS2L=?%W8+4BQ@ z^VTuM4JA{Yqq43q3=G{+Hs2s>l`nzxW45k^=PchR7i#w;a{Z_7u z_kO=m<$i|A`?tyQn-tdWU*td2-?P%2h;Lf#a2OO7);8$jb4M=B*0R9XC7qgezhbpl zQG=7Hz`%ZE)rE$JZ;oVW^ZaXq07Y(fbSMLOE{}e$YF=nyx>ivp(N~=$sMoq-G7j-k zElT~WemF(iK->zoixGzeCLHY(-8sHD>H2g~^@E_}7i^Bsw&$T!=chHxJf&wHNpE<0 zl(C1Lk=ig`s5hJ!jNT7D86%V3TnuP)?Fj&VeZitJDb#t+ixGex7Sj6*l}M8}er(Z{UR)>r)v&4B>U0 zrDv=q@r>{hCs-F%s_`vWXZDepr^QRv4LmNA%TJ5^LZ@G!63_OApDZS$C*quw&&*S9 zE0Bj0CzhwN)NG(FmS1q{=Vu&v38_3kW7R&5q{8btR)!|OEP0JQXro*$+HR2LRq*|W zIQG|#X9YKoj}F#BcfI7%@&BzMKUq^(;#yE&j6^?|ZV6%u)Vyr$<-&O^9ph*dRhK~8 zqeb;nPFjJZ3c$v)0o=8W(r^lObITk=!JXw0Wckz)5? z7RtsfL@aiVmHW9&DJrZ+Q{)&GR1WvT)n)9|e7fb#vF3bbYhg92v8(GYr+@o;2DQa= z*Q^is5O%qx7b}GdafRY#d2tpml!q&X58U~>;zD0n5`XcgC3b35$49h`>^GNQrR7uL zj)c)8&3GuCe;eL>>`5|@=mvRqHRI6Kh88t(i{3yc2=T{iind+eu>BRNdsat!l}xMB z1S-S!qE{bd?Fn#g5h}sm8hx*)B`lEospg_fEsrS!8CBWRQSI5L*xUO;-8fKEq3uGv z+sT8AN_)C(?^X>395wVVEy={6`w4iy# zKRVzbF}|(Mu0`vFgV68tEMMDi!v3aZ#&54=6UfufkpwB;kysbaK~Pfh9*Th27UCEH ztU3Ted5K{cw-(?T*GAXyZQo+mn`xW09eAP0h{7fE4A)92YOcn`1oh*BiAaTU(Y?pN zeaB(aM!Yg2Mve1q*MM`Puh`*r?M|hMZf=&BHsmZA2`Eom*QVQ6Ip#u9P^k?B89zu_ zZk3OdaXgt=NP|x=J|>IArnW?C7c` z?tsYN+L$u|z1d0H2lBQ;3^she>DZw@#{gFCEWs#1<{>0IB;NZhVGLi2oqBWwFz?}IK& z8Kya{*IiD2`7GGK=a|lT{H@2a&o84bfD~qdpwR~K`b{N%OB*K<2_b<*dI6D+yVh{2 zwT2@tc%ClkopscB$MN!!65D$HTTqOw+Zp$yxJh2y&?jZ}NON{i5dm{_L%yFTL0Ai6 zkS4Ofth{_4vjICjR%|llFt>SPX@#4DUHnG-qojl6H@JvnLk?RBLlmliMjgn$Wx!4+eXC@hA)Z z7?B*Cg$ANOQ5LwyRcggXqqw7`Gd?Pf$5LtLS;1ZFvaAK(M3kas<>V0qcdaymo-p=+ zBNd59!Yx8&1MmLY!bNeWX;OxX$$Tl#!Y~tNGD9=ndirP|>ZFjf1b=GEK^2}Ii%csP zRRkIhdWs{LW6Y{#uQmb(Vf8`|z|6f_EoD~=Sv#y~Y9^r8Ia@MnEN{qkZ0Z^rO3htF z8qrtmX6ZAz^aDpZ*?;$i6~r9}Vd+K<)7gq7fVa^pp8~QMyLAG+rq9d;GO2fuAs_@r zO+t_b6Zq?eZ@D_1o=cfa4(AxMKm>+@c8>~{0=yLE!UQ%w^|NdeU|zX-S}jhxox@=ATlVvxcHDT9v6f~Xz%IH%DRj5Oii?r^X4h5uwAF5oW4Ng^g^2r>9XG!53RIw5ouRxd@PK9(s z?M?t<+Dtb`J#+S2wewXXuJuV0Uei$$j$6Q0@i`V1NZV7*yjFgu2ryx9Nr05H&154~ z^Av+ZZ);H=!K~rBvroByg-hvI~xSmhs!3NNqO- zg!LJ?K~-1GBPG>J`5sHly6^=3dobTKFxjVCRx8eHLSHr}x1O%xavZdm7YsAGXt1JT zdYLzE-A_F`+7GDzoD1Fb*gQmizC^a}n$=I@#bjRoI+PM=V0Vh9sw}w_2q)s7k8s(U5L+!L zP}-<-8c|5r>)jK_lKBK^5BDZ%1~+-I;++9so$T$Muz1GGDT)C8-uzY663r!`YMZ(2 ztD+fu1;Xqe?DOUe;%CRFCM|68J1719M5tw-a-X#8UUqtHp~cERt!H6>(c%uAzZ=ns zLdxl>f?T7V)1Mzj^3~x z{iPo7KD^e4a7gUKE-|o4BPb=#{L0eEm8QaFQ61{(sU=h?--&%n=XE}xj|xLtNBvOW zbs39E`<5NL>jzA*+I z^%M!4bdQk+Sw`Ok2y>sq-KiG4p)%EEBkqODhmdkpmu=5u!l3!hcDYQGD4p625hF_k zF;SP#qOaY90~o=iGVMc5n7#u}HlkAIzv|sh7k5z;(N{8d3{S2vOS%zQwa$wkVjbjg z#e=BGuG6r2nx*9_BkP2zQ>bPcC_;!?ypLi8t>2Z=n0VHlSVplDp~|UM?{V|f;G}9> zq)pKi#f<`>B$dI2=-#x({*dOrZv2l#`G%{MG!;99-8o%yH0?}?wEiU2W2Cddjc3 zuXu-W0>}V#$wh@qY=nD0?V2wY1EPa=K@I}#v;>5aE8ij))U)fGNGrEZsmR=^g2UOd zo>p(dK_=?6JgHA}LAb}T+R_`V0!a?YSqrLzwPiola$4oDWBiE&DzS5#$T>~qoGOD5 zOl6{`Qflt|oMERDwU}v4cp9a+t`FCFZ1#XKAGDPCPIk8T;NT_=?Rrp~pmvL`2u>kM zN+F5O=3TaqFEoe#@lgG`2UAT4N6Nuz*;;}kWbHb+a(8Pi7Guzu16i@F5nnE-f1l(R zSxAKxNh~m_G#|5et34A+2cHOxQ&?>@_{(vPfT!}%{)mGgpYk}p(6_MNJ3^LEAg zZKSS8nP?2n!;{bm&3YpdZsJ_Fg5f)GS(stMN`476GS{0T?HyJZ_m$qb+_jkjE8=Gc z;Kwu*qXg*$ek@N`vSo)WuOU?32~DwRIec8O_nexPI~v?~Uc1BkE%3;1E5?biqTC46 zi+VAsOe~yZKO$JjFTxM|eOjWj3hzy*n}96MK5rr1KCz4~wntXHt{LTmneLE}}DT^7t;2mUq(Wh4&VJ^73L3{6QtX zFh|y*nRe^`GK*M~h)S@_ILfEbq$6n}IFm#<*hpr=zonOf{5tX~&o(B#(o>C`_(m6q zY-@Z;Gfl)IiIjh1#BKbH4Kjx{8VUKgDgYeur#IfP3+ssC4+valKe{Py;8wrtHw%_M z#;JW@hC%wZ{JDppdQ`=z>5*c@vb?3%CGeFi^qN^SZ5vQ-JabaVs9~GMSM!edXy9V4 zgf*Hbq4CG=2F_*7O$pAc&jf<*rdE+Gb=B#LC^d>?Nf3;R&~Jnrpn-H?SD(pBPD~}r zB?nib!zrQyB_)8kn4O)+x)Z@j#kOVARu4avPPZF(o7(T33yjpd1W_5@kX?^xc-G=T z1*b+=r!D+~iu$O^vON&1f3|{L^BRt=iHrmgq}^6fTWLTSrQ?DlQEWRE$rvc7N%KP2 z4aCaSG~lVWlF&z}k13PlOykLG8**fHIo0R=!;8-TNzh6pu&NdxCVJV1QhTe`!&=C; z2Iv|EbdFfHVEr5Z%OWwQTB&Lo#SAIxeU1Q*Kykl&M`a;4S2w@XX^zBaYi)49mQJ)d zSkh7zjMsRBEPD|=ODwwZt40K8=_-52)k!{0T=Y`g)Jn6(4b!oROLnE~dG%0L%WY?< zO|&O90d7Zo4WFvFqdmtaYAmaNnqJD&aMDD7TmAbZ(ccYtEs1Og07?n_53rg%xQlX- z*1IF6ij@f85donv$DUkj1QQ=iwdr=9mIk7NJ0sT5^_+`1RxNy%fle6ogK(Z`2W#4= z1Py<=oypF%iex_Ohur~ijGCPg7wSZ(R%eT+dEp5Ls8GTcKJ>6`wWFO61xG7HpZGTn z$@N=VGlXPTI9%*)rJ$mY6-l1hyY8D4Pn&IV7S0?6qm{L0~16LFd-yJY2{sLepU z3MBhkM}b3mb2eV-s7+~figO9N3m_w$TLMCp9YUtC;vj^noUtR)65(zBy0F^pR5a&e zY(iahBKb5-u}#Zp#sS)uPTLkb6{U6D!-oCJD)Il~xpi83%k=JU*`?QbLtqa*6gVmP z|J#Bv{n55654!_euBb2`fy^uMZ?H^~p>8gdb49C$uH|%FqJ_lu@kPgik{(*1S2Bj$ zN(Eu_qPAt3GrP=AFKf%3nPmoD2wx93$7xo!u=SOVq)#779jey&D6K7T0v&6)w(z?q z`$6PAVpnv>TvKIINrw z=QT6|yc7v}PL$zcv42u$N{QaT36W@b9Y(a>*y8bR65*pDg$A(WQ@|N z7}X(A!zlI74+#DUEFx%Q8`C4TQPNS`!Hc@WO)3>`q^3yP=3hvO^{|pO^B(Tam{g`X8+?mYZ+4&JQT*9?=D=@3^n8d~=*)KOO7jjB}ralQZUvMw`0 zxc2(QmRgcTx}jDLty+tmF-@KNiX$a&>Z%)Qnmsxy~5} zL64eQMwb_E;Ckfc8KD=st)NE3c=$wd=qg48|EzU52Mz#n zXMbq6mhu26w!=F}V>M%%UJC#gjf?d`J?2C-RIpC#rvIiwu0&bP|hti)?+%1RV)0H=-g=*7sJpj2*imB z9|nz0=hILU)|lzexxfY6%>hBq#kP(Ch)+5~%LCc9C>`Ak?+a%Rq7xg5j`D5zqR1&U zYK=d3^49ocQrx2>Sm-$DI;Mj3wJifB6oKQcYg|pj+AbEXEwbDST6HdcZQ&+*sb^nB zmom@BT~VU&4nm(%aUCl~X-#}fQ=VV=g$Wk7tVL&Aug`^E@M-6qLpepRPpM#74jVZ$ ziOATyN;zDyM}@loAA&Oi6;*EKdNNwCSE?%6vGqC&Hqtdie+YtooU0&J)r~Q)l$+!H z;b1fkmnOO#hT_k4q_C|pVe+eq4yae^*k!B{Fjuq5u_~yN#ok_tFz)8f-rhGfU>3Ei zMX!Bg;&kXpD9uO?)|r&-{5cl4Y6%a4d$OfbCQHbXT^Q_DAGkk#yd<0BZ|LdvkL zmIy&n&;$Y+fKQk}4$a3e1E-B(Xtp}_5H>0) zXn|{m-)ez3`Wu9xtF;dYUD$m|u;u--4cETyo~2$lIx6k?a5W|~KU48CZ_?QXsqNTM z@3hsKvDZy|bb2^2cKGzzQ3uM7K1+6JBGaFf9ceAK!;yS;X2%Pe;3x%%11M9grThis zwPOMs?C4kqJ2vjYj*WG&BjaD}$OC?-TpgdK)y|vbitDA{C6ua>pz57q#hzn<+jiw# zm#-W7{}u=GurE1WY2_dtt?dy1jmvbc7vpc3qG5gSt~vz_(SWJh%H=wMGY z^_hJmjZ|^TFXK#^m{2k#lpDaQ4It{5(+aCAc*e!74!ctie;90r%_AU@;~VY{hUutQeHw&*(HgDE z_C~2p@`1L|-0vBkp1nmrl_oP+iB>ZZM_`Lbptx9`{oNPOw>7O;(ghWeEq$uMAq3e| zB>eIdI?8&Vy!_%zR9FPE;-^J^_L@IuVvJyM|MG0%1=bCuj=`|8vguuB-PBEW`SI03 zd;-qT_qfU;DT=&s6t`*Zk>h*MGNL)DE*eb;*#fTfcp8o|+VSD!&c3){dBEr}+dmPP ziYApiSluVB)3f*_S<@CKQe>Vk624%1eCtgVZS|8R3*1|$gQTIN*J{VEHcL$_foNOW zM#uUJAsAtm2?9`K#~XW{?dTZh3M3^i(gUqE@rdhI4(oM*ZmLy(iKx$fk}|^}O(p8; zXGvh%OS!saoK9qo3^Var)UtuKTgi6Kqk8y0;@-n#Sd6va)+9=@DG^9tzj_8Ac*L@@ z0Yb-%yiUs-mpdl}ur<5vF01$BPphQ3Qs?-Wi#W}Uf~21Onlp`5$|peL3M;8Fv*_4D z9pJ{5t)3FcA5)=--#ssA=!z2H5@Mp0sm0Bjno(Z~;Z#96j)2{HxLIv}+D^B9*h*u4 zt%kr7yF_g%$8qk+rKpvBbrWc%)vfA`e5huCkLki3E9q5Q5{IH$e(2Lb*1+tFkw9Ht zCe~-Id^^=1;&-(xcHsA*J~$VuT!~pdppw*uXvoO2U?k;ZS&Mw^8UqOvx%@H^qjj2p zkq8elSrFzg2wRF~?M0yLjxjgz~Cj!w?RONEa^T@(%Y8_xHuK1cp5? z`0tNNr2SyUCNa3n*ZTMwe7iN?F)Nk15ldb* z5w!H2Qc;6191npp@h$v*Wo*gD3BLB z<)dBVEvA8zG=_e{;eWxJSyO{)AY%N1;V$uy`pk^o!Z5LP2qGk?2AQQR$t_^be<9?1JmLvW)ow#ui^EoScJL~=72of z5p?#I&xz<+git4H7#*=dD8i~%t6uY+rR)HWky6@CF>fAp(rk}&(ec2>E;&)%xM^E^rx44@v=|m<cHd2$!MP*RVQcHH4HW1{6FYVksREvZeFje&=j zvL#l-g0ut~y;PU*xwkPuwYouNCmGSM;f+(@E07?BUqWi{=5p)@WY}mdgifD9=uDXh zMV!&7iwc=9RA!^uHi4bruwH<6t8$j1{PMb@P;qLv^=hdM_*pJ0hrM2(N>QkMWWl8M zf^x3tCORwqbKI{=Y3y0ese-to$H(zv0nc@?Yssd>LlYNLz$ovSDle?H;FXu z%@KlqoNPP0C){)oSFmu}Qh&3lkq(I@9g+=t^JuZCcc^Tkz!sxCG^#SMq$827?mHkQ z+gY%6#N}3RTC$+hd;zs|b$?5T6>XLxoh4tSXEG+@jiUMm8yB9ba#5vgunW{a$87Px z2oAIjVl_Tci|U%IB$6OL2Gc#pUjeL#pU<>Vru0EbFO)WK$zK2JCT26?7&9EXolX1IDWMZN(EFtDZx4}yojx0-GJ1z z`I_3iGoaDW+AH*qi0{6l)#%LhZcAk{ool*`U0b+>8e6O#^qMHp_nytWx=i{=@Ee`D1W_c3U1NmbB2U^Vet1%YV$;1N@9 zo{KPu4o7zsO*gK;nT&3H(*eQG-FCA_p4jm^KPDY-xbsug@AU^BBt4<6hTT#ywY~&G zriqY;RnXGkr7*9@%L!|!o%UqVk=I0qYo}Rmp1d_Z7YE`L*Cpnj`9NW8u7nPDe)7Z> z$C*5}9z zO;R3}Kt`fASv20!`$SGnDHYx8)8MNI_xt_z`hNd?+_u4^uz#^-nC`guGNDd1XCPMe zV1RN+0EZY0xWEZk=EmCC!^oh@l_5K@^R{Ta##Rd$ii*I9AxlsoC1V8qm1qjLu`9z9 zr@`=;#{C9|89IXti6EE#!UEX9!I>l(S2E6Q&$1ii6gp)cyqLhlP^xN-ISfl0Y}l_y zj24XId%i2jzPD$)k_RFi=aY?pO4}qRwA7s6hNopv?4XAi?z}48&oi+g5?01OWE#uq zaxfjoTRO?^km8B**}u?BGt61Jj;|UWmTjy8J$%aijO0d~48EJu zBVcC?xo}!Wvl3cztc;OJOGHQYuh^u3SBEBiI$rU5s3$P* z#Hs-5E(q@6>S^RRj%oigI%wN{&*=y@C$n_LvUM&;mvp|~@I6jo{h2zAsdymnNhCvP z3DQZA&l1pJqsQ=%qJsj23Kz-T>P+_|q=V#wISqI!x{##JG}fJD{6TeqB`kv$Z3S~=IlPDcG5joC#gB@ zx?N2&^Bq_l!JM;No#t&1n4N{VtWDJg$6&FQ%iOoBVI!hw+{!ryQqgF0-Qmqr&wyhNb4 zZSLG*uK5sZnmkCi3s$)pQejAqj+F1?t(esT!eRFVC%~I7RyX0P$ADBdi==t`WTHOu zmC>-0Au$!#%Vy8R03F=UHV$WPKtNV%S27Ds$+IkL4{`hsmH>{yhpDP=?h)Ls`fpVG zp<=8_Bi|a~kFuUxHr%HrPE^A{_P$j^xYqC5<=Tyg47!!1u0#&CaWu8cwi>HNT2EyNhAW=vO8ERgyQSok76Fq6#p%yNeozLTYN$1P%f#BvSiM~=7 zuUI5iNrnnfgaG6PIRQ~wUgX6L3T*VXBph9W)n!fE>EDit|0%Kg4jC4(^cb-pMa z$_FpLNX9yA0*}Eh#pF#2hw>;5U!bI3{3dA83vx zTrDXbD5YSas!5U&)#U|=mKSWcq~qKSoKQ-fTDwcC?fu#gVD(ykAoV2S5y_k4 z+Fg*jWt2tKb)vls6g#!+!W;6F=~-+3W!Ju`-72cq_79K!QHWaCC>Y3WzDlg!|M5+p zs;2#@mju)kXnx>Eqjj?QDkY8PTZ8J~^wz+($x?kH8t^L( ziy`@wqu_hO#I|bIRfkWb=jw7%Ns&@>>XXJ)0+ykhgh-Zg z!v+CXb-5oigB3@)jTE6V4(nvg`$WVR)~sCQ`=3FQ2YQO z`0&BL{Ba*VagRp*S>jOt=7vh?TW}Ta(uHk3;?Z~#`mJW*N}&ws7+ycyMmd7Xp8w9{ z44N0O$8iaPv%G1Ijh3{$a!s}|Ul9;D7sa?m{6wl5u#-)AWQES_Xq+@!KzzY~2AD=C z((!G=;nQeMP2jg9T4Rfyk9ucGagz9`&SkKIz^ZvD0RhuPH@a^jXklIuM7?ADZzD-a zyR!lY{yHEnbJ&r7>I*zKRd&^?D3ME;w79R8*NonmFJB*E_ZgD~fcBQKBqik4ke5iU zCMD7g8eW1ccK@$8Cypm7?H41Qo3r%%q4BHcJJ1LPQZ~S)6=c>}?`T@Mx*RIrVt;F% z4)gWe5t1{NOgzwpU4<**gpqdH`)XlDaiSr|b0vZ`S)s63TiX!{o0cMVww|>#tDuD$ zS1t=@?SiZm!sEX2r|C*djH5l+bjxTdYE>~_$#Ao=#Y;}!>{Q6yc-Wx3Dnhge^F|n_ zia8W{JW%QB6err$Mi1=i(vhcQv%^PZv`~HUrOR7YHH$vQ%4RG$_Mh-=Fltl}4}s|= zofLS0&@At)+~XOMreLKx@?*FcT9{4{Z^Gf&)2QHZe+VbM6&07>I$0|Yl-t}?%q}L{ zCN-Ffi{*a;QfWvf-U-{I9Q<`NbwnfkpE`x-6?w0(6lhuPI&Iy~!kCGlg=yvBp zxZs(Z%Yaah3^H#nD|0V5c|ftWH4ER`BN4woyt+1|o->tFSev5>Iu@7W8or4Op6Qg3%1QFQ>~I$svZ(!lpn$`}7iyxp z)OU?i&p9My$hGxl`TjHk&V(YclTbvpGE10 zoeOWFqK0o|9K4MEZ;$TFQD&F4eHLG;rE-gBwMf4@l6Vpb9f2K&C6K1u1A;2$2(V$c z37i#GvA#{{VTSQgg=*RMMJzuu0b`j?_d1mptb|Zu3%#5UG-Ny9v$6p%bKxwr75PTZ zc>~q$ID9r_dWusP{vr6{99H#`_t7!tLLKeh(3edJ0xmZYvc67=DFk=LGd0l8guf%=ne zJkaJFn5dE5`xtx~m_U#`7jT^gwI2d(b!{56KjDVSE^PB}x~kRGUZw20=@ssLwmxZm zFrS+*sJ!-N@Rkq;lBiUn2{J&~!cdzvq1 zuON)A(W({uK&9Mo(bLyKNo=V1vcmf;B7WQFmaMeM!As?Cq^Mz!JS(X-qPWR@#_x8ie>r zOwAECtA${lNjiFWp+(1~zYK;@sdS=x>W_!XPy+udnsTp(ozDCLWkNx1JeM&UgCRF) zzuK&%OkIwk&LqJ#mOBZ0{cD<>m4q0SjR|7lWu)z!qE?e=R>3QR`|~znDO_OL29?4E z6#xncFEW#ODz_=)Ys%EYuVcoxi7j_dlf6lM8J%7Swa5oX&EaNz(jD+Dw0J_c*k~}! z9yAMZ+{|R0bq6ly$tJ_8GWnzyW&Z;Ksr1x%4Ok$e+>DicZ7^b5;hGkJ8nQxPa{kQN z>D#ndrPU&dykltaz^825Q%Meo&`Yv&D0rsy#mPC_#`lzlU*viAp_GLv_gt{wNA2K& zpJ7=g6Z1L$N{L~ujB*Z6tb)odIXgK^D^4tn1cI^JH!ANXRbR_rm@2kUtnmaL1(kt- z(Y52biDH%ggf6_3N+esZmca3hVIk+dm~9`r-oD50PG@QHZjEr!8=6?1#pRoplfA!r z%b6+tc4U4ZLf_24cMl>a`h6erc>4W-DP#ZMhmbU7{;v_W&I5fUPY^>vUof4!wn>EHLuf4#1Q@kRAh9~YMl21m*h4t#s zbgou+LGEzbdqjD8k^U^BnRy?_WjfvQI#d6oLrhWUjDs`Iv@VS_Eo;~-<-XRgVQZ?= zBwlNm4@JN-jhQw@z-_HM17_pX+Qk)Gqtg<~3^PZWVZAT2-1jba+^O{yHy>st|Bvv9wvX_f5l8GV!bi;?YAIIGLv z@zOfr}yOX%NH*mef;v(!IMwCTJ6~K1OnMCE@mIgW9dmo$Fy9^E1)jISx2~{Zfrxl z1KD%^Wm(K0=lPqIS*FnyLzmyo*Uaw_T+4DSUrPYM>j^O`8}^vo(5+(Ff$b-q^-mz~ zST;EX&7jQ*P?X@K%bp#X*RPpDj0{>!CRo>|9v9z^VAnTBOkJKe^CDJ*tpQ{JvT9b# zYJjXIuP|1=Y2E^BrTUX>T1e=MKmx(BfY!cLxD4I^w>(jK8q*OJw0skY8!G>L*{piS zIX3myw(3>zoju!gALMV63;^RiYOCE|bxL^9JrWSZTYI-1*SqXTYq#nh9X)>Z;_;K`M@Mh1 z&ydxHqKx5?{ z+nvt*!M(sKlKJo7Z;?e%vHO8gr}#Grw&UL?vWZe`A}D=2dC6sr%h9gM@&uLpCY%nyRe_lSCk$Dl|eZ%SCT zTVZQ?llKT#NAKE^$FE;K?H;`R{K*UIM4`2kLi#Q9MWVf^q0|mn&$RR$mqp>uDfPDu z_xBe>ot!5N)Vfa{F89%!UvQT$yH3wq7RUA9WW1s^)$5_V0420C^1jXt9ltZpi=_Kw zxkEdow{uWjnf_$Uv$B`w`z5i%)6>yU5y8zBkt~azM>M(j+m$dmp|3(TBgf_t6L9i|EVlKs=4UeE9I;KztUB$6j1qdE#Iq z#vXVMqHT5Z^Mqbp;c2=mQ0UmCU&)J#3uMd}>CzLAkR(DCr=%w?ohMcHF|vqAC{8@_ zWHJ#SN8VVfitIenZ2H6#-@4hvQd8DXP8Jl-^;J2&`r5wwSYA?4=Fc{hIGcoWzHxG1 ze(~fI+OD#K&+QvV^_2CM&2n1$)aHJQ^Dm}7=)C^kzWyR!QpR^S;{|JHQ{Hdfyv2fY zert22r4i*-mCaNP9h~jeb1yM5nUg1-E>mXVWWG*NVe01a!9bb0n|XD1oTEFjllisY z0hC`l`QIk-8#%;PBUiRItr{!%@oGNj;V-J%!o;`-r0ltq&1w<%@9C9aHeY@CzJ4`x zUOmgIU+etSdj8-;{o=g-!Zvc^4mx$JI%~doyr8ovnd$dejrX>#qsANC*l`u@A08ha zEq4w((awAC4fjvP1?TxHGoW1hg)>UZr`+TaUf&LbHNLA3gMi*1X7ba=RAdhhtH9aF+sJ&$nNcJ66AY=hJ{(iu#y^Y?CzHEte@xv7D8nD^9u9)>4~K^n zb`Va38w7`weetY^HaQYe8e9dHyoz0Toe=kV^~elR-BM*HG3F5X_& z?{xw$y)QndVq&zjx3e$4<%v1G?DxBem-ps}m-k~zyx%{({NR`pA5!91Nc?I(0Wkf9 z1`hqh!(c>DU*qWx^$%0EncPJ`4}0n%xEma9sL;gI+kH`IieEy0jLPLJ9Ga`1HsMi$;7WTFJDSwbtCBKqid|yegh5hw-(EVs~IDRW zzaJ4iP^pSYrNhJ3?AQ2~e&_SKhnTF$?BAD}{rexz>1UqY<45|%zN`HhCkdeR$VSTue)4$UU|9n^p)(p>Sq7L`Oe{Ch0l)<5Aki>=f-G4 zX~Jm6)RgHtTMa(^HA)PA&AmcJ@@F7_?#Z9~^5=c|^MU;NQ2zW%{>VD}AIYCswjewA z0e)!m`*h6EfXDCsfBxLx!Qp~5F(nDRnw`q0SUxS}QzD<{JWaEXhnSDZ&t(2gJ{9um z6i+{Jg;dKtK`DBb%D3klGVV{u5;Bf8gy4j*-=Z)mZr(7q{4Dv9%hLyw;QHRigJ)%L z>;l^{z2c0ig+3e=vX`nK#|pCgeImpzWIKp+)bZ9{=ahZ|4?c=z_E9bSvCNk9M~T^b zu~?t2i)3A{tMz#j1S2Bz8ihVtKYzadV*TpL`t|x?{mm0D^h7tXkPV!3109cEJ(9i6 z(OY_aokWk4&Xc6$%MXqzpA+K~c-f*!m?-bJYxobMN@Q^_fXmT0x}EXzhSIMSxHUCZ za)(A(e_5P>r11qjo+^9|-+w36$}^L*P&qG5&N=rtQbVMnzD&?2m-^9^8Xs}9ybSce z$NHo3G@<1a-W{*mi%VJZpkA_b(OOv5exYmU4E|aGuJ7`(77kt?tna<}WPShDCv=$Y zjtBSdPXc;<@EQF*rfS@>WwpMS&DQseS*>h!s>;q2ntZ1pa>UQgh_S}BmIUv#$9S>n z^__$;D`B|FnDE$S&3~`&i4EtiaV|`dlrL3+bJyb^qXA6z0)<=Kun#2`H>Qkqgni238(-_m1RK25WfCqf4prKG)p_a@@Y z1Q*XRG~NuqNXGMO64B36{>B zGynaZ|9-`PzvsW-@!#L_-%J)Q1%Y@ZO`ud)RZ(bpz-f7L))$$Rm^<TTkk_pO!UeQ+ZGFi-_U!wnRe}`Q0a}xcOtk+-DKcjf=Ir?2*;0NNH zmil3Z%I^5BYG)!oPoi(yEI$f%6SV6FLn_2qN%T1-K2MB%Fru%;r%Ch`W&f0DCwt=i znmw+gyYMNM`!vy)$Zp#AR#jSyB>=4w@m&&qPv3l>XdN(#bi{9*eBd1MTPK4yrl>Oe zEYX5UoGd~RB6FTGM7i-DS#3k*j$`1#WKzHxDU4LDR#USm{G}zeBC!#jmBbp*5Vf{d za6OI71o6{NtS=~VB*BpL2hDtuL5p0zyx01Y{tR@9`!|=MKldg!>ZhyLWtF8IB|@#d zkpgw)kq$rD4=b&|(dncQ@}U;O!)R`ZAQrL6{5Zf>jTXh*23E~jZ>YRzuRMEDC@+AC z&(8WYs&i-^Y}i_-TeY@Sn034FSl!|e>m+;T+I1?`5iJKgqGjLKJ%87_l^1r_Wa385 zG-sIRm|KFnHv3zTVl&&hZERVC)7@QvyBVtDQZ9O*qsm$Jt8KS(=xW+N4aI|$r-7AC z5oE9>s;5;-`UB=bsvH%8nQHb@?ku1-ly!8O+N)1&rOovt(CsMa=PlrH(sTfKiZ?~*K%K_}9QNS%IXww<%Bc5ZGWRZVoeXd*=v#J4wa^+&*U3s^f# zidv4c=n-t#WqfbPJ>qfQjLdY%joML3?Q+==r>)LhCa8r#IN0N&6+Ibfp*89F#b^*h zBdr+a-EP?F zfpE6O%k9{sQGHd_f-3Br@UjNOcvt!>q$y*2ia5+V76m&EeA2$>bj%jsY1J?&BwEYt z^|h_y*;}Tn;)r3U_=`Gma+=jb6xpvZtq$D%*raJrV+60Z1)jH$q%@Nxe||{( zcRKOk>%{+{6aS-5{7*XZ7b)==iN8yUDH8u7B|bsopHO0k#9yMs6D0lvo%qW-@mF-> z?@54W0N$lt_{IUv%Oh>%{-66MvHuFOc}B ziuw7o|A-PVk@$~w;y=-e|5PXbmQMU_o%r8irG&))t`q+co%nz1#Q&ia|4b+T4kf-u z;y>4k|3WAJOP%%@Pn6aT$VV6q`Y`Q3k`6aSq~{0};T z&G8u$|FceDmtdlQ_jh&T?JFLiGQgR|5_z}|L1iMU=U)X-~9`n_*W|N z`#+}>|Bg=lyJ~O!{@>S$zo-*`SttHIwe5cY7j)t;>BL{riNC5g+3)|lPW*>D@z>N& z`u*R~iNC26fQ?s3{4JgM+d2VAfPCZk|Cvtw9i0H!yhh@`)QSIEC;nTV0JLDI{Qkev ziT_3?{yUujv>YJuKkCH)tP=ndh`)aSKk3BZ)rr5SHvRAaflmA*o%qK(@lSN(f76M- zuh78n|DjI&FFNtR>cl_QiT_2J&t)_s)-!po}Ui&>ZX3i;~gZFr;|25Aa5g=cx0}Nht z_TsbUklsE(dQl#9H*u{j7HQV43aMq7P+gOyt5QZ9)2mX_mp| zr8U#=g;|(kKo#q@6WH^FM_Fybe%@}RQXx!UBpja&9>=76Z7*Pw=^G7k3~dBP1WpWX zApxF{&=wNl2@P!_fu2y%1_`S2?C`m@v0=K?gpTxWtnZ(b@bk>*viUzJO0)@&1cKfy z)9D+^6|WG!yC+RO;kkhYdlejNu*@F!y-80CJ#m$r^l%q|`&(TlE^CvX68PmvtB_MM z(nkD``$|Ve5?7W<;yh33wfnrF_hawqNNKJem8WsR3T6}BVXmy+CN46k2}tFa7a5#L zB}H|G`^-MBF^M~XMg;E9rPCpVCSN4IH_26L61`_nj=p^L^57+V7Yjz}_QEfVX>c^? zd7Uiu_}X*oGz$-i%I`>#1i0s=3WAP+%$Pt8!@Dz}61W$;<8egVui%zMN^8R$x1+QaJP=%tCOv!m zf?Ls}WOS7DsDJtN>#U6D34QW7i3nj79B1H5y%I0y0^>X2^^qQ$aOpYa#o{hME1zVm zv!p=0N?dxD2}Axg!IkH6GCGF1_c2=>zFtD}9R;bmH|A|6Rm3RorOwmyd=Q^d_KPj? zwzNn5HmB|iPby#ODRjbf!V3&79owliKia|1nXG7trV{_wVnWSUd69qlnl##@u^HvsqeE$cK$nfWH$FJzu^J}yaS$z z-*Tmh5)bR|s=&l2&NU>V6`hAWI>N{LQC}4XqR?5mDhx!bAN9pvAo4;lsDv|kz$&7Y zI9jMLr-VZgB~h%3=sfnTA`nwf!NzC7sf(bNui+1;1`;<(;}J`qgf$i z)V`ck*168o&==*s9(R@NwczovUon@rgBK?nwgAq5D-|m$R{8ce^&ZNHg(ORzz&Q=>;NH* zhn1a}C>dJgb?aHhDUj1)3-e3)(dLHOU>C%!v+clXICnZV!%PM}8JkD2HMY&&^ zZqW{q#T+qd(I9JXkRwZH;J`6H1X0O@Y-r-0kCVwzg*2}Gg1r{umdPR2F8^<7Y+IYls+zWYtp!QHuKS*L#;h=>fZjIXHoTzh)!T|`f&uTg)5)sT~E{& z92XvQI_2~(p{XaOb!v#`T{ z4Y-SIWQ#PWL|`KPkQVr}BQ{(|XvZqjGn;f#j|VCbNvU@%^+)9EWx9 z`84;ZVg;War8EbhS{I0p7}=fNhR8Y@RpWyckqEnkU=lj}_DxaaOct@9=a{H3VPXcG z8Fe@2tm4bUp}F-3&ihXaV=AHiN1T-m36zCvx@?y-RlLRZ`i?H|$a#zU^#a4Kop`vL zqdE(by<>eZirb{De0#us%qD!vZ4Q8}9-lmH62il`ssLL*6u|zdLctUUrY1O71MnP&5;-?5Kk(ox406Mcq zJ|0Bbs0uS8U~Jw);-IP!J`bE*nlB1_=^@CHV{?5dL{+{A^h1cscN=M4~ z7nKgN95f7$)1Bn@yT|~!vmM$qd zwc5k}MIbJ03`(P7a?GJlG&mm%k55<*tavF6mZ={86?k5*Dt@M4tiYw%U72gUoWpc; zgf^augXm}8zE7fqDEex>roW$}yHDKV{T6pbD;@^D|1FpLc)flCM9IS{frp-qt~cT5 zT$^ohI`U6w%sAN3BL6BX1BTrrFKNh#%XndW&WB3JBvHyG)5@rUU?@^GxKDdN}XRs#fZM0_9WI0LY)%$Y+74zggmwATRlkRuIipwvhAh%ovGhb z^?R;5GSOg z@I=>1IgN>feoa$|Lmr;&-Vp7P5y!O5Q8au>s5dTBKK9|K=Ztk1!|S8)q}Xi4i6aP% zu3V|0`^MdU(MchW+^^0G-~BMCy{&zDREXnSzdXL_%j1HG7xx`ln=SuFhLy;R5v>3l z+7lWOmY?Wv>h(wu3oZVg_!HduSJu5ut&MS23k%NGcZpaT5F8|pRtU%3)P$}M(3BUN zu}PfTK|fCsl3jy>CK}-WVT6)I%h%hZe!sOnjd=^M0>n`0R@|tutc}2OV6AnU`RKUQ zNX#R#l5o_T6FZi$p2##zl)LfEKVWsW3_!2w<-4z_u7h~m7t(=-*52~72Yq-Wj0}S5 z4n`_ENBGFhOeO{hS7_Q7eZt@M9=yY9Z#BO|t+Q4TaUX+8qr&ELs!v`t7;vf$DvR}R z-)Z59g{I0}{lS8|<99lCn3+UKW2yqKL2T)Tf$Wtvgw8awszZEL5#bLy1zLgTX=R>=8G?t#W< zH!Y6+4*H1PVFM?MGD!mAY%^IVA}SS|OfJcR+fqE6MYRm9=8c~B_425;ra@bkH;lPn zfgbjgKVW-HIZ%CzKl!7!cybE_DAAJ08kOGw0rg>NAb9d`XF}c$yMq361=*K{B@x|1 zyVyU4LShf%?!c&Ax_j7}U*hBCTyT8LtVJyMm?%~&1aASs$f-ew5&X7@rn&z;ZR4Lf z^<4qyUsh86bxo3Iv}m{FD6U>(29dPNt6wz#Y{=AxdiD*9i%?r6>MtQKMbp*0QOQx& zaV`;|Ob@$HOQMd6*efA<3)shJR1zH;TdK_T;G5cb#*a`gkSYHvF1V_3lwF+Tx{vcN41m&Mkh6!Sp&?tyncsLk+4kjdlSeGjg&*4d% zhFNfY!Bo{hA+}zRGUeNjuZn1q`+@kBe`{;`{gy22E7^Zyk?feh+bZ>GOR3+~OU-Z4 z*L+hd!Lsw|Z?^h3g4r!mpzS*J4zEZs$~fl(`0R9dBV z=FCMcEEgITn9zb{eooht)b)6%r0JE^m94_xgfEl5y_YP#WL~aCOTtP%UnU3uQH@_h zj$d3|b1KS}>m6d=?B%LtsLu7L_IL-cG?E`?p(bzfNT9?z7Kn;2^oNNG+v5o0v$VdL z51n^K{hgU@S9ohnr#FYC4IP}47L#SmtY~6U?HK8L0^1IkGiTwAf{B7FBK4XJAN1q?$PF28rG5?gOTDV zsm5hM(6i*8DPNSd@lK;Do(-!xdq527TqEp->ehL*B#u#>5T%m%=d@3OsLpYB0fzH@ zbWY^Uu75iEG4oHwNf4&~T%16>u08*j!){meuxAJ)&oY2r{|uj;$xkqH^_68yH>Z1h zrxGun1Y#~z=fTEYYC3jZjr|}&+pnTD#tXCa0@_Br%U&a%ytB!;JaKDboJm=^YkuW! z8gDYVn>yPwcTjjal9j2RyR#6{3dXX zT8_RArId9Mtzg8|6GV&MELgp+uXNCfcp>%L5MY1gY zeB#^;?+%76{Fq0(gN-b?JJ+XpO^hYV^_p+BMlbSOzuQKWV?5gaPRT|WRmgE2t&kGY zckB})4_j%bey3trnBKybrxJ*Qu#(APUHH0tYb(1sJ2<|;eitf*4cHLYDp94HinG%1 zRHCDe1wXgee(YWhu@mSQR;b$F9A&Ab5o))SFCtn{20Df~y?jQxW+_{- zFyc_E`P67PXi+G(i%uu{Oy5;jH&X*J5;tUf=gOt#DZCZlq#Pm6qNRPCG~S{rTGBJ^ zoAY#3NyQGtUld2t8S&Nn*)TSBuugs4xS(&b!8^OV@UmDhciWz&vCxiv&bt(j)-?`C zRuVW;#OZFdpcO0ais{p39!-aH=b(V^bU9JwXo>SFafBwzz>%a%WbdvybzVN&tHoKQ zR6I^W09y!9z`*hW-IWu<=WyaarOv31NW@-^mlJUnCG>-43Z_$!2tyS#hi77m$ZkLg zg^z2jB?42K&b3}3;M43QNbXe@3 z$O$^G-@}NDs3yf59`8mM((S3KC1paenS~AHxA^=#-rGB_DfrN*+oj;1+AVc~TeVY; zAKbEiOq%BXbW;PQz&@^07~?rMw&v6^B6oXFnnJK#vFc}l%w;RNQFtb>*JS&_7=vBq*~Ma)5w=K1-Qaq(AU!m9U`d9shiK6g}w?EW-8HoUjJQzEnUoV<1|A{#c0f^$E=ukv%h~%9eYDg>$y=s;k%a8Uc(? z)g?Zy*&9CDu9EPpDv) z{!d!EE`(|Exl-I#de%WBrhjFY zuRoSdlu^imHqOngbWW2q?sTLLG(T^^z*s%O)K1aIxx)YrteBTpfR-M6E{oa0f%I|E zpD--6sOdu>PZT9#fJ{dDb8MFDgZ(a-G%ILc)PBnIoYZTTgmbZ~%U|X8#7{Cx<-2eN zV!FT8ei1jUH)iaRNQ^L5>uB3x0ea`9KQ&!ZP zB0R}d%m^v0*XP)$D`JbIRd}FMc}~CK>4-Qs4!H8$)Sp7cM4XbzOLCnDo-e$EYS-&y z{EU36OdPAqzQS+F+YGTSi9ddWRKO0KKc`ygY!6ZC29YbJ>fBV!L8t&77CdL7)JF*w z7ixMCg_7sCq=&1NSIJHoQIY`maXCQ^dd^asv*AjD0Aci4RaWvfwwSu&lB3!ds@%I` zjV(SUa;Fm|5Bj5wSjLQa9{3Y?^)?$!Tz6oQ1mCCXXf;VVeopKnvBpnxAIx#9%(A8+ z*el@?n`l~Av0@t({E_6JbuQqaH%yi@%gbF)3n+l5ad?yMZX(5JtSu9EI)&1A;$Z3Z zE^gt^8)SyMNr8xK=7A1&D_+Q4!D4LB@!U@#Tg=%mnwDWD!{+y6&kZ8>+7-;aCdOh_r^?>c`pj>2i;I}fGNo9W zwJDZ5z}2FYSM$OXx5##(&`YoA=);H9S^68<;APM*&5e7Qm{tgC zH=V?wNJp3SxeH(KaB;MYXzwZuC&lir>wTed`t=>BK1JxBs9wenvFlF77_+4hqNfs< z3@wVyqoR|tyYg7x!$^RH_7QHDV%A$8#Sfw+j2{qzSVVbe5T;RX?_E^lRjPJkO+5O_ z*(wM^kOa>5=tIqY+k;RUK}IE3>K!Gg(NmT%Euut{XlAR-fj|7T69*@Iv6z&(!%zU|p+~3)37Be)$wO|-Crp>4; zXQjLWR7ZR{R7SK2=@HZH#+#N?@(Y(UZ2}JX(x=Snyb3$cx$so;dt3U#mBoSd`ohJE ze2hL#gN{5Lp1SYOi_y7}1BRZb89tO%ed|kQ^1(ix(%m0NUbpXs4v+TP7IBDB@R@@H zw4&Y^oWe?yo~&4xMzc(=J(QasMoKbAkTwG3gpYD18e8b@{*+OAs6PHNqx$4kid2sy zdH@J_Wp7Rj<8W;*uXLBZdUK-prFvfW7WF(6Fi~siOLfkRV(*Mt7SqvAxIXa1N&9>>K=2@Ne5asJ#m(Oev#KDcL9&_9!6A(lbt zxl%MPOd*EKotAzjuiI%S(DZdWE4*6mGVKske)Ms{zw31=_}UFUFjGlQeU2DCgP>uy z^36@OeP!j14#C|Z^NGKF5RX9W6GsUWpLfa10HmXL{JBd9958aKLf4M&tGCPAo;v{k zX&t~XRE^*9Jrz+-)g+w7{-=zsm`wUUjUJ_zF8Qv3c(z<{DPL1Xm)N&g3#b6y;LL8sgb8=V&JrJpqb zbFTk$GeInIG8>vH1APx1Re)3c#L-iEnH8~ZPn`e(4Mp?^(;-r;o6ddQ$PlGeJxtiZ zj&S!Bw`zF9@)=|xDzW@Y^(t}nMz;5hWhK=Es(^ko_7W)Htk)};HDI}6ZLyD4LdDIpN(6ePHySLa&( zpd1GNa=j*=m}OAoj4;Q9@r$JLh6@~6^dSfl%BCN^p3e1r`c55Ze#!KHif5~4Pd(<| zuh9D?^?r`t&t&iO2lHXD1ae~nS2uA=bY!P9g(|~xeA<~}a8on55}Nh?%nS}ivT-@l z<1moEbQC{o0KM&K5O@6vBjrGwr6pR})FM}pb9)HNGhjCxN^Pp^P2lh()KY0J4mzC} z3!^Y=GLAvtEPh;(!Z8;jV)Abh4%Egtg9yj@h~^p#L) z7L3X){ILL6HNrhA;eCBl6z@yJiRUEC<?dJg!D%2+yiKmH`1>=I0U+ z^B|Cyw`QuGd$aa_=neKXgJgv@Qbs=`KV+mn!rFy&aGu7+qe{gR9(@Oh8wAQLzXwAb zc~V-zTS2G2A9PZu2g{p!pt6YMd$aP7A-ypESw2YwoWdYds82%t@ksh%d9;X|x>CZh~=Kb5s(4YId;0JFlNPphfr9XVP()8y8 z{mHNXfKTYphZ9LvWw6|-T*DoZXDYI|u;RsMC6PNI(44h8zikI7adM&wfyWGjiajTk zmWlhpFo~p*v=iUl))@OK2!APfRYbX}-Pp@N><&ih7-Vrd2}@jsI5&oF_kPU%?Y)s$ zx{uD)sAlX`ofa!8@y5Nb^3meErg>8CK5RFpi?@vFz>z>Tg>b7>uz?Wa%N*BuO36if zBq82=w;!-ZKmpZ{Fk=jw8L20};ie(!aq60mUZFNZ=OvV2ev|tP!1~_a0_Gi&f3QAY zU#_13!7T_9%$+G=n#K5|$*3HEsXmz~04T)*5FAp8*cAT_8ll2dLIYvR^Wa}v=DGOC(M~4s9f`7tsa!@9S~)d?45~I`g92phz6hYjx%WsMMW=Mqow^s% zwr1ZR09SSbG_W1hF>n=#i|A^20bn{FUrcN%dSLBP28zq*i`+j8#N&od!M5WAn%%y0 zGskiu0vm(1H~z7>)IjJ{7jnwL$j@Ow&siz5FcHYQ>PC;n`QF~u12rWF(YKkbL-Un# z{?2l7)^lP}(u5w1138)9D~V*DM6*#A@@hPyd3yqe(Z~y~tik@aTw-XxS!@=I(vEAWfNjv7j6{bWr5-p^81Q9Lt zm%UHPq$}w-qYoZMN9#4g#3P!(M+!inh~wH7|7~=QCi^0m$w3+b9utxP@jVg5Td_-X zT=cOX0^cHvlKEL>Q13K)^j1PtU59B!1>;R2;TOt>1NDoJ2YFovKynKf!!0={5#d{T z#fX}DN>m2U8Tu{YQy`1JP&r?44mbkto0XnBH{7>i_1aapbGhx($5sdd3mHQzx-uzy z0?EqtQt^@A`&ezv7-vdxtet4RaHEoiV0^OgZEL?6&xz)~m1W-Z6*C-)h2O(&ED-W2W!Qi))ce# zp`oSL$=>lWSx58HW%yY5$zJrQ{(ST}ybOZ9BUF!!v-OoSz*t6O)(;VLnu47Emi!mo z6aK2YCx|_64DOc(Jbok9k@DD|QLdP|7&t1#3i);0j3+hmlw*|as%l6n5XR3+WZFu; z9}>#_nV0?ENB07~gQUc-<0W38opL6|OJK+MWr>6xQ=wGH@4!7(2TB6XuY!7+Ze}Iz ztP{fL$RpyU3fyn}l(Dp|AZkf;iC_ts!ufI3vWHbUsv>;qT$R4Qm21*U+7TNAm}|=@ z`hN5o^H7v5D!D5ZpDi7%O4~V_yH?MtP;jFoi58wc%BF<-h+Gl4^dkt$ zUe7iW7{>??$Qpv|y#J)VFrd>+77>s-4j~DdNvo(fW|c4G%BI#qPXLFCdsG`O=Bd~a z`{G>ei7l}scEvaGT09W%#9Q$un!pKoN7~-c_?Ylm&%`B3fXjy zrkAfEDo_H|6%px`%%XJV0?yDe&vy|aF~)*bqnh^!v6V+h(Xq_& z+dq!9v>6`zI95FU_#*HHResdxKaQxtA2`rI&yj0Gu&5X`g0LT zT3Gmd)%SPcaP3B8@ht+RYzsAMUKsq6d^_qZOE$6el$kXNR1qTf+lOw&T zj~~IFfPu1FKzb-wN$opEIp;^jwT#IJ+=<2~UGR9f57!DF0l=io3MQSwwg>tJ{_UH)7m@z}#`liK_7=zX8pl={nOd8B^zv=^I#6@>uKz%_r~8EMY7uel`)~SN zNtnff{{m*Yb|vpQtZNNEtcN`@ApdKQi$cUi`=|_QYS^k;is@Q**e;Gdlp7+iuN5tzxC)IQen?`%IT3l=>j;KX@_?swP$Aih0er zX!fT@vVV{=0`HbCo(k+rco z6BqDmZES7~<#S_H3VIW3Sc5kwYr5SuW!#wjEd{JhvIPt2ohQ&nsmye&B@wF{z(xts zJjhf(*XkE%(2omr?nXUSr7Jo0iCAD7;|OEExeKio1v7CSHxvb5yE(~rdO8)~U44KD zjU0oic%Lx~;9XW#oO9LYlnaIme)g6sv(Sd^08y(tjfxz-Pv*vsy2w!qomw2G;XVs6 z08%cnc06#%GHcDVlu-4Sy>4}XoIx1}!>76M96;>_{nI=I0?kQt6oE3s3p>rb+5zrt z_F!tLPZX?152pQ55|*M4Qv0QE0Uq3=_63a^oJ0A-B}(yR!lc#w`8di*=*x`ILPTOw zH^pi;LPF|6J(39?BK(mt5QRrAYxl7_A=;uDP(%c0>7l(l#tCz`88iK! zMXzKlEupmV;6~S$Ut*N{ulS+Kh}d?m}y16eolI`Js#gFmx3VRIl*@zFXLxR^$t-smqbE)H61gP7vx(O3J&rEVA(>s<}a5A#-js=a@OiJKv@ym{yQ^BphiMB=B62 zg9LIRr|Ei@y0u6_gMeB@)e*EOeS{iQrgCLI)=0v+|5;5=xb2WKwdKGBGJd z=}78nM!#gySoY70@Rf6O)A7_^oX}w3H5+!&+%}_NNj8*vMQ5{tyG>;5;umBp!Zrnt zE>paK(OvUNf}0oJ*C|^T_BgG#W;Je)GM#Y4X8wsHbtF}Jbp%yOmj3qeYYNyf++%P<#wsMy3v%j~ z^)tZ46f5&-Nj~RMf-_Bl=4&2m@3`xASP;5`Y!amJ&|eDPGr2|mMb7qi0C-%j80T!Q z0c0?)wrXP6Jo*fe(2cECKVMDRF;)aq{$!2r0ic_oM0>^!iGyA;l_71-*4?W^;yPL| zaZQ`6qMM(q?W+YOc_>MzOB(Fs05wj?6*;08J^aJU`y|FJtmT{%#Q`=$J989^Ni2qO zgt zeWr91)}(Y^V8zIoC5~K~pbN}cU7B1Oze3x#b#YLz-SHvv>~C61^4=$X{+_LcgBc*} zd{LEx)L?$qRUiK+t48n4RIL-Wx(a{WB7u;?tx=!tDGJ(1fk*^oloQDyEWzt6?0SJnxPGkJN%vch9q}_;g{3^W$eywW1Un9Ha00On1V(9cGsdh zV1!6piD0B&i`gYm+w1$(ws>DO>&}Q9VrSrizCF+udfBiKlmzrh-5C}kiAHG!O{B61 z?hQ#Ox>+?D2sD)yTMK*+&>r6)^$S-0(bRa{F|%qR?7=7o>G+$V#WqOE@;3kA7KB0 z&g;{!o21oA+uBY0sL#rP2yMRIt@ho4H57QxMKnyTv^CfM=6#K3(kJpq4REyen{Nno zK~RsR2G>{1PJG9X#I^qavJWE%OtThp&GOgsx~biBWN`FR^Fpf#rJ|E|eDaWuK|I`Q z2)(6Pcj5aOvHK##;0Vg@4gf8C?>oj27u9fb0c(A>Y(w<+Wx;OAZDfS{1|%rslIZi7 zR{ujB6;<$yNP_EUso&`!DG)wjA1v#-0u$5P#Csm3tRFuPhp~z53HM{Fnz8 zHE_=Q=eGQVH2hx8kL`Z?d5_60ilXFnzuCbp_55Yy#MdnH+YS>@%74M1Qg9m|q9p

R{&0C8I$EjknjI!ZgKd;zQ~*ItF?~%K4F@Smspo*C{TV|_Q&(0vc*z2us9Y4h zr9^zW44U)3x>`OVmkuy{jxM#O^5N7zj@I@4kg^I^+*lWj)n5>s!B}MQjwaQIH<=$7 zsN6VyL3#dL&Ch(h~JRyDF7RqZSVA4X@1OWQbNy3DFw2o+vk?nU=J2 zN=AhNxNQf(5$D_s67jxnuqfS4fibonAe09oMKjH!lzC+oQ->lhBxYM@q&Sdp{joV% zsseKra0O9D3L&Vig0Vv5dHp*^D;X8tO?;WnHuH)P|4E^lbN7JnkE7SZQ+ZWY`Y6`zdgXd8RlM?x7} zGm800BxSyW?aUf2G7In9JME)Va7SfJQ+| zBad&ybrF4%eb@q@T#uTIJjM@72hk#|xWUldDtt!|Vfnl&COjybbyGcTtyDzMVJ2Tq z#YPdmq;9>uPq%(Jz1r6RgMHLjMea(n>#*=%k;dwW7K0h%@4Urmp;SK6mmgGzcWxTr zxliMNQsd9*?|1qu9j&50XRexR5F|qXQEh(fXC-R7647f%@vv|x>8f6WU0mNf|AlpO zsU;U{-fi6&O~1jk=HH6w3$65*J6D?G1VqtiXQf}?IojE*x<@`a>T=aB0uzr}mZLBA z^+QA#~d@9c36 z#4Nv&hg8oIkNYXX+cSW?AMb#?j}h`Z{3)|191<5^H(l-~O$*xcqjL)Fck3K=YYxw) ziMs1{+-R7wV`x-x0@xw1QM}w8W~uB2%I~xC=I=N>@Tpc`hmGNY#8wfauOCG)@6LUhSSPmZ@I2Rgi0HcIlp z9T&IAdF{(p=#X3NL$wocbDv4#HSsoZ^Sv@yUgI2BT((uDvG029jkyergui*fRM3RI zQQtga0EJ!|k&bpBl^{Ynccj0|ji&W{bV`cRp=HwV$b*7{1h0&w+brO0_S_`)KT2Qw zKKd0Vum5ZKk0AVDzgJz@{prpd`B@p%97;Z~!&vO1M@Eq9GW}dc)B5H|L2*ok2Rj9* zL`!F-{fgUj8CVF1#M_QyQK5P~;4Pn8nxHBNIskhhUN-Wu+P#~PP(ktKuAEahXE{Z* z?VSohET;=N^O^}N_A*k;d08m-zes;tWQ&W8rmBdA6da(vDN)VBFI5p0ne+D3(9uXv zrWGJ$?nb3Znj}j3XByqKaq=kCyx5ugucts{$E~^C_)z4eTKlZ$Q1!5KbMXuYSI`v1 z6*Zx$GZW17+(1@+NN)QbU6}TKC}IkSX8(<+&E*VkZyZ+Ws3iw3xOK*kxhB&^a>Cnr zGdjdls#KHU5?9v9E$n`yo+~h5TW(0EC+Iw9PSMp%@qiT~(LM`#b#Kt$&}258or#?+ za9tx<9Vyh9eu~mo>5@FD;;ar^Mp8S$xAX8SK|N`)z*Qf)v6q^mow^(IY`TD1dplP^ z3Z{bx)!g}qXcuY0`7;W|L@u$@iiVCWju9wi*3z`gX|$jD(20QbGm;`R zTTTaJYw?kv)?t$*gMdIh;DC-X2WLbZf;oxt9L)uHdpFqzB?}C8r_m|Kn*2t;l~|Ln z-3lDM<3Xk3q)ky65urEO!-+bx?^-6T-Zi^?u?_eUh_`>WsFIJlbMdBj9W$bkL@$xM z1sH--JD$z7O`g^O1}aALuL5#fM-n1YGMijgK(#3hJd2in#l-eV97{ZCAVAVCy&~hC z7yv!H!j9(<8}1U_>5-CUE@HIfwR+c>*kE2(CBVdw1(MYWE&RHK+t3JULx-;>&juRA z5k==LiMGsPEssX+$J>QVfq^>hk{XIHd~N6g^$g3+)e!Zei~ilq4+_+l|%7iY9*qM z651=9tU{9gIgO4Nk(C`eN5%5z(`Xxj+CU~_FA$&aDDV&p0M_w|3yj7Y@();AJr!T> zDEK!nSPrOQo=wvsG$jX%ORQ4g^zJ-`wHuYvi6MQ5Qcl*AB?ssmZB+XPnS-;R?l)GJp5)?RuMr=rU7+(h4Fex9c1QT?Wr z5nPeRTqz`#?V!J|96m^N_)t*^ZRl?%6wAVP&N|;*6GeoM#dGutlRU&Iv@tnVJ}l6p zn>_u-BJM<%aG7$ivx^Bpii&ki%?7h_1x@?G1K>6wls&XflQi068d(^*D4 zQ+}PM<-rdU8ztzf6p1`CGWHc@Kbres>Wf94#`?@9N1oodwf~p28 z_ZR@!n4c3gpOaYCMO;mQ8NaC|5g0w@#5|ic;L}3gag7GB4|~MPNsIv)`Fco@D|oyH z`4JOCHA>w;bsqm>D{I(JzS`E9YOUGUM~vAMwO@;lUOD;o*ZS+rYsArTs`}SzF;$F` z@whfckKAZvx_Ni_O>{${s(fXR((pr7?~lrj z5TlW$hTUNNqN@Tl^X0QPx(Y)95;O-fG?W znE}K%J6pPZy-z=@LR)<4Ghr)CT6;o*?hQSJ>?+!w>PJ4N-}pVxF5bpTUi@W*fTAf_ zWFTQ=AV0(l3C$?ijct3*oly0+UOx`==w)u1N0o9rtxj$0B?q?TDPYeN0E?e!<&rch zlX!f;RZ|dYb=}N!l9uSsl8QzLaXkr|*={5f{M}H=mohJ-Y-(SiNUUG8bBSl+CFfNx zEushX`CQ&W)-SPoF5rIFk1!stWeFqt>)hlzrYzR5WPH5Zc?8QKz#c(!DI zDx>k#Jkyem)O&fB4WoC?%ibt@%dg2b;QnvuIhCh~;Y_H)v^)dax~>13q^kIqH(!sL zajh`5>>??n$y7f+T`)Rg4NnT6*t%+b8R7;l&1Z%GD>guRazIULl~%kaxqdmWSf$PJ z;Q*&tv_F z#OR*;)|l!0+EiJ4#&Yh|r}pT-R3n)&I<=M27Iig`Q#c6kr{|M6g0xXtqvkNk!A9BHRc;rUdV^e6=NG0Y~teo2TF{&*SCdEv# zI1q^r$!w_AO%6w?fKV=r`T+l;rXmN7R5!L%H@2#qydpGPgh48p1J73;iO8 z@6+UaE1Y-Gh_@6i-Km3%ErRFuq7~axsseo?EL_^3QxhlmiE;?54T<07-`b-qCWG&*7uByQC)9 zJxN4SIM!Sl{>sWFnK!$hJraHUmT^)=6Mt??OFL#=SQ532&ljHQ7DRI~iCwAj-y*Po zMqWF_ylLxSB8}Hw>KLo~7iw|JFQE6xM;PB^xK+w>ya;qGBk%Grn4hQyv}FT8K4sQm ztrJv2h)&_|GG2f?MOnz)+9JvT0%qLi(_8N>pGNQLZYVS6Tz@b4jj6QDsq+Tt|EYPe z0BW-eqKm10`6(NLv{xK90=g~Are*Y&26_d=QxTn0g(#a{K$#W( zpNUeI!<^Sy9=(ax6KsQL`kBW4g5Gd)pXw)d3R^43u$QRbASC_WQDZfw7W2qzCHE3j zlKSzHT9+x@|5Ae*^=3egye?%rR02GH(c-%|Q*`8c3YRK;iigAKPbz^7uZu|;?Xh$q zzK~;72Z?Uf`y?sjF`~f7oFL)$ea??#z>gPm?$|z!CH!bmxvUd>9dQ7u(KH0?K&o#t z(oqQlL?I-8dz<_0pWh(EdwUrujuAn^>fZ0l@zB8o9Hks=@1W=!pH_>awlpc(y z!y2QPFXd8sPAJ0*;6R0zv+TYbwJHslD;EG*+Op3-7$W2BpB3d1fYa2wkR9| z9@qRa&WuoAT*#7MHHp%JsNhvW%`H}Sh)xQff@H##`^H{YqiZ+qbpo|))>9OoaiG$l zvOC%q*tNXIF4ifIX2a6w{alsjhPPF9=*c14P%qnKi5+jdE>&ezbvMn1@&IluV~3}T z0}fF|;2RpzOyg~GW!g7#o1s&yMG5jN2C_=XXxGZR0kf-I+Z`fy&~YtU?bxj7v#`gu z8P;f!lxrQ+Xu-va6C@R>1?6 zrtqG3>UBRJNxgn8J=jwd@+4Y)t*lm6Ik@fK>N@D`MsM^hSsX^s)GI=XA%njcD(O93 z4}fvVVOx_F3`Di^QO4(0^@BPzQWlg&>6F-)cfl_O45wTI-Q(VkRd`-_oH`!uvDCRd ziBE_=x*~`_M(k=o){i^?k?YT5jK&G1_5&F#aso-lfc0jrj#}DT=P(fn@X%?nwX0on zqh|7D8j?%NgH(Bwq|s@kh3I-}-k&8o41bdo%IImq4>J0wgx%LaW5WDEYzk1C6dKKx z;Q0DJjF=w=4MWR5Te|&vT-r{loxo&T)W&vd>s7gv)*9PJac29GENo%nC~WDXoSyK( z`7qV*&*Cvm{wwodXF=%tZ|9~8bvzqcJkMk~$fvTIL0x4RKszRr@lEuFCFzT(T6aXp z2z8YX3=byFHi4Xl%e$qs=uhex0Ubpl^6BkDa`;hIIGaSzxcTfV%9TKQ*f?Q|c2jgH z-1yvw{(D?Aj`OxGQMt&cA3wN|gbZ zCV$_OUk&fHVI0x>lfinOZGl zj1bpJ{)W*$J5QzWt;gxCiT>X*ymbfGAfIR~d{c#aW4>$=#F z9~o$^K?m~i0aAa=-OBHhMR5xkf4PP4riF=&7w)LLW#Ze3uNX(BF5F#rD51lWUm zqff5sYF~!Z>`)4YSdv>GuDSpKwqOnxM2sPvKbg(ozFnuiRf`^Dbxg`b9ZBY>7pqO2 zM+uROQ1ZOMu;H=xY8%Ej6f^Y=Ek~kqK+n^3Cj}T;%Igv~=&4pbSS**{&%=F3E?-qp zXra88))shGU?|c`Hks8lq*I@1rqHF4xC3c?E(td{;OhyMgYMdTH);6Vc#FFiTa2ai z%(!a3gC01?%gMZ0x#wL~ho@e!hQuxQl1$dndU&cAoF=pfx!`QB&dq&oVon+X9+3aO0CJ}}^Tk1qhzj$I8B^b|Ian12NI`y9B1KIIr zUT5UK_#=RTdu9K98P>941t{55O*r2kc)~jcgyJmn9zOi9B~E|;?sWu(JoAQ9d-RrP z5<+jB4ddd>&K-Rz4!zD&r}GxWmv}+P`99`vJAbi5vBRWr-J z4NMije|%gnMN3dm4)p{}QA8x@Qh(`}UzWlp_-ZO2M^m%K$!CITgm%Magbr=;Bt99- z-X#nQ7L>xe_}R&~VCfb+Gr_r;V|W)>Za?l%%pgw)!3!sL*jFk5h)KAngdUYOj2Gy* z4~?O#DV%xLj(>>+G$t<1X4C-~cLyd#Z3QR<)5Ftw z8tqM69K=cK1F2_9z?23iXv_c&U(p|_+OW|?>=*v2<`|voOYf8cL~_y4)rP{pQbrCQ zqdV*;uj}rA--;ZW)1`PD^t9KAay5GEc*oe{7&{bwrtAgM<)SNf62%65uM}7`FX)GQ zSG>Fr`W5N@HqhDP0_mDYuqd9bbHU*$|c^^*6x$;&e15L$w zg#?l~Mu&0wX3+^@(o0d94EH^MM3TjLA4hk?UUrcb1u~oIJC2~MlY)?aS;Uc<5eG#i zB}^STn8z5KsMthtK8fP$Tuh^M^*uTPrFg>Dw3{804x$2eIty63shyTkUtgGtM4sI-uAJ~*Qfa0xTg> z&`!Xxf0SK$EeBYtgZDcsbQ{)awALF(31=7uJqkuViN-88> zBuqT&?gh)yl>0Ws(O8TY-Q*6*@1pNkIOQ&!+im&)o2B?*7&xc{HkCVV zHp`vP(3t2^g&nC>s}z8WHm2sCS*rW2d|}1vvMy6+&TFi+di!lo5!1;txj^tL4-Duso{R=AP!zG&O^EpHLdLQ!Ax#KyDCB3~Fe;0ywhv zf~_V4|Bu@9lJ}Pwn~{kzGAmF(Tw`|I3j=IwM2jf z7=tU3S2pehQO=b4EnL>n^|0*tm~EKk$H$M?`X0{_;ydVoe=e%y?5OX~cpX9n`>kvj z`~W5ezS;M=o>d+O%iZqAGcu~`|iK%I5m0Q1W)73rK*rVJt?2cPq(|JU-POi}>DM+Op=^VA8 zWKJnSsc^Rawz8yRw5Yt-hC>&(HtNvZ zfNm`Fd>vjB1T2|hyTR3OyOPv^X%T8cxTp_)v_7ysEG3Z;rfx|*J&EbYWN}n7#w;B z0h)d`BQs7hY5}QW^WJ!6C3Qm-nBy{7Sy^8;=pG_} z(KuRO=WRNvxw^uDAj=D4Fp0A$#sIG3GMd8gkg$138KX0u%f1ZVW2s1J?O9(6t0!^# z)4)ul*1$}sVqy?D7{_J5;OG=spM9 z1Ua`F5#-Vx1H1YK9ML1dO6fN`JTh+5Fh|H0B7T64L7t1u&%{kY&3=VSSRPLm5^)A+_@;FJ9 zMWLKdEh>dfv1*qeTe>=vffQD~gDq1O^z2|)*Xu)EUL$hNFDtlUpiBHMvLR0EU7}6e zT%G!|=*=u-b*@uo?vzJxtD*wB4L7wWvIghE_1}cKHLdQ-`?4rS=}nB$R8U(`#WBgc z*k6uf!dM<4^HB`Aos7?WCRJT7a<%%2ZlO>jCFw~B ziRlm^{v1f!L;!I>j=$7bntF@*;1bGEfgi4(Vl{^AChr$v45J-K%{>Np&sanGNd%-a z_HQbh8;w~wDLQgoSxF+i2!`-ivl!l^fT(dk=q@-zGx9RTiSJEUSK;b*ImbbW|Xj=iZ1q@Hk4OQbE;GX=_gUGTva>#7MzxMC!GK0@sLPuS(U`F)CJsaeyoN*p(K){&@_`owyZO zl8xoOYA-v`7jK-!Lpk)stxN$u%uy5=F0YAGS%xiB*Bv81wXm|}{uL!y zQDdYLI!m3;Mg8_|xUKf40J6~*U{HFF+kc*2qlh1^_0q@2^LyIqRNVWbxti{3{$8eB5x)DfK+KemJG|(}{6&m(oBr;GD9!*t<&g*i--< z7#dz9h=e2ZW5LEeSm07tM&!w+TAS}F<3lg#OKpf4O14Hy@5r2*%r2d@j^TmAH{d^ zUi=Ur#4GWqcrHGRPvWKcB1%|0SQl8>QY59wN)ZF*RElXS2BjF6VpNJrDTbvugA1?} zr=_?k#Z4)WOL0<)t5RH-VzU$nrPwaTMk)47abAkOQf!rCrxd%T_*RP7rFc+^ccpk+ ziZ`WrR*Dy;cv^~&rTAWo_oetziVvlDRf<1L@w^nDOYx}`FH7+SuqoI;FmV8{q)4R5 zq=SUjl3u zds1vku_MK<6yKzHEyV*V-bwLRiZ@a`lj4OGPo?-M#dj&*OYuXB4^q66;!i1_OYvEX zPg1;;;tSj~08LYo!rnEESijI8DVxDsED7oQjiF zT&3bVZQ*+?uUjyX+CZbqPx!K>A{bD2k3%SGAoEedL>Ig=7Xbx79X6@1&>{@eoA5-`T3fQW|Er zKqCC#|Ld>IzyIwG0C=T6iy;xNh^aQr26Uj%oKV?fJ-yjMhH2msOz?4EjwL!~c$Q}u zDx+;m&l6HXSkp!cqdJ+E5{_70#;ZUVK&l5-uB>?fS>Ue3r|Rhvr#cARr%H zljKo3-zfbxc6^zWY@pRtizRBhpZ$6Q%qgm60kDTFHB5~>?V%sezIJC{4`*MGW?~l2 z{!j4mluT~dJ(vV19zbbkOL3KREoyzT*3U!Ko@qo1pu{7U!z$W}^{oj~5c@ zFSv+Xwu3E)s@XUYl?&CJ(jn}XVmcu_U_mj{nlk01*wDuKuBBU~_&S@xK(*oBoJ1v? zXL4}9Nf%nUh_p=@?gMsuPtdO=y-4>aNcUny6(A#HxV)^1AS$+clApcMpGL8`of`!W zu4@{oP!A8k7XD$p`eS4D%TXtI7#yx2)k1wOY8{W|66p_6n5kOgox7bYNq;v>%=Pk+!&3Po%fz8;r> zZK9s?MegHKG>NS0%{20h$crIs>4`z)b-fO31ptO!PD5oiA9fI&(Dbl45~JvL^Yz~T zcIXYpSs}g7K+yY^N*Hp=&gQG*t&M|?~|ch=bo@WH@swEPIrCRJoK&@{{1!&~sV;Fs<*hOapG@ z-*UPF51UxuHZCnnW|LxzQJl9qEw_Zl*OjvqK3xw&2f&_^Vv%(4g}kJvWWvUhkqkAz+t1##07;tO<42{MYA3@3w(2+9_gFYo7 zasj0lVHYlk{CEh%&$Amda};)~6&L_9xu&F-jZ(DmwbaTOsb51`#sFmPiD=k1Ey{H5~#}GjZ`EMN}}fzzd&_A$j2^1aiU?Pi^E%>&q7bBuXyeB zs`p!Wt-I#U=jh^pj?vp4i~{oEG`f&T{BlU}XQ;ZY;Lp)7;<7dxAE)FJ1I01ii!K|p zuRnD+t(UFn(xl#LFQ;oAi)OuZaT$mMfXf#EhNoo-NSQchzNowVz;F8zP}!3y zKo(`u#iSJ9h}bci?^>Pjh;ni=F=n{8vM0 zaQ>FjuxuH9Ri0L%t_6G>2=ppKu0~cuS@(6moSiJ@@i564(I)8vegTvlusk_jS!vW^ zK-4v$NfdW)Tu3GIJ4M8l8ad}iuzXSlgVzBn9kQ_yIUR8+g`_|>$PfvD^p0=j0;(up zCq;?8l`>A!qF(y$90~#HnUO}zca2n>BBrll9ma!7PcgbSdTxR3Kr*4__4yow!@8BA z|5RrTs3E@!5T!t@L)_V7fIiw98F+h`iEO{3PZwX&H|bAv_+9g4O7g(e5b5pB>T#rv4L)B;&*{o+oX`lZ30ZEJY6r~~eHI1ms2$(u1rgT9gH^O;74 zgHwjHQM0G|Q{`URF{yXn3%b^;mKgw`7>I4S7rsT_AkMRCA;+G0z&FEdx*1GzAl^m( zuDT7r9f`NmEg1=2#^dmJ`AEP{ILYFCh@9YJ43t*5ye=j&lnuQzbPNvHNghu^v><=u zl=Xb}EizMvyn$z{GRD2CM$cCXGxiMwc~uKOCpeW*W2S<+coummC-cOM$ip4wiKnR2 zjS3wfBQIzD)bG*l{_CABAVsJ_uk%3QJHO)FtL@KQdmnd05BdniUW#dN_TKMrZ|@## zzYDz!oGe7#bMxi(_Qr>8^#%HJNwpsAz2AI6Jfbq20)$Jc_SbtGTh2{}40}j+Nt{J= z(1R)+^xj?1ANJn<5UGKQS2o9LQp$@}H3NUv(|L-X*OPfJKi89aW>V#P8wsm1=cWY;J<6!UIex+18Oa`dY z4;Ty0p;~Q5PiSh6#FFy{D9e$``VMm{L?%Z+cy@}9ewmAxi_i-a0ufBd?iB>l6_D45 zz)j=Fc&Ro33y$%=Qx=GoGKBDQ1PNL1ExR`EPmTy>!l-WbRtlu`f0NHe0D(s$~ zt!?|g1!4KrwX^LMkB37d+VGB-sm$RPz6-NzmtvbSYBHFX#eYV3D;D_(hv!35Bqvp@ zY}s&4&ywL#rYkFrz9)cigqK{@!zhxR6Iq{)yaNE>9_3BONqK|WDF?@n$G+gUyLb0r za1mV3DlY-Wd9~GE3&f&nHzxyU9eu9d!9coB+u7LFntAC4H-oXnXpDh?hdAF+`_!*) z+n{Y5-wvctQ!s^w~PMghW<2F%YiWO?j%jrU8S%KK)OoqZOX z#DL-!Dh#T%BLS^+nJwZ%v>p0I%k6+rLNTn|gAVl$fETiI@mnfoW~yI4aX^vrM**Aj zF)X%G^D?_OXmkbn9pAeMHboQHz=8S{o}Fk8By}Rk*8o*Tn;x*h%Pbi#IlIjmV?0Uw z&%zfy_qgQ3>GffGgk*gyX5H-WXyiYD1GE_9ejT`#w*XBiMYcfY77%W>cqL84>U+WZ z2IO^Fbqu}R9c5)`@BJ1-spEmKiUa&_q7-jP<4?AL=XQ0LUS(Tr)T{-ZLb>lv{(Efna+FrlKwc4BD-6a(suksTH)}EjD9`^^RP`o}#Lk>m% z1}5kSwHAxUu5mi|V3cORmzB$TWrZM}7C2Tv)-y+KqRG}#h~L-NI%N|G%^3{o1*rQ3 zS$;@7m>K#RiE|l0Obl^Cv<>{6?#35DrYJqfvU_!w49-~5ap}?jgiR`qcQU5>t{&sk zqdyjAR+p*X<_4ZEG(k{jcDH*GpGz_?z-<$tYa?WpTJ!`VX62k4dqKrcRn0xU*-CO1 zE38dV8NCTT6FL395J?;C1)M62+JGzqVARG&>*ZuNOG!sE$^vs$sYOG=#fV>e%5F62 z_|#^9bv;}Ug8yzd(9_N}AY0t8z{{4-9EEO<_{e3%BcN(9;zdQyUcYaU1Kg5+&x;K0 zFm@G#pYN7!0qcxh@!_hBgz+J-ham2b z2Cr@Gb9)Qcfv~+_nxCpet8&zS1Qm%;qp;E6I&7WWqn<>N1C%#_Y+WOt61pDSRtaM$ zsC_8n`yv#gXj{Y>{e;NbEY_^E=2?M;WAq}@Qkr~rZrD02|?H z5TYA+c>4@*AE9v^{M6y_{6O(uu=oA!Y(YN z{hDIJw4MLWg}`J4_lVvV3UQtP2l%tQF8B2{f>VXaRXzkk58bA~&T*XK zk#{l5jeb3JVDe@#sJ;v-QrD1t8`l}Y7*eU)31$J;9skN8MI>Ja=D;;tN(hD$evfw@ zDvUV#8mNxIIvc<2W=kBNaS3g7hFVqQm!OAbU}C9>+#%{P7@Rtash)lcUYOp;Ax zh`nG{g~}rEMBgFE#<5q#GrRDpMKO5za_7LkH+~~%>ggk|QGLk({?fv?=v-+QlNa=2 zbRc9#%Ea*kjy1N0-a=y4C$(pAS>ix6C8G0xT?i^Tx2kGE?xnYux4&EE?SHTH_CIwk z+fBS|T{NtORcTZe6|QI%6shWvH5as=EKN)Yy_XASvxIq8D@A61yUgE?n)&so3K#yR zNxhQ`uWNlLn)S}b%Rp?TkzbaPH^|20cv47D09rzPcv&(dUVi}KP)7b2#fdM=BaxKR zElV1Z`Oe61h^|~S`@yo(^>@uO*B}n^$B=*j#oot|Rc1Os9^>mSzo(S&f*7V}#2x3? zt?k`V^Ug470gE~{ly7z9-*R-70Ju{ly_;zW59K zc!(O9k@sJ}0HK!*0bU-7g;^cV&Ps`2;GVFm^I!a7*>&azub;JE{@7T)m~n-wAj(=Mgl$?d$}oT9ja%myU|Qcy@LNUsg$+KA z%BuOWaQ|=QNxYE`H}&3Pz+UAJHG5U#^YNrBgn(b}W^*?c2)-=D>I(EKgWvGD`AQ)O z_cpVf@u^{Pj!x{DabuC;+I|I!^UgNS^6-=~$?D7626eM2iUPKgI)i4_)izdZ2RD4n zny4KaR4ku}x4Z1KKG_$fe$uS0Z5ws>N%F*#HpUs4JNk=4$K$e#0FAEs#uPa8;&ReT zDUiyMzvy0G)Jeh6l9b^nkLomC$O>W0ER5I!JUA_AMSG1tx~S~5bkNvAS(D{-i}GeH z53{3at?g{tqo~`cZ1PG&V{8ae7o{H|uP1O*lY+#WY826Tcji%cpja1`jh-(U%D^2; zWn2aF7Fq`7c_MFV7SN_@D|%7Y%I^kqT>HgDmUhJD6@wfA1KhtEpnx^);!OFCS`S*5 zsGWj}@1Ro_EnFq}usG^?lWUKW;+WAoNswUyZIv@(#|HA3#Q$6o=HU_D?iA?4Fp$adO{_vh9{O z2Pd#*;&@js%;{d9%}n>WO^wstC=2L5b=L|ZO5zM^@ljn@*XaF@YPslamG?ct&cS0G z5l-HMrS`l#6mR?gfddwr<5C}0RY=*2+?5MtOBU{5vBj z%vzSTpbY`C4O`M#w79n15G>)x?ziuFb#sh>=4a$aW7w}Rtqp3SYhG7qt)tvOM|XN= z&QfcsRHa}kW}CbVRi&w9o}vph0OcV2Os!zQsDh?{*-``RFMjP(=*bGoP}6N2mtr|x z0C`fyAZzRDU$itZSakmj8yJNDA@#^Fbv<@To?7*>Ni}Pyjr+z<-O1;Lp#~lzYCgle z*|>`${*Kx&2ukZ}^2pIKjkBhFgN0XRtfBJ!y%}qg8EZK!asC@xocCi1C8gyFeW~(< z;6kV|g{stNMa$CLAOUd~ok&6j2NiK7I$2+!9I6-e=L|nOfbFa!TS;klMPI9mhEi>c z-q|98fDJt07=BTlny4kK`B-5qos~BIPR5d{I!80)7T(es?BxA4@=r_lJ4jlK;zlu- zE||GwJ_O>pte60tl#zc~DrVDZc_gmb<&QGS^B(_xYope)nj!UhN>3lYdnI^;gx7E+niTXGfn>Z<2=Uf~Zj?}iA zEXXQ0)U#5M+*hxx7kTc?>MHE2sn(<1Ykc4WTaXTz1l{1PMH!dLz!N)V{X#Jda`cPX zr4>;LOBnvw6W`o4s^1f@F)fWRPvU$PemwD@k!i`kqU1M3YPG8;An?RHC{bpURn~zJ zZ`CWRr;0bYRkE_oF7)STHidG$D3v0u&hWHk6;noYiva)(r(emb9SkYCbXh@XzDnxJ`F1?+`6IAwhV=$_cx3m?q+8T3(Hic zENYtv!Tc|1O5qf9)7m=Xb!qv0@C8op@Wu;_3WQHM0&B~&)c~Em%aQ`V)mbIlxTi)~ zHf?&UJgeHoR;)Bi`dqi&a0uG^mfS_-`1yR1O5$a?m`=PXoxW6b7H{h8JQn~FfT4HA z7;sn8GH-cPDnn7fzTU`t#4b-K!@UyA`KQ2B6ap$CKbH0HWRX?gR(z11jL$ZxT-u4_C5a>Ac zwR2sx19RH4e+fSKoL$3Po9NANM9)@ye^DMoqND4LdOvuLn&a0RcS1&QtY@&#&nkOXYLjqx|F)pf%@J91bw;T?E50%_ zC*6=kgk-+Y2AV<9)NIit8%NZqC^^(CapIW z0n&e)p1;(-f9PCZFc#8Kd1+Gf=%|%Mn!|x<# z$+S2n<0GnYx1@?90zOJ|K1JF^dPFNL3-pp@T`zeyQ?uceb!P)n4Qpnc2;=ZXp}Nxw zqKQPTh=U&YRNwp4Kui}n=!s@(N8bqF34@wxYKvqMVC~6acBClPTrl)5 z_R-9?M1PRcboZR^v=eadB2F)s#mgZ%HM2wx8880 zl{rqb;&(Y+i zo|lpTLo;YUltFp;=Aqroi9*vF45~cC8^cj*?+UMzpuE% zUFD63GMIg?vnUR6B@@|+;U3nR=q{d4nwhF~QMN{H3s}~F)-)i$=*3-SCJr)gFJxRy za{vTDldF$9wR9D}HhO5oJ%~7*e{+yG0U!!-;>E&sPejvKSmq#MIJe7`ByIzf& zaVPFu*E0Vppm>`2si=r1r)ieUr>YiGe6}AmxQ3E69%JdZ@sO+$wEC9WBOg)wHCp>C z+~9`Y9Cvtua$(t`U0AhRzDu>R(n4^1mp#jm#a!rAS~bLmy6u{dqx3^#t}hjPxXw?W z2fbz^`m#@h>iVHqq6G+kZJqU+kZ+=Wy)dqUP%|+eew!BM7Hu*}{9&*0F46-Ib5=^H zqHXe!%A<6W-w_1V$;~mslD#xnA0KCd+u1qZqGfxqLK)d`+e-Oh=sFuv&{fI*ftYYr zRYw=G(6!R=kJ53WlN4@oLZ#ae%o+<;)pWq3=fbQ~Qo|jMWt2QFwK==WLk-hy(PY1Adjem z&wKOmKbH%b@*j{X|H-JfIS^t2NMHuXzk0f=5-Q8^sQaU{UyNI4pl|7Y`}Ou4z?F`7 z_qMiYv&!XxK6zz2+(d>_t4Eir(k1Q2ag4qn#~EOzwLH8l>&zyFyEiJN#XQI`G*C)D zXxbHVoAYB$Rb_S0GLdg)CLLw@Kt5x_U&n&3UHrQ(H@~O^`jGY7?L}jK1h$&?)b2D5 ztn*;rOZzRCWE)UP?gAN!+RyGt7YN4g0<7wOR<9K)nJ&Z3Vwxem-1ML-$;gnQOS!U> z9X^rLV2OTVWk_h*}|Kr z)}k4xbMM_}pnU_>^-!3kZTC*%LPT;>61v5?V_cs5V{V;0yRrZjq_A`Akf4R z0eiD*m&t8+WMJ-(X6^M01H~}YD<1klEt0Lc3Pmnh(uTqHC@rvAVp!5QobW# zQJ>p>^KuBKg&HDnUUX)}t&`whb<5fz7V8abUoY$s%XbJ?2eWIvRLVP?htF&NFlu|r zZlZ;|-E>%a6sX>%)$`<&H(azeztexkPqzQP2fpYwC9mm;b{wF3#Cj(dd0Z5=t%}fa ztdA#)MfXx2t#>}$_`+y*ydvugd<5)$>VLO zISf;9zmo2od;O078va0gkLXAxvRmPi)>X_Y8jD6{Sr*zdgMh)(S9U+Cz9iuytN3L{ulDuIEzsnjDEsew+2Bl7(kUpd;bw!0EkV}mI3gG*8W#MneJCpE4^R+ zS?{YnAo}r9x z<21P-r4L7)p_CUBBrQUHC`Fl+W7#k$a*dD_+rbtY%5i*S&6XHKK*qE3V(h9;sBPR9 zz`$zdnvYJ4?S@hCL1{V4yxCz}_b5hKKRAEeY$GQBA3rgqI)k~}cz>`LdhxW(JaMr1 z79$zJ&X{X3OI{*v{V>QeEStiLp2xXtSER$ZV} z{+#tYjfnirTY8#(f*(+shuz48-f7Z^iD0w?clSI8>M7&`Zu4xCOf+Ojdn z#X}V8Aek7uREH6FB7-VNl1As$#%Pw7&6!pinOGp|t~xt+&jQX-LkwOKaJMZU&#aS` z#UYLS+aFM1+FrmK_$XCMR>DUh+jaCS_3>F*(KWzJLn2_et?R|58HmL>@r^hD`phr3 z>~r7lGVH5D2%FMS3D96+p*K0h>|*f{`WA1@W5I#jP>SR=BlCK1VeNqefjeLcn^*!! ztWee^RY&1zu`cWd7V9=wlA_-l=WXM>Z9wL0vbbdEq0j9~ms_ND zut^4xAcrS6Hh)3aL+NPnDDj8E#%Qx=j@;r{i2;h%xZX|`1~KZlrYgr6NXli^Neo-_ z3&)Zk#s(xiO_>Hj=-I}kD&JGw(E(gHI<$nU)Szbl3LhcVo1~sC=!`K;AuO|u9&?It2!50<4f`eyQNQk+Oq zc+FClvsbGAdAT_)lY3N7E1#; z*}RjWN3yx+2B>T z7tjZPVF{I%gvK|dMK)v`rlEwqcigLD(M^cBXId35|7VU$o9U!JD{TnY;kG=oN-EV) z2a7*#@~3}!OQ-*;G`o#hT;RIlUvtY)!J}F@c$M%SbUaFSybooYH*hr;`67D5fJ@}c zt$H1{IjCf7>4`yF^#fg-?q#8QNY-MbM00fMAU#dt#-d*KG`ggcG}ctIdB~NfuAMm% zWo3sSOR|wWk%8(%wWh@ZcGPf?_tYNq!Rm+3W>R@I1WFKhnsg!{C4i0u$eD(DDd7k! z1OxJ*dJG0!;sW%Do)-?&zK5-Pp@(VSkuUAilx?A}E9g#3lmmS`uM%g?Gp4{46YyvF zB$eqmQY;?XjzCf-$E}9lqiwpvXY#PDy7)cPdI0x|j;0$`)H;wt3DA{G0H;P-zFieO zb4+h*=^hY|z*T}OTfj6sJ}GrenibiuHmF>kNF9g|)qwC4+Av!Tb&5A(T3cFjn0jrW zwRUO#(>j>@x3G@A_XoYk(9`=4QE?u0pttVm1j27!VSD&jB!>w z?b+6fxK|tAoiweg336$T>C*KK&;mpo^jgoC6wPW8XD$smfcXC!>o3J9J%~x6R(Tc0 zFidySSj5HbAIOjpk2QtV-O0Gao4~~r5bx|)lTKymSGaM~Va3(63PV_sECg{v_nlZq zeOp0moWdF|95F5|oiGNCDpA*xKl3Wnnf?FTDx(mkxy^Pr?-Pvm+d^-#2OFY< zeDfTcr%K@#gQr5?-wRoFqc`uF+qq%F@+L6uhNM|b^c#92or6QZ2$b zZ&1ef$K?giGCQay*)Ux27cs^%ok$b0hW590b6rK{0m;r1Kne8Sa|dIn#jAXso?pbi zY~yx1tgOId0ESjvfRWev7_vfG7)}w5od8kwO1@q-je6v564_lf+GYXw*ig_63l!6t z0Q!OgGWZWT{ZcEh4(e1Q>!wvVi~QoGv)S@PC90}=>c-Kwg~zMczsM)pf7B`1|EiAG zr8GHF%CIO~VYBJ`by3!-%}TibqI@?_J}skLly@NiimRNVw0dW;R60V@ev4w0`C3tI z+IK(Psn}#|A4}2Fz#I|-v6V*to%N%aMR?RVlhf<(Ww+Je}wCmyMZrooF=j>6f?CP0}hpdNl z_UK7V<*UTrtE%b14f?JzBHRJu*7SL?Y8I4!YxGUt7=4pxxCpggi5G4n^+Y_aDc|fg zm2Y;c$~QZ8<(r*$<(r+l^36`0^36`W^3Bd-<(r+w$~QZ(B|o;XzeUROFbCvY00Nx{R%;~MBVemkgfUj9{=#7?)r*S_M2M;$AJezv<48+M z^QJ0F_ER=xjTNUOy`4JJ&bi2b#<4vADaZ1gUPFAmlHAkGssEz$*Wy|eNzramUXOF&R_UIc)bLq6cXeRGqYA9gf>4kU1xg?0p zeu_FSn$(4;R_9HG0 zCCrj#z-vUk4WecnxK3HabtIBz_r-Ne#&xia!FBTgJzVF({cxSV`{O!W_r!Ic{smlT z=P%(p?-Tj?p^CY|Be=ZJ*n0no2gAjncNt-lDzy zS*lpE8wYSh5^n+Euh;niSNG<-z1Ocp6a5d8D&dDiZ=A(LOs~3f`{cyzpwJqgcnL|& zxA4Rlbjn77kKcaQUe#a=kg1J*;)%3ExYo6FEq|}-wZM35$Bpgu{B1YEkVQk zhT=7$Xx;3}fSfL=FJvXg^Npx>=Z=havVWGdI>*`#obvR2sGZJn87Dz8#Mr#O;aZ2y zlA|^8UEOHa*&@-#XW8Y=-sbQq?IKDxkjRiQf{k2`a#@^Ng+rC&PW@q3XrX)bw#AIv zQoFpK{W1+@iZ>oX1(R1zE<=I73|mq$x))wjsSOvrubtQULAcht@8nk|xmFja!W?~< zj@NCw4r-SLh(b6{F$^l6-0ejfZrg-VFsLz69+b7$UAfS98a;8dUEOS!bvb0VVxb;> zM`_q$3U_d~%qE>&Bqk;RX@;6ezHLYfd@ZuPtXWFxuh6|!i7^J+<)EdRYmW3RX*i{s zaGkn-q?~xPC>DQjDi0g?SU+U2_yCMX{)dJq$i_k1ZhmOG)iRL7NzP;yphbBXd0HiW_gOsU3yDSI|PPH11K;RU~ z>V4_NMd@f)#tI%WWqBlsn|^o(Tg9aPGS`k{@zzsBIn)nSyp^MWk{DRl=z};on(_+l zVr`81I?SP$NRC%tgV0aw-&sAY@C{C>(foici2WL_Sw|`x?cK9JRoEy`y3JJUTzqqC zO;5ldH4W9`d(TMf^6G6}M}stS@kQ0PT@^9C{ErBRzE_wTKjeL*!Jrwxs2Bc!A`;C1 zs?g_8M?eCDbG*^Pl2(XaSKZ0T>Z3Yh`$d`V18J*RWeQYD0#$YiNR*qV3yjiJ6J?i_ z>c30H6Np+$*sm34Mw&Vw?qp`z+F!wfHXE2jVjyZcx)ns-+*pR7*e3 z(#WOrFVm=vyKsuhy3z}Hd9pN4mYyt`w41aZwiKu5NGw$oCkb*m>Z#Zg@;#2-zG%LI zIHBh1!Ao_+R|{DOjVPXX(WY${Z`Qrja?QWizwNViWhGA+`ac_|!EAQN_fe`N!N7(E zO17C*Us#vq&FF4IvQm=F*EA97y?>2FbxtE8KFUQJo~Aw6@hLRw9DV3Pu5QE(`gWLF z9Ba;AFCaVHO-xgcsdQ(wPdHYU5^et6(>cR#n#$B(MsXmT6QVyBfmkkDjS^N?mg7Zn zLy5|UN)?seiU4U!Q&|&Eo92_XDL^kC^+|ANAaBe-BR@_-b?}l0zraPUQl7S5M~R!= zv0@s8(>t70=jmbsJ4@RL=tYR2aC~mjg_}ga+HKPAwmdZf3NO+ivK)*#vAcn#v8Fx*ljk@ zm={#h#@WW_y5d%=RHx!K(pA%*G31RX{Iob$9$`g$Jn# zR>iewhfb^< zH985ys`z;V7*_N1bd&xp>eJOC1kubHBQfNLHB|^$)KbcRtsse4sq^7ZB+=Ghq;1`r zLt@Zdt}){*b^R0HBEJ+%FXPL2e~>4WQu`Mvmbj6w`dzx17Uj|jYE=%#Q`m^`45+wV zYOkj2S^sL#ceV<0>`}JqsQyB z8KysperuZe|7=Wgxr9ytN<8P+^uV@T_zk$`; zDXcLoE=-27G>sBJbM$U-JpdjGX*6nc=uL^{V5x92qMnBH1vw0J#%~J1w0hI-#&wbv zQ6{EJ)u&B?q44wWg#?rZgNox!Oku^+0jw7cd@V4q2ids+m}pv^bccX&npj@0ST3!swArau^qQ06 z>2x$AgUi-*?5OQno6wCkNl?2xC7Zowi-3`m4sJXo2c56CTe}5!IY2ZtG4|D?zb4ku z-~k79tox%N#r&RI@K@e~YibMr!CUarT73`Ft*55C(2TwWVdg+iU_J^A7EAjuq>U;7 zKuln|B)`t*MRUhiW~-@%f@h+^bj$2@cBRb(6|g;wnUf2cX&ULh3FFGfm#8&OJl)1T=)pB*W8`>Xniglt zh}5U1;}MY1a!ATk-w1OuLfKl4Je3QoBzfAx`g&S_sTaU`?3QPe_78P}0+{lXjpPdG zf@$c`8%o4$Q3Vx6{T}L6!cR8A<_jSI-L5!Gd{`dc;iFSck+0V)#fa8sl}dqYE$7TJ zaytW540d8NrN5}Fb^jg!o^@v*^0qz*MADEpqpsLvUfdhN_u60sNmI?ZoWPabOO}Y_ zRnt`tf$XMevx-#kA}y5|zj+?q_vrs)QRg&42Y^AV+rd?spk+DQ3{8t3kZANk6xnj( z>-V4!6X|>Jcy5)G0FZkid~^Y!TVVV&HJ4e6M$72X^e2N^Gy;)} z#8G)w;#~@MKvp(GxKLSbkwH;T&wS3JM3ncPL>EvdZfvO(+j#$O2T4PqF-gu3Es(~x z|M0(mTYGq#2=D)RZsMa2N(?KBzy9{{RCo`nX`7T$R1?=QW2ch#`%}tzSWVob#3Qf$ z<}#G)vNVD&R^TNjO@J4Lv?j=S6~2G>dSAx*;OuRj#}@@!TV#=I_e8l>7~ex6XwBxA zwM$#OI#^k8eIZjL%%9@;ah=FJg#HURo$St0RZ+B_EnFMq3K5?t=%a~MneFWF=`M!& zl@UX3q>Ntf@9lQ^UP?yT1l*1cZW9<{$BVoUt~o`X!wWQ4vZoc8S*_oU6?GzimE0DY z0Lot93;OUU#Jrs>_$wO@F6=`uK1HA$PXSux$&Z+IuRNs{yUGyXNhnF((353+ia*yA zMBp*Lc#i%%y)y=^$!Jyi3>vN$K$=KSo2HFaAg@c47vHmi?!!fVy$aKR73V{20RZy# z&1$`)xdc=N_50wd%;Ec}q&FCGz6xVYOYf-lY+Gh&h5_p5#)z!%JFBj#p|2R&sz%9W z?lQnp6Eez?wRVBUrYA7pfLCRxVBw+!fC1EVWu5gPP_8FqGIMhRWCu?oEhg^a$H*&W zxjM+Q^Mw4Z$v&0o zfyRS5-cB$5pkHT_!&E%QF`#Su38eb--G2*?rLL0b1I34%Pl^IoK4W3Kgh3TkxM>`?h3UU|#&`^N*A7zbXwNM@BV*j{;W%`v^Y*u$MFX?mfiq zfU;gr=nl@}e4~UF?At`l)v>R04P5}`Rg%80K7nfHT*j4RPSGwPSSN!%0pFV$y zPX>UkpPnV(&c_#NHu;_x<@ECE`sT;l`mc|E`~C0#^N&9|4yeU zDpC`VelgJ}Y!R)AX_WUev)VJX!11RM+$o~?@AaS;ccQ8O`u9gG^2w8btip%C{ezYB z{WRjjo6uXxQgov3|NI^VPk#OVcZZXcppM1k$N!k6ddvKWLCEb@o>aW65%QHFbmonR z3YI;k^jFfA*@7COW&$>`OIqK6W2~&GBKod^ayTBky-`S#J%4jx?V_E%oj7Ijyx<*O z$&fi2hS>#d703cN@G^4uV*9O-1B}=mSgkLH-cg}$`Q8%xYeKR;Y}`*#t&p-TsoDAU zzJ4wK7WiHVCMJmd!+-XU{uX$a7L)yS`_FLn? zk>s!O4VP!3!j3_Dj7Z?%?xg`$&$S#s2azX}t!~kP{4^rx`{rvEs{$afO;|TwJ z9e(Y6UH#fU`db+Ezdro>P-Da8zsQ!b+<|^+vBs|tJB*li5X+M;+WD}<2<5%0!}SI~ z0u{*(GkMvX__a#0?x358NiUS&&0rP0b{bDzp*Wc0*E zM%sGnQJX5l_B3foS642GI&^a7ds?Me73BdsDG0ZVY?=?69=6nlds`w1B#w1bwP}d( zUSTjvi*bKgeJAj26E7?CZ0&6IXDiDVeZ(Y7&dNL#R*P21TydyrO> z<7_I$rm~YXc6xot^(=9*Bc#>_%1}D6eHgWrU`%gOiHSuPo#1SSP_RhglW1*ah0GI1 z<19nPpos8>qRyFMG2*KM*@gU0kK~?D&c_7to4$|xzUU7bb|CSKTM9$S(?R*+vIHI?9pM_ z9L>Y_Y)5n238&E|VEN;$6a$T1v*yYLemIu^Q$Vc0Hv*XplmY*&7K3aeMN+uDjT;ku zMC->I8%Kv1M10dHtLuiHN+SjkpNjQ>J9FIJmS=IXca>`Cv~BaGa+MsborSQ%WVy2I z)iUS3I`pyEt2|7P&|(OFB4od--d#rzSN*S7!NXIr8C|v7jG1fey2jBpYgQ9qkS9cd zst1cIG>njHxlXC1pX*q9J9_x_seib!`sE0FxAC*yZ3}Pp!Ma!D?zsSRzac#~VAV(* z_4d?ANk4zG-q(;@I$kx58g7m zgwo-dmx10{&R13pjDrdcQ><6!H%jnDQ|ibTfWE3GNo};5*+Z??5(Rbqh_scwl=+Qo zNofxojRU8P)nAj@OjowNt|H8-(+j`|FH)fE+ytxe5!<#4({@~MyV5eWs~evWh&fR@ zi(uF7K^=%U)*ia9>oYNCQq?mcyG>2+EKq{+)nO29bTU=Ms7mb~Y7{Jb8?j@MLgwhu zCzk+ba}+|ni4Hvnl6%7>mjl);_t+i7_2PMhEtlngJgmULwSkrn+p0UbEM1=W5ri$@&)rDh<7Be;ykS+aJ2QI;^7zJ z;gNt{hU~@mjwA(;eYgh0gZMPub>5L-PYLhS^EA6kRh5v0E?C%(znB0t4tF>%?b~r& zVsF*A@S8yT<9$J@Sy1eFoQ&k)W-yj7vh2JFpE%eg={8oyA4xKVOCRUc7j3_JBbdU*KH zuUFj%M-S;lzZX9u&p1Z%k`%z{hsc|z-0IL$Xw23ZuUbET{Is23CV7^!2mR&cN&@;7 zr1=zoMz2;@+P~GrThF5pD=SKr&?4-q<^CK!Us-wRN{W>0ndgD{gx{W95gX=O`U6z} zpR9?OwU2UmDj(99FSReQHS-Kjn4hcx`mf($@%|KLNzEsdGdSwAWD*bIPX>Ro!gK@5 zz$@pMW#nUl0Z@b&5(xQW$9DI2-n~Nb7l^b(xCGGbL!d8$oP1)oZ;ZS|9#BybfVr({ z(qdg}D6~^k8V`r0|F8Xy`yRHxYRc0I%u5Fp)AmW)6j%+__I_o>e;+M>aAf5e#FPOe ztA#7;->ytAyC2?fA8+q|2*mrj$Vz9ukB{-w*7k>ky}j4_$IoByJ>7VH{9o;hVy z&lry96>%lDbD*R$+7V-P7BVUu#gkz)E;}9N+G~13eE8m|*2n$nNlQ1U zCt8?;TCQb9s}F?)^-_F|Q#sKnGL#s#pkno0 z4l}`kqW(3JI;M<|)1D;kRUWCZ8$LHsT5E2a&Pj=4EPNW4HPjU~jZoYr3N=M#e5w^B ziJK~wcT{EV5~a!=oV+HrBQhr#Uql?Zx0@CbpJwAzl`^WZTBCGRCAgVJ=^iWO)l&D7 zV%-^5QsGW#uVzT#swNc9q$`PQ={sprA|g?#hix?~NqKx_e8iHtuuKL{!n-WPs!;1& zht5oj&Fq3qjBK{eF*G`K685lZ{TdfH>EKz6D!4aM;iSEhFx75v08__w^5J`yjR8rd zP?cu@(rGEe+xnF~@12#rOlbA8$Yn!gTIQ|lVMQaWTrpKqk2x#=a zMu7ly5m}?-3l7{s@A$!8HAhQ5Mez?-ZMREl9w8O?D$zQWLyx+&&nUAoC!(~^l&-gP zX`d-wpNi5xbNy=LtqJzwtp`TxXXf5Tw zg%4R?lA;!16@~PUTGfzw&mQ9)*8Aw6z&2c0x!L z&whehe{fT&y9Tb2i~_M^@2xEigGKt!<0-qvBanj3SyDAf8T;3k`JrnXUL*F8% zstp(RAO=1q4a{X9LkKXC1srZz8ur48GX-Rg$j@%>&9aZDi9dwbWw){jsI^S7Mg98J z&1PeEpsCh1DYnsKh9aACugKWq!<1CmXo)t-Rrvbia@$d>rRF@X-E?M7Qum%$K z-f`x6vF+1cEuxMQDVX~twuW20a7QUM&$l{E^Z=00W^$P{^q2A3pZ2Gg2OmOr?4(P8 z6$WgOEvCcXHRlIbw!RBjRwlmXrWw|vaZ+Wj2{Niv(gZ2i5jxTd;(!f!hUlp-T(DKM z1wpDL$ojw-KCs&Bvl-eT>#f~S!@Tdqmd+Vw(J(|_1(InHYWsxL!87)3y!HiycE{r= zj@#4Rv%PW;+-$~@5!h?SAVMDT?dg>(Ti>?lh8u!9XqqG=Ds;dOsLd2$N+uTdhz2-Hyup;yBc;f!qpkd&>8u+vaayK zA=OaF8=yoT`t@aqir`KKuS?6BWSK-2hf^YZM^|vqPY?hl^DU)CwLpa??VDu@rE6-q zY^S0OIS=gSXRY|&7I7=Sp?8Zn3o~@Q!aIsyVd@dE1n1S@RW77q9iH4g)8sOA-=d9e29-dfg+AKRKJ%g z=uqz9kw_fWl}G%95K3H~>~IqR(ouAaUsy5LS|^Qlg|yhX@M(ZW<0FCPVkn2;te=Ej zJel|Oie+=MS#KW@FQ7XjlX%y#cnyh^b5$Jj!<1AUZqXhm#OHbY2r?dl+PW$3*&KJ} z7)AmBfXiodv=!m!cs{QW!$e2!8VMOKjSOm1VKpw23%4wNj!Tb%@II~O(Z2Ige1T$n#hU`d5f)D)69CXYC>z%+Z4inPYz>N4e zK5{V3H;pR4ZSmrWm?H+P#KCRzN?z!J%nQQ z6;Yk;?9~&H-2|v!jEsWXm%^HCm?eEyI_DQB!-`jwiNV8vel7lH^7~(}I>AE+NkG>h zsCi>@yl}9_I##0R2ZxH964bh*iGoC##fBf}S{(rMQQ^a<{-4OC(pO{K(B7~)7)I<% z<#;w*hI*=LC|1-en$<6kL!l!);?=LND6`rvC(h=drC3uvq#gUYBsNdACh@6^K9^(& z_-ju+tanjB&!%jD;>^1Ze8N0MscJ#Zw|RB=26b1u*ff3WS-H=8SvGuF1ikl_W?q(T zi0~&e3jw@18 z0);FoVNq9gXSZpDE#sv*C0h%_Kzx>GKhW!jx!VxZDPD#~<)q89N(h{hbt&8ox)iw^ zhId^0Od-j#f+Qo@%?Tr-Im9eUo!-x*q){e;Qdn#bYf&OcNy(kn@l2)2Wlgb?54E?# zPU4KJCPr}hznBJC9vV4lP0g?joJ>IUFRM)HqD)#PpUhv&D6ixb8R|8qV!(hIa8IR4 z0fb9M*`1cruBr_us>KY@VRRbHu0b0pyV28hbn>l2TF+iqsSz=^C@`bE#;u_=s3bLw zLOn<8UK&PmrZ$Ht#8eDi)E3Gh1v3SN4t@qvE)j!Bd@>?U{AtjKzbFAN5Rc>B5llvb z(kg#I_N-9rpo{Y;P&u9E5uVgM(i6IJ2!gNxNH~RRrqtO&P-WAgf?Ff593wTB`@ZT< zz^!l(bqmgA70ZhA$X<=tPlN(9GP-!}qIU=T2B?0ZaV)~E9ef_GOIj5O#SQuhNMSbN z3?Z?e5VL&dT@{y_Uig zfL}^Ou>-%N9^fUpOYWpRK=^y?=`Lftj%XK&3&xJ@3J=^B&U|i^rSK@HQXWUNtlX9x zOPBG3E@q9_JD`s7R#h(Wu!2L@#WKjxL40}?=)=jhSs+ke*VlwTcGYQZrpW+1b%aLb7Q8z&^Uy%0w4X{KHqwDqf*pFR z8CsmFsicpiQSIxctqkS58HzO6By}jo89Lz8%uQS7RJx2NW+hoJVA`t+TgwH)R^nX5 zQAQ9Gs~~|O#(F8tLl>=DR#p@saT7(RwKrI$FteH!%cMn%f=IS5im5>0r+PDxMW6P` zcX$NX0PW_?k>nJr6q+X=7HDsRKbX1a>9Co&_cA;`{i%C`r`R>6PBEK7iEygxhecFV zGhilJdN?>zD)1M`&rk!!Wb5qA>c1@9rpwiR3J_^Yg(QRdaCAAOW3Qn&Q42KYYx^2W zbzX?ftj*j5z2fd1VqfhSGCH{? zmj_nU-j%jq%PRj?a}gA{NQ0j!F(CPZ3}<7<#}rIT6ikZLd&kr4ffA$07pSUEI={rT zk{4*W3)B*sDlwC$ z{Oy2vGz%o3ZA<}oGpva#4lzNFno$v|jgPG=p#ATLr=Ws}oL^q_^6oJSpky8qdm+cW z`VE_StgKw4(@103f&A+{5C|P#bhT9lfWflM%QzaNju9{77RqmT>Yl$D?^B990QwWs zC^J1^tsR@7RchIJbl+DRKynf-FX!~Y$~&l!Pf7T|N#pp(4JF}&(&zMi#jKJE+)MQXcQ>Z$U2 zl1m^%FgSi{mS~ZiZCWO`R&z{)$MhS4K+a zZxq9oti|aLk}YkW3y^>juG*KL$v$HQp)LkQcyGszvt83o)j&bTUUW2YCYlvvqP;n) z&81_UkLLpcoazPl)e6@ww}ytVH<95UK4lGnU$`8y9hTI-rhvfJB_-$N^AQ~J@%ZM} z?9G)GDM?IaI*7f;m}F{GB4II|;D+)o`8*eunPENgrnfjmgNME9jw9v@xx}cYBa=n1 zq1wV);oaTGrhKV%<@xjmwJtgRzC%eJ&yC(^r2$~lenc@%TmNBP&;>{LUMa~tYs=m3 zxks>CQ{yPs5;QMJ*a{2hxVsZoJ@{#L67|(kn;{0e4I2eI3J#=MRwn{M6{pkn)Z%)n z)ciBTH0Va7oy&I{z_0y^dYQStR@Zy!lXF0KH}OzIB~PfP0Ih2A##1KSoprH>EF8jo zHnscRptphciyH(Wzm)!-L_H&IvQQZ?^t;V%g+_*)Z9%7>Fen`dj$j3fFiRZ|9S=`s zyHFMrYOkVG%BMD0$8K=q#CyNI5JbCF8MFG+FPQNp9-QCnaMR2zZem`W4~czj+X4>9xkILSgW;!olhR;?^rN3IC8-)fkNKWMxe9Vjc-2co1PVh zdVi^NwbR|J+{f#4RuS6;gzM*OoI9lExZ|jId^1GqZsTro>>l9kz^RyvU5Y8vFbv|) zQQ2+`LGC4>s6!jSmh6hkOpN9?Ne4UcNtWVT<sVSMwl;qtsox$20YL>sfDgBJ_Jpcfx4@rLS1ZimiB}`N6aZfyv1(ql*m~9&S zu$nC*;W_CzP&rbOV8x;OEY!!}tLbkLzmTCILbU!)xbL&7V!#ckdbNkCej6+`O%Nwx zjK1oD)I;^MR>Z8hy%rWXq1VF6!bGBS~?)h@LS_hs3Fp*&jbb` za?;c|g{JEG_^F_qT01>C>aB@5nPbrf4YsZ63gGKm-A8DI7($szf0o75_HPrL^xWSeN(TBH9_I8m{dGq5ej3*7`Ye zQc)re`xn+r=kGEwRi(&JEjf;b#9$!^lvNP;)@3<0^y}HgrM_Eqkh*%h(&I5wPs#Tr zuAaVM{lFy-q`C57YU!qRHHVD&!t`&<4AmUfxzRtbQf+94E+Pvk;#(nw`+!zWIpBBn zSS+Wh{JDpqY1ywC`l8RgdH{3F__&WfMc3Yy^B}A#dbjM)TdP}=^Z6@Liv*SIPaIYHWtjZW?Zaw9d|A7`aLv>8U`V8n&a&Yr)Aao+QQG$fM<8v`QG^`Wn- z(v`%hq})B$zEG$$FeoU*Hun)3L11=0fP!hL2Q|mthW##XF?U$ZS(DzXIk_h2%wI$nc;9J{pz+$f+;t$puM4E$z{E!H4x1F7G|iUeKG5H4ltw~GX7&a2RAGqgO>{wcEjqr0*+G4 zTGRt63idLz7kbfkoSXP4hL3|jhLpd+go5SV>IHG0P*p)6=m}Hj1Y0slL^};uXhtR@ zcG^|(v;9r0vIhO9Y3m!bh?ZRGT1%Hr7Sc*#Z5!W z4zgi!r)q&F+^Vu)#}E29fILDDkv#QJe(W7oV)*4W=DeIg@fAOW3Cjgu9h6%H<;-EM+`SYLAIn@v zcfyMaYmWdC((eGN6#pKYa zHAe|rT8E3<(CX3EMjXuMrCi|c4z=xB?`97B?~}vvVd(z_=nM3%S@&zNjdGe!AdnB- zNJQ+waDxKBCC7r9OQPFjl0b8AWZv+`3Z*#arwLw!?N0!FfWU;z(79oe*-2yUBvCo6g{3z|CHDJoeMs3`TO^v9yXpF!Tvyv-J(ji(pMxwW$zMa}uuUbO&NFa8D) z`jR;cBfy?$-W3D{xSGM1S1KH8QDJ*X&Phyx|n0DK_4 z!V1xn2bGoxl(mXnnjNh|KdZiJdeTrkM9_*s;8BmRu6DA9Y9}%vVVBh$!SIK!wm`3n zYRRVR2q9g*^8t+rQFBCAy)=>u8;(1KylUWNIxfm8|M81-^o^y4Erl5HcoHLc9!IIO zIWZuD^>pEv(b#Iw1+7-cYWs_c7>YA-DNe-&8NJ}Ok_-dK2FkCysNxV+kdf>=l$s!0 zw!pGEZU#&mtI(C&Qef!h(Mdg3=QxXpfV`?&)myDzCzX}teY4Grz}iphqzIYR1upua zU7)i@LA$iP# z=2OhKNrd2SWG=(0PZ?X*Z$hRO{luNrl@(5}M`hwnCDW6@y>iThBm4Fk4dkg((jN_ZW$%%ql$4)M*?S;>_K6-$)GEsV4mJXEhTsV%&ZYhxHP^c!tGoj&6cqz*GG(4TdaaO8>;xZ+d z!vLl(052E^i)0D(6|Z)BpsQy#^N5-2(e+WwKrz|TGiHh*OnSZ&F`;y&tHotOKw9Ri z1Phau@hEc^q~a_9_K0>PXodHh!%QJ%s^^&_n?kXU6y|-?0A1P;v?}Ui z*qR3#gM(Q!To*!FOty@yA~Jt!32{;o%{5rw@CQM!z<}1Q>JC7EHlz1~-`4{tcovDJ zH0;xT(tVD{W~VfeO%o5xj(LjR9z=!P*@5lm7`r(LrUs1~2z11v&}CF@rs2Z&077Rf z^sB%xjN_Rv{Mus#3NiLi2>hlZCSz3qV}(Nf9gD;fP+bman>6e*CN8ZeCXUNB6t%H- zu@AY1B$rhWw8iLS#ChO|PLN8t;fs?yc}%;F4W%-~=GnFAUMk#ak1-A-oHw-#P9n)X zPEtKQAw+^2Itk{>F49t3ixO>2Qnyr0+jPrQ)+gjrWV8`|Az7D6iSmA$0)dhv)6^Kj zU)eP$qZqhwTUHQ+@Ay}J4_>Z_=Ojg|Grv^aBCQ3apg}#u8(~n- z@R;Dszycf$UW{5825U*HYNPhLWREd0U*-{Gv#3Y*#+|3px_}k>)o~PSbs(pR1}QUx zI>Qyt7;;|jz_@3>CS(I8BYNsp zoa0c=wO*YK*?RTnhie!T%vY@Xz$FC%yt6DO%qcQ?Zgiahu%IG)+Rvk@M6M>j=Hyav zku#vWN^Fy(hzd=52sK}S3g{|rdDOgg-0~o})FwT>Hg^T;Y8m&?590$xpX#S>S|Ibf z=E580@6+f-x8e;P9ondk{i56E;TucgN>+Dri8#11jZ#9+!YMA zr6unIOGIOoNBc#uYVmm5D>PUcI2jI$qnZ+4pR){yv>LlhxF`dXvNTCrSzn8IPq-sk-YybB^q2nQiy+JZqH@awZs^0<=OuKIjaE z{PZ~SbLJGN2A$&6+sp&uJL5;+BB`ilQg)1Vnb@s($T2~WA`AuA40dsv-X#yP_H}7g zhl>3)BrA}HaWQ*{>Qyx;N>@US+MrrQhFTO)qP5v9t)@L*3yf_WC0GlyCTsq}x_v%I zZfGSW(2crD;hKupl;YNUPfuP|KMdnb-Oe3_V^Ye8lCBmm^*P60{>m3i_sCESq#UiF*0yKsvce9xNjLuZ}9Kbym zS8-JrdvBb%cFir+I&vJi8Wl36N#ek1piSGT*@n(lZ{}UKnN$S?;U?5Vrs@Gw$mb3u z&@a4Ww9XX&E^q-BQ^-toe-1b)NvPcpse^>2+naOk_?fbxYKGl8TmDKcKAsZEoxZJx)Gk9tgeSm<%qr;+Nzd&d3i*vpOVcJ zD$$#2WF9FV5y(W26Q3EnlItMSbqj?Qil#V@ao;!0AbSN0VSPl8t`XVGNLcP7hSv?T z1esSZC*%$?Ay4NK#zl`$kwe~;e^|~LTSyaKaYJmE`Gs@f_?))g8SGn~LkPy*<0%}& zvy{#uyMQE~F?oP> zNT(qUSCazCh7<^Z1>$Y7om_y6m%QK1q{8YGUxN<1E1@UQdR)ly2%hfJJ|#B|N>LZ_ z5Zt+z{A0^UhrO!5pw^4=AcUYxzoZUlSVuE3s=z4A?LNpeBvJb*KZPhiHf6}B2|maX zX&5_}UCMOMGNpAw(~h#Nu#pFQ$UISSxA zY`h$_wZ=j$Ju+9*!vsvIEuN8lRnHV!T9Wg0<|6c5k&LYGX2F6nm+nGYmn#}J8b4Mv zY-2PaABPsvRe8E^*)=70&$z>+3{AdYJr>4!c*A8@2De5kA}WVDqCmkk=Gx?@nZ8E8 zQ#C>rAgL+p2>_}Z2X@Vr(tsoqxxgwmaa&n3#4qKdaxZgIs<8A z&*wn>+?|bp>U@#4LXo=I*8|r6o~Da5du!PhvNnMH?ibj)vW(8lS|XdJM|<`3Hg~B+ zPmCSUY^bP9UTEoS&vGZ&QglrbV_F5=$!c8NBo`NQn7~?~PO$ZBt6Vz=t{eZ{*u(e~ z0XdBx{*z3a9}0hrs}r~b$kM2K58tI5#m?lcmArB$rxaIGZ5vOlPo?R_Fb}q_(K$0I zj^hCwL8?tawJJ;vBHA!v_;n6+b9EepdQ^H#$yky)C&x3&3q+1u)yn0M`gf)PY8V7i!3^pJ@h;wbQ0RN_bDIdIwobD z<709{9Qw_4t`WfxTux~N`Cu&B)q6oCIcU!rHFHM1(V>SK=~3s<^yO3@AFB`4JmIsmC78p|f0jh2p%PK794=LSTb=@Hrsq!Q8A zIzrIkhm0U?I$Gp9WtG{LiTF@`*AW3$R~tw*Y8crqsh3pn7UB7_K|EOz3qT(|(C#U# zyGLQz53DZNgNiN}3#8}bGoWw7+F=dEu(HV71!7zH+b`N07u9eogtH>rnhpV&(S~%B z!Nc)1E7~rLs>G}ENoZh0)n~R;c)4aM=yx<<@e6BsFq;)5Cc(d`;zyDfn-{|&6|77o ztB5=y&rJ=x9Bxr{4m0vlP$Ef7HZ4_6Z&^d@>;73J#xKI`xTZ*iP^AW%Rz;hLPq0WI zL-FxKW};*QU^5j5sn|}%22g>@4KfFMXRl^QVjob?S6ay_WCw;GYCWySV+!`DPYtPc zBCZ(TCh1^29m;S6x_WIMFXHP^x3Np+F-L&eGU6Q8VG41H7T*0t24I*!VYYWA2BKE2 zA+tbJU4R@Q^Cn7L73@PgnzrliuD#-77I(C4G{se$Lj`)I_ba(Dt9; z`zSaG>_r8Gl@&A|P5sRA z*r)l@&KxR*YvSZhwRJAO!c9s{Ns{}ff0`s-SJ-ZFbTz6&a=V>WnfSmr<`c#O54TV{H0`baZ9;wS7^yt0?~3m(nuW+r28&R4L2!FZvgNc zr@DRd4#?ohWePcQQ+y}BI7e(8Ydb)M@=>ZJ2~xXCsjRP7@I<3NCpbA*)MlkjqGk@R zkm}$H>0HNgt86hHXDMv|hLMQd57kALb&(YN96gvL8Yks_3l>GLRDqhokli&hS_Sr{ zYs+Fkp<)a)n4tHkU%S6|*B^Pb9naI~(r3dv!M1qNHA*9OHi6~gu8swdUKA1yhVg#rOa@Sva1oCj{;x`;Mt^L zDB@|Ejk3YCpr^?=z7eWJ0{1IGJ<>bOvvC2S5auqILSK&RSLau+)Mx#ohmP-nIS!Ko zyEhav-&z{B?` zoUfr!F)N3Hu}atz@x>T6gD_)4?|C%afRUvITE)QARKncDN8H{Wy-r!~$T|NmjIA|BbP(@a%P@vfV&Fj9c9voFwx)TcBDwY&y5UOlwISS(lFC?eq@T_d(h&VO)`J~&%A|z9@on z@g;y1nk9gbmtqLOax8|W0Av+CpJO_?Vv=H#49L~`SuT=`Q|Jr~DoI5)78A5+6VtJX z#R<@eC%HHoCh$KK@Hhad2~ZrmMCWNZ0z&aZB;x#J2o>b#;=9Nv0$^-j4n#gZxd9Bl zh%Y7rKrWzD#UzFs5v~BBB#Y?@{GSNakrz_|lP|8I1-d)q=`hIzLTRK>&jsCA(9#J& zLk^iC$S*v=&1sIVUCcr8gPjwfO5Dhj79UDn#06~Xq$uEgt#T%63I0HZa8G9Ao70ScsT`u8fcdr*)5V^{KqQT!9(y=B_&20vpc`vXUbXehYGma6UYCoiZTPf3Ow7Q^r(*=cv zBJjo-tiDuNr*sTJok&-1a#y2SP zLSK8Nb5I*^*>%35!j=Gf6@XBCUgsO-Wt3;p_%)vqzt07-Aoc;*gpJys*~u_R^?uZ; zZqKPcQgmaS)lG(KjqaBATg17|4r!UU$_Ao&1H$by+~B+dwc0;XBp*$|)?21M3Mjwb zuFGQN@f&g2!tsE0je14_)_Ra>Ne_TVA|m ze@19L0wnibpp%s^X>_PGD?IB7-P4|}J>-Jp7WRyk!*fEXlzGj4IQkAz+)0vcu)d#C zgLi!7Q=;QBKU228D5cF5lxi5UAuwh_nUu;=_tLngSFWhg^O0IWHs*+|$x})$b=W&n zB=5>&RGe{nX0ul*nRvhtdnb^jV2`8OKx+E!7_Qy3Yz+8bP!CZ-<8%|ys4E-g-82Z1`nb zIUBiJVr`6VBY>i?ua?wqcc{WhudKJQ)Gc*%{9F0cE=b*|lK)t## zD#nxRFC;CdK^_%sYBj~5yzeJnSe*~#8&=03k8=!DsW&-V=!pawR^%H&j_mnndF*gy zWvmYua>J=K^4Z$T@*RO?2Br*x5*@NeyCrCmrmzR`fsttfAqH&?a2%elmYbaSvVKfP z=$!7MLGZ{A=3`ZOWraUu7&ww%dSYRN<647bYOp>Bs$mtiEHE=(1XBtr3I%E6y=lr* z1Gu4x)sbPvkGvICGn-9$+N*4vwgQz1CmFAOGiZjNvr|E|Ax(fVAdgtNeTx!w*?$P9 z2>qAj@zpNzViV%YcD%0FaTVecw;EKK`j#arxr7gn&>Rhy9xag^>8a(}7C>iHwbq=- ziEEX+uE(^|0&OVz(E(~mB?JxB@SZ7EekHcdNoG8U57T$DCBs7E#`2e4&f8snhnf1b@= zV#Fo}5E#LXpHwDoB(1(X)lM;@m5MC=8n23eq3}L~@c?f7Efo*&V-W7(&urGngdZK| z&%oRQFxk6=(Y!-hW4~+^l&C!u)p@;SfVQ&YZNGWCy`|#MFmBiPkYnkE=-H})m|5RC zJJe_+3-w|)^F}e`BQ|wkJ%?Pct>N`u-8PegPD84#@F!{fmqArjNzgWS_M!(_KEr1G z1Oq;|juD^63_peL22gjfV1St1iQ?|X)Z2uabu@vWTt17&&6>bJv{t+ z6?8jaSC1cjJvw^m1U|}LTajE@i!86R*M}+op};2NXibdZ{{;RIRb1K@cYH`8L{dn_ zq(}FZ8|aJ>K+2;(c$59{D~G4W_}1%oLl?oL-)b3(N``6h$KPuirj-nX;J5#&Wf)X4 ztpEBO3!~t{$PsiGJL^IFhgJMAp3O$H*(A7Sh7X>HK@7D%`Yl8^E37Zs9@6T(&d{I2xCKX$3210QJsRSA*W=%F1!hy1$p`LmXWq>NvK>TTasVhY>oF zO&l3a9&}+RU-$saPJIWZftOpKyly%w#=6+hDJH1Sxru2^4YjrqBC7_lOQ>4tu&%f< zv&zPdt`JGh$Xk&%igYZ-YJfx2lAdov+@z1!`oA1Q+An}K{Ne>ZDwTSH7wSaQwKTBb zg=apbu*V$RFgkhFz|r2}hpAjr{s;4;5K$GU$YzbcgiGWip7?Ei3zS(mJv=)Z=0SLP zWUD?f*Q;L|rzYyjSt(Y=l7cRMmZf*$9!b*34THArR#x}D`d2I4$SvE1YIqY*SX!^% z@jh8lp{-?KBb5Z;6y)v!u8XF4fsU>UAVUSYOH4>tiLrJ&5{VjAWOuVr@K=ced-=PH z{px1X;b1A$FXjTcj!~K!P@(6*nz8gsFhO2O*%_$#6I(`A~%J$qG50 zpnI}JdG_erHaUUZx$q-hySxT&0(lY%F`Kj*r1_-0WZvr8?4i@(oZ{FzGy`WFL=*_F z5tcOT4~(+QGzbSK4lHJm2JroGDh5a4;Xq7}!fERUIIq(G(H_2g6a{WJZQG}!`1tsv7xOLnA*m!5Aw|3MYW>R&CbUKQ>xRD%O7%c@G|3 zdcB;$D01B*&sRKs_^So(!0;g>$9XmE?q7-J&gGRHvmbut8m+VS&F4oF*uI>zL! zNbewkrATcBKsTFsH{mY|n*JiRPa~_`wB%z;OSfJp?s#+0S%jSmxOTxCOyk5FVF+tt z>>YZ+<27VU0vc8B=+q-fo#aM&%fF<;ZVseqpRBI;Z=u(ry?!opj0t!{1JJ3Lh(+LF z<{{^%9OOc@H-w6zP;{C{7r8>9dduH(xEzDmxqp^NyYL+J-{Er*{()rV8JxBU^sa+> zSNm|P`k+%z@fN<|cn|j4;&_oMsYt?w6rsG_LsKdrbt9ohxTKcw5i;;2t&*K>?3fW1 z0G)_puag8FY_nS#MzIpbLaLURIi-P2q$zLCOoj%XvDrWVY)7JJx@qNK@Og& z;2p`9G;&81hG7{x9r4E_^}M=DKG$HUTu?oHh-`aQg{#5AEs;M#Y6S7cUeF_q`)w}X zY)(!%fCCQ$puuS}y! zw46rTxoXY^QLQjd@i>yrRG@ap4hyQ0hq+K=#f3_l3zZOmmE2XKbjI_XuVd+8&WaMm!Z3}e z6`7@*1!JirdE)3k2Dj7hF$Z1Cm8&>(vDf#!kc|)!4=JLNfYUk+n2m_~1~87jw+B0W zyB?iT!=kA;T-G}szyrn7wZd-J%2sk?lm*%NP!7US8cv=>p96U2TkBW(I0o3|8;$9) zmnQTVhzml|1+}#8fjNl zQeRbQ0%t@g)0-M#xZ6z+7831L@4*%oSL%ds*@^q%v#Vbv-cC2rOmjIeqiv; zb+pMrInS5e74)b{`jW$8>5#ciUTp;qkn3|fsq#LES(?|FrK!93h!2{o-O7y9`qx~2 z)&4~*_emu?+#~=yviu?*CqLwn(tsxC@^saVqQPu7785)p>O$el>B>md`7{Db&-(f$F z;MZhhf+5CRD^|?rG%=7?CK}Sp#FdLDHWmu;C^TFT0&*We<)`ocP)) zo_C2nwLS7tEVOxR%f*RBg3v|CGDRw|tb5@E6;(L`|JwZPQSuK6F%+#k1Lh0(C z8PW5^SjO&0dR3`zos>Xz(o?-o{;cXm+@wgN4=6Jf(OR8s*5%@PBo4urpa(c_i`Dxa|Djp_*T_L z-_<3)q*HR1xZdB-FwJF>pC(m8pefE|FO!XAvWQF_2Ym=T{nR*+QMZHB)J3Az7n`-M ztlYG=f-dwGgmv6#CRIM?31=WqCn>n02BPw2FJgE`kp?+8sCrpJrwA<0O^_B$`lBQq z!d(~DVkoprx(k60GB{pBRcOxPLYW`c=}e!HnguRRyGQ=%WA$BRopl7>4K@rmMG)sW z0VMszr)fg<{#iU82fmm+2weGYQblsq?t(6DDxa2wE^*Ax{BWFfye*6(;2jyyi)f3I z>2K4Vw|Vt*4E0i+GEqRr$%0Cz`dfP?1FVEBt(CCZ=#BG}5b!6RgdAwy=Yw=l;b z=?_$n0p=J$j;YBpiXhz|M`+%Hw%9;m4uasx8q_-IPm^$v5T7>ar`(vXJJeN#YvLC7 zQ)H|v z$>4i>94FqTVOda@38entxBwi;D&{&s`{>F|He9LBDU3c?u)_??{^gVJS z&bQKu3hSxfx&y?G(RdnNhuCyK(0+07Y9F=aQXUU6lb5ZAi2`m|~LXUZQy+EAS-u^k;tu>pUSad2;AWo5$< z6Wfu$5$V^!xDCYl+mDvFE1{8=QKk1?mLY`$yOx$@e>S^9v=6UN-U8>*{>sXvKM4n7 z4<9f4mv9)i@Nw85!Wr4Y$CKIYRP5qYi=FmW|0=u?-|*}8Y<44FJ&+XW;?5n@BU390b5OL!WawE2(-D)`8>4q>TV=d6bxadb8wt%# zj?uOOa7y?2kTffg(9?FVG{sBS6wmRHnWIyG_XDxJva$<(e}=64ot2dx>@*JfRE_AA zhb%5D_y4IoA?KC(n9bh6RllA14}!jPQ9Tn6g75~j7tk2O5#h`P+b>GDKhO=T>X~2h zi+ivLhS+27yft8mO1}pTNl3Sl!;c$~p|2HRsqVVKOS=GWfh88}CRpO4+XPGMNH)O| zKG(1D6TuS7#4rR;HGeoYwVN}I@PoqnaE=@Ih#$Lq{{;8`FaVf=N988y-*8pojXo;Z zksV+4RJc16mHq9TvqAmVbhTNv&oF(w#%ec>VCApf<8`c#*8nyns_NkJ@^PzN0>~hb z4o)Z4A-m{b_?tnv>ASZNbo<)fr+(!q`t7}i53T7Ou15_8!?*w_!?Sa&&(1)dol)!A z(W4zy20NRb@Y%VloSkcNWzNnupB3Q>g?gU1?vfx@GnIC>fT>JTqGrg97hHWAw( z9Q8bD^+}D)Hd1Q7$cED~VD>0u!t=SOT)A@?k1vLQ+vzZ0E{djxSUoFi0$u9&R}cfLnPtB7N&RIkivxe$$@nKp`% z=C5LaB^{VPYYTFU@w=>NAXOwPC}s}X?BEMJ0LP4I&$UhsiT27&C@v0 zC|2$jZH!P<+l*Si7>|d392M5GD2^)dnVX1V730TwSKC_I<%|QHdKe%1FDbZ@a%xj( zjhG=voETq-#02t4FuwHMzUj$T4jRV9T34R-MtsS_T91OhPc`9QEc%0nJtiijSxN1S zKSE{^%2~vjkKxWF&OA=)1<$bC#UNx;DP`(qnjF+^FS{)Et(L82wpN~PxgAKtOMR=& z%DM{t@h)W_b2zJtn2@41TWhNE;K%dU;=x{y#VN|5d^H>7G&LmaM8`#ubEbxxpwHOh zY(`N%oTJIa^q^{d%4J|}T)WX4)8TAkP!U-VGVbidJQ$%=??~vmC1MMC?Ib-a^z37o zDvh(WqrO&3608cj-8q~`3drZS(?l4!ZUrnI4f2|=Wl!oIJJyXk_ zKaLW$(Kr&ksPec@D)AgDH%G!77yFdh`v5zrEE;h@bkgWX&2_SSv6@7<8T~S_i&Krr zq)p1|(U|b!J@IWiaTqLj!|DQYkb%chPDPX!CWU{)anSCfpb7vht}z_@30W78ArU=c zy727U@ns{)myH&uDMlA^|M=Ecz3i|$8DTxrhJ5HmNmzIGYg$1(Z!L;eSDiq_=&`J{ z$_h?^dKl!~*T7e>;ceSZ&K9;tScuezx*@MQoGQhq!cv#@xXQK3!QJUXv6-1j)-4HH1Kcw*~f-%$#F`Maxf&@$=X@#QPk z6@j##ivHMQF^tE$Y7cDUllJ$`iAvFzW3x2s#%N`O7@<+IPjY7HGdE{mr83Pfm1#ET z-WEYO4&WgJcH3XP+&iNzk!hSI@1hc0Gv<^(q5 z$;Y}dHXN_j81~8dvG#dv_sKZWX@_I8PZ}NI9H#j>YJ(9Z?Pvawa-9YJA!_h6kV7;O zG`j^g9LL%$i2BSOAv+VKC4jn>hi6CP!f0c_r<*9nPeAG&UL1+*XgTgp9%EC($I%5K zmi}eX$KsvNgw512)pl60G;a};4d1F>5Nosr!zbXFw?0xp-k|LnRIz- zb9MECGhM1omz?Rk1+BPL*SO;{!arq*WLvIOdaAm2%H3WMf*Rey}<=--fid%rlOha(`5G4Lnl=#Stz7pl+Hmod~mJyIPSbB8cRN8oGg+}P|Ma{c}6*8*3ODKZ8;nraQ9K@B|RQ9heZQDYLla4Bdn6kt`<4&f#h z@NPZ*w!?G4vl;eH6@m~=?bM?ljv{*#R(}zg*aEPf0MsNGG1V4N5>6_hSi^U$t|_@tiKnn z_x2v2BUe+`x(8>55)Su{G}8Mi#r_>btvt|qhHP@YEzrHznpjiU1jUR+Qp*Gh;q@B^ z49(`ukKi2stzuV(&r_kenBn0Vt+2)ST>QvInurg5tu?ow2+hjEXWmCOPfo-~b-3Am zB_$#BeIDMmquY^BfGrsn@P=(Ydpd*4l|e&YE>$kjAMcSP=}Q&({$0YNEi{D) zlu6)T65VR&D`6cq3~sfLH4@XJ6Lz1(RVypYN^Ethyk?-kmUNLrmc69mb4J~w^B~+( zIv>_LqO{eb#l=NDQIVL859@ElKl8K;L(@I-XH<4MYAGFZN9i3lvoUoP&NHdMvrTJb z;`Mw#04ngyqCBkmPd$U~l%9BL;%ef**A5RiCYf(=qbhS6!Om zwlR}Et=(?sQ-#a?>Fg40?iLvUwd%fBF#k2Y>y4PAdNOl13$A7rU6? zvP)~hU_2PDtn4D?!X^s|t{`t&wF-!bS)^vm=nC!VC!rmkw5#Kod~*O3j{RXx>Bw{- zvIAokhS{th9Nhv&G7zH>*EZ5@Ey6WojP~ncfu7Q`=z*WZi~}u>ilY?Im7Oue%y5`k zoe*V?^AVqlIKdV0l0h{Om6C=4v|N*bbt*n^f%6B^ndwH!Uix zuo>8Ql4CpQ3cJ{d#!J}7o!>%bnBtfeCLB(YRjjNz4LT9ACWQ+!DSvGBlIsHZkNRLB zpVQBG714EF@Bfmt{t@qE6Kjvv;*yfTAdbrTKzEoF1oilXYx(xpnTh2VMgxlDSS7q6 z^X``+mR_u!PUyrN0N?~W!acz2;id`MM}8e*)c3ZZMo>G@TdMiA-aL9W!_Yo^d2*Vh z@pucyIF4^3FNMx1AKhg3G2{=ZVqQx^?T4hVcw%%7BBr-VGFVp$uTm(==Ik@4l9>0i zkXvqbo!A&*FRwus|EB|PbTse(tgCGk1hDPkC&ko2T8G(Ir5ZVUjZ9k>m#tLQiT-K- z5Q>UtuxrS_f{LfZT1es3ECa+d6@#KM&$zjCHqx0>&yFs%N^vxq-8@>0+09i;Yp6-P zd0WyQrLJny!o0NxwfaSP$O>{orL$ACBj=iVkvO46@RTmQs#_Tg_oCA1;}OfD zuDE1~WZwz$VcG2}j4teuMEf2zpRhiPS%b#_cBlUGk&!6npuszKl&e?04i}jw!;ZJ= z^)lj(vlS3M_cP1F>Bk+!N%7GO<4#7gl?p#&rX;G=EBPj;7?toi=u--n!C(5zE=I&a zj3Z^)M3(WyGC5KqfGdN2*`IB26@MW-`R`^dwOMZa(N_)XXr0a+Ai|8R5dV%YsPzc7)|Os zMVnLi$nIrYt|sK=&RP??TLG`97=0{xR`aYva!Duax~K+V^ii`=Zz6U)yP9!`mYe_p zj59M^bi}3(;?h-|KSQN==)hQYV4E#rE(5`dRi#+{VTg`)wHlmPvEM(7%DJW06TG9| zCpaazB^7Wcy&VUN<2B}RIe#eRkmyp%dxG%VdG(cUgHbv(-;ENY1&AM1b!?C*uij<_ zb8b-I{%JNP*6!vwfuiqVKe|OM3E%?P@vQ@o4_mlB`)l2 zmY6Q!5KL&wOm&5>dpP9AZU7FsV>?4Bo(bmSd!4-FO|FI4N$@xDMCR;3t;!#HYlSzT zD|ZI(69$FY?4JOWY=-RPqcc! z7Yaa=8$BI@o@2_H*ywF{13GEY(d*pM^|6IUK)?aDX59G{T4*|jTeF}GfMJ306EtO! zBQkpwsGL?oB|zs(6e|PSk(u~%>EV>M6sH36D@ZwQhm?NM{^++4mA6-k?t3mrW4MY8&55Buy4h${ z$nu3mH(^exy4wN=AXL<@1qLc+kU%P}XsqRchP(k9^48N2=dQ`T8YQUI%W%|nMgyA? zsJ!KerWuVdvT+t#$RKY#Ag$dtYpW4PNOtO`eR9Fwr|_1@5WA4&SvD-fTQ&JLZYv^A zMN)BLVIrGj_Uav~5_@{)2B;}OOn3^V+hW4jmfF5L*l$EC(MpaLKw4=(-UJvt*Hto0 zHOKBG@zY9ZamwmyuBLh)slmWfx;9I5WR+bICvcRUb}?+%`-0rOMq8<{AClsoEHK91 z@E|^o-nV~35lM9JeglAUz%mbj(*qg(s1|&mo~PMWs+iDqmtM;stZRFCeVb2J{ENFssTF za-9#Gr7Z=Osapc~^d3eH)U=}G0{%@BRKGcqFaJy^B%l-VSi{TcL;pMpcdB0>T%i3= zO(jat36irP`Ve%dPuEFJDnG=|m9ix#6Jwe@PB3*1$a^H>Fj%YA9dBF*2@SW;_2_l)}bm$PK zE-Uy2AO)9YmW`3DKs7*spBHGni0nNNN)eBkDteBVnYUkWeZL=eXJ6Bx(+|GRzs?_? ziqG)raJBolPXC_|Zs&e5JN$a|_2JjANAT@a-Gp2rK6q(p1NzU2@VJ0bB>%at@l1Rq@=9g7JApK7$f(@S&QJL~~EFJE(_7w~W1xf|T z|FLnLhl&ajI+v3JJX66fdX?uB6+D@uVhbY$nQ`bkMee`{7`Y=BG)AeDm zcQ8bDb}rK*ER?6BFMtHIvc)TTQ-p_}qlJNvWi$+~MYj=TL5$IP)y2c!F14$Mlo|E5 zs;Jh*)EG#eI|T*$M9$eq874gCNdiJ{E>2~NjF%P$is#kc5^2ZP7_ZKa{fF-Lop*GB zYrIgXwp6Ks$?tyT0&>pb!pls^tabKCZ{@Gg9^I|A*<+V6wb}cS;{;Y`kPACAND2Sy z3{ta|8Ke|v2I*bTpxc%yh53ChV87xP9jWNygz_Rn$8S z30PvdKP_{dP}n;R8ArkII2>NCYC@ZJ?qO?KLhiB+Ku1yZT7&Ijv(;#K6rWH9C)CmP z8c_;W+(Qbx*An#*`>JCiayu|t6l+n%<-@RsI-nj+Q$0iyC;vYv5(NE+(3!|Pj8}hbtbRE@I{fg(6luV1@IfAho?UCd%#og(YN zWfcZ}ehfUA@W{EZynjCSI@2r=W8Jy05BtHBhcKbFY+t`V#GD=P@SjhP{`LgMGr=N{ zm$&vd4?e%$Uc&UwlZS~o<8<_IcFB)l4Vi)K2|IGe*xeE8;L-7q? zFIQ)n1h22*)$jfdaG|0DJx$VCHlpY5--3tY0els(p9Ek^N3gpfhx;1zA3PNAXgBz? zufYQt(A$i>`TY%E#2fq)V#_4$dMKVj!q@KMKf6aA$oe7!)GEy!Z0BjFg@DRY4+*N) zF&JD}3gfOhltMdLUci?k`3vE}#s}NuocyrKL-e*l?13W+y`$xfCZsD6dn^eE0qQ6= z1G;5*WGor4$w3zJT{fFt=P=3)7FPDwbr!v>i;VkzJU+kwu&6NKM;b zdX)=~HOE>=EHgv7lF0F4%J>qp)jaK3=6(N6}4oI6eX* ziGC|E&Ice`eEqtA1SiLT4A5?@fgONNFG2cc5|svgja;}z8d++%Y{O1&v7tX>bl;`p z&En}EBZcO;>UxNv651}f<<-?EaE`nO>t5hy`FgmywQ;cV_0Z-#Ix;yCxcFm{21>rB z-4>!o9XdBG$cPLdkUQad09vOVbC6ai#*KG05gSm^baf!6tk!p{?IjGE{@5eY1j?X> zH2G(lrCLZJ;pSdl{d)aKauJM!K>=>gvgvsEO3I0e#7E*uWszA-PZ&G&ed zcWKs8Uvm@E=RUHN4)_u?wY8L2DgCLxA|Ua;xs z?wThmcr6jI90VaX(&FwEf!RKgUT0B?o!Io#YGJ@cBOuJHwhWektVTczY__f*>Y#ZO zk`1n`WE>W41A_n+^oXpm6jRc`LhOMUixI%Fn1X37FHgl#M@`T`;EbRmtI-Y7_GYbj z`FPm7)Z&X%UEuKYsCNpTSVg67Iy!nI{o;AQ;H(iMcY65f$iz{=7on1WbCZ8F$-leF zzaJ?qkgA!@FeCo`{RrLTh?~e`6;nW-&T={88rF~c8pv!(j77dHuP~N$@hr<nbdGn1jYh`ZHgy_9{g0CAw9-#C^1cku2%}Wi8A8~RtL@CAWdmB z4HVBVqmUBtTGEwXWSkpd*T89-%91E2UV zb}AY?iQ?V>3dk;b_oE*_IUF33-3#`=r?W@sAUCFFSJ&w~-AUdFMN%vJjrl22sUMx! zKrJDk4Pu?N=Ub4#RHX!m!N1 z1stzSv1Lb$BL!JbCM5pv_k8E{Q+8&d7OEyzcdNVAkMn-e;g<8W%s4<&wR(I!2YWL3 zh3**AqJxIv^Rt^yw-G~vq?jh4G`x_%sf`+{(&{JqmB}A_xBgWt!6zE3T7r@&`dSN& z2$r#n+pSt;%xcs`i48En1x-C$CPtQoXwdU)kw~N`!%(7lu1$_?h8H$cr;i2u_UyO( zHuu{GjgY#ojqwEw=$tOg*{onk6$@{#X(_Hhe`6`+Yg&s%_NE?PkK+ZW^j$dGn;rL0 zfGMGM7R}1*CceoQNWDWOc?6((1LB+wWfqQ1KS`KQUwCvn++~J4#N1vsClj^X564`Y z79JXawo-iJILQ~B=QW)4dV_}?$~4&QNclPqC7b2O1O}(?kUUtDmFR@--Xclyef6|h ztvCSDkMwzo+WsJuny8#d8v7F|I48>C51a>1<)UR!X}i(>NOo=cO@=9=DaKFUKwp$8 zEo6YoHls0+F%grp-OXgHkg$q7oN?|d-izOB-f(I>G~n&=v7(vsS(OgyH1IFHdQa$p zzMzsXFfH_lEMTEZ5PhNU0k5Iv^-ds+Ahx=^qQ>!Uw(CF`0|i*k6-P${=czR+#&XcT zd}jmNruqiHGDzfT?IsToZ~0?r&bKn>t8mU&8)qcHIEMWPhom5^X_HY6giMy&w`?2! z_}GiIl}OYZAl^A%n!SPf2ty9MrDX-8!X@%a8PL=0JB6S9X1NNXIl@{*9k`^jWtQ4+$z_b+X;6)fLH@iUAZ;~Eoy zPK*r@!&H-Feb}b|1d2#&1Z9_L59yOr0^j>NfBinklA#mJ+OJmxzK51Bf{qhkxb280w5`~OPZJ(zzsUNSx%Bw>FWLMy_y)Io_3K;?1=B_Yar zRxAE{UJ<%~0wcGBLaww8w_E^LI8}dPof9|KnH4cXx@mkOTuYnY7D*oH8JvmM+5r0d z2_%XEoyzMQ*tglYkb+Muh;VU5;bLF`l7{L^Dmv0F5Wz0nQ7nof_pSlj?`%`2ezNc+ z18>6FgFU!Hbous7aSLqkkB_fhukeNE++W++Xb_vlC|72R)<^(T^f6CaflxcLBTI8B zeX9!v8Mcn1kU(B9EJcwc-A4AJM(D1VDT;QmzcnwHbe1zjct^~{fg`HT#&|0}uLgy7 zizZl+e{L*Y_Z8E1iTc*P?pJ>+WM2&( zG)FonIz4B1D_AXQ+D7S&MD6vGVN;X6v?F=Ridd^WrHL=OhTFiA>%N*E9&)bANV}{= zeN?J*8dF6FoSiY^48k0s)o$V8;g?z(s@jkiB)W6R**zQZ9R_pt*@hF2mo_L*!otgjnEjIY`b2Inzc(FbU=xIE{IL!~VmX?T&VFQbo9!ZvWTF38fW=tit8oO}D6L#}{kgW#7m2MBvEqt5uWtp$MZ9b5eL>;z8lecwqN=VIis7e}+ zF^H)?%Stx`f|s^OYoY=j2w>rB4pZg{n*OLwfcSF$vZl++N?#_%{Q?{W6&9JO^PXLH zy}fi_g(5kZ9b&K8X{HBy%80MW@Gt~&<=y=Ts;BqYOCkoodvh`CKU4og3I9&LQ0)Jx zJyFcxJ4c9-s`Ss8SKRcu|6l5C^lPb#8ZI-TB4*pBQF|;}T=)>MPIORN_A;U)#vXtX z@7KsAP{kpR#zg*)BKY?^k(I*;{431F6Hr(x&vtM#+<3Lczi{z7 z<0TsWi1-xh1+vk|fNM@pj488p{)UrDi z7099^M$hlin*^hjy2s@P{PWsygz1Vi@@mF!Me71XPBC-*xmI7->epJqNZbqI#coz~w-4;us)ncsb5o zJ5iSeg4wpy^IE@4Zi&=fWD|9t#rIhc(>gdXF3CUT%e_n|KXV3+;?pG#>w-Ux+pjSs zv7cMp>sebr=S>nu)z?zYTOP|eD#Kl_-W$?uQm~iO&p_GR>&NkRhQQX>I#J#bnRBTP zE(`sE@i?6oNvP95M--_joUU;!-y3$mISrpjCu2d}x7H1wb|(y489<6VLT~nh(ewL+ z2r)IKRh}bd0w%pQS%Yr?2yAuVE~GVVJ9av?Kc*b*G`_!NDzc9M6Z69QlT4b(8QwWkz2@O%-w* zqZ5CSj}f&eXe_!i~+UO(s z+(rNCcnI+0vmpk*69VtV{NZfW=4`C6&CT}Nc*~`3e|dLG-W{TubG=F{UZqtcHk@zt zJF;CgTSQp?;4FPn1A~hFE*LYREkc^D)Tyy+C$*#Rj2C$+fjE55x z^kah=7@9EZ&lB}l5H63eY$k3^-%kmaTNCP-h%7{T6fWd)xh4!1g}#6hs~`ZP?MCeX zGjt4y?~g-R7CQ4T3Vf74}4MrVU{PsYB8er zQhY}%LfwV3npP_(Cog>0iO5sqGNa?VqwCMG-J!R&`Tnr(!O?cx8R#oOE_LzCXz~{+@=Dt0|{1#nv2a^r~L8p zc~Hz5fH|U+A8N;Rj06F!%i$IC%TARS@xhDwsLCBX)qGyE)~ofxL~xjL*iVQgb@1Qq zNgF@09n7l7aIH*)M{ROrLws>}Fd6qBFXGX^lkrExiv;$=*%y!hHrqpw7x6Py>NiF& zj(*LL{+Jw$@#+|+#uvxk!pDnI@A#u(Z%jWT`Vrv=vDf(1Z97grqHYhKsTxerYMP#X zLg4N){Y*lJq^Hl7z~JX+YJHPF|5U+~I(`0$YT!eeKL1S3>bola_(W%CPe-4skN-B_ zs65|cN)H4~K#>rN5oyp}97G$n%Hg#@IKtq0JOF9ri(FJV0|c?)I@?A+e<=cBZbFp( zJk9esBp3GRyzS|d>AC>~5;-ywskP0L-`F<|sNfsO_tomEq;K`>xI`-RWmrYeQUL?> zpaum}#cPME-sQS&>#7G)$HT<4v* zKf|cX!T7$e(gz~qYW%SPvP9>^l=ib;)FX1W7p1-aG$%r|9hGAc^VXlE@l&ONoOYt8 zY3|%j4W6b79|CE*7}$i2zMT zTRMtxNa-W^Jknn}e8{^^2Q3;u9hYT)%@$4J8o9fvPXymjoL9S8M3iV=YR^qrFKq6B zec5LMdq3VjaKOU-oCjX!rdujYx{x4Iwe(ac%I&=FT1je z@<}seJw7(3mvq-Kwj=*^E^{XsB{F7JoWsynood{agv&^@%FB9&_|5B{=xi!KB z{G+J8s=BSUUz=^`R57n(4ns4~C9It(Pi)qyF4;ejFbetoCDPq!737Y)U6^DhG9(DL za|D^^Crvi7AMjVK2k;>MEcabmVJ~!(gR0i99y-oQbXT@l62`*-RpxT8a#lv9y~$L& z!u3Q*(it-`3cdOxc zd{%+-+?wO{u|`HA%A{552%qPkAPqT! zKLG|p!mDWpd)=gCBp|V{sPG`CWsG2CnE5rt5;3rF3Qhe( zp9{o=;h76zrnM&yOz1oBWIP?%fa_Cq!Ho8K-vz!j*ugm=a$|BGGluHEZxbZ+EpTG? zcMJd6c`kf#7Jwwo@K0XKU@Xt$3gl+vqT)3oX=#{+eWXf_HwXX^l2RRds}Mdb zw&$dVDH&6E{0kZmx)pIP{I$OSb`07NuhQYU+=Cs>uo1K+`!if#eI*A&%=ny_zB2>O5g4Gyik*8!FMG&V;+5wSmY>6};>WoDhxd~o| zWD~B7Q7ulDgQ*x9?@!slS{x)3k)5xxT`OYVbm+)lUtS_U&YFsFp?>It%Zs(vj_!f> zvB3Tq64EyX+#+FhvqV5keN0$p*kcnE9)*5j1XF9tJ{rjxsO;#}aa?jIH-rQrX0HQAfDAHe3*%WhK3A24v(qFHVFRW|9k0~I9v=C7%D(RP3 z?%B_vwg*oWS`C56v=6l@S1 z|PDD3`)Ib*~7SQn_Gm9evgje16ez9T@9=zq~5TOT+HJ_dyo+Su`ZD-$f zR>c~TGjZZdA=K42j8c$J*2G=+eR*^B6vMnXVPJv02u)YAfQS;9fzAxT6BToT2hDZ% zb*1HwpmV>>r|-NfQ$)4FZ8G()&~8SE=8d3uH#G7Du!9pLM6a|2SO^N@4Jz6X%{m!? z9EGE#hDY#3HAFY)xJ8&+mlqhJx{GI@5K9D|w#<@;YfT0E8tYw_C@MDAWmY;LTy^kF zMVc~n+$>(p+#_4fc35Yxi7n%HrI)SHip+fDC_1!7K;#eMhe#-*f>yZ9iiEifXitT^ zSR+kNz+IFfcQN%EoOK$2yHMp8Q$Y+xER_8ABBl+;gleN8>b09En;_r*t}+JDgS6#rw1hZ0SSV<%whwcL2UZC2bPW zj%S)~jEn!K+rsaa-33lv)FERCdkVhwv5 zO>V(~K!XcJl}P8lA4U2$??%?DOln-_1vMG!xnSp^8v%k(e%P_E5E*`B2*9-0r%@X# zMpagfTKLqI=h5vixC^!L>|%X(a`q{r9bCvyKR5!HeC8@YqdoNFI$T^_MtvehR5?eS zAhj%71im8J%vNfS$p6cGW&HlB!R30fKr|!09w2Ku zZqzC|UfnDa1dmRvKD+c;kNC8t&tE9P{5sz(lafsKoa<_5KdX-}phmp7NXGOlalLjb z{JeFqj4eWQZ}to0f90;+h8HjPQJUD17fpjOEHJBWH;FnI>)sc+dXuYngx6l=JBg0} zW77dKfF_WrgPMT6xGw*oTT?N;VeGGG`bkS7^A$qBXj;RN7hAN&7Gbg31Rfst>xP+0~&V;ziUI4!~mIYrO(~j2- zQ7LM5+?F@1tX3-x2p+(?&@D;}rGLGx%^6z3!I3)J8E86?x0YB@3DRC92`{+5kb7QW zx-dx2&=Mf7Ea|r?=P77LwT)(q-#VVZ?4E+3At9a7*nOdnvq`riH-~HO>?oY{JpYrV ztK6?ON2{grv(+ZqSO}fOZJr!&ua~=>uRS;#zsa*V`Co}aH|%|$?;)B(hJaMhzUZaB zs>8pt!g!EVF$1gGto!4*bEx&X@;Z<{yl2 zaGoo7Nz=-^vBrB7rRJxovDj6@+(nq0b}rt)>=;3#E&WoKTdMQ7T<2gzViP7FC7v>0 zKKdp|F&OEu=xh>U$&Cp+#B}DX`1vOZ(%hcY%w{FH>P-l_ED|x7*gMIz*2{Z$r*+g= z2`ydDj4pCAT^2dbIV(2Bu}iMzXN2(Za~W$3eb-V+xMW9bB#OJ#)i848Pk>Pm5psW` zW7G7ho=rV>?T?k|$L+CNwtD0fhX^GbIBeU6T33KoehHp^nLBLXKogo zXIfNEy2CG(SNl0SMMo3e=w)O)rODTccx>w>sz2x zZL}s(2w*#H+3E+9KmQIsblCmuR84`>k?faq1sh@{w&hBqlOaHE=E*we05V}SjKjSV_((md+wWEcYRVrQUirW}(^y6=@SxJQVgJn zA)0OBM&7U^|H@!m1j#|h!|GaEI<2xT^6y9iPfiu|9TP(seZ-dmKl!PHfP%H$?zV`L9U1mBbxg*q z88W=2-E)!Qb^vyMJ9q#Do_fNpKgN^?BGrf+EB%BVW$y%8ltmUe;DS>sK&ZT+@Jtsf z#F!6ip8GZQ&zY^(;*(SpMlmZ>H|&2bf|LiW1_N<;W^Q6nYKHE{wVVD}9 zTj2P5EwX4V0P31zX64qaCo2FRV2)Yk zrgn5|vSFEy5#GgWOPD=Z4?$K4h?8?#v?ki9(e5TqefMws(sHT^$rklRYbKoExS^Avj#$Of9ESV7xp zBp#r}Ud7_o!m>%GHZ*&158~syNlGo~_59yA|IFQkI;bEJjDu0E0Yul1*Y4v{e$TJ= zi+Zt~FK@4J@*p5B>^lKC`aeO)wyetcmo0tUmDMhqA;P;xOWSe4#A*j|oCg{{U8nsP z`n;(qMJbB1G|j$1v{GlxS=me0roK~@2O(1m-6MR0s1(f~vX;^stC`Atc;x(+@CaH! z@7Y0-6r3FaZ~ETxPB07PNeYiN){wQeioA~L)vD>4F$1psFQhOq2qslh9GGZ(z~N8^ zjP;`n;i##miT5yNodz6Vx{|{9Bk^y$q_ChT{%oD0bKVDk0PVeNljAs&Ao%@$g(l0= zDx*tMk(8)cSk?7T+nm{$_3Ve){jhtrd1jPIg*Zt`TvCz2C|S&JKRih|-~dTIBC^k} ztE++n9PSQ>!{K;2{+jOdV{sZh7ulEH>6DkNUw_>m52tecbUZ#~NtCDMcyrwUx+%;5 za~ti`?De;QkFw+?dR>){Mf$Wm4Fb>iN8~??!GGT+3Y!1&eA*q0(hB^No=!pDBGf89qVLG|n3|MhSG@?Zbk zzfk^uT@?i6JQ}UD=VbMh=grsK?c->a9^&-rxcX@}^Sy0A67uIF&j`e9I}J8IO-K*l zRzD?+NwV0GpG6X{eu_7fa2k?d$09mB5$J6^Py8uKculhG_(Br48{eCggr_1&4y&Jn zMKot6PDzxJpWbGtPRrPU?N=glT5ONEQ{usy0|qvDVro)orYTCYN>X-1oi04%o4 zZNvZoBkLlGzU9aCa2i$RN=qsCt8hNcE10^ESM%9IPtBfI%VnUaR@urAJdY()r5WQG?ze563c(Hp%M0{a^n^zDNI_JUwSoG5ojWkR694DcT%| ze>)z^V-}UePk&F>NfDjW<6%I-KMgd2U^73g|qkH?ya20K-@~+m&_E;q6 z(Mz)amYzoIV>K$%KdEfj$AYLaO?gC9-*%^cc2?M3DfP(GLo(VWtPT9JPZ~rJI3?AI z;z~wQ{2$LH$@RR?kE4C*$T@ybiY?L7QF(eLs$Oo2W0tL>qCpzvqaC5e+I+M*X2)Vh zI@f_zwnz@Ax4%*>-_zuU8l3Y>8lQHnIQdTc>%?6{cxX+K4PI>9wr$(CZQHhO+qP}n zc(L>1Wb)5?_OrN)K2=|LpH+9)t*R6LML zDFM2zSPHS=I+488FE{eYiFaG<+`v7&7cPQSY2PV3)kD-1=zJr~&hFCa)033*CmA<9 zv*Hl(x{j4^s+3VZCQYRAZ=YT}dPL9CFsIkC$`{355WPAtBZR0hc$iA6wOrh#`D4t3 zdw7Vn?`CK*pRk=@b|DJ`vBdLMS;EixrKQ(4ciP{h$B08ckDp;0Ct>0?D*6?Co`1r} zm6xHJ)ag&SCPy{BWhR}oiETN2+aNkNQ)Rbu^a(te5+$i=k0niLWH4h z+aQNZ;pRQG-{D0{!qnQ5j78SYl5!dkF5`}`FBuTzq@ZDLi6~6V&9m}64B-OOeBu`0*vp%3J;G$FdJ? z1#9sb(bN|O8Nb%ytuhSj>{{d+2v)lTzIam z$SKV!xVSby#>No)^{WK>B6Lr?#}=R=e^ z=S7F}ieiith8{EnQy{~q*r%qFT|};K0>c0&P^_SA%tg?MH>QWbb(2lb?eNzfyfmot z=wc}bQF}If`zO8t%kw@QB}# zmaIc0!E)p_XfE)_FrES3ik3^4r8ktxu*oDT`peBF+E5K=6RtV@&sU043~WnliSjVKc}4f#&l*#_8!J&5AiRi}po98#{vM^6()J~teZuIFBrYP0o7DFqoqh6X z$J+ckM@D3by3D>?(1;X)zsKmwt@kh)WZ14S=#PmdTDFRc?w#}C(x2imCxyUzP7n^H z1OZC;KxjS0jD<>zq=GgF(Ni68?((hR*Lov@l?2To3?YTT(3f-j)hTNxkEKD3-UN6o zm)tE6N<4K1tk<$KD4W4X#7hh#EDtDi+Rw4YZ;7sX1BnM6+@==XdF`kz%P8 z(DIfFs||O)=Kc<1G*RLgrfH`4LCEM@y-2mQ>^$)nAPre3x)l$J${J2!xe*HFtiz4H zF)7Eo0gmM8d6HBi{u0?d(&eEGg0cnJ_L>jQ9)Gy_7G&;qkPRo z3JyrHsjm5j>Cxp1d;x)+cUG*)8nBDmz-(Xw{qh&@lxLosdF9rC9ZSxkTU0a-gegAx zks?M~EP@HSvluA24w2YciwZNTHhmdOq$-}VZ&jvmcSoDqgejg~W!R*hJ@TfdaRXWm zDEg`C?G$v5^uLbkK8R(D2GT~gjH?FW%tEY}dITqruoS~-Fts3OfTajpORXuIvo#>^ zNz9}u_WnY%W^C(k3ppMgzA9G5Fh;mnFO4VrWRav~iu&??o6l7G&Oyg{}uY(DfxeEvzvi$AXa@_1rHaFaPxe z`ZgQpP=Vua!QQ2$Q(jIYc8&Ro`8dZ()HGksp`{?=U9n*wcG^JZ2443!>| z>qzq>;X9@(?<0L)YyO73tkd{nsp~_1UTYkO_LdzH=OoC9=ir>UnNZRvnX>vptbX3Aa0vER#ub9Bzf;NQW0g(V-wGgZj zUIfzO$hKl%IEB0a2L}V9&Tq}63~1U8Gz|?B4;-5EMncS{hN2IPtWS&5|HAAd#-bl! z)jv4zZ;IUXLRS++qd=m5C|S>(+z$sY8CSpOQE7@V zN)EduCUYus?KziIGR*V5f}AI%L`dvBLzfe|L`cK)l$>WxYEJk(gPO-JS@*2uZ{d@6 zsxG@E-SZ-pf+v9#ozA9V`IREF-s;8SGON&zcZHpAkv(n9qfAPbORXyBUdeCsUDw`a zt~^Rytq@Ypy3W^rM5u58{{DLve`x@4EE17f*#-RM`<;0Gs^k7GR zfc&QDKT-gUYOiE9(8ob4{b7uMT31=9N_}s+xPk^3Y+1g(bc8Qv+6sF{DTt*#5RP_OKjEj0*zf6oYg`@QdBwwBbjS)j-6!j@Su(B5&#MA-*yS^RsI;+9)& zX?{K-t=ge8!6{l{G1;_xl+7@0<1Cl<(ehC)En;O$IoGQyn@p}=(J;w0epKs7Z0@Yq zhN;%Aa?%T>#p`R@qFk-I-EUnKx$o+%aklx{wWZo&X^+^FWW%|+Ze6^N_Ufu}E;=Ut zh0QwJwj|x`|ATu9vf68`tiq>P-hV-ii(l}x>=#^5{SRdK?5ZgpY45jTp&10Rt=c%EbI{UQ z8{AP{wbStdXAh&nWS0q>}hGuH?sU zZDvCkmeKES*r1H@-u!ZFB5`xkI!DO(rSfz)BeSjQ>H7C~=R7FRc5NxSY@NSsWXWmn&x=D}Y{yNfo}jhN3>NIpYPrM{NQo7*>_ok3Ym{9&ZEK8OxM*X% zZhNbdZqoWvE$yxGrCL(R+M9f;Td^~dT>Ul5G``hnOK5(n)`GhBxl9MWtqXbUuU+FL z6#N++punB1({UO|8~a!Q^2m_Cts#Z7m?$lfNn}bxL z>V}VJX~v$zOZh0Byb4pigy=$@ZDOA&22JTpS(I74vF`G$ElL_3hzG9#d=qC&-8Dnv z&WmI&yHjQiQflDLcKyB82?Hm2bBx{xQ2?>j2cq$cOH)s)28D_3IHKjUm5B6D6MVjT zDK&ALgfUP4#<;dTM@O8vBC$y?-}8of1_}yF4AGA^xg*pqSh5@l zU!yGPTdOej)2p&Z&3G;;$AZz~4CYym7(+%{B~G)ZT1JGI$4XYfuTl!&E1P_}chzR( zJE(_SE^x*s$uFWh7>3=I1$M~}*xR&8G{(+* zjsrCRs{{bovSG%DLW&wslfI*G6S|WUphc1wJ{T?4A#|_pVWjl0p@j!M>DTkR>^SxZ zvBIh3L!LflizFfaAvWi6pVsqYN%q#F0RR`}-5}1ouC|Cf+>(C-wGr?u{PGs92CY@2 zmTaiPF>dWa!31x%qg}gnq3JOfTAK^qPYcu_y^>etUK^-^)IUNS#K+bG6;;fQJ+AI` zX5Z_FXQ5$G5I!VMrzVXEO|b9}bnJxgv5(D{P8CLL#gsgYlfU6?EJelOr@>@~!S@x! z1FeDyTF%aM!daUQ?&#m!NWrkmm=n6d0L~K=*q-@fuwO{6MN7zPsq8 zyO&2qDP)_gc6h%SXGjT0r!2LXS(ZnY3(Y)Sy7y=@T4JXuCYLD;aifUxpBF|SQGeT^ z^ek3=+}PHv>LGgH=UTJGd#PMWoUXQRnauS~*EEsqu{`;hDrwPF`8T9oHHp2#*zbqj zdd(eY!QF}gqazHtwP8b=VQtF;uVl?r+es4Jxw3re(AvnX$1>nWMNGU&8d2sD94cI8 zGQJAo&WyT}2si4h4x3nByh{r+#-Xg1J*k6?rw5%r7AN3pN37Ex(%E2mk*e(B96sY9 z3bPq=nHuiAEkV?O*LAmQoT)V?6snb$?a~Ue596-ScCh|BV2mDo5Pvp0WaK7av>1pU zmB~ezW0Z8I?(L$1gdqE#E|&H5G+B6F0bV{I-6$m7n1-nSOeg5c(E`d~d=wYx&Nn9X z?;YiFwnJ`J|JWdxKo@l&TN4;##``BbXOWwz&!l=wCfTO@9Bt5chB!rbadcZOtAXuwer36a5i-_&-K|seBtl zMi60p&;`6H|atFsuch-u3WlPc8z(?Vg+;Sy^Cc|=zH~BB<}St z@l}#KI2j#jbUdeN=<}v{gNY^;Akw9AN1c81U=~}5^;xjTLe69+sER%d@KYgck!D_; zTFSb>#XoF~-Qu-Jr7!_mIGiD8H1e8fm;wK&_jH4*{%oQJ%*tBziHyo4SwJXzrqGU2l{iD;vS_O@Y%FPE5Z*oA zH`cQjuJ4kiLsKVoZ+Q+6pCP0|>K5AADyLCp z$%wY?-<$FT;`fTgjpur{e>RP5U%^M`GqjWdi>^kc)VB z=3d;5GzaG*5W4^7FuYBu85HB+c;JU~Uyi*m-1ufVh2|)_X=2H&V0$uqJh0}#ro*+L zvv|~A1SVT#ZFC7z);g3=)FZsd$VqeukgBss*Qe6xY*io*`7J=-jUB>B%(%AJz?V54 z4#XNYEF)nSIkk~8hhHZ3|5wTQ8^Q=U@v31;+cIS*Om(Aku^JKr=Htz%37q-Qg|8Pi}98sa^5do zg-^+Nui-#rO7*?)w>d8+ZQJoIugUK6BJ{R|-0VxK0l)I}!Lp;^*h@NUSo)}9%AU=Ffn?VM62E*@{5?smzk5@6LV9R<`2a*Tu8*IoZ01YH z;FoE`f0&uE?>}MGt6pyVHz7A|Q1|oa>$J-@_}wgT40%jJTsFQRfvgsqR%x=GZDiJf zc0EGp)k++%qRJ}VLUeUYd+N8E&L#z4-h`=Qd6^>r z`ECR4`Is(rfpty(%P`ol^pd#LW1DIpff-|cUh-kjt`(Tc3 z8V-Qx=qvA;s7Y0BJ(`12==!&-3dngIw97xIl2J%DvdJcQ`DHiukD}89@=hr1=u-%M zY@*QQ@-rrva9~Bv$He=>BTgC1fJN7}7?SAUFz2~Z_22wRUA3Ez;yL#_2wE*<%VPLvwzsY#A&Pdi4)UPKbxqv`47nk8&lXLbS$K<;Q@q)1b0QMl zpP%WDR?C`()OD*Q~;)WRF!dZT0P zU8pI358ZE@?Q+9NfT#=0H0`ES>V0R@h(R-;`-;njqH`-nWs1&g5Cw=VVEinWq?RZh zG2@(85^ac78im^nO3wM5BMfAO`9t4JN~z7PNEQe|KlV+%{y!*-sXCSfQ!T3}J&sg_+Mfbs(HA>TH;kB%n(ENJ`yyl=3Ba}QPnJ-Vmb#2zT1Sz zWmk7hK#-}lJ>++2Q4zaQpT@b;r2uGWLO?!lmxWgnZhJke#)U7q1Hx^+OB%*Jp0dAZ zWnaR|`I41;)Y(jPh8&r$-?y5$mhQUIU1xWi>3q2&jboPBz^1%i>U#d!qTJ_%VQ2Pv zIC$wur$DhQVrY98_IUioTwp<8dMhMQAmy?Fm;to&CC*Oqa*tJW5YbnlT7g=b{^8oj zztdgst)%OeUHdc_uxiNLGGIbV4WC`5VA5FGwE!i)tk36~&iJ!Q_60nVwNqV9eTssU|Lsv$Gh z7?v?BU29Jj;TsOrD;{)T`wVy6qoUZ3gKdgf#5$viGlc2Vq?na1*sqTCf6JF)iQ-@4 z#hUome8}J?g)V6WLu`p8)ht|YDRdz5aYR&0=Gq6d1nu(r_dp%&PSs3_bXP_0BD>Q> z>(4x|K^+@(Y>9UG8-+n+)*ML0O(SBdV-e{fP>e0f4%WTIjXTjnmrm%xdt5X4)l-1i zW!RB+#*%FbFL&iQ`-yghmy2)B2Q`P#-;YeXuctmI&BvxXAv66r&}&1RUGJ7dmdme>!3lP6oh`@+`i#c1 z+B+ZTtDLdm85FXWD)=6c@oeax$GD!S8>k-mZ0cR9oPUrz{>mz0Lu}1ET%ns6ciOZz zUkRGCukaQpd6kj4<`Av7IhQ`!t_s|=(|5fz=#nJdm@3!rwMzGaUhhEARpe~oX)5A>+uCYx*{poQ$0(DzHQU6A3`MwZyzFQ$ax^-tPN^z^bsPi>Zij`MsCtA_sd9%|>Yex~`@cZ${jWM9@ z-FNtnwF^88Vc$2;ePIucdWEjjByWwI16a4=+>}bo%NnK)ob=7Q_SVm`3~r3_*vj;3 z4-pK_D#D(}n)F}r+vrxEX4t~9lZ`$p-Oh+#{(m3L_E>w`y>#0;RuyMnRLR;mNZBH5 zuL~}A5sQ~iqOEso1IZg^-+H;6G5Orrny=Pf;5jJAzHuH0dANEUeMYBpX*?X#x-IGE zbOv5l3F_O(UW#gQ8B%Dd`O7ceH(KdH;|*~hcHDpo#N5+{-N3lOagvOGU_CZY^Hm#q zi;U}1yW53yn=H<%_r=QNJ0r;1hH9@9F1|A)%cfq}I|<4NhsDNTHa-4iuaAltlQjmV zBW&m}lIqr@K~SxjabK-CszryQ-70ie{^P|I8u#P6RMYyf%~l|;C-cx{*OKEz4H;91 z-BW$H`)tJ@$V685t`G2v?+>Pm279g9UzqE{o}MkPJ!Ts?U1L7vqk43ng8zm=cpnv# zDfTcvW3@Ar&%VNJJ_;$rJVgW1jxin+^lG~N>o-rv>8Hh)Cp$K@2I^UEj4F|lP3>{= z=$TiUVF?g<&l;KZUTf;4r`$nI#rVdeqWM$q?ri2%UHvyVrL0dp%1l9*6u(+OY5E{w zcvm)rM5~+1-P^+mF0NJJUbW6*!3A$2T=xs$&@ye*!LeVs-vI0=qP_g%Y+NY<+KOqU zFV~4Fxj)NbvhX^>d4C-jWzefSLv08eeg`HZ(z5MLq_X?^=R9dy1J^l^+)IM4e=U>R zuz$+=YN6ttg6cj?Nh~V8Md}}i0+_BCjkq98ryT5DEqMYVdM`Ap#3DC3U1GDDv!+7* z7-@6lEk&I(gcBi2JECiFEa0e!-&q@F0W^95HI3XMj4okywsTN)b3LrV$^8=a?N;9*wY#3PRoBO_#ntc*NpQ*^7W+uTcbuqBFdQ!ym z(xavpf0OgGAz}K2JG1XWJwC$(!Rjd*%oI;j$cb=x%t`;%gm7+6un1-(VDZyJBH4a* zeO+VV2B|{z2GIRyLXH6dfs%w5#mf$W;{|IIlU@epQe4FYoH|OKpSySx)AWfDMi@hk zE4IN*uFXui!kT+MRZIN_;0e8!6RIU8>MR$C9!XLKD4Oixz&Gu#9Ca8l=4&*Wa`9Rn zI#8KRtT-SeeW49|c{lhG3qUTuhw~r<=D5*DZLyIMg+om}ekP!tl(KSm3x`?vOx#}` zttRUJ3Lb#mS_BJ$&(9g!8AG4wp!mPLT9W?;$ckb#1SgY9T*@4HkbKseYP z&C)2Q5>DDxW%?8-O&2UA{JC@hM5K(8_obSn-zosMg#J zEUlCxUY+Xh54gAs>24SH8#e>H2HRYg` zV1}=*_UT`v2LA$vw0{txeOZn0x7Jg9D|0N6fx7nw*xZVhxEhc?N_6A_pi?jbnt54j zR0@GjVCF@%0y)d8G-waWwX0hB7sJV}&1H8iyEIk9de&pCkHlD}YM^)eOPVc~!9dDf zpX7QVK-I^;tt72(iMNX_ur@8VboxD|9D-k_lKHtaVfRqc;*#}>kOCX3yjL{=_GH=m+r2gI1LEIr zv43_EleQbAJF4U8$)i?0ryUzYIdnpG*>6!xa3cB!Ro7>1rt?@OxMPT>{*};bM2TGj zA+ z2I#syA|61(dk$8Q-6i^I;Kd6;yZLvc5mt|k`sC(SR!#HZ7dMEy`u1-UB~U}|utSC3 zv1|T|zku7&9T0LX%f(O)gT#7zPp3UWC6%kk!%#Os!u*UG%FEhi#~nz)2#2e11XgR3 zN%M@CYbLF=03ZxqySu!GzeY#+lB9Qeaz#62*Ni2nLxk7{eh~#ZhP;l~3-tn5PM*LTb3_mfo194=c|IeF5O-CWtV;j703TOu$1 zdDuf+o?Dqx@B8ywT3Xuul(9E;t}hJtToH*MuD4uLnjf!f-Qe(c|Ux8uLG)elMU)4)j=m zj!wKe(~%O8k_XoHWWPV()MC>WN%Zn?J0T-Boe05ybx6xlvz%?boC%%*YZ!5)S5zq= z*!B?DX?OA0W#~{Fn9I;Cc~Ha3$quYAHw1EJSl-bgR>t>3>@09;%kE}1*)w6sf3Pm= zC8A_fp(FQvb3dVLf>l6tTzc}p?-Q0?eB*HNkcKu%Ct3B%I|^?W>4Na&dgtWy*TwZuPQSrc6K!s(V z0kQ92&kTW`>Vz>4!J!KIH4F!~>V$yT%tX!<_lj-&m)xl$1lMTA{=Lq()MnLmd?`Pb zFY_-&Mr`A+UEZgh9^SEn>>Vf(`?WvcVhO|Ub9(+wNz`HWs6>ib*l7l%9B^S;;Bv3D z=b@lB0{l*ghGkY*-t*$d()=(P)pU-rduI`^7Z+HsNz4s2%v5xq0J8@HH|w-N4}yM! zH4}30SV`^jmTv!*@ngJN^kvQ{Kknb>__vk^U4{qQ2JIkzuEU(X?#m(k_wbf|q@iSy zurj)d13)jhthl9T>GQm(BdH8;PbPx7Uq!{WbmNZdMLQC-4YbDlxH@VH8hy5e@vCstnJy;~P& z#9ntRYi#qjbablE-m1rXW-C7k$~nyCJ{>qU$$SReX^#GgatQ8#Vo4QRJ@IkC55ut! zgXdRO*gr-tPQ1WY8l)(;AvP6Su8>KYl(;luP>`7rwF*J+bh=t$;9c8{{j*D1hsI$S zSf1)Aqj#bHJ0bd25uVE^sx1OvX}}fP>U4h^j2zRl{}i=59d>12__q{;YUGisJ-muM zW2%*%=t9cin2R{kv9tZ&wjG8)*NroWDgV8Dn#>x-kf36I8A zN3580^4tInh?=d|`KoSX2d~OWOsRNh&}g;d(j+TOBjymFh+Y*b#*PG#C3unpUgm*G z*7<9yRB|oa`$dU-EX9x@qd7$`2uO#=ws$%I1Ts`to^F`8A3edO+n@%eL93KTM%WwE zjt?Uqumt1U!*51Yu=pbS%;T5UV2}hlvRl80mY$7;y`AV{mh~Be04nJBiUY^CaL$>x zG)6Cm9YR&3G`k26Nz@&sgy4r_=kM4dGD1`k zbc8tVj6$MKYPYLs2#H-n6?4-(2=l=GII8oj!JiZ9U-6e=+G#VVZQ3g%u_XWLsOv%R zP^52!t3h7YOK-BSe4^fET7K@H>%b;=wck<4BJGpvVANIM;8UC>8Zv6vItZk1j*dF5 z&o=G6RMsjfUUfog+cq2 zu)Z?4y}ed@je0K@g;JhRal1JmPba!K8mKd`8I3`WY4py=7E{&e!I;)EJRU9H_3$B< zhN#e7D9(gk@bId%*X8igkG+xGw^J9~|XKXBXe3lM)1zE;-$D za?GCWZ;xAZz3fLEIMZ$d{NJwxKUmV^ddKUuv}B!I%SA%_Anv72S>W-oJn5ln zZR#*gQbiE}XLh9f&h$b9xG%)pKDdfapvFdYmxklEl+KEevdzv;1x66r>eZw}0`g7h zu;Tjk((IQ8Fxdv?#M7Yxp@MTSG={GVHXuA#?B?H(2B^T0RSf7lT%o2+@!V>50@R`G z05~Z3GlFCEC79ojXExC?)wX*vFo#?ih4 zY*Fx-M7Uxqj=~!QoIGOo*E6}yP&lI)lglr1T}#f)6dfa1K7X3QJWD-79NP6qhL86= z1HyQOkn#`duhag!^FmAt6WhvA)CmK0k)9DoYD5}7Tn6W2|D@+n><$2f|1LCFRx7C` z>E^+ebB%CL0f0`mso*6GDfjY8+!(5~A`RGlTt;}@aH#qPP4IjiHbOnk{93#tYor*I7>YN`ZG*pMbq`_@l=FC3?ZXyQ) z(rd3N_tbxn%`_gzC~F^DT-dg!LZc*9Q7B|p^wE7>SAh)T6~aA&H3L1KhVh)?uftNq zB+N;<&c?e;gmR#$Hx;*U*5Equ;6D)cO=1TQNh>G4*hY(xtRj`=nj4V9oY#Jp!rr8G z=hkR7AR!*-yS?K%CX6Q1xJ@~j3yR4Swr_LN0`68}Cw!D5D&qT6_uaZ%Z-%Da{)|VB zyghbvFH=}uffY_gB{B#gY}N@iqrfSipwZ2aD%Z8wFG6JL@+Eb z^fwO%Fpz~|lh<)Hwg)l(UVq?!z^%oK>*WEJOEB@{!^Paoi56x7ec%5{Bjv5bc~i{mS=pnFoT)K>g4@o`FZyJRsN{-^GWHt+;fvQrM`?wx1C*fV|zc^KD_ zi#_o9-|U2}{K|Yw*gy|-aegD_Y--Ybo?FwU^7#6DuD@%vS)p!FF?Rz+Ze_)+OkQk zCW1kk#RAk3?uCu4N0TMhsxyU)^?B^y%a`fY8Fng?Xgd(KP>h`z%I8#$VAJ^o z?6})xdYlJfL{so{Ry}f@_XmHFgGki3DMx@mxM3skdr3JTZ;pCC|Z%ESC}2NW9k!o*s2B3OmV6FPB{Y zV!!89KOI&B=?bBsA0$ z8@7DMo3srwV(c<9V&;VsbstX}v&l8+o{=S8^ z9#U_;hR$g~g3XP@YcTHwx5R=HNa68hnNOTF!s9Gp)%J1+9g+RM)<&YV)=&N0HAql_ z^+-*^N$M{th`r>76njeUG$sC@+SCmrX};rJ5%P<{a2WN%NxEw**bVkUXtxynZUp#z zTtbFERN(QFUYw~(91knT#g`Ba2K|jWaNS~MiJ^#F6xL$ zReI_NyP9TnKw?l>OpXyXV5hc1A^6~7kg2bvtgPE#JmWRk41mI*c*KTIPP8-b zD3*TFMb_g#(Xk$m>{eDGK*bq}wo8-|TD8e5OXPIcxUt$eF+bLW#I4js=af z9w-57?gi{IjYmn(GPDs7=w}Z^Wo}zJO zAm;Z)(=wweP8Gaz1M;4=pEsCjvMld3Sa!Jew)?)zLw2)zLl(AoSXKv@nHKG-#ylF4 z_#z-o*yceAPT?VdaCS%0ZS+k7vEx*_baJ#927OcB1(QJ27(c#Oi}EWv+RVQk=MtNA)WRsA4_tqn@^X= ztVN&bmM(p0**6(2J4)bXix4kk2zL5fxPQ+OI9;?Y^2zL}{pOiWOgnQ?Ph@aYCKc&K z;jM3<9}q=d0n5GMy>l1?hX+9qc91(2FbM8Zig-zmhTLN`p%QlCeS zFBA%38m5lG(=Y|`kYk&3E>bW^b`Tub#llZ@8P&%|0hwUj0`&s+?IzDu6B<=oMZ+ij zAbPwIB1~1AHU_nmU2WSNy>uiNk1mJ*#wR5rV%$V}{rStb*wYKE0`Iv{v&)eA!1U*u z{LdpNDMhyFR}U2_0Vveq`zZ1wqHsw_FveWqqu8T8*dt7~HoX)`$l#Fi>SK9;c?YaH z{Xn>yUx#_HSP{Km)mR}Jg0v|m4F!wM?PziqDG%?kTPmKNH95ZnTWg*ngm5~zM{07x zI`KsSG!jbxMz;Z^8=43`g^K)UtDzakLTZ>|Aq7uMG{FF|FjKL&agbiQF(r(&JtPGM zYdc=KB|0VNsX=96Qu)#l0Yx%GLI{kX@a^RjuWpf@+Ui(JEC<~FaC8*IQTl=q1PkAC z!|RXO`Zv*4mo(dDEB0huY|(QTEFI~b<(LAj!hA2}*~D)D?$JubmYDk)gEqr1RABAB zZs7dAZdVhoWYWH9ktPmE7VGJwSm@Z{hAk@oC7p5O%Y2T1vZ0y#h;W8C!Mb!G&}Pl( zcsRi&syGEe>iptRvAy_ z%yR{vaY{57G967=PemQ&*$XcN6@Gfd+64$8|m zYC|i_C<=nhDu!LI3d6#yAP_2RPeVv$9{Ib<(|0UHI)v;svo+7ASTR@YAZtGLmzeiS zUzX}sAqW@Qqf%(MrEaT@fH}ga^3!8Gi{e+IRI1_T#rPYZfu1L`!B5Dub#~b3(Hz^H z-sPm8GbO@<4U*v7&DR8&qCLVRVP}PIti}e6OQi6cW^Cgj@n7RR;0%X8o*H;R_mMnJ zSXb)&TE=JNgYm1MXk7@$-chfh%+#9GHc=E$;iZmb30R%c}T_9;H z`7c(4j^LPA18(H?pTWTTH2O=AoHC40T}6|~NJm?FY+9$xu4dRmcE(0GxbNR>ph8l9 z(^|(vQf=jZoEmr>H0o>F6q;yucs_Dd&pBt#8td52_Qw-T9*fBzjfeDZ|GgH5Bow!d zpYfMo3enKeCEjXga)TGmsCOMS!U(B*(EPsgpi5GR$$(<7e|`)$e#*}6{Alu=-X~=_ zULQvK2~InL%`=eqk`{=4(Xpcj(9i(l8vgOJeDu+<6rJ1I74Fa%b2>~GX$3cHX%vq) z-Vly&spumzv7Yw_uL{nzNzATV+785ECt~$9ozYeRCQO&hI1{P3vUpxFp^izj!7y%^ z;A8@$^G%|@g1pd;xFh?z(xC)BdZk}oJbIg-#@4YF9=4)52CD1~!Sx^h$Jd#}*SwBtz)pvebPm?z;H2VFJTa>%m|8 z)|}SQGTpG_^MfTVmhcUgo23SYdS=0RUzZZ!z6wZ-DFyzHWHXIJ`?%H_4-JT8tv*E@rV7JJXyz@^kw zoI&98F_P}C2aC9?EQb;x(sS|w%ujdeTu8?x6#k7=x9Y0oFPf*yZ%>c6ID+jh618ow zS#vrkH%(2l!jw%C028fn$|7a$jA?F^lLMPDITxck!`L&|g5cRog z2DTkZ9Ha(dS-SOljPS+GyTK!<#mtu@0a@)h?t6Jf!LM1E!hpIUXC%G5+!XS#BT7V7d|laoR!x@m@h`o0cRF&2(*TA z9Lf1xnVx5bk{@+p4qpErCg5b@Q~DldFaIvmF8s_ApJt&lfVwcRHsA>zoOj(KKC;ek zkSZe|UCW1OE6^<~e^UuQC-SZY8-6NZis33EOcrFLG!m1lK#D*Pa$tx`2l$R0qScaB ze=x39wK^dA(ML)JlmEt@K>ry$ayWy(KP3PfnI5#E(;r(5pq(L9+}?Kfw-Mn^vef$K zpyk0n^Cjg#Nx^TEeitIOD7h8xtR4$_v^{3^qK`H0N8O5hXyX}Ay}-Wt!2XuLhCmd= z%K%htadlMa2=5VK{E$fx3d$BLO`-w)@a4drk%s?%KbRB3^~|AHmjwivlByRPW~myg z7UrN7Wh`6l26)JD7a~d56OSdg zZPni!z`pYOttq1`s_(1PS@(;!u-~R95?WWvVl|f2X)Xx8sD>#Pu408xSG9?!{|~q> zhWYazyE!j@Rn#(I=0={*sjQLp`=Pm%7jRYqQ!G@%{wY<#A(s9H47e(Q1A2=%nGrwW zUGS5b7>UTd>*H8S_W5z1_(86!A-mW(hFniFCC7SIxB zAtm1P#3UJg1c2N7A=xsn%tbB9xE28g^!e(na%oQ1&N8FZ4R-Lspf=5kbNHE3pv{g` zBU3Tx^vTu(IU#4(pBT-16Xjku4-+5KBXxg?Kh2)^yBdq}!sY`Yl zhIDP5oLpmHzQx3ct3yOEb||VN#weMS!k70><922!$-4@=)hKUOnFb+U&3lEIJ+>&| z;3X2k->r=@BXc8&6q-?ZGYc~OHVj+>iHZ-hL(cq1e#(ELej}umB?Q;tI^t47 zroWUz*s1NRRUs&4?AiE2j0rlsIXo4(-a@i0f2bw)z^MXz2=Ym0Ac8_W2w{aaR8EZr zq(o#5T{WwaAv-2Ppk6Zue8{SeK(JJE3Ap0WX6`N%#%?cf-=^O0g+jO^tO-0gWHmxK z=4j_HV)oYeQiN^pmrf~8OZ~en3hm!FQ6-o$8>gr*F^rv=2QAuT*Qv$(h60a`G^++>0(D=|I@g z^9VfMqAnrnhuiY-3?v_vE+KgYW>sx0b^Y!@;@A3r?iGHvE;qN{3X&}&7u@!ShljxU zcYVQfzy2p;ulN3Qh#uJoU}-sUte2eJ{K#k6B_!dJ8%~~ql=GVP84~3;9 z(Uuzqo&-qe%kzjra<;tOa?umpC8S~FZ3E9j%hj4PvSv`WoZR$G_oDNN+F-WyT>N*p zsB;K<;kMjd{Ym?!bBX_9@15c+>w-AZxMSP4&5q3zqhnhg+ji2iZQJPBwrv}e{$}QB z=DytDeZ0@-?AmMB`mge;@+yqYt*Ud$&Q`|C$R?z1n3T7`z52<&v;HYOA#b7o(LW&% zd89TazudW!J}wW&@syQ~$V|Y=Pc+Z3=Pjgi-+xGP?|p~B$WLUo?&9rFy~r~zkC@bU z_70|2)IKF&g6*~a9@uy(J|U02&^Gig@(%(O{~+M~9|YvTArSHsR;JOMl|7BU{@dHY zVmIoLq78Xd&)ffai|&x32XVvQ+n=f*?G9yE11}#1VQKK^3QpPdLmm)s5pIay7C9tL zWG_)e4Q3S%eS=Y}t!d7>%Z}jSozRLMzHxrxlRXc?TMG;UbG)+J&on-oMQr&C&uOnT z=buKf`Bk2j2}+t7lv*?-zl9bjLnE^SSeXCBk(7WF&gGEDgMC%*mJ1rEz_DCIK+_v4 zrU0lFuB|o6>_Nx8^L&j6S3Ete2@kfX=8~dI=AjM|u>j|^ulcUJp%lYrk+SUj#nBJ8 z9qqg`cC0a@L2|7j>9;2rD$m3?gRwC_Y0}w?f zJh6hZhG%xoyCM(k#(y#FdDJ1ondKc`P?EivNtPHRDH7AnOHZ6BI(DLx6$J(WSe`PH z4yo)x_0&L@6(T$8J$SAdzugyBBZt^!=Vvqemm5ItX6k zl~@$1^_NVIJg+Uvq0|^#%KT8Kl+t1?@$x-aSHUKJ7rb8>pxB|#s|Z*iGP?WSTvn+1 z`OZMcKC!WVvU#4)tz*Aoj~<>F4}XX_Tz$h3vo>tB16YNzq|A@{^anTFBAXSnRMSXn zaO}`ucFgR>NJEEgW&@=jZt`cUe71#@A<}C7#B;;ErPy0&!a4+FG;$MgAu)Po8a)9t z*fNWpfp$9bJ8bse4&am%$d`5oY)m*t9Ry{<0IZbv#t{xWnMqn19V*7qu#qJQ^6|m_ zRKIanX0DU|hqpAbuZh&85nUX@h}4nP)Dar+X%o}iM!z`=v)ge2%UV{hlkx3Lg-UE- z609Bz!bV8UR)K18MJT-diPdj1s2m(zCku7Z?lF_8o!?Xt=ePIAxA&7kg0(U{t+(q_ zGI&`O^4KXTwA&8xE&ZtSIfC8|Wf_h4i`?OA*r)9^FXXbY%;dPO#Uo;*+NZz-t(AkH6Dw9}dm%_r!sPytskYfd$sWO%R$wfo_$$$<82?n!0o_&FFpyw*LKkIj;*`&Pn;3Vn+i4# zNuigado;C0QR_GKMdGy92kRyr3xMLC+rKDtXn-!Ri2>*Iy2xaD50ui)N|N$_IWdj@wLtx%)7z^JEA(8JI|yImB~3%h>1p-lXV8Ysmyqj2({ z&;l3mr9jn$E&X(VZ`w1IG6EMwjZOX@6gq$=c7sTiS5xQEKw3Yk+n>XCqfzMH>7;tu zbJnaQ9#jKO9}YrqLxX5#YT!0+6#kvj!qqbQmC;4lEc`+P)acOy(&4`i&yO*5b?a=8 zteTyyfgQ3oCad@f4P-r84OB%~vj9G5zlNue+xZudUW?BFnCQasM|TSq;DoGVvo;`n zL);m$kJospYccCmL_^y+ZHQCJS3yk!NEHatB?*v$u7)KV(K<@HbwRBJB-yy3f)bL*>6~01iCptN<$L&qdd^d_Ye5CQ2MwsHeU#EBq&zaOxpfq5@PK;GO0s=I1-%B% zx3O)M(xkmSlDN5L6l~x>YVH3wYX3dc`F}n$-m?75*v9`>4JfE>6r|2SwxvAMu(4&7 z(_mc1WO3kx+Oa>O8$_YKoVJGs^3pEJ{+(V8i$dQiDOJP1b#xu*o0qev)G)`;LTlQ_ zxb6KagHYcDO>}Xqm^nngdD+l93ew`=qpF5AWbKkE-`~Q*E2d~aOSy@NTucIpD@0#F{4@op` z*s9RC2x*W)2_R4Bhb+P_hJtYD5 z(RII3mtH+C`;EFBh3~f(I{}kk((I|zbumX{-X*27*}smiU+-4~&Hr2QGd+;Y^C+ix za&=@v&FkoRMT@7gjqmpvG5`0iO&9`eUjMMJ_KkJVe^~GOUs(UQ;%6A&%5S1;-_(!4 zsk6|_>87sbzN7iBdVP=h_yMsEuZ#69R+7HOif-z+SovPme`4j|l3_sg%Bv&yn%+h^ z-6Oug#rm5SY&x`oP2Yqaywz#+ACmBt&%5dZ5Kn1hrSF*O;$UJyklIJeYl?J_j5Xe=t3iy{kBr@RS35vV z*F&4Lw@eE6Gq2c#Pd&COdD(0mTb3Pg|LFhS$Gc|lI`!DD^kfrE^SC8*A$L9FbJ{EK z!q^;DL&>b9(0yQyO)9UmiB!>08!W3pG0a0BN-kIyF?K-jB;!t8GpyBJz=HTZ;vI^Z z$13U)cXKC>d^b-tcbFGi+uLDv*XIC;V|C+g6;zOCnSvfZc>&+g!*~8PVe-u+sfp}_ z`#~dLtx>m2B}SHdz-b%3Qmg*auwet|+aVE#!?OP2J1ZT$_ucHEi-Us-l2Jm09EAJEC{@78 zpvL~UtD-M^0%8Jsh`+K3Hep>fsE+~ij+dCK@xMDZ2@s(Eq^`&nVu`Mjq00HsUrr4#j{|++Q8LHm7Ji{rb|yY^w0xbTjG)vgC%}GtlR;e337;OEnP1GmM70b; zLduVVMD zV$790W&a;h?DJtadz(mN2g9MO+rSITZ@ zQ(?C#!Qik+L;2mJn)n7`&IrRs+YyVXx{}Z?3M@JFIXPQ_eU_AXpNL{7QlamufG7Qi z7&*;f7`V@X1H&heza6M=!%YTEIg1 z{T(V~4Zh9{V_i~z0Zju1|%Cl#`f0&Eolw*g7fRFW$Bl9FHQ z9tBSx;n&jDjIuc^oOdyAIFF6yB}H9et!3F|?DAs0MWgznc94F~lW**j zZBx~G1)=1ecx>u!*YHtXrCipShTq~wD}5V7{L=9T&_&3@rWT=FplPUMqss1 z31>$7N%6B8l9qF)GZrp-|IP)aBm_j!x;XAGjSf3V%s}REqw1njSID-;BcksbWlPqw zY6{{U?7YQ&z)rlOvDwV?fL0u9t5DRe7RQ9<{cbuEolcgdgHx9OWEaJw3f$cN*L!x} z*Ot8q{@nus0Y>&5B{D>M)Dd;wE&v5S_FYQV&~NgugWSAv#Ssjm3|mO>k;L7K%+qUB zs>wRqguzI6VO#dC;fr}C{GQHmFvnhLec%oIK(;5V1XGgQ*we|d0%e%3OsbKMVBI|& ze6onR8)j1^MwkW%?>eUsB7E-ejsitQf|8FGebH>T!S735b3m*W*4!-kq8@df#tLm9 zrqZh5mXgQo<$o5!4^9O0^r!2wYD}K>9e3}Y5n%Y> zc^1O+jru%ycfQnVGT0z6bs*bp-Q-R6w)@_^1i^gaM8&mN^)bQ*Km82)tB5&U1Nf-#=9foW6`vS^cqNyovz$-h)D>$+d-&Zzbf~}<-xm1{NtWTp&c~J&@S6>+<~19~@WUw&Tkp%qHR}+%pVIWr9|Krz5`v>Q96T2=U9(}{EwqwM_PdPsc75Py#5U$q$QjiMD&8s(K~MmU$!lhl zQFgBFh(1!NknsXaDGf5**gUX^8ap#)Lp1(IhiF5(w))3Wx2jE{=$yn3PQ|d?oImU? zjc;P9a_2u#R$R0Xhp1CIK57UI=krn(zaE8`Dv^s?;H}R{Y?O*ek1k=7uHKC%)u^iu z*ign(#~heEC-g(ssCu;(W2sxg^QM;*BY~0eTzlks>_U#z?F&pJJ!65=CX3r#Wti-y z4YV+>T_9x1O*r$BVs1mG(p&{iwy0* zY*fx)+5Na=Tm0N%emT*^n(V=5GG)l6tJ;C>VCPQn#ydCs!vDZIyWikXe@76N6SV1L zMC^D&E4L#|lCWw7AP7PZ`2Dck?t-@uJvFLql=N44bB z+67W;GIxOLeNmLUOS}y(sbyVkrbv~DJ|0R2g>Ci?!Q#S0`lDhS_4h#0iPS4_B2ICm

&l5J7IUx$D)PM*cyYT@+o#z-fE$SM6!qAm!xIT=*O=eGH!5 z0kVgy1lmhWs7!*x*TFawY|VM{W&NbHo7|HdJ9BgcFu#m7AcsWtQg>;K0{mUkj)mo z+VsKG@)`;h{i5y59UVf34i(RWd%Ee?2A^$jm&9+&OR&o{`hfG5=dmsJ-rkNd>p;ji z*(wPKwIJHXB>3!>Yh{w{A+-L0*ay%ISQKF$h^(~i;ea}e%*i+|uPaG5_vGi`z@J}1 z3hKhP2nE9WZ&!GW6bAARvLm5E2-=udLK8v5H|Sd=g;Pr;y^<_8;b69^|b4K!S%+GOVyxCh3v|FoDBCX)_Wn!KuOyAeW z=2f!9L(?0DdJo^dAB}Vb*nfCuR`5Be)1Dy1%;d>i+Xtamq5%k3GN$0w0jG#q=tV4} z`Sx0J6KF}`<-)ADo?V<+Rkwc}X9e9bIqL_(Li;)5)9OG@fb%lY*z~cf(ei8-S@$DL zgVSZj(Jd`Li&fGNJZ6)t2~2TSNfJv<0Vqn|SuOB~)95Kk^XQZP<}-*~!iz`|zV)p7tqIwSA0;j}XSEudP7+$gJ?<-pPSO5@BO?lCHENRmQ)ibHOSQnzcc(1bDDd z%g)VGa$wX!1J+mxmCAHV)5A#%MtNbKn90JXH8ESB68c$kN=HPIxmpHy5dkJ{Yonw)1MFjI7{ev9?J%IuYDgUKK9J<7iv3kTUiv*dH@1emd>06bI$jKX zI`MM#+N@B9L=hZa_aSbwl)19=rRSqg;Bs-w)Wn)AZZl9&%3DdDfa zKVU|ekJ)Tgag z@54bztrBYl!*@B>j?+IEa)*S|W*m+;$C7w2^~5KtKbAz-kIWMwLQU87QUb#>N@wRMl!4l@!#*Bxz|UnpGiv!%itnjtW&11&ls?X~7$bYmgt* zE$M;iB4uH|WxdGN3!2B6s8p^0;wx|#ulqGQ@tTP>v}6-0PR-XQE;OiP3dhy&%!>+T z0eSO5zir2iB)k?ZR$OE@tdWjA?o4FuhPC%HcqIKYd8r zZ2$yJ28jqZXPWW}oLOz0C~l?cVP6n9Zj2HTDiObQ+4c@(gz7E`r$MSzbg8SqWQ}v- zR^}^cp}K6$8ara=o7(t9asgzT=qjf*$bBN1pw|6fgF0jbYu&O!fe>KR$=fTWfK*9G z6a6CLf$1vFz26P@H5oy6p+Yr&VVRv)eE6sHL(|$vQI^h!de>%`_uJ{klnZ~SRwZki zoOFwBJ@3$h+1)al`Ga^WTWru)-ZM|5n-m6V_jKi>vGx{Y9Aj^iX@PYX} zhh%xuJECHMzff}O?5K(gz57)1tt984X{i@Ml6AOs>N7ulfv5Mh`i=3wyt5s~1Pd3~ z{EwQtLM!jMdeUyB$xeox<9fn{49|WHQebnX{Eh%IXTzXqy`t!@*u-3pB~s1M2=5hg zR_XO;SWN{pT{bsbyplyF8%w8Z)@x(t^n&D1K`4!oF z(C%-Y*B}yViv;yG$(+i97rJ)@ee*&iU1|iq1kWXMT!~B5@2QLzhi1+InOKu(<5p_E zu~JG3F9TWG0nfF)W&;JX6ybn$9aa7g(Y4}c#)9A1^LGF>>7}L!&1K2J*!u^rAYDL% zlD~kPl-!>dAN5djOP_FC!Y-7MYk(1KZth{Bj$7xbn4*18u;E}!>2V+`O81b8o^?Z-mv%xp z_{)};y(Wfi6U|vNxZ;&3tfS;_oge&yam`@`;@7Poy?ZI!21ya3%%{iyY7Ckp>J{OkCpw#COM1l1m-qw=rMot=$S zpUU_Nxqe2(z$>@#*4E0^ePF~82T%Z1>5s{DBc*lj=$q9bDPG<)gdPOtOf$1p4NW-Hu$=CsL~yTmSGkTNubQnYL5COjpWDUX!% zEn60SkkCpYTNfrO)&?uY88o{W<0GkBq!Vv_xN87V7)5JQX(jxZJ4FnGx(z2H!jDUBkBbcNaYoR) z+p;O_92$BnCjgV;DShGF>V5xvtwv(Rg6BhrXE@N{BxNL%OHpO(sU^IFHs;uVl%7n8 zNEgMe@bLacB5w5Wc%GXVU$;LdSx+@zap)!k|xWvReCDL7A7DK(&7 z%^7pkN_@nOIU~~cs`$@eL29`RxKY;`Ao0h&1h|Hi+#sH>P(I$TTc)xqDmU&m6W?Xnx}LJ&L-?afBCX$% z1mxZO#d2R;%fT0h=rLGtqs9T-4SJ3x1~hU1GgvShc^yeoF!OGsOD=G-67p+eIV;N~ zjw_=M0HKv96(*B29;0NvI+L(&4q$Ek1TIvR{XPJQokwc> zP3Tfdnl*#oImB=&)XGu_*nMe{BTeZWHSM(zTQ2-Ug6i^}6XWTr9Cs%8S?Og^Aiv69qE)9I<2Xisca8rrb8tFBw6W<2pYSwG}Jo;#Twc&oeF znHJwGXCvMtnU%3N z$>yaKyh{RH4;|5hvXn1q#dso7vTOM%&%Q&g?N6kieb3CeyuFC$&jRzX0oHhWlbF5C zM>6V<<B0#ggV}(ItDnE?JPE6Q}^`W33#q#^bVR0QvX8 z96S~>qEM)?W7Z8uQ?k-h6#DfdUx&yNuTRIclrUMEsfu%|QXhD(E`p)v>%ZGk_ zYZI-k2>9}FpT}`>{8`6~S_rHf2yLoIvbaby?Oo8uBbngC*k>AN{g-wOB3-}>iQ2a5jHto?#C&T~QFvg5 z`Mg&ssYGQ_5LLmcXmBhOLFk(XEW)Lx7W!C77tErc|! zMQ9BkKdbTV{+-2DBnyYV>EyW$b7wPcv~hoGCpgulo-kR>mQM}Snzr8ouMSvwQui`1 z1kLpz!~w!hroZJw2=yRie1SpVbF%=I$gWMSLtXzmEWCeOq7W1crCoL3?3$}1!+p|D z&JbJT--Q|PIpdbapzk{NI$M^2W(SXMQrFB#hV7*HJJV*z0UbXb|R8Db>(B6 zO9p0G^z8SEnPBK)QB&Zk@yk&oka1@K^n=YqqPnrDO1Pm&50@qzdOXVf;VZRUfC^-N zMokmVqC(lr(u?t@^*h7050CxOSH>66xu{-A583&uEwWT>7_=t!u(rA^Adr@hPN?Ci`T8Q$#j45 z^Oos-qZNmBrcM5JTHI^6wZ5^$cH_hfXf-VFc=Hu1GtpXpjS+EZ>q%)}n;i0^g`X{Vk?du5;|^q?s(KG~?5=T76kF84+kga*iA+UsQX{Lps%AAe__&e7 z{V35^VrOpAK2;sG47gh-c(GzvgJwO-_6^|A?H`|JNo-cD-?F}7OR<*7(&$#lhC(Q8 zxA06pDmE9>Fw7_`Hg%zPRDb#ZHJ@5xPGL%8Gv%xpu}Ov^PhVp>NAuzDCZlD#a)E)Xr(9rkpYtep{u8y}G_ZTnZ$_1`Z?RVj)sE zSoOm6vFt|njU->qEKw}P{bUL!Q!6n@&v14~GGIIke;7Dnk`ye_k$3|OG4xFtn8~9p zQGc-$6CZ{MtF1x02Z9$@M&XHTsLTwa*q;8B@AW!(1nyB#4|EB>>~P`!@S zu2p6KK(!5EJt0xhzUCm?5rh%R291fU`Zb7dbn9wmkW0C4J6yIFhs?d*YZ@o%qZ<9_ zAVx}YRlRC*dR4`*^k!a$7h62O(dT4`g~&4rY87N6Ehqv>onjzIeS=SvD`LUm`Fx*y zn8^%_&j2|c^^b@LUK)TSR{33K@aM|!1R*LoUJ8?>+uAFNMob251sR^Mz)^bENE8P? zTq4n&mO282I4(KJ29>RFr+Z3^4Q|X?RHO*$0f!X4!WUjDW&Bj3E<0=SsamNT^GKOcjCosclHsB~Ez#%3pu%6em zlA29?xx$$1`+aBOLUH6Z>9Ls*a^#?;sp|J6@`saz-CgBP*_!;l+@~JDAVA;8)yIRQ zj#iMG4B7+Sp>8l-rN`T|P7>hKGk>Sw0!#87yx#NX3*1r%yoY6yQ(hivpQKxW=Y!ua zt_}~Is??eg{HyU!HWNk@MIYZkp$O_2N8Pzr8egNLHKB2d`{oq+2Z2mwtxTvUZl#Kk(-pqqd?iw3FCJ~ zoJir8Wn%cwDa#*E#SH_H#LD^8$*Ad{i}p^@T_iuE#Vtkqk%z)Ve@>?23qAyn(iS%^ z-N)J@uTv7h>Lb`J<{^QTJB(dJYJ!rhxWbw(WW9#nP~>lETZEV)tbe{{={k`j3a3$L zg7vDewJaOB#Y-Lu>~uX{VW>TH(}#ejt>7D*yrfssdb$KGlyx(^D3nrc#G0#nlH%R1Zz5f!iw6+txqK%d^NX!TyZi(#XcyZ z#cMe`5mlz$x!M%U#x{hF{;>_i7ugE$FMulg_7Z*~h62+h0!0s{sa`5b2TNq+OQA3x z|3i5H3Y^sWY2DgmI*DAw0XXWAYx>?w#AH;#i}vWkq{{XM#}*IKe;PZH)dYMIk1)TT z&sZb6(`MzgT8zV_B$74_O28Qg9Y25RB2H}S=-@ZOYEz?3Fx-&hh#Rp0-qXAd`E8kcQIh~JeJOlxXQs}h+Vfyy8XbuT0 z@Szo9Dyfq9)9*F>p&J;7*(_O zopLRRuJX>>xOU>B<=@zOx;WC9ZjOoORI*`mVi4bmo=U3}u5zOR=o6k$^D<~EVe33C zdfu~FxyVeF8}=vJkANKh7_<)5G-AsWQU=>9(Elz>+2LTnB<8*oCjJS|dH^aqj|6wm ztD>r9l+vT+OyvBA{~`rZ4oj}|oZigIjgMyi7T0;Saj48nSCKFmvX)C2{vpV7nuk}0 zAw8xx=ZiCj)IUBcK2vTz5d*R%caSq&tfAer(1;zZvG^EawTLQ;IILUhf9MFSWE+d_PIsNqYK`Jjo* zIv-_9@29G;A|}n)!|C4Y<1+r>x_s9;!V>rKO~x24nhbi9l{8I6 z1ZI^gH_3vYyzY~3(uGti{IyP3X`!hh0xOYBnxp~lg0TlaV|o&;Wxs|4Zdu{hTFuAK z)!6Nuwc5VeHNh0MyV#*97w>=6>MJV{lMgptvWBy86pkQlNt7a;-w{n!%eXX9xjUSA zVJrMd{vHl^f-V{KPtI-d1K%1%n(;)KH{zA}GJst{--G`Eiu&KcctLYSRJHvF3>8#b zX#7BYS*rRGWzZE;qxeF?k1HFF?}?Aczxu{Iu5Or@ns@jhcWTJsOCVKaTCH!_I=3Px zm-b9}v1F_0=?}CaZ^>RK&BA}d#J!Zn!;w%Vh7zOJdG)V5>BZzrI9@B_qOOVal8NfYBrSw`lkv`gG zBZqfyLYr{-08a(nT=EC4JS{kABqO%giuH2yH1dG4ZWhng>#Hcj6G!s#Wd1P*ZZ@F# z3G5Wf^PL4k6?=N4VLnx@$SL$cE=ZqdBb0R40(JX!OZgA`l=PzAH#{MZoU;Ky88w)a zltxd|P0UZG{OIXbiN-3*Zx_+oq0e;Bg3Xu)aIt&34;C^_t6_m1BO*UZH}xjwBE15*kkHgHEZd4k{VoO+_P2BI9?&=oC|rRn69F;E zO#fNk_q|5C!{B`R_^#N5gct#>hEP9S27Wh#uGOq9I#cJgUfE!vtE2voTMLn0HTSu@ z8t?Xc(;*wZ1Doif#0Gs?ZPqX{wQjBGPSFux;&@2sV9*VrEA0E3XW^d3MxnM*SUS;? z1F0{s*-Pm_wfNQ-lLKB5h#il>kza&SJdde3pch7{?&kM2M{=p8Njv}<$vehrNJU!$ z;UL`=cyxw&4x~@v_dS8pU7*!2wKPV2rt$6M3rwh|!`tO= zgqXs!aP?|b=YotY6xFg}6Y10igY#a0aMfushvO<=)I(ZOFg+QaLu5{Yvf32>A8dy; zoZFNb92n)QoO3oOlCi|og%+S+TczqvL&KtO2P-O3=@h+Odt9ub#Rp;Wm9*Z zl*feC0~JlXs#NnmX;V_Gz9sPU?MC`Ba<;7hY)q-pKr?DuUk$iyV}jhTg}IDwC)W|3 zzka4_$>t)z!2KhCnQ$Jh!St64&G|WH5b7710M_v+mp6w9(DDe^2V9X1MslLq>8hcz#>_=tw?7kB*H#MUnd-Kg4fEZ|hrtlH|suK$%cC zj%ex-SLp){!0AY&v}m0u+TdVQ_npaOFLb+S|kuB?en23(-h?*`QP}|#&P%4iaVc*1te*l5G!>zhm1>Tsd*eIQ#d|(dpL5Y_ExTJQahgnWzwzSGg z9FH>NJfe&68$k{@Vn2SD6a3P6ty^QKy zV|v<`FF!^-G_;*Db6(k#7I{cq$kKM$R#+iO?CFD#B4+G$uo(9yXZ{M?ExL0a!`@lw z`nG6hw9D<1b^&E7%F5$>f8ka2HBmeY6xeQltZZ&xl0r*U5DboJG~bBk1BM}vKlZ1` zg=}B&G)S5JUh0#{A>Z2_hFW_bqfCHHBq$a_e%8Q>8qXGg3ceit+uO%ne!^*As|BWi zd6x#tG&T5fc?9OEAVB1c{9+XpeiO9wbYsxgp*k^K6B|9}6)CeGvrKw06-XB@wfZaB zqdyUqb{PSoyJ3dZzoE#!@D-lrL>M`K;Z3j??JldLzLFE<4oJ*(wkUiHT#{mH<1+pZ z-WmvBAQs*~Hf1;aPXuNYa{7udyai{# zuTfVC1J&>fa#)8?2ec<9klr2+J1U#oA1*&1t9SzyvOkT1s4Amj&fFH&{6oTE` z{Wr25F-`ylvo%!D3iqC8R6KQ~VOj9eaiFnYGi&AShDk#O+beH4?e^feJ$O`O&SKF* zSN$O8vog|cg}y{0otnon8`vmI2;41WYmx4Ns3)jXbnmy^_^w583&%xogOQ3rYtSxN z7F-R$=!76mA9oH|AtlG69qwu$sX|A3XV^j58u#RIsd5a&+!VI^1?07%zqP%yiI0JC zQKwObUVb)4)8rI=VetE0QnGX>z@r4J7wBGa&A0X7cIsZo@;qq=X&Hvw)XOO5mmQR! zHIp+NQd)>WwjebE@#We(3^{e280iZs>tFr+q#QbIq$IY6J@VAVpb(cp#3^qXGyUxp z3wwb}vJHg*O|`vnb3n9(d1_3>5{ZhOos%~WH=?bv%7=j?ugE<)UxGeYx=T}thDvsc z-@CqeRKg|)SFE4sw^~5f~qxN=t zU46X7SHo^QNyjx3@v4+o&KcvqT6s>Mbqt5E7?r7xmm%vn+EzIIUcyTPqWXk$o-Ay- z(Uhlm0zk~D!MwK5lDxyf< zLb(u=2ez77=)#_V05=9Hb{d}dJ%3Cqqcg*Vb6k(OZ+!q4fCzgI=*GZ90FHm?XILei zFmFEFBSM7nXM3EF+BikmLVvrPB5$65(fioY^ZdiZ&4^6(3p0_zMzSF2N8+toKyS9t zV7S1F(+oBZ(P`Qe5Oug@3RwFJ$1l@t)2HEs7mm5p;7sOjM5@A!Mr0gpqe^Q5NJm2Z z_ZZ6yPR@%oVy$p1IDgrnb%S`t&fsw?yU7%w+8ngY6LWZa{>-FbO7Vhq2s1HSh=KB1 zu_5;(%Yk>kkc%kY@2oL!KaYw%xhZ8F?NaGPMGH#z|4U%J(HtU51mTCHHAIP`(mtFn zNUWU8tqL%=PhJ`byl%1J#-nl5p!GSUl+m7N$?&yL9<6vX zX{~Rp7sV_VO*IOg!3fhM5pxC#O0-=C9ZPEjml2*YX{QTlHiZE++TseNjbbm0ZwknS zl4iM%>C`<)Kb9!Ez*wj1Zc2D9LK}#_!WU~o&^kIPjvmj7JhrL~*93mTow-8ycc=v4 z`j=mBw%9^yXiyG=ma0NrA|~wMht?2OxAolA&{JcLFeDcb54%?U`~?&%mL(EN0GyfK z=c5q$lQAl^n|rbbVHYPVmM`Mlrwo4kKvRM=ryamu4+|d0EyvNU9>!O3At8=UM< zP?+~880Am*A0rY=x!E+ka@Rt;Px9{CnNUCa2np*KjA>AWSZLP7jB4k`U7{Yw!RYBw z3OF97W0DK64w8qA=0LVwUjRvx6sUS1yL9#NU~KN8V&TAp^k*f;$|aOlA)*0WU>oK^WXf&zk_C` zsE2`IE=cRtXcncZL&F#Z9YRQn^C8dY%%2!H2L9;d()se{w##P9J-Cshg!_Rshq#f} zyzcY6kwjZyZFNyM!+g*ZEv1%=jx@_&ho&1|(;WnGHtXCGXTr#ZBh{wE)(kIuiRTO( zYP&QiRg0sHae&tjuVU1q*h%($qRf4Yrrv5ooWOXlfQ_y7dW26rxTLI?8Pz|@0yj?nwlsw9}0L5Zfy=ytnH*Yo60 zltR%ly4ZD#A0!6Qbct-F{0YdK@hGKfB0Qjr+@V12R*=meRBcP`4=a2%otzWSAew0jg<8yiU-2sk_GL~#uNZ?8RMKVV> zwmH#w>gNpn40d@d^m<|;EgTb|v}#qrgf`vvGL7Ai<&WFx^@)HF`1^oADYqISMW$L5 z8HdVM*2K!ZQe}YmPeVtC)^AljF$Lq#hzoxG_8ER9sL(6IOsS3T7RoKoL3GIOn6tKw z06Fl*2X(smZR;LgKZ1nf)t*ZHvn zH;0Ffs)KB%LQs26rBTF@$Y+eN3v~s2aq$QM*IDjPEt^@%8ZMcfyLj$gQjy+UQ)Ec6 z2z{ZN0lT9KVxcz7pB>zg5GpEO9KDQbhLM|2VSN9bdZ4E#nmRJ@UITDz5=i*G@+x4- zJxe-P0#Y|s#IN?h8v%R4YyX3_cMK9GYPJR2wr$(CZQHi(?$fs2r)}G|ZQDNW*WZ2j zMoi31#Kc6rfAy!LqAIiY&b@N2mDhvB035cAptkYbawggr@ZYL69Tn9V4LddLNC6AI zkbu;q z*seja^YovLZShkZEut8SBwh7o`iqW)F;92ZYiNnTf-qR$Zz?i>f9 z-7J1hSwSCblXcFGe9_F4tK659Y9Uq)i-fBAPOKW7@WVeR^c){<(XUZpg>j1<>RAPBuF9+**EOJXPw<_UuUOd%CpR(~Fr+tjQ@rxD8uFC;^l6bi z=Ba#!?RP+*G!^$V2l+;g*r4Po%kR{~=o)*lDZlKT|H6#_a|qNeBWRwxK!bh>_BxaF z)}~m$Dl2%iA`>k5x0}`W7f;9Llx3>x_kctSALsVFsLB9pjRgAjfU5pE9P|`s*qOPkGLau*hF76kx5A;{%`}76XEmapukj}S!u(ie2H~y6ngCdZyz!&G zr%UrYjmv_kgmgcVaDf~g@gs20Va5s)g$>Uo_$h#dKV+n?AT?-FbdE>Wvd**Ky6GC_ z`fnlPuH}iQM8*1ZLK41RzB->QwcHxzhLt^y%aTGq?@5M5!(W~#A^hT0&s4lCBr-ZO z6Sb!i!Khn+H_*12ovN5ioSYVA*0%OHpl#9<(NBq7%Ba%tYrfsw=4M9gZA+WO=o~sd zHlQTN>Td96+BeXYg#Y?b|6vDkqPU?`NzgWR3BO&_EX~>c7Ls%*Ki=-T ziUrM0&RAwUuNmVxh{CNZN8wwp07bE!=L z7&1o9Xvz;WL&Rf5W@s3LzTt1j2`C8lbyugKh0jn6|A2K>q<|}}gcxTs7Z8Gc@72%u zu%3^2y~P4jP8BST)swL>-35pGGVO#!6{`i&7b4oGh^Lu#imloeZhEjFaeHL8@%rt% ze;z%Y$-&PapfAl#%W>GcEbnkA5Nj1n0;_TTj%qa}S+Pw0sUZ6ZB;zC|TJ~WPJYFc; zRG|ydYKqWmO;mn&%d_|A32MT2eu4Sb{^{~9Qz`uOiw6E}j{~^I3d#rn?=tH}0EnbG z&`3xd#W*IM(9*_;HHOtBFwkgScgu89?a<#{y350XE~)Fbo88%S`0319|E z)LE7O-+YdW-_7jS`V$cZqS6C@Tg*7#&VeUZ4OnBw$M19wDYs>j?LRfDWBuyN?fKzlGvgLtkI#$AB>AWx zf@&`w_Brj?UF#r%TrriETOsoWoDno>kGk|F#K-ecJ9{mwxsd=-k|pNRf~ z7TKE?&mXkdq>tNkH?FLyTs%DMV!HrMde;3oJ@35ERDn64Fd6Q6ws=p|mul|If(VFQ zK{#P+^-Fqd*JAL)3M_cB!uRM7Z#mj)Ua7b5QEzw>#B7qq@Va#alBdV^$M}V6Iz--A z>j=@}y?Xc!Yu!m*L(C~39NdR;AK<1(4ElfmX6LSkY0=?{#OKN?)qa4F64T@3>J-22 zCP~F3AOP2xNvI$J46)Zol*UEPBht{sb09?%1d?XdFvTC0Im|5Sv{v~7+s?t!eeTNZ zIT#341vec)w`N6K%k(#x{8hpNi95WfKIuN1W?#e093SvUZxs>Ac$z~{QHlP+7N~TT zq~VuRYU8!R*jvwUWD(Mo+3JLxqZIZPoXDzg_q5$U5W{SI)Br$H#N?QvngH}~SvtZ- zS>Af-5vkl_Om3YS6kkBnm`S*x|1jeoQ4gWk0^=$EGCC4_w~~QO+(}4n%50V~es%_! zd&}MM>Q5U0XdyM24a{-jg;s8j#&?q_0#@XXWojX)73X2??mltvxLYQjIQmV&&4Rac zj6(&O_G@)9ClH6x?*-k*;DEa6L9$zgepwAd$hYB@`$c`9LnS>{F;GZK#n4YTY=aJ$ zn5XZ9|96M*0O)@-BEMI%KP#fyv)A(2PwxB95T z+)(jLQU($I2ps$Oj`5oI`u4Zs*wV`&f|v%1aey{OYM2Q*=I%vR{-7CmF1ix6|P`epK_D!d?YJ7($GkM z0^&HD9Ta__6ZymOF-h{{@rO|xbRxPCiAG4PG^l#SSy5l)FI_HY=I-jjH!z3CnPv4z|q5xOZ>hrkepdlod}> z<2mhJ_nG~z$F9+NLb+OHC*LU%H^U`|77)%KDSv-1xu$ zb+Nj?C!k*<@*}mLVf>Sk?P)S+gWMf3H4M$;g4Ec8yEUP#Q6|UNYs=pC&CD?8tS8%R zjcW)?S<@4Q4MdN!V2J)7;d%)U#)i>;$xwocHT;9~{_msJ(PP8*nN@doYPs;y+BHv< z6&c$5rw()=rF_jyvt0|UOJ>aAWi__*e`1^wgQPlNv4r|tEA)7b`KKl&`2fM@WgR<~ zRruAA4a{fj;I@j^(gkZkw51T{m#)sD{Nna1)u3#!KZ-PxC!34O>oTN;`noA89)6pS z58$q`|5d7qzTT>LiRRz1L@`C>qcy{5nT5;A<6?{&^5sMPcw5cYbatOQ+`83bf!-02 zLCKzBS8_JC`%*G>Qb-7*(PFAH418W~EG+-I!JMgVi%w$Dq*q%{vB98K1~C6-Lf@eh zT(3`R*-LbR8q8+&SP)5N=>q&ip$;`u=ai_qBUv@N@*~M%3~1-zLvbnj&&rrZv-DVC zt1B$ztjn9rmG|im@c%?mVJM_zC#wLMJ`Jp@+uQzU1yCwis8dx?2M4HBo1mhlQyi0^ zl~Qd`lA5Gfq8OJ@m5`*Bl%*S=npLg<{hwR@kEGUrztR7n$5(>=KN9l)vJF`4uxLS% zE`t6YBapD7hAI3m&u3;!qqENw+I0I9H|Vam*>i(PRbUYcCKJ6ZYuNtvWU#^2`rTb!&(>BG?s;215u7ziP236^pi;SCefVNzPaNjciihM4zo%jYNuT zg&das=AO3Q2PT+|%NhVWjeycTATj{`Lw4Y~D9g%pA10MbM#--;1M{D4p#MLs2I%bv z>Lp~GDxso?@qy@#o-9N%sHD`U&Hvs8oO8y#OQU?X0#b`AG8o))!Oy9G>W^kNivV=v zj%;WlE19BY&uUL--E=pK+Q;D6za7cxMbrSMpl9}DB6t{m0L^VImI&&hwch(JuF$c` zJ%)}YUagc>6*4n^n2xe}RmhM{1tL>Z)}FprN1%=a`!5 z_6CRSGaL2k-chn1FAD5|KVM{~A6yFlcfJoKG1k~~I*e`jx1cKx@uDg%f zXKBYf$^H2>a)DEFn?)J1{%RRv0%Dy z_E=VQI80+vFg;$I0ZUA*Lw}9DNA3Vd2Z6_wG6-Mgu2Jd13FT!JNrLcl4_RanOIMGn z#Lg+tQGdMvLMu~-;Hrq5U3OIs+&C(drRLBF_EEv@G%z3HZ_@taFHL|LZok1Thzx-D zNC+kt^&)_^0XqRsnQ8WD)jFlxHb@dgwYdiFPc7@aB)Md0HC=hZKvxgw*OM3!xIyKi zzegqY3#5irc!gdmK0*J#)h4cqhW5WM4q?3?>RTf0}_f7A{N69K~!M5Y?0<+>D?w=9rqt)e@Pb; z&0xlg=+d6eDBQZ$FE@{`b#HI3FWG%VP(JjWhr`RVBYm>TLIeOfRD{xRQYoiJQP=p| z;-WVO=|NsxnG~f8GNh_5?g@e**rwvigmeI%X!hDA1e-$(u@CBab{}{J#bi^1)5u&+ zJ;p~mqksABt{RW$_}&lA%hi?zm^vbeMJ*(iEqHjc_3-NI4$-35Yc@>8kBU6lN6G*zuDP%()%ES=aJ9W_hOc8Mi>WM@CX!(h- zFCByEw0`z}G_VaOFW}}P4v(BhRoEG<`FI|9o7Yuhma#Oqf{-&*x zR7039;b(gAa!ULrGA{kMUTzIX~4J$uURmY+Xa5C;f-emuJ9@WF4 zq2wd=yBVcn7q`52kuR{QY@6^L>;@3(g1dsUwaVxoygM;mt=)9)lzBVv$AaN=`8p4G zU;sP|!W=Y#oXA=fy>8ms6Z7#PENh=)J+xBO+2tO;0#&A_-Kgw^6HPfujybB{_s5QwJT=Q43zuS7kBL z^x^?iTz<&j55``kY`P2^naPWHH`Y&3eLew9N*}&jQ#1U-+i)u*4u&8~N7L81@7iU>h@rZ(qKapNL*9s<4Ll-`RoiXbZTT+>p5(g)S9HccWV*KCh~n_(&TlYyA|T ze5Yz0UQ_6b8;s^{B7*LxPG+$$tW=R+rLAR_{U%g z*JQ~jTj(%jd}JPGpBK-?4+n@Zz?hR;_|XZ%N-!<@>yh16J!g!N{~#Z!Z~iTutlJmt zkYeB?%xLPKPP;RL(qXN)f_dy&Q*FfUGyV#mQbExUBh7(?dyeLOrC@)`{@=uzb{g&)-WbABdWAT z#R#`g@Xy{bH{Mm>X{03uW@*4}7zJ*~qYUvtW5#VW>j1KT^8#V*+%&T<#D`%Ruzo+C=1MKrS1<#x0-J} z-rrWY%aiEflIF4{s?6@=*q(Y(AR|(etUY6v#-1_R*YpsjY|i5mC|XXLIP9=c&Q|Z2 zn$#hnKj1UEWd1={0Av0LiV z7@k!j1R1%qi*NTXK=QG%eY2d8hThmW>Gh&fHv50>1{Bt|u-pl0JES!Z!YHHF2|9^DklV=U zFAX|)xQwvKc{IP$Z?Hln8$3N@jd1LE~8Q?LZooOZ#+ap<&lwEx# z6c?!gR}I;!@TJWpMG2RQ*Bmkqjh+dJ*hSm8;RAm}x?iMhH3k!1h(vk4q9j@IO_KPV<5;3C%VCOFqh~N7|wo7q-$9zOKM! zD;Y4nUHoG7zP(MGpU$%VD@J_$V4wUd+e!(mA0dh245B2FN0vNxju_~y*C`BlMlsDM zn3GJYt+*!UhS9gTQ*U)2>~y!^=?Lq;H&1aYs+!Y%I%FwAu_1snmw|g36PFA%ItXZLkA_Z=tYUP$>iZ3macftx1zS<{{n8v#7)s7eBw*TkYMUJMeC#>Yg^)jy{ zYP?mv1A^~Ee#mP6X`g~xudVdso_=)N!2{@Dsli=--H1)Ks6I{__l%z_Sc)()6{nqL zjc?``hh~-jzBrH4)r7HM?b46I`=`SrJ2LR)g)R z-6EdLs#q1@lUy`~%0ze3fsX7?Z)M9qw`HkcTiA=g<0#h~vxhBB^FhM}1=$5`w>u8u z>R3A)sZ=hSlQB~7zG6LLHZ0hSqZ*+E7Xm?x-lsdrVhQbP8H22YK-2ttrR+J{>OA%Q-lEsFq)(0cm=Bx8viXKoy=+6 zf|Kl&Yt|!8T~_i~`kLfdl#BYjeoIN8p(c_S`DUKia(~%q-MYv%TxXf4maOL~P>X+o zUaZz1o5g_8{fJsK1X!fD;YMxCe%_*_9`Q0KoQ(5=MCmk!P!%NDVi{cV=^Z26H*+9> zOouV+!E`&F<=Ep#pE&U7hESXi2-Rjlf`qMz+Ly-{rW|(w;}J+Kkd?-?nfihMwtu7S z)7$Oe<^B2aW?1dM8danr>IrCb;Yo^r-R(DHDf2=)7<9Dsop~s6RF%1dU=Qid!9$Ml zRe{{eH&Zdu;yGb2FJFU}e(yC5fx7*%!ayB(4%fJ?x?i=b+QuSyiJ9^sY0w(N5y~}1 z+rL48F7K}L88Qgq5X|=cPJha%Fx3%&u`Yug+@ z+cA)Vm)aFSYulWWONU8_pAFRbu+%guBEM~*l_D3PXVp$2-7YH`a*!y-ifRG>_r$)K z16qiM@k|ArW%XIu2&IcSI~c+z0pWXJd(;gBEB5(u1eli)%0K!{ki%9UXES9v}Becd~zWaI`eob|5CQ_p5OSro6Es)1FU&F`$7;SHor=IQEr7MgQ}w{ zM=+O0x6`8`8!Pt;{{~_~S3PO*x}9t2#p=CpN96&2q;6%gKyF1&QmW(0DW05HLnmuys zioC6Aly2>x<(jW-CwsdpGo&^D6Ch{%?C5Fl*@!|*=m?=|Fvc?M#ZUpvtk0~zK~F6A zyLoP}9{|^1qs`w$^*dG9DVD&9D7NG-w{EbRFOscp`eTvsf<$Bz4p|mJyJ-`%^7)zr zNG^cHPjK@q$8yh`{r*%f8gOi(nJG6BNKVzP67EKqnZE7HP+kb46f_3gcUBcyzQOj- z+9HRaA6z^-UmP9HzXzLCsWPHV&*qdX#^|yjD?j-K^`veRM`~S0{g}&{^eXzkStKP@ zN3dk`l@@#{+A#hZ>!o<@bq+NBg@jf?HS`Y#c?yv_T1SBRQgWaUYLdHSuKEn_0UqWs|Jn`GsHHcGVxJ0?ChBZ8E1#8Qw9zWAaw>D5C4y57S7yEpW z;zTsz+`M7|b^!EB_Q+>4ywl-}PBm*cbwfOmapmTqbw167!8iTiUHJH)o@lZG7D-pB zi$zh1Pjz#x*a-_uwz`)0CP{2|Yzo^$y7P^+*`psLNT9^9aqj6=&i8HAC&k6!d*amJ zvGD=N9MY>nwxVXFI!sulu8h+-NKv7olprT-pXefe{pSy_iCG<49-r7F>vncb#TwFe zY-8B)JG%wOgE5w`NNKu=N<9N#S2mA(I;|aFaoR!10|m}}qp2%5w6XcvAU7p9(u{(a z=*8|3oT^@??x|u_-j0%g7H>=PJYq-$g{BpkZQ=XFke=ij<2m?1&*8dF2MhSYJC%kM z7bm^eu-$`hb;(ees3pztHYv;*@Z!W*%IK4P!!|Enu+oiHXlt4~aBrDY?FkdQIIcId zTpYfEeG_DV0jJ+){u?Ua)tr0z-@FuM`miN0XAGI9L!}{}19~mymgzfP}tp%G*Kv4!zxjX^Rf6+HccW zQeT#>*b})~Fw2G`0?BqL-5sKRHOQnoPu zhcki>WiBNSO$b8m9})~kI0y8K`#1hm(1mkvojI*@2Y+{_K>kX+b#~EM*^BqklIitTxPd5OR}lazu`Z=*46)Z9=gVbSF|lb_Cz+y1vp|UHF7Fk zP$RQuB>}6nBLA`&HqYAT%yRo@%uIFV(w9!&4y(Tv9Ty}Nz6cfYloeq^fd>*&vsh?5E+LhM0|#LnppQ2w@5sazIsm{sihBe>~7XhU7-jefT? zO-eCAxnd?QlqfAc?!LROraU^=;7xpabm}sD`($b;B|HY^F-$YJ=1JYI|CR&D-YdcM zR|9s20nX)lZt2qOLzX{JKeF<9dHZeq`dN0~Jj&G4sRnk-z19~$BRC%9w{bw?}UdfYDDIc^@S1d%O>hIYkXXdTd?`ZBOF;iQrGC@yq;m9t8 z$UxA%bnuC0r(C+;Z?CcQ?C;p@W6QDxzt!e>sNr*73*XUA$F?fVq`*U0rcbK9y5f}_*r{zmqYDF z$ed6e_4d}SFccO(M}>%WnT{?-F>UxPd<_o1WckFvd|2q7`ip8WH;(P3dH#ac5IAsL zaZB!24u=@%Xb(o~G?N4ziq?`P5adODhp+3+U*yvkKysUZj({7&&K|a#3vvHN#%=M^BIJdjpGZD;n%R>MmOX zZm#X#xQ^b-<*(dN;2ag*-$g)}Vuq}75w}TiM~QKB_bjkUown;a6wl9bI`fDpAt-5vR8h4fSAfiBV=XNxlh(>L zH9(GwjFbEms|vlXxMO6&#S9mcrm1s5>p*xD?xVLILUxAGNm=RtJbB#)GIPM!93??yzWAE^>;xF^G_vyF2~M7oS2|c zH%!MwGWUy#cLyP`%$@bYN4QuwLb98&GM0rvp;jFN>K+`yzAqO>gRP*e#NWZg3p^40 z3SI!ACx$Su3kbOn|3i00=ueF>bxXqo@QF`jNsq#V`wiUPD?`qoLYKnZo7mP>hsIOg z%XDa7maZdV>SZxaDpGK^@npdf=bvy^HOpNfGv7hknI2v%`T1zcAB3DKvu$h?a68*y zadn_|88a%GcWG8D@5;(W>27?lXiWVtvkFD@^%d3Lp>8ium$q?WGgq52LPO-?y^N-12d78bv&c3-HTM7yYivP zMHraq^qqJ%dHIJwy4xBXBS{;sCY=1QeoM{vKcMe-uD)T(UoxV(Zi0Tb2^IzLB!r3< zNdm;Ot)oPZBDcDnn&qXR*z2$r%cPQ|yR?$_*HgUR<89O2lUQWIbVIl?)Wjr(*kdAa zMvnV;xM5`kPB_3Ae)L7j1RsOP!^M9#PHuia-!IQu2G495#c#S}PJ8|0V#bLmIMM?a zyN*zfZPyT!4BMYCG54@J`@bW#cqmH*jverXdLF8a9R-EIYAHu>V+qGNNe;{1-a&{H zoP|uc+>L}B!%&2T@}*p73WJvvy_jhLL!+T0#99Y0vu+RPm%qO5UZ}D1?QU1&2*&Vx zX@Uu`z%M?1=Fd^L{f7T33Ckldjn~Um{%sBsmN`+yJi(z?ASc5Nw-+g);x89KYSvQa$YfNRl{z3- zuhm-6ovKNUw9!?3ySxQ!64p_77nRMzJ^`<+*4KBi=-)O6H}mD zGm8`$*&LYcjDO+)gX|u||ttve_*Um=YWdBmyKE zM&`vY=S)T*6l5Atix8y%RFZZ&@mvRObDn3ot!w|7JlMHKo-?0)sK z4>-9WvhS7Hvj<|vk=uQe3=I;Q|LnNr(iq9adpG{X%w-rOanp$%#;yA#Zu^I$+3$GW zlHKm~dj6c+`)@rgZ&qsZI2B@$w~{NKy}fE46#1dxv0-7ZqmSyyxVWc>mh+EpTu{J)`31}h z(Q8SDx5Z7;fn2qsMAJw{o-WK*JBBD5g!wya&_nA=;W;G^X~y#MOIWw`93ErP`y?iI z*glSIMND6Ew2zMD&^-N2U$l%(y(%5zSr89$!6Nhfa1TP;x?Yekp*N`-;ALrcjZ!`n z*}5tdJ4ts^8+Sg?m(wZfRa6Gz`_k^_Bf;k?D-vN%$qx}bm<{m#A zrEp-oiL>&gkN*dz`^J?V z9nQOD33FZ-A6-JQ3{{mBxH?g39k#=##nC2cmQ4Nfu~=N@5)YtZI;ddQAB-L`gZq;i zFji6@*cTY4Iq%5O7^` ze@LkeVuO8x6l@`rkrbHa>Q3qRkl>OH5J3F;)NR&50?_T^W$%+UBM?-_=`Vnc2}lK< ze$B3dHHna*;WoxZ(BwOcq|(G@dzJLyHclV2+iAaHm<*zY3=iL^M!@TAZi!iKCt4kT(#(2Q!1!l*d{r#c1b;z z2~>(WlB=f0@R`|H=*+0ZxJ7mK%T+JmURZb+_AP{OpX{ zdNqkbm1|aGq^|;2+(eO@L(0uAEHBDPz+}49EAH~}y-L~%5fMx^A_I#RmY+GNiST2f zTxs@9Eu*Yf^G7shA65n!2siB+Gww|(o85p;7K=uG=j|U0+!37%@<{KDQ)>E&yb=xu zSmIh#(`K-ox``GoxApWi``2xeSbT+`ZT8fuh$f>5aRrd4mmF%XVHu<)_XW}z?IGtK zUf5-fs8gWtC)k=RTv^Cl#@O^N#6_}!Kj|aXk)thfY$Q;hcIe?4s8qSn*w&ImiRqan zaycsINXhiX8Pv@m8?zx?0>6;0BNPu_J$bs=I`M1v~=^FRKO|jCHB1VgL{{D`-V}RhsF&q#Vufb4K4|d)z z9DgfvwQIKAK|AZ^DIv%7pw8X*p`IV-m?w2QZJlI^ZFw!PIe+APBPCNDm>JIAkNw`$cPvZ3N z!^NSrWVl(?m-?b_2(S#JP-^jdjwijbi)+;ky{6_-hB%1lUT~B9uvO7^hv3iU_}l5? zH)YlY?gTBr!x)ppU^68jjL(rfJfX>S$u1KM6Oj?}X$yWiOF-(0ON2+6sfRfVaf3wWs6~st&EYcMDN4vAo9%bhA0%7pZO&baH6?Zm&bAGKfFhPAtX`@ z@RQK*S0x)LkXo&7K&I*Go89r2x^fI~6VqUqsLxZ;uksZ8(c2 zif*!h)|Z?i%2Q@_0})wgt&(xnzREaW8n;T%B4k0T>8*=eA*52>_AXJ%`&TEi)zmmq zC)+3q%8gThHp(8H0*AsF15#6Kw(BG2SLMK?52Jh*80hpUEd>+EcWmm9RvD{yCn+b4 z5B-Z(W#j$}B&POR6OJ!yx>mDdKW9^_+ z-;3Fu;_1zh-@H%&6%=ETwF*+xSpr4lcC3K#_rSIbV&X*7BTdiWcK4Jy{)YB$_f6X^ zc7JVs#ozT0@2tI_6@HG^6utOIP5o#@e<^}ewFMG*^j$w&YCK`_d6@EqzK$*{RXUxh z+_CB*qL&OQDqE0Uc!RI*1p5tE=2_A4F9#w>X@sJKkRJ8$2;0)H0GYc30M`O@8Y<_FpQ_Xf;RnIR)CU{Rgm>UoJHrA+!l-B zXhaejW}DyM&p|9EWFOw4NOfu$B(Df*byg?5VtsFSw>j^RQq z)&r&SjFcXM)4n`A9>8B8fsSj>(rUtWHtNPuS@7R=cX^;SO^V9 zcoE|owf*-qWjVkZZE&G0PclS{@^5vrl|h%sPX|v34<(8x+<7y4!zw1zR!)o~##1!6 zBn^5v#=i^G_Dr1ATf4HtGTh+40hhIG?KdP4zGrIJ)Kt=!B;TDDboUpyr6jRa$P^`O z)dk@&8v|60we`3GRo`w~?ORqaHGv?x&L0XOKbRh;rkEQWrTu@1wM3dXP&#{%jZG3r zUbalKtB7Uf^w>6=`!tP^IR8_wo;xMZ?1omN*P7Lm*Y0+CJZ{qW#txmGIU&MsF$~<7 zVS_Q-=Z?r7gbH@5NsqsHgI^w#Jc*xXEYN%0C`w$u2p5IxnSl1fq@l^yWV{m>MC`lID=NP?kwaiHoRxQ)*t_N?)?gm$?QaoZl5V9X9fk+>vF z^SDE{OU+WB=hbjGwy*69$rZS?9VoSpp;PpPKi2f|@E#t6EB-95`!;eWbG-^-|992$#M5(uYQ{sNEvY9H z7aPzY`W%veM}>?VrU_0JKor97QCBzyCnF;v@JzGhaxPJjgJ66)9DZ?SM=5ztQke}h z9Q=3mTdpt1*8?AiWzJrpV5NmwC7fGr?_Oq%z7Pf1youzk#+Sv~)9p&;ivZd6{_7Q{ z;J9mYY9@G?9)Cag`Q31m#~?5=>>c>r0dj4E36Ankak$%YVNT)c@CO|#4Xv95En3Ct z>cYapKL+CiE^dCyA5L=Zg~ow>Wlh`k*+f#@fJ)X7FlQ{$kD~A|s(5v_eTwszI04`m zH>7O{8wP<5u*HL7DLdLJy@%slUE*7E;MAyQ`F|F4wG9M=W|*mF+T^XePDyNT!X)Tr zxC0gJfO8|Q>gwGvA5Ug%(V!Vqmy8lL9xZBPb&#ib((Uel*OEf4c0EDZ%Awz zDCFp!X{1fmaB8VvOvgWq^rC3l1n0cA%9?g3HWX$YBI}qu=qft=yN*rqB{Up6(rulT zI&PjsilUZE6vf&33$Tl2-;&yepb2{caYg14nTwblViA-uXge%tP^g&>ru_7T=^4p$ zVR|JKhsB13HnC|OTL7s^JdJAY76HEnuXY3HBhcHS;IZT{;QIA)e^s%{kF8@m+|(VX z0ax44u|wAd@}V;IRe$7FZ8Yi!G%-jfOqB(BNrVXav4+7?awtyvcdpaTIre5pa zk5W4~u5IO|=ImOS{`@8#SX{M;-hhA6Hw3LjYC6GzcU273GOo&o8eN`OB9DkBB7!F< znC{^2D9oOy@7tt^3so%KEEl*`t3plzk0*S--z@71i=1`mG!7pu$B^Q>E#u^PZjizw zHzV<9zdaom1hRSoPmr(q?cg?Ar76?s0QN7F4g%tcIvFXl58@+dc&dEBY209It_9%x z4typ?1xR=u(1(KVXyzNi^Vdu88ts*tpPEQ2yTsDKs((GqUKC9|>!hu;LurPidQ#xw z??4%2p=?eyd;AJ=4>!f0iUU^8shBB^r=z1H8kC0>K4_fXI1=gDIaJvl8vD0cZFB&4j> zO`JMxJ#CYS{x@c6HUR#X^jLkJi>s%rHFFGD@@A?gmGNGn!M$axcuE~?1R znEbYEwl__IcKxUupF%-tPF&pfQAh20B&*JxvrhQGdYQj-)@Xyyg zM90tL0s}gHPL3T76PZ$8`yi_6N!c^mf}P^C=vR+vPZRYjpRAU*6Xj%N#{%kC{4kOQ ztpl57{|$uOJpwCYeu+jci0mq}Q`F*xC1Gr>Ln-g1dCA;WsLn`uAyAnX-zJ{TR_>om zHE<}H?9XCgx+ozzZ{uGg>q4+YQ1}A)@g|*J3UzX4-bFK-Q%*n!1Sj3*I8JPuVneFV z!L&$f`lgG{oFoW`2#h6Nc@qqFER!eAks995WAswoVpz4G?KD;-*sa{Z+l48>EDkOT z%(j$JfSzmO??mvfhhx4$d(FXliud(iY<+(7fUMap|081XCT64)%*&%g4n#=hNTw1| z-%X!@*^enfKz~fekY2e?*BzqCpKZcIa<=ka0B(}@BI!pk4k%)28?`jkg=kr(uHaX? z_E@O>BY%wXx^ku)EB9u9z!cU0GKT$yI2|RTNt8&r(wpX$7)PR3B5cDOERhTovhI(6|@#aM&C8_FS_72@yLl) zf=D3lSB;kF8|dGPJd^QW&HaBm-CK5S6T7pKqkR89+mLY2v}R=(}$xea0`>$w|cPSR9eo5!p}?~L;%^3 zMltBKn-}eu{)#I_C&wpWHlZ0pVN>^8d?k*|&LvY8XEQL==A&#Gh9yN2lF3taqr*>` zGmMH9mLscU+0EYG*53FH?fUDv)pn}8^Pcqu%YT9WBeqUvMYf))Zf`0vpQg9|)_9!G z9Pqc{{`Ys*qCv$()v2vOUPCI#ypEr&st-YHZq`{%jMM7HIg5RanU_Upt2jtg!rL#p z1fdFtH?+_Ja>NhGm5@_}(hv&0U{SOtS*n;mOe9gHkmVfbNcho_bum{cwM9$ii!2(B zE@2*#6;KeCU@Iq(K#4lZqhyZD2$*eW3s9(Zu^d59Kj#j(70z(>3iozqlgla8KJC}KOv9V%#ur}hmo({7zXILKidH)LN5*37rEseM2y zcv(a<#aO{}=d20SoImpO;UEftgF_=vJJA0hd*smMw98K&Lz>1&tB}o&fSV-k4>R)B zBSdtjH=;(C<0$IwRf#83q;MAMb*<4kqG93#Q_Ye+O)_-v63|H z28N5pneV&e!jOR&zgrg}8u>+he|PMp16T)pr2*B@1pGoPs99ET)%nwpt&rU|2oX6JJZNSPxr$)7?2 zE9~9_F1}1#qXXoVo*09UEcLkI zZq8#^jYfiCkftJKO5`ovVf}Ven&&}BQg!xWD=1*q*d8?Z72rP{&BAAE6kD?YI{zjz zP!)9`?+P=#AA1llM$pWP7MzFWq6mgF6$+hrPBR?N#= zA0FswQjuS}mV?%BBY@d)Qkl}CCW&Q=B^ie|U9dFZs3ytf%kxkn`#}#RUS7Ts--D_s zK07PqwXpZZx~X@@-c~y}N4P0_8(gER1i!RY0&l*GgU>ah;;Pi%cG~#%+>BM1U&d3n z7IIp2(jE$`f4#9N$mttP3S289h+;DzgQk|6V@o_gdb01t{B&!a+i$X={M7UYoo7n{ z`H;v4R=Z`>rL6*pCXP@ucP9%6vCfl+YYU^H2ZzW-EiNRx?Lp?#;|KN68M%`6X6P5q7^mt z>W+_eC>fPAYRfmpWJNwhs(XO|F&VFhU8ygq*Jed_*BTIJnfTt4gT96z! zAH;;BP)0)ULJ93)1x=_d*c)>gE>E5mgKnR+yjIX}LM_Y~NQC2PxovX1xJ=T=6E>Bsd;Gd1&wIc?Z8a+WMwd4H5&2wS~_`bng`7!aP;Fwu(-jJH!5%yP6C99 zg>a#MYpojuR&|ChnOfyzKuBt@4 ziie+bG$o+axL>fG=!zr~7EzL6&6)z7SYn(&t`=TRg_UC?;UkVSu>zG-pw%+`$t7ML zS+-R8VqI9EjY|0-2!u2uB(w6r0DVA$ztu(=pkXH3VbG%FTx9js_b^`;Y)c}A8hzfJ zr`2GX!!9386OGlpsQGT64J}3W)d(U>K?o7k@n(4)-j4&+?Mgsbkd<;Y4 z5Y92%z4t<-KIufjsinNOWB6)W4cSJoSqItPN5 zIjIQha`8t~-=6cXtY+Ga)y>Ei6}1y#k3n`$9!t3~9*ye_(lBYIUM_YU08tz3i6l<`d zBXH!kI-*l50aKDPuaP_vK3vEm$;FM+v#ufEthd~+LS#X2B3Qi*#ey>7_2!N+lj5`Y zLS-l`0%sL2Q4W^On#7<~NF6s?JGCn3p$)m()`QY{#i?PgSjCtjY6qVl$kCPYs<$s$ zpFTr~^as6T!TA|L>e_ayn567HKR=0{3tjCdcFm6=YJs)3C010C2&||ewd;Olo;`V@ zRGyFj(oE@yPhX-+Kz=SnODKNg=`Mh5QUVUpP3hg>uQ}KU$9%o5TyR~xUX)SRb8vG>K5W0ib(b$)x|_ymV!eUZL9C2?u!ns&TT zRkm3zz!ibYmyN~?D-^#khUi81SA7&pZ_&4{=MxcKMT*Evoid*VrM;pX{sFaDZI9wM z5dO}u@UFNckzl*mtJEI5u-X^4MXJOK?pmEng+k(h)nG@lv+TCwzjtgWBr(qNa&5jO zaXfF&JTv25-^9zf(Fm{~c?m`i7YXsjOuWXJ_dd8+4eG5hk3Aow*)Qp8o-Xd#%jNgs z$N9%km!E$6;m4_7ojxt&iO*u3Oz#=xEW%ffM#^iY9!ZeA=3C*uT6r|c?_<0m;K=LM z;TncCPQ`RMdQ-&aYDgAo;t9fN_3b}X^yvA5?TKabx(XSU(X3)dF%{dm1}{F2mC|ua z1zF+k!ifqsP!mWqU8v_1^^8&>73tfM3Y>&Mk|UKpk_huxF7T@Kc)E@!Pb{@`m{MPY zggnl1B8)|!0hY#E2x?~d1$f7eFLj=HA)fLWa1@~-0pMm)_<)u`55w``{_|-3bv7A1 z-HvbJ@v0FL>O~03j&r05^DFZLoHYJvAh~pJK<4XGw^R6}$u zL1cADf|OzBQAUaHMdWYw#i{dOc~j^mA#%2;xFwI|m0*!3G&fOz{5ug#mX=gI3i2)1 zIRJ*OO~oorqX_kSJu?I7{d;s~2iWw_l2v?VvDM}M`*Y~=9FItKwU5yYn@%!-^lr<(()|Dy33=w>OH-% z212?q z3^t*$a~a^sI-3c^Zmn1~R9i!7BAdB)>$-(sqi<763e>hb_<$P}uTkkJ2s`e0^@)@tCrL0%QjIgxvpiVREAThzrcA{aavM&%kA{B?k|pXWjJyQqixL}kkOm~ z3l--|+{#WNPD{h7$b`UFwwed4tU$`lRb|bw9U|d1 z!4QpYZb@D4wl}+nG-cCdC4?Lf6NY`UN=mj`=uB2lF7ehe%o?Daq&W_`8@q?d%?`kN z?}0%p^!LPkKyqckm#R~!f56;f)n?TXiwp)724!8Qz8 zqHUovDUeheV@QAdj-p;jN_L%f`4Wph-aU8s+>8AFEL&&2UQD7i5z7dlUN9;^&VLd%hk@fEfu3m-_II(@EhV5p-((<3UC94ED@k|}}YtFug3&_j(F(O$J zkDHK-gs$p?y&A##l2Mi3O~xpp64IR#u@#bRjE`DJZB0Kea~dfFDC9ic&LfuQ8`}BR z7qS(dUu?q6IgeOQrE_*@G*(0cip_@6c^1mGqb1{H1qNlbMxj6y=<-13&Ea7-@4_XS zE+H;+ZH!8Ynd{rWyCUNWmCI+`$OYqWM1U!CPO=o*Mf;nSlvx0HLZS~>x8sZZ>)XHQ zcjKG$+jICh>@5=-rU-hG5U4C|9mL*0Jp_M%@*$L!icjN@AFglbplN<{et$o{{fI`W z{|)1_lff&0I_;xDml-(iKM)!-zF%hW>RW$knQfSdJQeM`l7*&>(Z7%vn$66VvI-P= z!migsGGD+|BNy|+X`V+CcjE|l+79;G^9y@A!0OMF>qH!#Hi{vtEq4t;!;q3}`D}<< zc9n34<4>LL7lQce8hHr|kkwp~7Zs0zu3qG+DieX?95fu{v0TD%_>k}bEf`C|sASnS zy$q8y=fp=R=z%4%8-$dtR)hmaO1_ri`>Qy@ZMOr3${)Iyk508|bsLb}$&gE2f;~Eg zO1jtHf;T=!os$&0$OlEE?2--NB!B z+%{k0gm;H+Qq&i4*893nBuQcBlDL380|p9`E>BK?FFVt|n|tcYg&JPC7-3i&^34mv z9`zOF?Ne;sDB_^%x1+3&P9shr0XkR)TE~u|aLM3?Vx&f=2I0g@XbJxYN<&qcdcG(} zTkU)$Fxx@gI{COnU1>U4^Fbxm;fkBd|I;gzR&l<`w1X|&mhw)xo*slip_*qfgX2%t zDV_`l19OqW6;t0p*J^#|`mDrehT&w1FwN5xy%>!Q+bu7{auWwoxaq&G{T#PA!wA*BgT&T8MCRvrF zF<&m7uZ2~`%DZ{I$i`LhNfCo<<0 zP3rdwTtg1p>GK2BEx0EdU8Bz>oL$mocUWBA9dduZ zz~vh=?!$JK#)vYBGLRv#%0j>a4tn{Mcsc01`v%nbabQ{=w#veNL1~k`T$f4nV@fxj z3s>xg@N>xN^|D&SThXgkx)ZdOht|&xjqU%?d)ObyPPTVSgbyQn*7HI~FdowXR|!JuWzCMJ;rNhL7~ z|Mwm#$);sX@fsAEznG%#j?djacRXFbix+XNHluz>Bt=+h$$UN0%b2Rpxpz_HB%&%N zK1Gv{NiziN3>F)S$5s!QkQ#t-1xDp|~sMQixs&&O?a+!;c z-^X#te4?4)?ivw=te~@Zak$k1j}tu}3@cT3;NLH#cqCKM??3r8wwUgSRE*Ep86zthk#(Z;pgBn;E#%j~3o1FoqIAk(R_AmP4E zf-e%T3wsfbgj`PMR8L|`{+%R>%!_)C%-+|`mqN3sxG&+jSz)itF%r$W$kMX!4Cu;h^9MKLUrQu$`u_9QJsnHy&x1sqq@>W>U)dZed{C@)nhlq zRw|joL5@QcA5x%Y;%i(9tC>OZteMmw^Q-FxQ^#E^D{%UUwgcUORg3=lp5cr*GKYVTiNCWoNL_|umgh!$m z%0q4RAlR%d635*~$utc)-i^U=7ieh8Z;i>S0^V!JjpOH=T^1nh7zM@8R8ECrdWk*M z?Vv)rwA-KipL)a5;Qp`4Z~fk%zk3e40zyTt@3>)mQ^Ubh;!4JD+__6Xy)

U273ZR0GyMcgHj8t7V(8J(d(iU=(5fX$01tPl;MfDf}*R4&Mc zOlcrRj8gK_69@L3O?oQ3 zPvRSL;C0^tid7`($N0Cm-qFwK?@Pu5(H;JAd1@E`e_Cl231NGHdzd|RdaisEgz7C}Y za097>c?o+>lqJC{iok8~z(k^e?mvPE*`?g)GhBV^28=Vcz~g{G2l!8hzc9VXnReJ2 z&Oox-9?bGHGRdhBA*u&poX)CEZ^b9m-ko{e3!vq^Hq`lPeNOqEpZA!YsaewrplK~p zdXvL#dIT3LSW+j35PRSmVvwhirg3T3U&7_|lBsaR7f}S?4jVVp?sQ|Pd}Jqlpz}#9 zxalEmJ__>anb^=e+PahkADvZ{k)fAbx}sf;=G;>dU3v$3$Fw-+9|&cuPc# zeS(%B((Ni;|H_}f`yu>s_2Vz!{qpmFep;B-#dH(TeIAo!am|_F5rNx2IdkHZ3=Qt>$0{@%Tb@Mud2k2^P|InrPn<7ASjBS$~1u z;S-hDJa~yWV-}}!F`gV%ab>;)xHui|OAL*$FL|z0ZCqMLH(N5mzk*i zRNNS|M;y^$kgU@!VRHE7lla-7&G*}oGnKM!%o(Uz`Ofqgvy@4?C0}YVs|NLNj`7Pa zOtEH_3wOqp+vTaw`Ya*N~IIu*-+CWni=gi5@MNbOA%8XS_v znn-^ayg)(331u=g8a{+lqMPw-czr*a{kfbEr-Ru59^bY?%5a2$$%R0JAebg3q)!=n zAi9ksqEc7zNf+5<)%t%6!5_87A&03q0OAqL9KdvN|6w_py+;?Q9YYvJ+bK00j>dm1 zC)0Z^rvW(yClB}2hx;YCl?(G!LY4F&=uU;vWOhBwWka6$#3?qvpG=pNyX9ywzI~V# z3PSR@$5}7LG)fb<>h8&7M&UV)qT?ySg^`=sohUfUwUrO zx<|d~HUjLP0;;1ynaWjv&g6!Q-etKvMCBmLmS6>ol68dI61U(f;e&8mq7|4**b&Un z&X58nk094Wu|_6u!Py(y+efxbbb-|ob@+6w;urC?P$h$l!C_WL=H zbgOUs{qY=@XM;aM{~mTyn|q?8Hi;7s8-VG~-(dK9gV~xW3cjHU(b4Vq{e(bSn-=4B zx~SMaTgvt>*a%5k`K8sci)Y}}~IZXn7}+RRWO z3Ua?xI(8fCZ*=Sp01^NJ1qAIVq;N%0kZxmQ@T^iA14J)i3i2Jkqrt=Ny}@!f z`0udOh`Q2KKq!r(qiKTk$0dq5{{(mvgpq#;9S%+B82gYX;-`{$;|a7CuBZp1P-j`n zdzS$DmNMdXO6qeTMZNfuC!e4KigiyJw@4EPA|OdK8QCV!4C!3tvl68{5R@OTuZQz_r7E(yz-~%qP)u#W5dnvR z3AA%iDCWQJu9Qa?GtQu8M)ZF~<9Do$Xwf2RBnZ`0c$OVb+lzN0rMlLK@BmS zX78(mL85o5nGXuC)bChYrLSeAKNnBNFAYq&Jr*C~+MrL0*HB!T8P-)p71UT}AYVPD zK#d<2Lv_Ha{qHpS)NhJFdzdLU3AD14;?IGLBuSDq)(ye*q`Wn|qTmQ>Lbm+T5rk@L zg#-rkYZ#RyO%3uj%U1&sN`nb&_(BC?@450JSp+lfV2 z4O2~11aSmYT#$_%;#~DpD6~|$tfw84VCcXUF0|P!(nSGBd!E8;u_-1+5?r+*E~jP; zTgX;`({y&MyC?KQC|evr<;!B?LZ*g;=G_|(XikkPGn(;}tyhGyo89J)Q(dj+ zViet@M$^ac5LxFjU&tVv<}y((d1dFy?ie$&hU`E?SJYfT-(BZz$Pog2+umgazm+cP zs@-yUuQv#nbY+yPR(luOPHCLrK;E6QhI8^XS6EYba_0ch++l0GH?-L+XP?dQmzgr; zE+fQ_J0P`*Scl{e2290Oc`Og|)NZ4Ti;IK3uJmXM&>FjZRpG>NR0q9=Xy{UR&m6_4 zJTGLm?r?G0j5RJ1mc`U?Yc*lxY`Bf(-h}NIjzckTHBBh>O>K0i%~fZGjV#^jM^6Za zWzW%XsTiyTk6^+tomI@NA5;d_Ab#%pya=mCx;kMLC1Jy9Zm3P4x{GJz5<@6KE$N}6^L8wIu4z@96)d>l74S$^q1_Nm1ZJB)bO@#(1R{Q6W>%;(m>wORj9 z&VoRg!-+tM`ld`jk@v+W{V ze`3!+{&n@!;-{Z~{P`b$`}@5-y}#PTGoQsYxgW47Wg&&%+pQNZPmYeVwDI<3uuOLK zE9KSq-n^#k08H7vi$k;u)>-25fJMYhiF!~ue*@p)bHE2|N#Af~6vY|8A5CApV)A>b z;Qit1zJ<}H=X3UoTVF^B&Lg2h&5e9G@vk0ygHuvK=4E!ROF0$=*r=7i%t4a|3KUehoIZeOE z&S}Px(Ig8)`SFc>E`yM!yOh(d_VN8Zt~|bxw^v!@EAOW?;kCmRT$$5nz>2i?B-qAb zQMKi1H20Fk+trSnRP!;~>-oO5Z zoDnM(EUc4e!kaHw!<-shhmm$Lom`H7nO}~^!};{;25EhP2uAA3bgnKa+K6?G8#QkgmrLDzzF04s1pW9|d+lMIEeuqMiP)J4> ze_DJKq@7+V8JxQ3FpaRH0MZ)VrI9zOfj3|F**Bx-plm9Ru^r+XTXD_Igz_wjNJO8E z)V3AKy6C8^ZWC*;q5jvD@C_yF;E_f|J|ZiY5KkTXI+J7anTGKy3khdr<2_R1AvC1H zdcz6mzF|+~>ho1c_8syRgdsfUG>Jhwg8m|qDD-fbSA{Zh+Wz`{S|W)Q%=!gaXb3IAE~xqBZpvA-xC^ks>?S^%DxUDBF^)TceJo zEj3sHfB05gr*qwZ^~blvdFipjsOqeT#c)i1!)PJSqG2RhK^kO$oQoY<(v_Em9Bnn# ztm|G3FZ;LS8>!2r|Lf3c)Vcz1Km>i$sPtqy8G=a{j6{r+kg=~SE&YAfE$6{`sif#L z^)pWG(Ui0ThV=wqsG~%{Iz3Q%97NQ1$|^SpNF4sglCMFuF3xRauHgf8KL7#Q26dJ# zl}=|;C=fJeztib?=?q$6v^H?Se0r5jp6eQ`!G;Aswd;ARt1g_3yd}s%`6dB_98v60d#Cc1afwE zR@cbUoK=^24}%6R*?g-9v*O%NFNEJJQUau=cCNAjq8xI|nO^u58&)B-U03g5XlzSu z0s8^}b8v88%&z?qG$h!${BBMy6;@l)IOZ_bnu-V%`T+rOABQkrmZe9_7^#Z<1Knhb zW|dK%&|btM$o(+*k4n#yYA;PyK4{yIT~YkJBEuE!$rPHDgZf&B(ZQ%5n;e$%?_A*0 zc@a8I+r43zw2}g%K%GYxENj8l!0>|kpF-w;t@R;|LIad#UdE;M#w%)@0wX_DZy92D ztf4L1qa#5>BgI_#!8Q#-GJgb{dg^G3;GLxz@1;jIS82&ZXb2-sBiDYkL&j#lMuvxG zLlKQ~>M(>)coj?B$HgFF4lR5-n zUvkk#m-way=5G>#K9Bj>n!<#74D+3s#uK{rf{0`hXBlQ1&UDuBIc6!`R`gB6k*F$K zuv}0un683npk8F0KqY#}4M%cVw^a3xT5*4037;f}J3u0fw zbm#kGaHGeuP>E98gmnq`wk*LJ9cIO?Kp3byN?q%ro}nQWKcd;4b-DqzXB*Dr)a`cH zph~tl_Sv?(5w9w`@h0x1Vxj3SLbm8`J!q&&cgg&;TiuSf%Rdz%Qoig#8W5-&Ilbmj zb5Eakd#LvxtbuS`lLD1Uqx=VB?=hxy1wL|^GXU8kxn6Jc0&vfXHDR?0KNRAQE#O(K zFH!}OmsR3Z@EVT5y^mm9{@JR++$&ghfFBH)cnIhp;+De1Tn1d`P z;?&>G!!?ykDfNmn;*Xkq?@F1SUXTv( zjd||mJW`GxBcxJp6vPG5!^z9aT;5Iw_%iMPr_ZH|#=$DVax=T>Pj23(628aw5cCi9 zlA0XH@y`I3W>}3a;`+-Lvq!W?+f@sTKGR^o^fkb-Vn>GY#-th zw2#w&XEa6DJ$!cBExuwWu}*1{u%x&0VD3Kc$}iT7>+RkKm)A`!mZI8@+pyy1Jk)ZC zzo67L&i39r5e!;?k+Lx3YIBV9B`}ujpLm6LZ2FTEoJ#64xbp98xDWpz#u{Ct{S~zO zJonGHqyGV=kWov+FcgK~^DFM#Ht=COLDxE@7Db_SgVcv%rKDN91=2JmDOhCx-E_4Z z#lCr8E}VS#o|F6Vs16FD9ixJ122*1-r#7>VVrF$c^N}PaGm20K*=t?q_5P{)n%@+Q ze6gG_?{DwYPE9ulg=wXjPQyx?N-)>UlE|HpP{Ab;;2ewr+?07G61N|HBXFhaTyP51 zO<4$|0OKs#Zk}NUlhee+|BJo6$6@lej?ZLKOKN!~$KY@s<0Oca%i2~Z(vHDFyghS7 zHls)sxz`WFMB#atz*o>IgKWJx$k2u5R{bo6$Bl-cQR%=h=I28TfU*SQFl1h+aXI9#sLP1^H4wX8zOJUJ! zilQ{VX)PQ(vK^pW^}o+`(np@4VJjY~bD!s)WBcqPT1Jh=9DL?UAhyzy`r6g22vqyG z=4pd_A*jgnfw)(3IExp5iI*-+hs>@^*27()>FcKWN z-nm}^Gm!R$3_Yzurr+NgY*UN*JBr@yqa3GC8K{-g5N00J7(vYa1hD{LxbQ-iR)sfp zX}kcY3()@a{1+TlBq`TH>fvL5nsf&@z02Ft&A;xXH}2eY@bR<}Q0_5;+*XPVIMthJ z5*kJfLTqX`;KqLqf**|gJVdd*8>y&TS7} zTo+4oUE&byljE2ll?cV5+;>oYj*i%y@w&Z!=Wdu(koPWl?gv8-%`7IHA?0lHY(m2# zy%Otq#;BhehnrL-V_)0VS&rU}+)N^;NSn{0UQ%^@z7K)@mCDW<7UH*SYF}4&fGo?& z*lv~vt<6cLklX7&k?L4B z6-Kz&4A)HY9+)pe_+T}&*;0EW3d6`W9g;>;c!Cu8Lfwe9ZNMY#c023d%%D>0!%<#xY;fy`KtVq1oAU0CxR6Qrdyv!M$ zvnsLA_jnf=XvpG`ILOf6&G+_~rb;}9o85dx(gGaP!E5JA#6-oLhi8v#q6M+ci76ps z=JHAPMTrj6z+(yubT1&w9Ax|w^`>gEGtYxD4w8ZK5GKl zQ)v*%`H*tBI0x%mRKcI@+LVs9JZGMTDy+n$9;;JUWgMTM52s?lnh z_lbHM?zh5`jji04d zU2obj6n)RHxT`vmNHBHlq^)Fh>L~4^O{If%eL;$heG{xDc4RwzXyU)mNf?B5R7~>& z5OdDqoO5kHe9UH<<1nNNQ3$R!ic=GsRff9v+VdSjQq&ot2;t{Eo#yc;xjcIlolnm% z&Mw}+eK)CgC-<{#Ol5{@(w9QZ1nqm862W}O$@M|@4QFb#r350&%_JE5B_$AZl5lpV z;yguR21|;W;Zpd|36583Bt;>aW>Q$%R(#8!jB;UkivKIPaa+6&YdTRbXz>Je#<&8a zK@h{|7?#fsOvDomamlsOjc*RwArVeuG<3oKfuJ+Rg^7G8;({b#hpjdAXGFwErU_ni zh15v3a(+2rmqpKsL7+CNfyd$KOK^7^UI(Lac>f)G&{|0KlZ&`x1GW4;%Fa$$vQy?m7dtJfvX~HRJW2>RH$`o3Ws!i8}F4WqsX%tJL$&~@s79NQ+QE3yl!2kDPv|zE;f_;`;~S#2h?r#Wu7n)(rlV4hw!9v zFcMB)>XUA5i%HZav2s)lWzW>X81?IUWRB)P$j<)_v8AxW-!c9nQZg++HSD&by5p|* z)cD$2JHG*~R&8(FHW2=4tVKl` z2XRsj9fS$jd5_bC16O4v4-jcH_lbz9QXumGxv?P9unMp~pnBzFPzz^z@A?AOR*q zk@?dfPh>*IozR#r2=()0zA5G{19SFwyp)KNwv1Z?6Zxkem0z{>=~ve}0+ShjvPK zldE7)^eRI4ExsmKCD)oJ1w=q4+@b6cuuPJOTroM#RN}re<#M`=VATY|F6q7okZKCa z8O_!kRlu?(QVV?{yMjGG0|BV)bjXlMh(ZJXUZz!m$^<7I{xP$T9+Zo9I$hGdmGq1k z(g-)gWHKo>hNCSEhi%#J68ks1Lq3tyFpSf-(Yg{D6rWDMz#r9R?zgdTiY&4~IZb8i z)=SfEDo3}i1l{(ucWSx3QX3|(KbD@6UUjxqU_~?%2?K9@S?r3(#;$be&7@a44eW_vH)t%xLaO^ST zVe;{f9QVwC>}od-@TkP*Zl-o)@Mz|h>h~I_z8Q@BdltcBD;30*wI4!qe%#YQC1P8mTEMv9 zL$=O@IZsO+RMH_e-D!S0tY%LwV3*?^b_1&y2qVzYVk zEGqq+-JB7V>g2vWYA42~ZjUE7XG<4q_R-LJF7tAtW&N=J1#QkT3&JoEhT;8wMMmAK z6|__l1i?|NW1ytz*%sR5$X&1^{&xkzL0uo-55D!5S^}ui_9md3u!PP^DK|pi%%(Xw zGa!?(sLDees@UxOn9MI}m1aq{S}gTj^|7Us^@Ks6+u*OJ>QV1UizJqi)p-TB;NT!Lg`gprq-w z4YbLTOE4n7yMoa5|Azlq(MSLt>PdMND-Pc1(1aHupEm0fteTNYX%x*h&TSmdE@j(3 zZ}TE64!gblS6PpA*N)K3$LQTtwG+xq(n!HLTLSdPs!9AV%%WDFjU5&im;&04#Vy)k zW?TT?@PRMQSY2z|Fcf|Fuh1a04m8lMgRRS&!AiUBWsG)@fnY0&BakH{$)#oOf1msz z*Ro|tPTdbbIg0MN=N=thA%FdrY!lC0BS->{fG;G+P_E=%g2dVT>D;41j6@PZ1Xe%O zI83AO?DqJ><|I5hJwE;X@zb&xUH;i7*N`QMFR$<7kde9PrNTYvoY4)AQXWXmXbB;V z!Fi6iNh!>J0iuY;jWI=j1sRnArlS2>beT6mzLCLuH&jad7 zIKBxa0@XN}aQ!+rX4rUwiLkQIL!a=Xi^Kbz7_j-Dd(_HwTXDwhP!AvaY>?28K%RZt`S%V6w980Pu+z<|931;Uo zo@V7P*B-9uawHhJDbV_9zjGjBq8%JmK|3~5E6y+DRxRe2oW(x~3j@-b_ffQ6O;3a7 zJueQVI(kJnT6Z@#LxwGdt)Mr)I!{;}Cu#Eb@v0&;8kmDW^N3N+nIodtux4j1Z+S`f zT@g1$%Kki{&AU`in|>O2D75%l%WX#OG+qg2TCe9oYFdsCuv!mstu-v?>~>pkfnv*2 zvM%x#scMDhwkvLAzLD?<94?oKJBxYgn*|noUMbv~wx+McMwh;J@*2|D6FNxmu-hva zWfR7?kZ#hZfpjj15~_XFMZBQm&}!@78vOiYgkK9TsWccpc>e*dTy1mPxDo#DU!f0~ zIp^kHo%Y&Gde>4UWbw?m5TtFT?qFn@ws|LuDoG`AJNMtac%ekRQIfBjNh0m@00b7h zi(Np{-~PUST%VkL`{kD>|5#P!s#zC{wyaj8FTXvxD;CRnU5rkfwk{VfX`k0c^V9dQ z-<_=HPerqyFNzVFZk}#8598|dcW3u+Zr}X)-H(6$(_e15^d@;+<3+VD>Kpw0bXzUo zot$hMYolL^c3jV43^&O%!EIGP;dXP&I_cgt;e7p$^>M#h@qy0kdj3p)E1nx8@Kn?f z#k-UHay4I$U_MPVN{jm?%(ALa{(CZlKkK^uIB$#5qFOcWDD*M*qjTa04#o(Nej5Gr z3(J6S@TaO;7B@FeTRoMFFTNTb$h^4;s@20Ge0NzEtM+i_%?4g?}a}z$}_rXirynK?PIc_0tJRPs;w74sqBirD`V{x}x z9<5YdEq*JG9UyP?a{b`v1{8BhBUIOiEjapkpq`H!Gx}V%M>P;`nil`w6wN{7*|uob zu)!P@F}V*N`HYu5;&3+&z#a#xTF*J;A8h>?P9n&hCHwRfnpAQzgSspaDY<{~u zXwn%h@ImXwh{tU*SH*1?3EgGY6q9;>m^JkpzNF307(>VtD<6EiZpZU>@wk&J zT9CU{LzY$b+h$D`5dSs2;TA7si)AsdS)0Wi_A?DBy4_$arO60%LKF7ByxVHXqAp<0 zMrb>14QZI3*Yi?>*Bi3hJoO|nnpGvguWFd>gGok;&wVis4rG3Jr^y!2i)Eon)v@)DLH0R?Pjny_r-1>jPX?9<%P{8L>@%6%wWnCoJ`B<0#Dewr^7`4hMuD;*Vi zb^9x-T=lq6DYjY%+qoz!yOZBBw=_bqK8-Y?9rPfr$4&rFRV_-OjuEI1RaE ze&0&A&85uY%~Ar(qam95xVRL%mLyd4#)Y!Zgba*H@3Sp=t|NpHiW`v~-clVEt4>F^ zJ(UdxVHe_W=0q6}!Z`E%h_;6pah{1+C{0-$t~adjEX@)9|9-yY8QsJQFq5n{Ts=`9 zu(Bs3krN{FK6e6t;v=son*e2IL{bzunSY76D0DJ+Do|*Goe3iG_!s16Bu-H3WO2$- zC(BZQoM)mPitg)2EZsL!#s90v$Ev&ikeA3NMg#9MdjL=g&zq2(3|Vr&HB3xhE8L)6)dSqb61pe$u`B?u349d6x}a<-H>4o6xkRZBLL7uB z;P~KFjz^ayM>sR$2BY4Ax}kE6cIqdWKM=?BKw(%Th(SlOd07MrMh7l>(M3u6+At86 zrrYwCXPpekXjJ7C69HDj8AD@;7Z8R>Hl=~nL_9|s%qm0I5ThO6^0+Ako-slZ87oGj zGlWr^`|)Epp;2JqQK55z_75n84d;r=BS|}MpS#-exBk{^f_Ro*ns%J7*z-B%QJX>4O$s7`(=scT} zERIP4JEuM|FTz0_U*rk#@+9zGCqq`h+Ht`FxQh;Oz=7i65?P0*5p7<%W9AXglO#?v zLx1R!dZyS7Q`jmm4Wx-rUxrCGGecV|F~fE>PO~gI6Y&w^K4Xr3-I-wx8?6UaPmGzz$_E<2u#t1+{$Lj7%=LuTIpeeX=!L(Xq9N$vHC<){?*}K zjy*W)jwG};Ozy1Pw@=f)A&H&NrY4RiP(s^R5IFeH#K|E5S>m#w?2X@oCzjeAV+3cr zhugAyjR##~Q=W}+z&0zJ0SMT-=TU;lo}oyg?nyU!BaNjDBtbL~qBQd-EKxF2_v;Es z^qDk6)YA}(!tQHwWY8{>55;EP_}YNd3WjxCE}B%4{~QHsuK z>`KAyFyuIn{*)^;78W|NfHEnDT-noAz9a2b{Eq5Jutg;Sf?QXtm{o2vknQhSP~h?n zat~{;w-hp%UB=SKS#GP=ZA^HT`m!O?m<^kZv6K;*9tf|q)G_!O^#`S(nmUlFkHMt^ zUzJjwraW$`(1*b9ADv7gou}tms)*IY{fjA;;O42;NO4*wi`e<4kxsWCRfYqE5>3d~ z3~OSnbxF@`V_nD+bK6V=jG?S|GZoBSZ8TY+jU@NZb&=LEkNoStj8zSyf>h58ByhKy zT0ob1JTV;NplC`TMP_kMpBjV~9?iOGhgoXKR3&0cvwrM<(2I{g4KflBGDlj|Vg$zE zOLd>jgm@Jh(&s)2lExv}nq&7#DVpRoAJLA|`XmVd^e6}$Vyr~tHl(EhoRiHGc6tas zR04bYChn{m-IZlFG*Yy z;&WEUDdI@3T&M{AnkF>jhNlN;Wds${0D|RBfQuF3g&aeJ_*^@Tr!v(!sLYQhu_982 z%|)5-szUHNUf+A5(3uT{y^>z^fG1?u4yF*C>^+3wNK-pZD~CYs8a7pJyjM=DTx5jK zFZ5}CGR!x*eMSrUo@hcnY+^tEkEnAXcq0$JT3&Mk(@~)QG16+LUhPNOHV=(y9+~| z!jGodGu5N-9!q%aIau$I#sm!lw`D+ibP>f@y4FSU+>=0-?PJQ@T_0+QwnOY76it6*RAw9uA=BsA6=sMYU<7h*%Tc~JIanVdLjt*w?jfd= zx*jaMQXj}t*_(UV2enmz9H=vOk4~?YBpahpdM8fcnAdiBzj|<`u;3r4;06iGDU^c4 zChKOj)>H3`6dtPu_l-u!>BX0NKh1A2R?FkFm1aLJqr#lmPIf5`;7=XuyY! z?t|8M7A8uy%Du<&Gr1a;4PLQznq{W!0}awu$xaNljxMJM&jva>4v^>rck>i83Td+e#4@Plc_#nvB4VTG=s48Z&9LFxCt@Vhmz3`gXmHk4K(TYoy+*>7qd!UFt zPJEPefO{U(V@>5eMJ0uh+0$e9eGl$<`tiKV+&j@E4t#gE_lee{EtP1w0n%vvd}AL? z&Z%}N{XFFMfYQ#G_>moi{P)6+rug#vLG)}8ePcxV6$UI>jd+$p2kaT zR@ve4Fs^I?J6tRvL@@ajNn_}@syfB=Q96yaz251Q_?_}N$1`0@Z##4;z4fQkp`b7v z4Gsze!~Vb5@elBLyQ}Z>@%_%d&#U*jv&D}GJi+b)du`up^j2%Pn)!fz4;l6zi`HOv zdtpQ*BM)J?u{Z8-bDaq2V?Uq=>B?6N_-`qs#Y5S&_4DWz`&EOY876fd z|9ShCcftn{z~fJMzvOVL{^rBmx8mm*UVk;>t(Bi?*vgOVb^GNwtzRACJ-b3d2amh^ z=7&D=E%KTx`}h4SM^Eea`A{{>YuioW6b5oEGO6lr-mu8>aD^dl2TBODp?b4uPc4?J zKBi}#I<&RO*UEHXwU1@<_0OWu(NBsa^eEfjDM@jJeox(#SYNUuNNcm5>nHF4=kBhg zZ{|xr^2R*ti2h`Xec~3l8wG<8Ty|M6U;oU00p+we3Rq@UF~@eAA!M# z=|I7lIU>7WkJFVU8%t0&q7C`!qK4s!-Mh=|*RLK!XIA`AKdchu2G=Kn)hgS+ zs}^|QS>1hcF*d}+4(A0G^y;Qm8(M9UE@l*leZUlK&cy zZupcy2c`P4b{8YNv9z@g8MBK-hCr!{F;G191mFUxff?lD!)`+(Je|=1OM&*xLV5H4 zp-T)QZ7aiaCXH{cs+Gy*gVDh+Dlsxr=yXtuVB8EBYCX~DPQyD=zLRkxZ+uJ&zqzuj zi>Xq2^}+sS>mWU|B_i+Hp^!Qj1#hNZI?PPSFcw~%3&bcOt59ky2}34P*;%Bc3shy$ zViqCAB0pl|u!@9(_&mpPgwR!mNNbV36qz>hkkqCVPEvpPXb{_vq87&Y=2XJLx~F+-(#&&1Tkq5Iu^N@(8#@R-0BD zw-s9A%Cn`!nO{K_k%beNQ*_oG!GS46E5U7w>i`3i>#%MwjfEeE2M|lokt@k_(Je zGi7sZ-!yMJ++-q5sF}BzfPZl9rTM&&wgz)uu0xLjEgby_J~dR^5>sO|~Mx5~n9bNmb%AT+&DzA2E$SCRG7US2U3s>?Yg=&M1WWDS*wl z6r9D*T?g2Q>c>#hY3PG>pGmS>@H|OaQ~Jm0tOr+mYGLz{SOa$m74EGRhGCUOTp#wE z8M1(sO|_rhu5&qnBVyxVT+g*?tJIxL05hP_KoVDK^|z8mq3zGhl&9@Z)Yo;%R#Hx?!9bYM zU2+Q3yrp(>Sq%?p{;0Tr1rfx+zfN+4#NCKp)7*x~x6gyCgUeIec+F^BmSaT%iKKZ& z<)MIInjP{$Hl0>tn*Q47jNaN27(udh4;s11YYL+V=14_u2wjG}l(vV7TS58WZ-dve zhh!Q*^O}Uk$lT=b4{fC^;jdVPOJCa318FADKow`XeE#_DrkAU*Ks|p3u7^*Lz%cBz zUO?FvkgDuP^;ATAWM$rqR2o`ZI@+UPf&KM>yQG++I zZ=%8BlbLD_ZopfFgvz=Bz;_BjDEd$CFkBKlDEAEc&f;YOELOnHBIoz^w`2iJLTsMa zSx}T(L5!k_o{q-)_ch=q>dS#@|#yw9@x68jT?Msn|>6~VGQ5?&fr;s2A zZs~milMjHuC4?bYoVT>YD!cz(`#%4>_E9WboflRZHmsL@o{B5;Y(emmX!SDBYCwi3Od8i^3_ zJ>I^1{H7k>PnI*^yzG7MFAlyoT^9|UJ$ffd5N7$LNH?DYN(b@Ai1RdGgcLeHi4weE zq`cRK;>ah~=QIwpgil})@W))fky;$rCA_&tvlsxvWu%2EXT?{=?vr=}@1S9ohtieI9t=O}a=D^x zZ+l<@(-;y6v`xt6v z(JEk?_X(Z%Vww{1b?%wqF$u&r`lI#Jgq$IYiy3&oE zkrM=&IW6nKV%5|&L(BbU$3yq?f)HUf82V8%_!ECw-I$3=0%}GbAD%tB?QP{}?|<7b zvr|)9-mwVs0~|`Pe*&P19bzLD&}A+iD`DeBE0TM1-}_b-&Wd*xDJgVyl(*VX28d7c zdzbAuP;Ct*ai*;vjJ7CjvSY2K_T0!T`LB3Bmq7XqFJDvP@PB~(6M z?zI0>7o&|)jFs=ScK9)gtmO{CY9~I>Ssg9M6(;*}+NtsmYhChc;9n^Rjx}I^`-4Vi z`V^ml@`QS_!5W$$uD?esIX%p41d9D>9W4Q|&4q0$7dNc+pcNrJU88#&FH7E=Z5f91 z7O?*bp&L;KfK4`4kfw^APZ*&G{|z1sH0VB+3}8cX6CiTh;>ya(O7=mhv5QxEZdP#& z%@mlbY#M4Iw=9;nOq&Si{sbc4qGz4x1-2e^Z5%<5UYzw=x>CO0%wN!8w-JAxtecXO zzWfk^9~$P$&cFs&Kl0+XZ4+oeF8#DQ#rnUzwKDSceh88i4#0Lj+yqX;Q;_;PqMmaG zcC2b%>Q~aLwAtC%RLVH!7WQCqpKRI0j;5zOC`WPIC$ZK+YCcA3*Fk;%iH_%&1fquz z{^fcJp6jDkDN&kLSD*27p5ocv6XWClMO3}`R;;QB}%i$YN<&O2g%y>dlE9H4G;YL|iS0}qWvTUyeFd}Qt zPZr)w95c|jg~IkYAA5guIj#AjTm@poNc9Uul3`vug8{@S3n2r@H4K3%JAZ#vlUuK97P8vPV;2G-yWQWz2Od2H{ec}}cmvvW$htQ&A9qbzAI z^j5M7Ya(7Xw&RSF`Q19YaTT zOu>$4)a9?zohZ7_v7L~B{LsQ5Uc!)(C{#-*UQq;U>fw3Cc#VzgU^g>C?s){?oiUK8 z1{IU->_JnhTJ(G}74a!__B$&Rf4`E)sX?^?G+K_-C1St_}5Y-$Mm*)+9-V))M98RU%c^p*x7kIobiW3h4k;l;`#ad`1)yVE(7Uw4eE0dJhK>=wtP8X ziTqL->zi?^0Vr>}fd+cHc%)^L))Mm_#=s4~b)Zoy$%k)!?pg6n4a$+NJp=El3QO0V zl$}CY$YqQbEC;I*<#POO7;`?cQAJABB}25th`DeTC;dJhLkjv=OxSzWGR`ueoRG)X zip}wKlYuZi1uZaZwhbhXtre2xW!&B6If!B)eZ?4Akmm-Z1s=1pmb(yygpw=Wpr$~G z5rxn)1Rg(Y0uqQaJGKJ(`Jj+u`7&(@nmv>rU5)%aoZ~}7QF0$6pjK@o@ z?5A|Tk7z}=hQPSWuK(=wK%G7omS?JywF;#80WPCr2ZIPH`Tgl53!g+xY&Hpm!QQ9@ z;$OPrKEKT-o2+eX0o=dhnnJK5gt^Zn6)?ddVy*DQutV0Q%u zO4@hpaE*)xN3$mD#PFuxN1oSS_t!)Gf_^3w|Y^Pk1x!E7rcivhI1e=Ze9`Jf1Ks`C4l(hyJji>rA!kG5t?m<;eq3#1>^wgx3scgq)ovt za<>t@cQ%gm)2>}Iho

(\s*)\w|', function ($matches) { - assertType('array{string, string}', $matches); + assertType('array{non-falsy-string, string}', $matches); return ''; }, $s diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 9a1f34bf73b..ca7b7372a92 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -743,7 +743,7 @@ public function testBug11655(): void { $this->analyse([__DIR__ . '/data/bug-11655.php'], [ [ - "Offset 3 does not exist on array{string, 'x', array{string, 'x'}}.", + "Offset 3 does not exist on array{non-falsy-string, 'x', array{non-falsy-string, 'x'}}.", 15, ], ]); From 040118971129be484f2403fb85678b9961e84f83 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 24 Mar 2025 14:54:58 +0100 Subject: [PATCH 1225/3097] RegexArrayShapeMatcher - enforce list type when no named captures --- src/Type/Php/RegexArrayShapeMatcher.php | 22 ++++++++++++++---- tests/PHPStan/Analyser/nsrt/bug-11311.php | 6 ++--- tests/PHPStan/Analyser/nsrt/bug-11580.php | 2 +- .../Analyser/nsrt/preg_match_shapes.php | 23 +++++++++++-------- .../nsrt/preg_replace_callback_shapes.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 7 ++++++ tests/PHPStan/Rules/Arrays/data/bug-11602.php | 23 +++++++++++++++++++ 7 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11602.php diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index 64c2f0c496b..a23e3445d80 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -8,7 +8,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -62,7 +61,7 @@ public function matchExpr(Expr $patternExpr, ?Type $flagsType, TrinaryLogic $was private function matchPatternType(Type $patternType, ?Type $flagsType, TrinaryLogic $wasMatched, bool $matchesAll): ?Type { if ($wasMatched->no()) { - return new ConstantArrayType([], []); + return ConstantArrayTypeBuilder::createEmpty()->getArray(); } $constantStrings = $patternType->getConstantStrings(); @@ -146,8 +145,11 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched if (!$this->containsUnmatchedAsNull($flags, $matchesAll)) { // positive match has a subject but not any capturing group + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantIntegerType(0), $this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)); + $combiType = TypeCombinator::union( - new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)], [1], [], TrinaryLogic::createYes()), + $builder->getArray(), $combiType, ); } @@ -206,7 +208,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched ) ) { // positive match has a subject but not any capturing group - $combiTypes[] = new ConstantArrayType([new ConstantIntegerType(0)], [$this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)], [1], [], TrinaryLogic::createYes()); + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantIntegerType(0), $this->createSubjectValueType($subjectBaseType, $flags, $matchesAll)); + + $combiTypes[] = $builder->getArray(); } return TypeCombinator::union(...$combiTypes); @@ -238,6 +243,7 @@ private function buildArrayType( bool $matchesAll, ): Type { + $forceList = count($markVerbs) === 0; $builder = ConstantArrayTypeBuilder::createEmpty(); // first item in matches contains the overall match. @@ -256,6 +262,8 @@ private function buildArrayType( $optional = $this->isGroupOptional($captureGroup, $wasMatched, $flags, $isTrailingOptional, $matchesAll); if ($captureGroup->isNamed()) { + $forceList = false; + $builder->setOffsetValueType( $this->getKeyType($captureGroup->getName()), $groupValueType, @@ -288,13 +296,17 @@ private function buildArrayType( $arrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $builder->getArray()), new AccessoryArrayListType()); if (!$wasMatched->yes()) { $arrayType = TypeCombinator::union( - new ConstantArrayType([], []), + ConstantArrayTypeBuilder::createEmpty()->getArray(), $arrayType, ); } return $arrayType; } + if ($forceList) { + return TypeCombinator::intersect($builder->getArray(), new AccessoryArrayListType()); + } + return $builder->getArray(); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index ff99e4699ce..96b810431dd 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -191,12 +191,12 @@ function (string $s): void { function (string $s): void { preg_match('/%a(\d*)/', $s, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string} + assertType("list{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string} }; function (string $s): void { preg_match('/%a(\d*)?/', $s, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string} + assertType("list{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string} }; function (string $s): void { @@ -222,5 +222,5 @@ function (string $s): void { function (string $s): void { preg_match('~a|(\d)|(\s)~', $s, $matches, PREG_UNMATCHED_AS_NULL); - assertType("array{0?: string, 1?: numeric-string|null, 2?: non-empty-string|null}", $matches); + assertType("list{0?: string, 1?: numeric-string|null, 2?: non-empty-string|null}", $matches); }; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11580.php b/tests/PHPStan/Analyser/nsrt/bug-11580.php index 2081bb06240..039a1895f51 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11580.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11580.php @@ -26,7 +26,7 @@ public function bad2(string $in): void public function bad3(string $in): void { $result = preg_match('~^/xxx/([\w\-]+)/?([\w\-]+)?/?$~', $in, $matches); - assertType('array{0?: string, 1?: non-empty-string, 2?: non-empty-string}', $matches); + assertType('list{0?: string, 1?: non-empty-string, 2?: non-empty-string}', $matches); if ($result) { assertType('array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 88bbe9fad6c..5e87970c8e7 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -300,7 +300,7 @@ function (string $size): void { if (preg_match('~^a\.(b)?(c)?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType("array{0: non-falsy-string, 1?: ''|'b', 2?: 'c'}", $matches); + assertType("list{0: non-falsy-string, 1?: ''|'b', 2?: 'c'}", $matches); }; function (string $size): void { @@ -525,7 +525,7 @@ function bug11323(string $s): void { function (string $s): void { preg_match('/%a(\d*)/', $s, $matches); - assertType("array{0?: string, 1?: ''|numeric-string}", $matches); + assertType("list{0?: string, 1?: ''|numeric-string}", $matches); }; class Bug11376 @@ -533,7 +533,7 @@ class Bug11376 public function test(string $str): void { preg_match('~^(?:(\w+)::)?(\w+)$~', $str, $matches); - assertType('array{0?: string, 1?: string, 2?: non-empty-string}', $matches); + assertType('list{0?: string, 1?: string, 2?: non-empty-string}', $matches); } public function test2(string $str): void @@ -564,7 +564,7 @@ function (string $s): void { } if (preg_match($p, $s, $matches)) { - assertType("array{0: non-falsy-string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); + assertType("list{0: non-falsy-string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches); } }; @@ -730,7 +730,7 @@ function (string $s): void { function (string $s): void { preg_match('~a|(\d)|(\s)~', $s, $matches); - assertType("array{0?: string, 1?: '', 2?: non-empty-string}|array{0?: string, 1?: numeric-string}", $matches); + assertType("list{0?: string, 1?: '', 2?: non-empty-string}|list{0?: string, 1?: numeric-string}", $matches); }; function bug11490 (string $expression): void { @@ -762,13 +762,13 @@ function bug11604 (string $string): void { return; } - assertType("array{0: non-empty-string, 1?: ''|'XX', 2?: 'YY'}", $matches); + assertType("list{0: non-empty-string, 1?: ''|'XX', 2?: 'YY'}", $matches); // could be array{string, '', 'YY'}|array{string, 'XX'}|array{string} } function bug11604b (string $string): void { if (preg_match('/(XX)|(YY)?(ZZ)/', $string, $matches)) { - assertType("array{0: non-empty-string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); + assertType("list{0: non-empty-string, 1?: ''|'XX', 2?: ''|'YY', 3?: 'ZZ'}", $matches); } } @@ -935,11 +935,11 @@ function bugEmptySubexpression (string $string): void { } if (preg_match('~((a)||(b))~', $string, $matches)) { - assertType("array{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: 'b'}", $matches); + assertType("list{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: 'b'}", $matches); } if (preg_match('~((a)|()|(b))~', $string, $matches)) { - assertType("array{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: '', 4?: 'b'}", $matches); + assertType("list{0: string, 1: ''|'a'|'b', 2?: ''|'a', 3?: '', 4?: 'b'}", $matches); } } @@ -1010,3 +1010,8 @@ function bug12749f(string $str): void assertType('array{non-empty-string}', $match); // could be numeric-string } } + +function bug12397(string $string) : array { + $m = preg_match('#\b([A-Z]{2,})-(\d+)#', $string, $match); + assertType('list{0?: string, 1?: non-falsy-string, 2?: numeric-string}', $match); +} diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php index 57c486638ec..c6ba4824c2e 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php @@ -22,7 +22,7 @@ function (string $s): void { preg_replace_callback( '/(foo)?(bar)?(baz)?/', function ($matches) { - assertType("array{0: array{string, int<-1, max>}, 1?: array{''|'foo', int<-1, max>}, 2?: array{''|'bar', int<-1, max>}, 3?: array{'baz', int<-1, max>}}", $matches); + assertType("list{0: array{string, int<-1, max>}, 1?: array{''|'foo', int<-1, max>}, 2?: array{''|'bar', int<-1, max>}, 3?: array{'baz', int<-1, max>}}", $matches); return ''; }, $s, diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index ca7b7372a92..58806979a61 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -900,4 +900,11 @@ public function testNarrowSuperglobals(): void $this->analyse([__DIR__ . '/data/narrow-superglobal.php'], []); } + public function testBug11602(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-11602.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11602.php b/tests/PHPStan/Rules/Arrays/data/bug-11602.php new file mode 100644 index 00000000000..4e1252e5b47 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11602.php @@ -0,0 +1,23 @@ + Date: Tue, 25 Mar 2025 16:06:29 +0100 Subject: [PATCH 1226/3097] Fix elapsed time format for result cache restore time --- src/Analyser/ResultCache/ResultCacheManager.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 80ea03ac06c..b40e3bb83ed 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -37,7 +37,6 @@ use function is_file; use function ksort; use function microtime; -use function round; use function sha1_file; use function sort; use function sprintf; @@ -297,7 +296,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $elapsed = microtime(true) - $startTime; $elapsedString = $elapsed > 5 - ? sprintf(' in %f seconds', round($elapsed, 1)) + ? sprintf(' in %.1f seconds', $elapsed) : ''; $output->writeLineFormatted(sprintf( From 88133ccb560f55c320a69a8300897e67edbfb8be Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 26 Mar 2025 13:48:04 +0100 Subject: [PATCH 1227/3097] Fix uopz signature --- resources/functionMap.php | 16 ++++++------- .../CallToFunctionParametersRuleTest.php | 5 ++++ .../Rules/Functions/data/bug-12499.php | 23 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-12499.php diff --git a/resources/functionMap.php b/resources/functionMap.php index f83a55d4aa3..c772596bcc2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -12929,7 +12929,7 @@ 'unserialize' => ['mixed', 'variable_representation'=>'string', 'allowed_classes='=>'array{allowed_classes?:string[]|bool}'], 'untaint' => ['bool', '&rw_string'=>'string', '&...rw_strings='=>'string'], 'uopz_add_function' => ['bool', 'class'=>'string', 'function'=>'string', 'handler'=>'Closure', '$flags'=>'bool', '$all'=>'bool'], -'uopz_add_function\1' => ['bool', 'function'=>'string', 'handler'=>'Closure', '$flags'=>'bool'], +'uopz_add_function\'1' => ['bool', 'function'=>'string', 'handler'=>'Closure', '$flags'=>'bool'], 'uopz_allow_exit' => ['void', 'allow'=>'bool'], 'uopz_backup' => ['void', 'class'=>'string', 'function'=>'string'], 'uopz_backup\'1' => ['void', 'function'=>'string'], @@ -12937,7 +12937,7 @@ 'uopz_copy' => ['Closure', 'class'=>'string', 'function'=>'string'], 'uopz_copy\'1' => ['Closure', 'function'=>'string'], 'uopz_del_function' => ['bool', 'class'=>'string', 'function'=>'string', '$all'=>'bool'], -'uopz_del_function\1' => ['bool', 'function'=>'string'], +'uopz_del_function\'1' => ['bool', 'function'=>'string'], 'uopz_delete' => ['void', 'class'=>'string', 'function'=>'string'], 'uopz_delete\'1' => ['void', 'function'=>'string'], 'uopz_extend' => ['void', 'class'=>'string', 'parent'=>'string'], @@ -12947,13 +12947,13 @@ 'uopz_function\'1' => ['void', 'function'=>'string', 'handler'=>'Closure', 'modifiers='=>'int'], 'uopz_get_exit_status' => ['mixed'], 'uopz_get_hook' => ['Closure', 'class'=>'string', 'function'=>'string'], -'uopz_get_hook\1' => ['Closure', 'function'=>'string'], +'uopz_get_hook\'1' => ['Closure', 'function'=>'string'], 'uopz_get_mock' => ['mixed', 'class'=>'string'], -'uopz_get_property' => ['void', 'class'=>'string', 'property'=>'string'], -'uopz_get_property\1' => ['void', 'instance'=>'object', 'property'=>'string'], +'uopz_get_property' => ['mixed', 'class'=>'string', 'property'=>'string'], +'uopz_get_property\'1' => ['mixed', 'instance'=>'object', 'property'=>'string'], 'uopz_get_return' => ['mixed', 'class='=>'string', 'function='=>'string'], 'uopz_get_static' => ['array', 'class='=>'string', 'function='=>'string'], -'uopz_get_static\1' => ['array', 'function='=>'string'], +'uopz_get_static\'1' => ['array', 'function='=>'string'], 'uopz_implement' => ['void', 'class'=>'string', 'interface'=>'string'], 'uopz_overload' => ['void', 'opcode'=>'int', 'callable'=>'Callable'], 'uopz_redefine' => ['void', 'class'=>'string', 'constant'=>'string', 'value'=>'mixed'], @@ -12964,13 +12964,13 @@ 'uopz_restore\'1' => ['void', 'function'=>'string'], 'uopz_set_mock' => ['void', 'class'=>'string', 'mock'=>'object|string'], 'uopz_set_property' => ['void', 'class'=>'string', 'property'=>'string', 'value'=>'mixed'], -'uopz_set_property\1' => ['void', 'instance'=>'object', 'property'=>'string', 'value'=>'mixed'], +'uopz_set_property\'1' => ['void', 'instance'=>'object', 'property'=>'string', 'value'=>'mixed'], 'uopz_set_return' => ['bool', 'class'=>'string', 'function'=>'string', 'value'=>'mixed', 'execute='=>'bool'], 'uopz_set_return\'1' => ['bool', 'function'=>'string', 'value'=>'mixed', 'execute='=>'bool'], 'uopz_undefine' => ['void', 'class'=>'string', 'constant'=>'string'], 'uopz_undefine\'1' => ['void', 'constant'=>'string'], 'uopz_set_hook' => ['bool', 'class'=>'string', 'function'=>'string', 'hook'=>'Closure'], -'uopz_set_hook\1' => ['bool', 'function'=>'string', 'hook'=>'Closure'], +'uopz_set_hook\'1' => ['bool', 'function'=>'string', 'hook'=>'Closure'], 'uopz_unset_mock' => ['void', 'class'=>'string'], 'uopz_unset_return' => ['bool', 'class='=>'string', 'function='=>'string'], 'uopz_unset_return\'1' => ['bool', 'function'=>'string'], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 7ce9a166c76..3114bedfe96 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1939,4 +1939,9 @@ public function testBug9167(): void $this->analyse([__DIR__ . '/data/bug-9167.php'], []); } + public function testBug12499(): void + { + $this->analyse([__DIR__ . '/data/bug-12499.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12499.php b/tests/PHPStan/Rules/Functions/data/bug-12499.php new file mode 100644 index 00000000000..1322e06e4f5 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12499.php @@ -0,0 +1,23 @@ + Date: Wed, 26 Mar 2025 15:49:36 +0100 Subject: [PATCH 1228/3097] TypeInferenceTestCase - fail when analysed symbols do not exist (misconfigured autoloading) --- src/Testing/TypeInferenceTestCase.php | 33 ++++++++++++++++++- .../Testing/TypeInferenceTestCaseTest.php | 13 ++++++++ tests/notAutoloaded/nonexistentClasses.php | 15 +++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/notAutoloaded/nonexistentClasses.php diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index da7cbe82c2a..38806ce1671 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -13,13 +13,16 @@ use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; use PHPStan\File\SystemAgnosticSimpleRelativePathHelper; +use PHPStan\Node\InClassNode; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\FileTypeMapper; @@ -136,6 +139,8 @@ public function assertFileAsserts( $expectedCertainty->equals($actualCertainty), sprintf('Expected %s, actual certainty of %s is %s in %s on line %d.', $expectedCertainty->describe(), $variableName, $actualCertainty->describe(), $file, $args[3]), ); + } elseif ($assertType === 'error') { + $this->fail($args[0]); } } @@ -148,11 +153,37 @@ public static function gatherAssertTypes(string $file): array $fileHelper = self::getContainer()->getByType(FileHelper::class); $relativePathHelper = new SystemAgnosticSimpleRelativePathHelper($fileHelper); + $reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class); $file = $fileHelper->normalizePath($file); $asserts = []; - self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, $file, $relativePathHelper): void { + self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, $file, $relativePathHelper, $reflectionProvider): void { + if ($node instanceof InClassNode) { + if (!$reflectionProvider->hasClass($node->getClassReflection()->getName())) { + $asserts[$file . ':' . $node->getStartLine()] = [ + 'error', + $file, + sprintf( + '%s %s in %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + $node->getClassReflection()->getClassTypeDescription(), + $node->getClassReflection()->getName(), + $file, + ), + ]; + } + } elseif ($node instanceof Node\Stmt\Trait_) { + if ($node->namespacedName === null) { + throw new ShouldNotHappenException(); + } + if (!$reflectionProvider->hasClass($node->namespacedName->toString())) { + $asserts[$file . ':' . $node->getStartLine()] = [ + 'error', + $file, + sprintf('Trait %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', $node->namespacedName->toString()), + ]; + } + } if (!$node instanceof Node\Expr\FuncCall) { return; } diff --git a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php index a2cf8cd4840..ede07b3c7bb 100644 --- a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php +++ b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php @@ -101,4 +101,17 @@ public function testVariableOrOffsetDescription(): void $this->assertSame("offset 'email'", $offsetAssert[4]); } + public function testNonexistentClassInAnalysedFile(): void + { + try { + foreach ($this->gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses.php') as $data) { + $this->assertFileAsserts(...$data); + } + + $this->fail('Should have failed'); + } catch (AssertionFailedError $e) { + $this->assertStringContainsString('not found in ReflectionProvider', $e->getMessage()); + } + } + } diff --git a/tests/notAutoloaded/nonexistentClasses.php b/tests/notAutoloaded/nonexistentClasses.php new file mode 100644 index 00000000000..5473def6fe9 --- /dev/null +++ b/tests/notAutoloaded/nonexistentClasses.php @@ -0,0 +1,15 @@ + Date: Wed, 26 Mar 2025 16:19:12 +0100 Subject: [PATCH 1229/3097] RuleTestCase - fail when analysed symbols do not exist (misconfigured autoloading) --- src/Testing/NonexistentAnalysedClassRule.php | 47 ++++++++++++++++++ src/Testing/NonexistentAnalysedTraitRule.php | 49 +++++++++++++++++++ src/Testing/RuleTestCase.php | 5 +- .../NonexistentAnalysedClassRuleTest.php | 47 ++++++++++++++++++ tests/notAutoloaded/nonexistentClasses.php | 5 ++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/Testing/NonexistentAnalysedClassRule.php create mode 100644 src/Testing/NonexistentAnalysedTraitRule.php create mode 100644 tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php diff --git a/src/Testing/NonexistentAnalysedClassRule.php b/src/Testing/NonexistentAnalysedClassRule.php new file mode 100644 index 00000000000..25c7a11459e --- /dev/null +++ b/src/Testing/NonexistentAnalysedClassRule.php @@ -0,0 +1,47 @@ + + */ +final class NonexistentAnalysedClassRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return InClassNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $className = $node->getClassReflection()->getName(); + if ($this->reflectionProvider->hasClass($className)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + '%s %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + $node->getClassReflection()->getClassTypeDescription(), + $node->getClassReflection()->getName(), + )) + ->identifier('phpstan.classNotFound') + ->nonIgnorable() + ->build(), + ]; + } + +} diff --git a/src/Testing/NonexistentAnalysedTraitRule.php b/src/Testing/NonexistentAnalysedTraitRule.php new file mode 100644 index 00000000000..8593429e983 --- /dev/null +++ b/src/Testing/NonexistentAnalysedTraitRule.php @@ -0,0 +1,49 @@ + + */ +final class NonexistentAnalysedTraitRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Stmt\Trait_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->namespacedName === null) { + throw new ShouldNotHappenException(); + } + $traitName = $node->namespacedName->toString(); + if ($this->reflectionProvider->hasClass($traitName)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Trait %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + $traitName, + )) + ->identifier('phpstan.traitNotFound') + ->nonIgnorable() + ->build(), + ]; + } + +} diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 45e25730e10..2f28598257e 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -171,8 +171,11 @@ static function (Error $error) use ($strictlyTypedSprintf): string { */ public function gatherAnalyserErrors(array $files): array { + $reflectionProvider = $this->createReflectionProvider(); $ruleRegistry = new DirectRuleRegistry([ $this->getRule(), + new NonexistentAnalysedClassRule($reflectionProvider), + new NonexistentAnalysedTraitRule($reflectionProvider), ]); $files = array_map([$this->getFileHelper(), 'normalizePath'], $files); $analyserResult = $this->getAnalyser($ruleRegistry)->analyse( @@ -196,7 +199,7 @@ public function gatherAnalyserErrors(array $files): array $ruleRegistry, new IgnoreErrorExtensionProvider(self::getContainer()), new RuleErrorTransformer(), - $this->createScopeFactory($this->createReflectionProvider(), $this->getTypeSpecifier()), + $this->createScopeFactory($reflectionProvider, $this->getTypeSpecifier()), new LocalIgnoresProcessor(), true, ); diff --git a/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php b/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php new file mode 100644 index 00000000000..5d1edc427b2 --- /dev/null +++ b/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php @@ -0,0 +1,47 @@ +> + */ +class NonexistentAnalysedClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new /** @implements Rule */class implements Rule { + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + + }; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/../../notAutoloaded/nonexistentClasses.php'], [ + [ + 'Class NamespaceForNonexistentClasses\Foo not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + 7, + ], + [ + 'Trait NamespaceForNonexistentClasses\FooTrait not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + 17, + ], + ]); + } + +} diff --git a/tests/notAutoloaded/nonexistentClasses.php b/tests/notAutoloaded/nonexistentClasses.php index 5473def6fe9..35d69e738d0 100644 --- a/tests/notAutoloaded/nonexistentClasses.php +++ b/tests/notAutoloaded/nonexistentClasses.php @@ -13,3 +13,8 @@ public function doFoo(): void } } + +trait FooTrait +{ + +} From c4cc6402a7bb2dd60e2f834c250dfb9ec1089fd6 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 27 Mar 2025 13:53:27 +0100 Subject: [PATCH 1230/3097] Limit int ranges when narrowing arrays via `count()` Co-authored-by: Ondrej Mirtes --- src/Analyser/TypeSpecifier.php | 35 ++++++++++++------- .../Analyser/AnalyserIntegrationTest.php | 6 ++++ tests/PHPStan/Analyser/data/bug-12787.php | 22 ++++++++++++ tests/PHPStan/Analyser/nsrt/list-count.php | 29 ++++++++++++++- 4 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12787.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 52b3d76d457..1e58feac389 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1087,10 +1087,7 @@ private function specifyTypesForCountFuncCall( if ( $sizeType instanceof ConstantIntegerType && $sizeType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT - && ( - $isList->yes() - || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getValue() - 1))->yes() - ) + && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getValue() - 1))->yes() ) { // turn optional offsets non-optional $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); @@ -1105,21 +1102,23 @@ private function specifyTypesForCountFuncCall( if ( $sizeType instanceof IntegerRangeType && $sizeType->getMin() !== null - && ( - $isList->yes() - || $isConstantArray->yes() && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, $sizeType->getMin() - 1))->yes() - ) + && $sizeType->getMin() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT + && $arrayType->getKeyType()->isSuperTypeOf(IntegerRangeType::fromInterval(0, ($sizeType->getMax() ?? $sizeType->getMin()) - 1))->yes() ) { + $builderData = []; // turn optional offsets non-optional - $valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty(); for ($i = 0; $i < $sizeType->getMin(); $i++) { $offsetType = new ConstantIntegerType($i); - $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType)); + $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), false]; } if ($sizeType->getMax() !== null) { + if ($sizeType->getMax() - $sizeType->getMin() > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + $resultTypes[] = $arrayType; + continue; + } for ($i = $sizeType->getMin(); $i < $sizeType->getMax(); $i++) { $offsetType = new ConstantIntegerType($i); - $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), true); + $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), true]; } } elseif ($arrayType->isConstantArray()->yes()) { for ($i = $sizeType->getMin();; $i++) { @@ -1128,14 +1127,24 @@ private function specifyTypesForCountFuncCall( if ($hasOffset->no()) { break; } - $valueTypesBuilder->setOffsetValueType($offsetType, $arrayType->getOffsetValueType($offsetType), !$hasOffset->yes()); + $builderData[] = [$offsetType, $arrayType->getOffsetValueType($offsetType), !$hasOffset->yes()]; } } else { $resultTypes[] = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); continue; } - $resultTypes[] = $valueTypesBuilder->getArray(); + if (count($builderData) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + $resultTypes[] = $arrayType; + continue; + } + + $builder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($builderData as [$offsetType, $valueType, $optional]) { + $builder->setOffsetValueType($offsetType, $valueType, $optional); + } + + $resultTypes[] = $builder->getArray(); continue; } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e7678b1449e..d515fb2379a 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1542,6 +1542,12 @@ public function testBug12159(): void $this->assertNoErrors($errors); } + public function testBug12787(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12787.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12787.php b/tests/PHPStan/Analyser/data/bug-12787.php new file mode 100644 index 00000000000..189d88cc8b6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12787.php @@ -0,0 +1,22 @@ + $listRow * @param int<2, 3> $twoOrThree * @param int<2, max> $twoOrMore * @param int $maxThree * @param int<10, 11> $tenOrEleven + * @param int<3, 32> $threeOrMoreInRangeLimit + * @param int<3, 512> $threeOrMoreOverRangeLimit */ - protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven): void + protected function testOptionalKeysInUnionListWithIntRange($row, $listRow, $twoOrThree, $twoOrMore, int $maxThree, $tenOrEleven, $threeOrMoreInRangeLimit, $threeOrMoreOverRangeLimit): void { if (count($row) >= $twoOrThree) { assertType('array{0: int, 1: string|null, 2?: int|null}', $row); @@ -371,6 +374,30 @@ protected function testOptionalKeysInUnionListWithIntRange($row, $twoOrThree, $t } else { assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); } + + if (count($row) >= $threeOrMoreInRangeLimit) { + assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + } else { + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + } + + if (count($listRow) >= $threeOrMoreInRangeLimit) { + assertType('list{0: string, 1: string, 2: string, 3?: string, 4?: string, 5?: string, 6?: string, 7?: string, 8?: string, 9?: string, 10?: string, 11?: string, 12?: string, 13?: string, 14?: string, 15?: string, 16?: string, 17?: string, 18?: string, 19?: string, 20?: string, 21?: string, 22?: string, 23?: string, 24?: string, 25?: string, 26?: string, 27?: string, 28?: string, 29?: string, 30?: string, 31?: string}', $listRow); + } else { + assertType('list', $listRow); + } + + if (count($row) >= $threeOrMoreOverRangeLimit) { + assertType('list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + } else { + assertType('array{string}|list{0: int, 1?: string|null, 2?: int|null, 3?: float|null}', $row); + } + + if (count($listRow) >= $threeOrMoreOverRangeLimit) { + assertType('non-empty-list', $listRow); + } else { + assertType('list', $listRow); + } } /** From 8a6f7e9a1c0aa24e0cbf4160b042826ed14d80be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 27 Mar 2025 13:59:41 +0100 Subject: [PATCH 1231/3097] Fix weird "stdClass not found" error in connection to array shapes --- src/Analyser/NameScope.php | 14 ++++++++++ src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 9 ++++++- tests/PHPStan/Analyser/data/bug-12803.php | 27 +++++++++++++++++++ .../WrongVariableNameInVarTagRuleTest.php | 14 +++++++++- .../data/new-is-always-final-var-tag-type.php | 23 ++++++++++++++++ 6 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12803.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index fbc602329e2..32208ad83ed 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -175,6 +175,20 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self ); } + public function withoutNamespaceAndUses(): self + { + return new self( + null, + [], + $this->className, + $this->functionName, + $this->templateTypeMap, + $this->typeAliasesMap, + $this->bypassTypeAliases, + $this->constUses, + ); + } + public function withClassName(string $className): self { return new self( diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 0c1d4e1b490..838d7e15e94 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -240,7 +240,7 @@ private function createNameScope(Scope $scope): NameScope $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, $function !== null ? $function->getName() : null, - ); + )->withoutNamespaceAndUses(); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index fe7bacfed49..3c54d1da2c0 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -153,6 +153,12 @@ public function testExtendsPdoStatementCrash(): void $this->assertNoErrors($errors); } + public function testBug12803(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12803.php'); + $this->assertNoErrors($errors); + } + public function testArrayDestructuringArrayDimFetch(): void { $errors = $this->runAnalyse(__DIR__ . '/data/array-destructuring-array-dim-fetch.php'); @@ -1204,7 +1210,8 @@ public function testBug5091(): void public function testBug9459(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php'); - $this->assertCount(0, $errors); + $this->assertCount(1, $errors); + $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); } public function testBug9573(): void diff --git a/tests/PHPStan/Analyser/data/bug-12803.php b/tests/PHPStan/Analyser/data/bug-12803.php new file mode 100644 index 00000000000..60e6ecf05c5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12803.php @@ -0,0 +1,27 @@ + $a */ + $a = $this->c(fn() => (object) ['bar' => 1, 'foo' => 2]); + $b = $this->c(fn() => (object) ['bar' => 1, 'foo' => 2]); + } + + /** + * @template T + * @param callable(): T $callback + * @return Generic + */ + public function c(callable $callback): Generic + { + return new Generic(); + } +} diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 92693793878..dfd42577ece 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -221,7 +221,12 @@ public function testBug11535(): void $this->checkTypeAgainstPhpDocType = true; $this->strictWideningCheck = true; - $this->analyse([__DIR__ . '/data/bug-11535.php'], []); + $this->analyse([__DIR__ . '/data/bug-11535.php'], [ + [ + 'PHPDoc tag @var with type Closure(string): array is not subtype of native type Closure(string): array{1, 2, 3}.', + 6, + ], + ]); } public function testEnums(): void @@ -570,4 +575,11 @@ public function testBug12457(): void ]); } + public function testNewIsAlwaysFinalClass(): void + { + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/new-is-always-final-var-tag-type.php'], []); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php b/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php new file mode 100644 index 00000000000..3b705fbf072 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/new-is-always-final-var-tag-type.php @@ -0,0 +1,23 @@ +returnStatic(); +}; From dc8d8d0f708e6613f8284dbdd6e16f111331d5b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 27 Mar 2025 14:42:42 +0100 Subject: [PATCH 1232/3097] Fix build --- .../Rules/Classes/DuplicateDeclarationRuleTest.php | 5 +++++ .../Rules/EnumCases/EnumCaseAttributesRuleTest.php | 5 +++++ tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php | 4 ++++ .../PhpDoc/RequireExtendsDefinitionClassRuleTest.php | 8 ++------ .../PhpDoc/RequireExtendsDefinitionTraitRuleTest.php | 5 +++++ .../RequireImplementsDefinitionClassRuleTest.php | 11 +++++++---- .../RequireImplementsDefinitionTraitRuleTest.php | 8 ++------ 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php index e75c5907565..5390b57ac99 100644 --- a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -67,6 +68,10 @@ public function testDuplicatePromotedProperty(): void public function testDuplicateEnumCase(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/duplicate-enum-cases.php'], [ [ 'Cannot redeclare enum case DuplicatedEnumCase\Foo::BAR.', diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index d61265e3e79..92012f0dbea 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -13,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -47,6 +48,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/enum-case-attributes.php'], [ [ 'Attribute class EnumCaseAttributes\AttributeWithPropertyTarget does not have the class constant target.', diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 2649996d3f2..54206ff2dec 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -227,6 +227,10 @@ public function testBug7766(): void public function testBug8846(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->checkExplicitMixed = true; $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-8846.php'], []); diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 5a24e755029..1518e6bc289 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -32,11 +32,8 @@ protected function getRule(): Rule public function testRule(): void { - $enumError = 'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeEnum.'; - $enumTip = null; if (PHP_VERSION_ID < 80100) { - $enumError = 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeEnum.'; - $enumTip = 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + $this->markTestSkipped('Test requires PHP 8.1.'); } $this->analyse([__DIR__ . '/data/incompatible-require-extends.php'], [ @@ -49,9 +46,8 @@ public function testRule(): void 13, ], [ - $enumError, + 'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeEnum.', 18, - $enumTip, ], [ 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\TypeDoesNotExist.', diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index 746c3b9007a..153f2b31736 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -7,6 +7,7 @@ use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -32,6 +33,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + $this->analyse([__DIR__ . '/data/incompatible-require-extends.php'], [ [ 'PHPDoc tag @phpstan-require-extends cannot contain final class IncompatibleRequireExtends\SomeFinalClass.', diff --git a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionClassRuleTest.php index 06cb9fdf88b..fcba96a92ed 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionClassRuleTest.php @@ -4,6 +4,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -18,7 +19,11 @@ protected function getRule(): Rule public function testRule(): void { - $expectedErrors = [ + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/incompatible-require-implements.php'], [ [ 'PHPDoc tag @phpstan-require-implements is only valid on trait.', 40, @@ -27,9 +32,7 @@ public function testRule(): void 'PHPDoc tag @phpstan-require-implements is only valid on trait.', 45, ], - ]; - - $this->analyse([__DIR__ . '/data/incompatible-require-implements.php'], $expectedErrors); + ]); } } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php index ad2f2f7cbac..6ce92598f61 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php @@ -31,11 +31,8 @@ protected function getRule(): Rule public function testRule(): void { - $enumError = 'PHPDoc tag @phpstan-require-implements cannot contain non-interface type IncompatibleRequireImplements\SomeEnum.'; - $enumTip = null; if (PHP_VERSION_ID < 80100) { - $enumError = 'PHPDoc tag @phpstan-require-implements contains unknown class IncompatibleRequireImplements\SomeEnum.'; - $enumTip = 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; + $this->markTestSkipped('Test requires PHP 8.1.'); } $expectedErrors = [ @@ -44,9 +41,8 @@ public function testRule(): void 8, ], [ - $enumError, + 'PHPDoc tag @phpstan-require-implements cannot contain non-interface type IncompatibleRequireImplements\SomeEnum.', 13, - $enumTip, ], [ 'PHPDoc tag @phpstan-require-implements contains unknown class IncompatibleRequireImplements\TypeDoesNotExist.', From e5b2baf24908d0aee1d048dfa65211ed96a2529a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 28 Mar 2025 11:41:24 +0100 Subject: [PATCH 1233/3097] Added regression test --- ...nexistentOffsetInArrayDimFetchRuleTest.php | 7 ++++ tests/PHPStan/Rules/Arrays/data/bug-12593.php | 35 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12593.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 58806979a61..52305f8c6f7 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -907,4 +907,11 @@ public function testBug11602(): void $this->analyse([__DIR__ . '/data/bug-11602.php'], []); } + public function testBug12593(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-12593.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12593.php b/tests/PHPStan/Rules/Arrays/data/bug-12593.php new file mode 100644 index 00000000000..b5ba2616cfb --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12593.php @@ -0,0 +1,35 @@ + $indexes + */ + protected function removeArguments(array $indexes): void + { + if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { + foreach ($indexes as $index) { + if (isset($_SERVER['argv'][$index])) { + unset($_SERVER['argv'][$index]); + } + } + } + } +} + +class HelloWorld2 +{ + /** + * @param list $indexes + */ + protected function removeArguments(array $indexes): void + { + foreach ($indexes as $index) { + if (isset($_SERVER['argv']) && is_array($_SERVER['argv']) && isset($_SERVER['argv'][$index])) { + unset($_SERVER['argv'][$index]); + } + } + } +} From a2834bcc6894f8d0dce299507735473cfa1f8f54 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 28 Mar 2025 13:14:39 +0100 Subject: [PATCH 1234/3097] RuleTestCase and TypeInferenceTestCase looking for wrongly configured autoloading - show the hint only if there are other failures --- src/Testing/DelayedRule.php | 57 +++++++++++++++++ src/Testing/RuleTestCase.php | 49 ++++++++++++-- src/Testing/TypeInferenceTestCase.php | 64 +++++++++++++------ .../NonexistentAnalysedClassRuleTest.php | 35 +++++++--- .../Testing/TypeInferenceTestCaseTest.php | 9 ++- .../nonexistentClasses-error.php | 21 ++++++ 6 files changed, 200 insertions(+), 35 deletions(-) create mode 100644 src/Testing/DelayedRule.php create mode 100644 tests/notAutoloaded/nonexistentClasses-error.php diff --git a/src/Testing/DelayedRule.php b/src/Testing/DelayedRule.php new file mode 100644 index 00000000000..a3ae3701110 --- /dev/null +++ b/src/Testing/DelayedRule.php @@ -0,0 +1,57 @@ + + */ +final class DelayedRule implements Rule +{ + + private Registry $registry; + + /** @var list */ + private array $errors = []; + + /** + * @param Rule $rule + */ + public function __construct(Rule $rule) + { + $this->registry = new DirectRegistry([$rule]); + } + + public function getNodeType(): string + { + return Node::class; + } + + /** + * @return list + */ + public function getDelayedErrors(): array + { + return $this->errors; + } + + public function processNode(Node $node, Scope $scope): array + { + $nodeType = get_class($node); + foreach ($this->registry->getRules($nodeType) as $rule) { + foreach ($rule->processNode($node, $scope) as $error) { + $this->errors[] = $error; + } + } + + return []; + } + +} diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 2f28598257e..cd0f52534e1 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -27,12 +27,14 @@ use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Properties\DirectReadWritePropertiesExtensionProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtension; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; use function array_map; +use function array_merge; use function count; use function implode; use function sprintf; @@ -136,7 +138,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser */ public function analyse(array $files, array $expectedErrors): void { - $actualErrors = $this->gatherAnalyserErrors($files); + [$actualErrors, $delayedErrors] = $this->gatherAnalyserErrorsWithDelayedErrors($files); $strictlyTypedSprintf = static function (int $line, string $message, ?string $tip): string { $message = sprintf('%02d: %s', $line, $message); if ($tip !== null) { @@ -162,7 +164,30 @@ static function (Error $error) use ($strictlyTypedSprintf): string { $actualErrors, ); - $this->assertSame(implode("\n", $expectedErrors) . "\n", implode("\n", $actualErrors) . "\n"); + $expectedErrorsString = implode("\n", $expectedErrors) . "\n"; + $actualErrorsString = implode("\n", $actualErrors) . "\n"; + + if (count($delayedErrors) === 0) { + $this->assertSame($expectedErrorsString, $actualErrorsString); + return; + } + + if ($expectedErrorsString === $actualErrorsString) { + $this->assertSame($expectedErrorsString, $actualErrorsString); + return; + } + + $actualErrorsString .= sprintf( + "\n%s might be reported because of the following misconfiguration %s:\n\n", + count($actualErrors) === 1 ? 'This error' : 'These errors', + count($delayedErrors) === 1 ? 'issue' : 'issues', + ); + + foreach ($delayedErrors as $delayedError) { + $actualErrorsString .= sprintf("* %s\n", $delayedError->getMessage()); + } + + $this->assertSame($expectedErrorsString, $actualErrorsString); } /** @@ -170,12 +195,23 @@ static function (Error $error) use ($strictlyTypedSprintf): string { * @return list */ public function gatherAnalyserErrors(array $files): array + { + return $this->gatherAnalyserErrorsWithDelayedErrors($files)[0]; + } + + /** + * @param string[] $files + * @return array{list, list} + */ + private function gatherAnalyserErrorsWithDelayedErrors(array $files): array { $reflectionProvider = $this->createReflectionProvider(); + $classRule = new DelayedRule(new NonexistentAnalysedClassRule($reflectionProvider)); + $traitRule = new DelayedRule(new NonexistentAnalysedTraitRule($reflectionProvider)); $ruleRegistry = new DirectRuleRegistry([ $this->getRule(), - new NonexistentAnalysedClassRule($reflectionProvider), - new NonexistentAnalysedTraitRule($reflectionProvider), + $classRule, + $traitRule, ]); $files = array_map([$this->getFileHelper(), 'normalizePath'], $files); $analyserResult = $this->getAnalyser($ruleRegistry)->analyse( @@ -204,7 +240,10 @@ public function gatherAnalyserErrors(array $files): array true, ); - return $finalizer->finalize($analyserResult, false, true)->getAnalyserResult()->getUnorderedErrors(); + return [ + $finalizer->finalize($analyserResult, false, true)->getAnalyserResult()->getUnorderedErrors(), + array_merge($classRule->getDelayedErrors(), $traitRule->getDelayedErrors()), + ]; } protected function shouldPolluteScopeWithLoopInitialAssignments(): bool diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 38806ce1671..dc3e884c885 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -126,21 +126,45 @@ public function assertFileAsserts( $actual = $args[1]; } + $failureMessage = sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]); + + $delayedErrors = $args[3] ?? []; + if (count($delayedErrors) > 0) { + $failureMessage .= sprintf( + "\n\nThis failure might be reported because of the following misconfiguration %s:\n\n", + count($delayedErrors) === 1 ? 'issue' : 'issues', + ); + foreach ($delayedErrors as $delayedError) { + $failureMessage .= sprintf("* %s\n", $delayedError); + } + } + $this->assertSame( $expected, $actual, - sprintf('Expected type %s, got type %s in %s on line %d.', $expected, $actual, $file, $args[2]), + $failureMessage, ); } elseif ($assertType === 'variableCertainty') { $expectedCertainty = $args[0]; $actualCertainty = $args[1]; $variableName = $args[2]; + + $failureMessage = sprintf('Expected %s, actual certainty of %s is %s in %s on line %d.', $expectedCertainty->describe(), $variableName, $actualCertainty->describe(), $file, $args[3]); + $delayedErrors = $args[4] ?? []; + if (count($delayedErrors) > 0) { + $failureMessage .= sprintf( + "\n\nThis failure might be reported because of the following misconfiguration %s:\n\n", + count($delayedErrors) === 1 ? 'issue' : 'issues', + ); + foreach ($delayedErrors as $delayedError) { + $failureMessage .= sprintf("* %s\n", $delayedError); + } + } + $this->assertTrue( $expectedCertainty->equals($actualCertainty), - sprintf('Expected %s, actual certainty of %s is %s in %s on line %d.', $expectedCertainty->describe(), $variableName, $actualCertainty->describe(), $file, $args[3]), + $failureMessage, ); - } elseif ($assertType === 'error') { - $this->fail($args[0]); } } @@ -158,30 +182,23 @@ public static function gatherAssertTypes(string $file): array $file = $fileHelper->normalizePath($file); $asserts = []; - self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, $file, $relativePathHelper, $reflectionProvider): void { + $delayedErrors = []; + self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, &$delayedErrors, $file, $relativePathHelper, $reflectionProvider): void { if ($node instanceof InClassNode) { if (!$reflectionProvider->hasClass($node->getClassReflection()->getName())) { - $asserts[$file . ':' . $node->getStartLine()] = [ - 'error', + $delayedErrors[] = sprintf( + '%s %s in %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', + $node->getClassReflection()->getClassTypeDescription(), + $node->getClassReflection()->getName(), $file, - sprintf( - '%s %s in %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', - $node->getClassReflection()->getClassTypeDescription(), - $node->getClassReflection()->getName(), - $file, - ), - ]; + ); } } elseif ($node instanceof Node\Stmt\Trait_) { if ($node->namespacedName === null) { throw new ShouldNotHappenException(); } if (!$reflectionProvider->hasClass($node->namespacedName->toString())) { - $asserts[$file . ':' . $node->getStartLine()] = [ - 'error', - $file, - sprintf('Trait %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', $node->namespacedName->toString()), - ]; + $delayedErrors[] = sprintf('Trait %s not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', $node->namespacedName->toString()); } } if (!$node instanceof Node\Expr\FuncCall) { @@ -303,6 +320,15 @@ public static function gatherAssertTypes(string $file): array self::fail(sprintf('File %s does not contain any asserts', $file)); } + if (count($delayedErrors) === 0) { + return $asserts; + } + + foreach ($asserts as $i => $assert) { + $assert[] = $delayedErrors; + $asserts[$i] = $assert; + } + return $asserts; } diff --git a/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php b/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php index 5d1edc427b2..706f0e1533f 100644 --- a/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php +++ b/tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php @@ -6,6 +6,8 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; +use PHPUnit\Framework\ExpectationFailedException; /** * @extends RuleTestCase> @@ -24,6 +26,15 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($node->name instanceof Node\Name && $node->name->toString() === 'error') { + return [ + RuleErrorBuilder::message('Error call') + ->identifier('test.errorCall') + ->nonIgnorable() + ->build(), + ]; + } + return []; } @@ -32,16 +43,20 @@ public function processNode(Node $node, Scope $scope): array public function testRule(): void { - $this->analyse([__DIR__ . '/../../notAutoloaded/nonexistentClasses.php'], [ - [ - 'Class NamespaceForNonexistentClasses\Foo not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', - 7, - ], - [ - 'Trait NamespaceForNonexistentClasses\FooTrait not found in ReflectionProvider. Configure "autoload-dev" section in composer.json to include your tests directory.', - 17, - ], - ]); + $this->analyse([__DIR__ . '/../../notAutoloaded/nonexistentClasses.php'], []); + } + + public function testRuleWithError(): void + { + try { + $this->analyse([__DIR__ . '/../../notAutoloaded/nonexistentClasses-error.php'], []); + $this->fail('Should have failed'); + } catch (ExpectationFailedException $e) { + if ($e->getComparisonFailure() === null) { + throw $e; + } + $this->assertStringContainsString('not found in ReflectionProvider', $e->getComparisonFailure()->getDiff()); + } } } diff --git a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php index ede07b3c7bb..c8220a0e9a7 100644 --- a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php +++ b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php @@ -102,9 +102,16 @@ public function testVariableOrOffsetDescription(): void } public function testNonexistentClassInAnalysedFile(): void + { + foreach ($this->gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses.php') as $data) { + $this->assertFileAsserts(...$data); + } + } + + public function testNonexistentClassInAnalysedFileWithError(): void { try { - foreach ($this->gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses.php') as $data) { + foreach ($this->gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses-error.php') as $data) { $this->assertFileAsserts(...$data); } diff --git a/tests/notAutoloaded/nonexistentClasses-error.php b/tests/notAutoloaded/nonexistentClasses-error.php new file mode 100644 index 00000000000..61c09f8bbf4 --- /dev/null +++ b/tests/notAutoloaded/nonexistentClasses-error.php @@ -0,0 +1,21 @@ + Date: Fri, 28 Mar 2025 14:26:26 +0100 Subject: [PATCH 1235/3097] Fixed false positive about undefined property guarded with property_exists --- .../Properties/AccessPropertiesCheck.php | 14 +++++++++ .../PropertyExistsTypeSpecifyingExtension.php | 9 +++++- .../Properties/AccessPropertiesRuleTest.php | 8 +++++ .../Rules/Properties/data/property-exists.php | 29 +++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Properties/data/property-exists.php diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index f1a70365a7a..467cf98a99a 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -2,9 +2,12 @@ namespace PHPStan\Rules\Properties; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Identifier; +use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; @@ -143,6 +146,17 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string } } + if ($node->name instanceof Expr) { + $propertyExistsExpr = new FuncCall(new FullyQualified('property_exists'), [ + new Arg($node->var), + new Arg($node->name), + ]); + + if ($scope->getType($propertyExistsExpr)->isTrue()->yes()) { + return []; + } + } + $ruleErrorBuilder = RuleErrorBuilder::message(sprintf( 'Access to an undefined property %s::$%s.', $typeForDescribe->describe(VerbosityLevel::typeOnly()), diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 407233ae0b4..8b4d51142ae 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Identifier; +use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -13,6 +14,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\Accessory\HasPropertyType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\IntersectionType; @@ -53,7 +55,12 @@ public function specifyTypes( { $propertyNameType = $scope->getType($node->getArgs()[1]->value); if (!$propertyNameType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); + return $this->typeSpecifier->create( + new FuncCall(new FullyQualified('property_exists'), $node->getRawArgs()), + new ConstantBooleanType(true), + $context, + $scope, + ); } if ($propertyNameType->getValue() === '') { diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index e6aa299b750..a0aa4a72f54 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -1035,4 +1035,12 @@ public function testNewIsAlwaysFinalClass(): void ]); } + public function testPropertyExists(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/property-exists.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/property-exists.php b/tests/PHPStan/Rules/Properties/data/property-exists.php new file mode 100644 index 00000000000..cb7e9980270 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/property-exists.php @@ -0,0 +1,29 @@ +{$column}; + } + } + } +} From ae5562fc29737d3f4a724bdd351c23f1347d569d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 28 Mar 2025 14:41:17 +0100 Subject: [PATCH 1236/3097] Fixed false positive about undefined method guarded with method_exists --- src/Rules/Methods/CallMethodsRule.php | 2 +- src/Rules/Methods/MethodCallCheck.php | 15 +++++++++++ src/Rules/Methods/MethodCallableRule.php | 2 +- .../MethodExistsTypeSpecifyingExtension.php | 9 ++++++- .../Rules/Methods/CallMethodsRuleTest.php | 10 +++++++ .../PHPStan/Rules/Methods/data/bug-12793.php | 26 +++++++++++++++++++ 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12793.php diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 68957aa2e59..8c01d0118a0 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -58,7 +58,7 @@ public function processNode(Node $node, Scope $scope): array */ private function processSingleMethodCall(Scope $scope, MethodCall $node, string $methodName): array { - [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodName, $node->var); + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodName, $node->var, $node->name); if ($methodReflection === null) { return $errors; } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index d20760f847a..06cbf2e9ca5 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -2,7 +2,10 @@ namespace PHPStan\Rules\Methods; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; @@ -38,6 +41,7 @@ public function check( Scope $scope, string $methodName, Expr $var, + Identifier|Expr $astName, ): array { $typeResult = $this->ruleLevelHelper->findTypeToCheck( @@ -107,6 +111,17 @@ public function check( } } + if ($astName instanceof Expr) { + $methodExistsExpr = new Expr\FuncCall(new FullyQualified('method_exists'), [ + new Arg($var), + new Arg($astName), + ]); + + if ($scope->getType($methodExistsExpr)->isTrue()->yes()) { + return [[], null]; + } + } + return [ [ RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/MethodCallableRule.php b/src/Rules/Methods/MethodCallableRule.php index 2d189436089..b91b0537bf9 100644 --- a/src/Rules/Methods/MethodCallableRule.php +++ b/src/Rules/Methods/MethodCallableRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array $methodNameName = $methodName->toString(); - [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getVar()); + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getVar(), $node->getName()); if ($methodReflection === null) { return $errors; } diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index ba67e53322e..751a69a41a6 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Php; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -11,6 +12,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\ClassStringType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\IntersectionType; @@ -48,7 +50,12 @@ public function specifyTypes( { $methodNameType = $scope->getType($node->getArgs()[1]->value); if (!$methodNameType instanceof ConstantStringType) { - return new SpecifiedTypes([], []); + return $this->typeSpecifier->create( + new FuncCall(new FullyQualified('method_exists'), $node->getRawArgs()), + new ConstantBooleanType(true), + $context, + $scope, + ); } $objectType = $scope->getType($node->getArgs()[0]->value); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index e544eeeb5bc..133e6fd6730 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3587,4 +3587,14 @@ public function testDynamicCall(): void ]); } + public function testBu12793(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12793.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12793.php b/tests/PHPStan/Rules/Methods/data/bug-12793.php new file mode 100644 index 00000000000..63339ebe09e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12793.php @@ -0,0 +1,26 @@ +{$column}(); + } + } + } +} + +class Model {} From 194ba99cba7ad39d433e04b33b74b38ec9276c3f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 28 Mar 2025 14:44:21 +0100 Subject: [PATCH 1237/3097] Offset on list definitely exists if there's HasOffsetType with higher number --- src/Type/IntersectionType.php | 18 +++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 16 ++++++++ tests/PHPStan/Rules/Arrays/data/bug-12605.php | 37 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12605.php diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index ab9f86d0344..1361f827287 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -28,6 +28,8 @@ use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; +use PHPStan\Type\Accessory\HasOffsetType; +use PHPStan\Type\Accessory\HasOffsetValueType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -45,6 +47,7 @@ use function count; use function implode; use function in_array; +use function is_int; use function ksort; use function md5; use function sprintf; @@ -738,6 +741,21 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic if ((new ConstantIntegerType(0))->isSuperTypeOf($arrayKeyOffsetType)->yes()) { return TrinaryLogic::createYes(); } + + foreach ($this->types as $type) { + if (!$type instanceof HasOffsetValueType && !$type instanceof HasOffsetType) { + continue; + } + + foreach ($type->getOffsetType()->getConstantScalarValues() as $constantScalarValue) { + if (!is_int($constantScalarValue)) { + continue; + } + if (IntegerRangeType::fromInterval(0, $constantScalarValue)->isSuperTypeOf($arrayKeyOffsetType)->yes()) { + return TrinaryLogic::createYes(); + } + } + } } return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasOffsetValueType($offsetType)); diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 52305f8c6f7..df2cb2cf269 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -900,6 +900,22 @@ public function testNarrowSuperglobals(): void $this->analyse([__DIR__ . '/data/narrow-superglobal.php'], []); } + public function testBug12605(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-12605.php'], [ + [ + 'Offset 1 might not exist on list.', + 19, + ], + [ + 'Offset 10 might not exist on non-empty-list.', + 26, + ], + ]); + } + public function testBug11602(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12605.php b/tests/PHPStan/Rules/Arrays/data/bug-12605.php new file mode 100644 index 00000000000..c5d31f966c3 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12605.php @@ -0,0 +1,37 @@ + + */ +function test(): array +{ + return []; +} + +function doFoo(): void { + $test = test(); + + if (isset($test[3])) { + echo $test[1]; + } + echo $test[1]; +} + +function doFooBar(): void { + $test = test(); + + if (isset($test[4])) { + echo $test[10]; + } +} + +function doBaz(): void { + $test = test(); + + if (array_key_exists(5, $test) && is_int($test[5])) { + echo $test[3]; + } +} + From 17beb015be6b6c4a05a0df996deca4287b9e7a36 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 29 Mar 2025 08:28:40 +0100 Subject: [PATCH 1238/3097] Set offset on list keeps list if there's HasOffsetType for all preceeding offsets --- src/Type/IntersectionType.php | 26 ++++++++++++-- tests/PHPStan/Analyser/nsrt/list-type.php | 41 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 1361f827287..149536a573f 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -800,8 +800,30 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni $result = $this->intersectTypes(static fn (Type $type): Type => $type->setOffsetValueType($offsetType, $valueType, $unionValues)); - if ($offsetType !== null && $this->isList()->yes() && $this->isIterableAtLeastOnce()->yes() && (new ConstantIntegerType(1))->isSuperTypeOf($offsetType)->yes()) { - $result = TypeCombinator::intersect($result, new AccessoryArrayListType()); + if ( + $offsetType !== null + && $this->isList()->yes() + && !$result->isList()->yes() + ) { + if ($this->isIterableAtLeastOnce()->yes() && (new ConstantIntegerType(1))->isSuperTypeOf($offsetType)->yes()) { + $result = TypeCombinator::intersect($result, new AccessoryArrayListType()); + } else { + foreach ($this->types as $type) { + if (!$type instanceof HasOffsetValueType && !$type instanceof HasOffsetType) { + continue; + } + + foreach ($type->getOffsetType()->getConstantScalarValues() as $constantScalarValue) { + if (!is_int($constantScalarValue)) { + continue; + } + if (IntegerRangeType::fromInterval(0, $constantScalarValue + 1)->isSuperTypeOf($offsetType)->yes()) { + $result = TypeCombinator::intersect($result, new AccessoryArrayListType()); + break 2; + } + } + } + } } return $result; diff --git a/tests/PHPStan/Analyser/nsrt/list-type.php b/tests/PHPStan/Analyser/nsrt/list-type.php index 63b9e0037cc..8101c1744bc 100644 --- a/tests/PHPStan/Analyser/nsrt/list-type.php +++ b/tests/PHPStan/Analyser/nsrt/list-type.php @@ -128,4 +128,45 @@ public function testSetOffsetExplicitlyWithGap(array $list): void assertType('non-empty-array, int>&hasOffsetValue(0, 17)&hasOffsetValue(2, 21)', $list); } + /** @param list $list */ + function testAppendImmediatelyAfterLastElement(array $list): void + { + assertType('list', $list); + $list[0] = 17; + assertType('non-empty-list&hasOffsetValue(0, 17)', $list); + $list[1] = 19; + assertType('non-empty-list&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)', $list); + $list[2] = 21; + assertType('non-empty-list&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)&hasOffsetValue(2, 21)', $list); + $list[3] = 21; + assertType('non-empty-list&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)&hasOffsetValue(2, 21)&hasOffsetValue(3, 21)', $list); + + // hole in the list -> turns it into a array + + $list[5] = 21; + assertType('non-empty-array, int>&hasOffsetValue(0, 17)&hasOffsetValue(1, 19)&hasOffsetValue(2, 21)&hasOffsetValue(3, 21)&hasOffsetValue(5, 21)', $list); + } + + + /** @param list $list */ + function testKeepListAfterLast(array $list): void + { + if (isset($list[5])) { + assertType('non-empty-list&hasOffsetValue(5, int)', $list); + $list[6] = 21; + assertType('non-empty-list&hasOffsetValue(5, int)&hasOffsetValue(6, 21)', $list); + } + assertType('list', $list); + } + + /** @param list $list */ + function testKeepListAfterLastArrayKey(array $list): void + { + if (array_key_exists(5, $list) && is_int($list[5])) { + assertType('non-empty-list&hasOffsetValue(5, int)', $list); + $list[6] = 21; + assertType('non-empty-list&hasOffsetValue(5, int)&hasOffsetValue(6, 21)', $list); + } + assertType('list', $list); + } } From b225f74805542bddc5249156d84143c0eddbc51e Mon Sep 17 00:00:00 2001 From: schlndh Date: Sat, 29 Mar 2025 13:45:52 +0100 Subject: [PATCH 1239/3097] Fix union/intersect involving enum case --- src/Type/Enum/EnumCaseObjectType.php | 9 ++- src/Type/TypeCombinator.php | 32 ++++++++--- .../Analyser/LegacyNodeScopeResolverTest.php | 4 +- .../Analyser/nsrt/enum-vs-in-array.php | 43 +++++++++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 55 ++++++++++++++++++- 5 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/enum-vs-in-array.php diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 5e803a6af95..e3ae5e23f5d 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -18,6 +18,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IsSuperTypeOfResult; +use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\SubtractableType; use PHPStan\Type\Type; @@ -94,7 +95,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult public function subtract(Type $type): Type { - return $this; + return $this->changeSubtractedType($type); } public function getTypeWithoutSubtractedType(): Type @@ -104,7 +105,11 @@ public function getTypeWithoutSubtractedType(): Type public function changeSubtractedType(?Type $subtractedType): Type { - return $this; + if ($subtractedType === null || ! $this->equals($subtractedType)) { + return $this; + } + + return new NeverType(); } public function getSubtractedType(): ?Type diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 37ae13258f6..29b57c15937 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -595,17 +595,31 @@ private static function intersectWithSubtractedType( } $subtractedType = self::union(...$subtractedTypes); - } elseif ($b instanceof SubtractableType) { - $subtractedType = $b->getSubtractedType(); - if ($subtractedType === null) { - return $a->getTypeWithoutSubtractedType(); - } } else { - $subtractedTypeTmp = self::intersect($a->getTypeWithoutSubtractedType(), $a->getSubtractedType()); - if ($b->isSuperTypeOf($subtractedTypeTmp)->yes()) { - return $a->getTypeWithoutSubtractedType(); + $isBAlreadySubtracted = $a->getSubtractedType()->isSuperTypeOf($b); + + if ($isBAlreadySubtracted->no()) { + return $a; + } elseif ($isBAlreadySubtracted->yes()) { + $subtractedType = self::remove($a->getSubtractedType(), $b); + + if ($subtractedType instanceof NeverType) { + $subtractedType = null; + } + + return $a->changeSubtractedType($subtractedType); + } elseif ($b instanceof SubtractableType) { + $subtractedType = $b->getSubtractedType(); + if ($subtractedType === null) { + return $a->getTypeWithoutSubtractedType(); + } + } else { + $subtractedTypeTmp = self::intersect($a->getTypeWithoutSubtractedType(), $a->getSubtractedType()); + if ($b->isSuperTypeOf($subtractedTypeTmp)->yes()) { + return $a->getTypeWithoutSubtractedType(); + } + $subtractedType = new MixedType(false, $b); } - $subtractedType = new MixedType(false, $b); } $subtractedType = self::intersect( diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 8b89e0935ef..167cad5f8e5 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8105,11 +8105,11 @@ public function dataArrayKeysInBranches(): array '$array', ], [ - 'non-empty-array&hasOffsetValue(\'key\', mixed)', + 'non-empty-array&hasOffsetValue(\'key\', mixed~null)', '$generalArray', ], [ - 'mixed', + 'mixed~null', '$generalArray[\'key\']', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/enum-vs-in-array.php b/tests/PHPStan/Analyser/nsrt/enum-vs-in-array.php new file mode 100644 index 00000000000..4a9a22e4c5a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/enum-vs-in-array.php @@ -0,0 +1,43 @@ += 8.1 + +declare(strict_types = 1); + +namespace EnumVsInArray; + +use function PHPStan\Testing\assertType; + +enum FooEnum +{ + case A; + case B; + case C; + case D; + case E; + case F; + case G; + case H; + case I; + case J; +} + +function foo(FooEnum $e): int +{ + if (in_array($e, [FooEnum::A, FooEnum::B, FooEnum::C], true)) { + throw new \Exception('a'); + } + + assertType('EnumVsInArray\FooEnum~(EnumVsInArray\FooEnum::A|EnumVsInArray\FooEnum::B|EnumVsInArray\FooEnum::C)', $e); + + if (rand(0, 10) === 1) { + if (!in_array($e, [FooEnum::D, FooEnum::E], true)) { + throw new \Exception('d'); + } + } + + assertType('EnumVsInArray\FooEnum~(EnumVsInArray\FooEnum::A|EnumVsInArray\FooEnum::B|EnumVsInArray\FooEnum::C)', $e); + + return match ($e) { + FooEnum::D, FooEnum::E, FooEnum::F, FooEnum::G, FooEnum::H, FooEnum::I => 2, + FooEnum::J => 3, + }; +} diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 7dd88c8e695..a62073a40c7 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1069,7 +1069,7 @@ public function dataUnion(): iterable new ObjectWithoutClassType(new ObjectType('A')), ], MixedType::class, - 'mixed=implicit', + 'mixed~int=implicit', ], [ [ @@ -1125,7 +1125,7 @@ public function dataUnion(): iterable new ObjectType('InvalidArgumentException'), ], MixedType::class, - 'mixed=implicit', // should be MixedType~Exception+InvalidArgumentException + 'mixed~Exception~InvalidArgumentException=implicit', ], [ [ @@ -2262,6 +2262,36 @@ public function dataUnion(): iterable 'PHPStan\Fixture\ManyCasesTestEnum~PHPStan\Fixture\ManyCasesTestEnum::A', ]; + yield [ + [ + new ObjectType('PHPStan\Fixture\ManyCasesTestEnum', new UnionType([ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'), + ])), + new UnionType([ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'C'), + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'D'), + ]), + ], + ObjectType::class, + 'PHPStan\Fixture\ManyCasesTestEnum~(PHPStan\Fixture\ManyCasesTestEnum::A|PHPStan\Fixture\ManyCasesTestEnum::B)', + ]; + + yield [ + [ + new ObjectType('PHPStan\Fixture\ManyCasesTestEnum', new UnionType([ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'), + ])), + new UnionType([ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'D'), + ]), + ], + ObjectType::class, + 'PHPStan\Fixture\ManyCasesTestEnum~PHPStan\Fixture\ManyCasesTestEnum::B', + ]; + yield [ [ new ThisType( @@ -4224,6 +4254,27 @@ public function dataIntersect(): iterable '$this(stdClass)&stdClass::foo', ]; + yield [ + [ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), + new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + + yield [ + [ + new UnionType([ + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), + new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'), + ]), + new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), + ], + EnumCaseObjectType::class, + 'PHPStan\Fixture\ManyCasesTestEnum::B', + ]; + yield [ [ TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()), From 9efcdf565b067f762bda9be55cd38b70c8cc96ea Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 30 Mar 2025 13:20:23 +0200 Subject: [PATCH 1240/3097] Fix lost list-type if substituted a element via loop --- src/Analyser/NodeScopeResolver.php | 35 +++++- tests/PHPStan/Analyser/nsrt/bug-12274.php | 109 ++++++++++++++++++ .../Rules/Functions/ReturnTypeRuleTest.php | 14 +++ 3 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12274.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0135b2dab77..668c3aa9bd2 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5450,14 +5450,15 @@ private function processAssignVar( $offsetValueType = $varType; $offsetNativeValueType = $varNativeType; - $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetTypes, $offsetValueType, $valueToWrite); + $valueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetTypes, $offsetValueType, $valueToWrite, $scope); if (!$offsetValueType->equals($offsetNativeValueType) || !$valueToWrite->equals($nativeValueToWrite)) { - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope); } else { $rewritten = false; foreach ($offsetTypes as $i => $offsetType) { $offsetNativeType = $offsetNativeTypes[$i]; + if ($offsetType === null) { if ($offsetNativeType !== null) { throw new ShouldNotHappenException(); @@ -5471,7 +5472,7 @@ private function processAssignVar( continue; } - $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite); + $nativeValueToWrite = $this->produceArrayDimFetchAssignValueToWrite($dimFetchStack, $offsetNativeTypes, $offsetNativeValueType, $nativeValueToWrite, $scope); $rewritten = true; break; } @@ -5784,9 +5785,10 @@ static function (): void { } /** + * @param list $dimFetchStack * @param list $offsetTypes */ - private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type $offsetValueType, Type $valueToWrite): Type + private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, array $offsetTypes, Type $offsetValueType, Type $valueToWrite, Scope $scope): Type { $offsetValueTypeStack = [$offsetValueType]; foreach (array_slice($offsetTypes, 0, -1) as $offsetType) { @@ -5821,6 +5823,31 @@ private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type $offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types)); } $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); + + $arrayDimFetch = $dimFetchStack[$i] ?? null; + if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) { + continue; + } + + if ($scope->hasExpressionType($arrayDimFetch)->yes()) { // keep list for $list[$index] assignments + $valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType()); + } elseif ($arrayDimFetch->dim instanceof BinaryOp\Plus) { + if ( // keep list for $list[$index + 1] assignments + $arrayDimFetch->dim->right instanceof Variable + && $arrayDimFetch->dim->left instanceof Node\Scalar\Int_ + && $arrayDimFetch->dim->left->value === 1 + && $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->right))->yes() + ) { + $valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType()); + } elseif ( // keep list for $list[1 + $index] assignments + $arrayDimFetch->dim->left instanceof Variable + && $arrayDimFetch->dim->right instanceof Node\Scalar\Int_ + && $arrayDimFetch->dim->right->value === 1 + && $scope->hasExpressionType(new ArrayDimFetch($arrayDimFetch->var, $arrayDimFetch->dim->left))->yes() + ) { + $valueToWrite = TypeCombinator::intersect($valueToWrite, new AccessoryArrayListType()); + } + } } return $valueToWrite; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12274.php b/tests/PHPStan/Analyser/nsrt/bug-12274.php new file mode 100644 index 00000000000..437dc09ae3e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12274.php @@ -0,0 +1,109 @@ + $items + * + * @return non-empty-list + */ +function getItems(array $items): array +{ + foreach ($items as $index => $item) { + $items[$index] = 1; + } + + assertType('non-empty-list', $items); + return $items; +} + +/** + * @param non-empty-list $items + * + * @return non-empty-list + */ +function getItemsByModifiedIndex(array $items): array +{ + foreach ($items as $index => $item) { + $index++; + + $items[$index] = 1; + } + + assertType('non-empty-array, int>', $items); + return $items; +} + +/** @param list $list */ +function testKeepListAfterIssetIndex(array $list, int $i): void +{ + if (isset($list[$i])) { + assertType('list', $list); + $list[$i] = 21; + assertType('non-empty-list', $list); + $list[$i+1] = 21; + assertType('non-empty-list', $list); + } + assertType('list', $list); +} + +/** @param list> $nestedList */ +function testKeepNestedListAfterIssetIndex(array $nestedList, int $i, int $j): void +{ + if (isset($nestedList[$i][$j])) { + assertType('list>', $nestedList); + assertType('list', $nestedList[$i]); + $nestedList[$i][$j] = 21; + assertType('non-empty-list>', $nestedList); + assertType('non-empty-list', $nestedList[$i]); + } + assertType('list>', $nestedList); +} + +/** @param list $list */ +function testKeepListAfterIssetIndexPlusOne(array $list, int $i): void +{ + if (isset($list[$i])) { + assertType('list', $list); + $list[$i+1] = 21; + assertType('non-empty-list', $list); + } + assertType('list', $list); +} + +/** @param list $list */ +function testKeepListAfterIssetIndexOnePlus(array $list, int $i): void +{ + if (isset($list[$i])) { + assertType('list', $list); + $list[1+$i] = 21; + assertType('non-empty-list', $list); + } + assertType('list', $list); +} + +/** @param list $list */ +function testShouldLooseListbyAst(array $list, int $i): void +{ + if (isset($list[$i])) { + $i++; + + assertType('list', $list); + $list[1+$i] = 21; + assertType('non-empty-array', $list); + } + assertType('array', $list); +} + +/** @param list $list */ +function testShouldLooseListbyAst2(array $list, int $i): void +{ + if (isset($list[$i])) { + assertType('list', $list); + $list[2+$i] = 21; + assertType('non-empty-array', $list); + } + assertType('array', $list); +} diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 54206ff2dec..12307bf5bcf 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -345,4 +345,18 @@ public function testBug11301(): void ]); } + public function testBug12274(): void + { + $this->checkExplicitMixed = true; + $this->checkNullables = true; + + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12274.php'], [ + [ + 'Function Bug12274\getItemsByModifiedIndex() should return non-empty-list but returns non-empty-array, int>.', + 36, + 'non-empty-array, int> might not be a list.', + ], + ]); + } + } From f9eeeb561d32249e4434b5952f1a2735f3413a69 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 27 Oct 2024 08:56:05 +0100 Subject: [PATCH 1241/3097] Fix signature type for default-null parameters --- .../SignatureMap/Php8SignatureMapProvider.php | 17 ++++++++++++++++- .../CallToFunctionParametersRuleTest.php | 6 ++++++ tests/PHPStan/Rules/Functions/data/bug-7522.php | 8 ++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-7522.php diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 59cbedb60c1..9c456aec188 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\SignatureMap; use PhpParser\Node\AttributeGroup; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassConst; @@ -22,6 +23,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypehintHelper; use ReflectionFunctionAbstract; use function array_key_exists; @@ -409,10 +411,23 @@ private function getSignature( throw new ShouldNotHappenException(); } $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection); + $phpDocParameterType = $phpDocParameterTypes[$name->name] ?? null; + + if ($param->default instanceof ConstFetch) { + $constName = (string) $param->default->name; + $loweredConstName = strtolower($constName); + if ($loweredConstName === 'null') { + $parameterType = TypeCombinator::addNull($parameterType); + if ($phpDocParameterType !== null) { + $phpDocParameterType = TypeCombinator::addNull($phpDocParameterType); + } + } + } + $parameters[] = new ParameterSignature( $name->name, $param->default !== null || $param->variadic, - TypehintHelper::decideType($parameterType, $phpDocParameterTypes[$name->name] ?? null), + TypehintHelper::decideType($parameterType, $phpDocParameterType), $parameterType, $param->byRef ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(), $param->variadic, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a3e82f2a341..1958ec232a5 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2047,4 +2047,10 @@ public function testBug12499(): void $this->analyse([__DIR__ . '/data/bug-12499.php'], []); } + public function testBug7522(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-7522.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-7522.php b/tests/PHPStan/Rules/Functions/data/bug-7522.php new file mode 100644 index 00000000000..cff0bc5897c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-7522.php @@ -0,0 +1,8 @@ + Date: Sun, 30 Mar 2025 15:29:21 +0200 Subject: [PATCH 1242/3097] Disable Override check for traits --- src/Rules/Methods/OverridingMethodRule.php | 1 + .../Methods/OverridingMethodRuleTest.php | 11 +++++ .../PHPStan/Rules/Methods/data/bug-12471.php | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12471.php diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 29a936b35a7..f5022dc94c1 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -109,6 +109,7 @@ public function processNode(Node $node, Scope $scope): array if ( $this->phpVersion->supportsOverrideAttribute() && $this->checkMissingOverrideMethodAttribute + && !$scope->isInTrait() && !$this->hasOverrideAttribute($node->getOriginalNode()) ) { $messages[] = RuleErrorBuilder::message(sprintf( diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 826586689a9..de2f2e9de32 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -820,6 +820,17 @@ public function testBug10153(): void $this->analyse([__DIR__ . '/data/bug-10153.php'], $errors); } + public function testBug12471(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->checkMissingOverrideMethodAttribute = true; + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-12471.php'], []); + } + public function testBug10165(): void { $this->phpVersionId = PHP_VERSION_ID; diff --git a/tests/PHPStan/Rules/Methods/data/bug-12471.php b/tests/PHPStan/Rules/Methods/data/bug-12471.php new file mode 100644 index 00000000000..fd242cc639e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12471.php @@ -0,0 +1,40 @@ + Date: Sat, 5 Apr 2025 11:27:15 +0200 Subject: [PATCH 1243/3097] Update Renovate --- .github/renovate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 5cb51461c6a..59522a2dbc8 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -6,7 +6,7 @@ "dependencyDashboard": true, "rangeStrategy": "update-lockfile", "rebaseWhen": "conflicted", - "baseBranches": ["1.12.x"], + "baseBranches": ["2.1.x"], "packageRules": [ { "matchPackagePatterns": ["*"], @@ -15,7 +15,7 @@ { "matchPaths": ["+(composer.json)"], "enabled": true, - "matchBaseBranches": ["1.11.x"] + "matchBaseBranches": ["2.1.x"] }, { "matchPaths": ["build-cs/**"], From c6159ef54aa96853e5ce093270a226f965ce3b9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 09:28:51 +0000 Subject: [PATCH 1244/3097] Update Wandalen/wretry.action action to v3.8.0 --- .github/workflows/issue-bot.yml | 2 +- .github/workflows/reflection-golden-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 8eeb63a4d54..23662220873 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -97,7 +97,7 @@ jobs: working-directory: "issue-bot" run: "composer install --no-interaction --no-progress" - - uses: Wandalen/wretry.action@v3.7.0 + - uses: Wandalen/wretry.action@v3.8.0 with: action: actions/download-artifact@v4 with: | diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 5aea839c83b..8d0050cfd32 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -72,7 +72,7 @@ jobs: - "8.4" steps: - - uses: Wandalen/wretry.action@v3.7.0 + - uses: Wandalen/wretry.action@v3.8.0 with: action: actions/download-artifact@v4 with: | From 50ef61e8e43272dd6c445d74ea1b839dcd57f7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sat, 5 Apr 2025 18:59:41 +0200 Subject: [PATCH 1245/3097] Fix DateTime::format('u') return type --- src/Type/Php/DateFunctionReturnTypeHelper.php | 6 +++++- tests/PHPStan/Analyser/nsrt/bug-10893.php | 20 +++++++++++++------ tests/PHPStan/Analyser/nsrt/bug-6613.php | 8 ++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Type/Php/DateFunctionReturnTypeHelper.php b/src/Type/Php/DateFunctionReturnTypeHelper.php index 5e154823362..7723ee2151e 100644 --- a/src/Type/Php/DateFunctionReturnTypeHelper.php +++ b/src/Type/Php/DateFunctionReturnTypeHelper.php @@ -76,8 +76,12 @@ public function buildReturnTypeFromFormat(string $formatString, bool $useMicrose return $this->buildNumericRangeType(0, 1, false); case 'u': return $useMicrosec - ? new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]) + ? new IntersectionType([new StringType(), new AccessoryNonFalsyStringType(), new AccessoryNumericStringType()]) : new ConstantStringType('000000'); + case 'v': + return $useMicrosec + ? new IntersectionType([new StringType(), new AccessoryNonFalsyStringType(), new AccessoryNumericStringType()]) + : new ConstantStringType('000'); } $date = date($formatString); diff --git a/tests/PHPStan/Analyser/nsrt/bug-10893.php b/tests/PHPStan/Analyser/nsrt/bug-10893.php index 469c8956bd8..0878d2f3028 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10893.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10893.php @@ -5,16 +5,24 @@ use function PHPStan\Testing\assertType; /** - * @param non-falsy-string $nonfalsy + * @param non-falsy-string&numeric-string $str */ -function hasMicroseconds(\DateTimeInterface $value, string $nonfalsy): bool +function hasMicroseconds(\DateTimeInterface $value, string $str): bool { - assertType('non-falsy-string', $value->format('u')); + assertType('non-falsy-string&numeric-string', $str); + assertType('int', (int)$str); + assertType('bool', (int)$str !== 0); + + assertType('non-falsy-string&numeric-string', $value->format('u')); assertType('int', (int)$value->format('u')); assertType('bool', (int)$value->format('u') !== 0); - assertType('non-falsy-string', $nonfalsy); - assertType('int', (int)$nonfalsy); - assertType('bool', (int)$nonfalsy !== 0); + + assertType('non-falsy-string&numeric-string', $value->format('v')); + assertType('int', (int)$value->format('v')); + assertType('bool', (int)$value->format('v') !== 0); + + assertType('float', $value->format('u') * 1e-6); + assertType('float', $value->format('v') * 1e-3); return (int) $value->format('u') !== 0; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6613.php b/tests/PHPStan/Analyser/nsrt/bug-6613.php index 29898f75323..20abbe4b249 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6613.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6613.php @@ -6,6 +6,10 @@ function (\DateTime $dt) { assertType("'000000'", date('u')); - assertType('non-falsy-string', date_format($dt, 'u')); - assertType('non-falsy-string', $dt->format('u')); + assertType('non-falsy-string&numeric-string', date_format($dt, 'u')); + assertType('non-falsy-string&numeric-string', $dt->format('u')); + + assertType("'000'", date('v')); + assertType('non-falsy-string&numeric-string', date_format($dt, 'v')); + assertType('non-falsy-string&numeric-string', $dt->format('v')); }; From adc504d93f78bd0b9c0154ab052d76185ee41431 Mon Sep 17 00:00:00 2001 From: Claude Pache Date: Sat, 5 Apr 2025 15:31:32 +0200 Subject: [PATCH 1246/3097] more precise return type for strspn and strcspn --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 3c2428f5db7..aea642904a9 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11987,7 +11987,7 @@ 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], 'strcmp' => ['int<-1, 1>', 'str1'=>'string', 'str2'=>'string'], 'strcoll' => ['int<-1, 1>', 'str1'=>'string', 'str2'=>'string'], -'strcspn' => ['int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'length='=>'int'], +'strcspn' => ['non-negative-int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'length='=>'int'], 'stream_bucket_append' => ['void', 'brigade'=>'resource', 'bucket'=>'object'], 'stream_bucket_make_writeable' => ['stdClass|null', 'brigade'=>'resource'], 'stream_bucket_new' => ['object', 'stream'=>'resource', 'buffer'=>'string'], @@ -12078,7 +12078,7 @@ 'strrev' => ['string', 'str'=>'string'], 'strripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], 'strrpos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'], -'strspn' => ['int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'len='=>'int'], +'strspn' => ['non-negative-int', 'str'=>'string', 'mask'=>'string', 'start='=>'int', 'len='=>'int'], 'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'], 'strtok' => ['non-empty-string|false', 'str'=>'string', 'token'=>'string'], 'strtok\'1' => ['non-empty-string|false', 'token'=>'string'], From 9a0bfd2d5687c142100b192243593efade6a5ca8 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 7 Apr 2025 11:13:20 +0200 Subject: [PATCH 1247/3097] ObjectType: fix isEnum --- src/Type/ObjectType.php | 21 ++++++++++++++++++- tests/PHPStan/Type/ObjectTypeTest.php | 29 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0859825701a..e5b2540d7b3 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -6,6 +6,7 @@ use ArrayObject; use Closure; use Countable; +use DateTimeInterface; use Iterator; use IteratorAggregate; use PHPStan\Analyser\OutOfClassScope; @@ -44,6 +45,8 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; +use Stringable; +use Throwable; use Traversable; use function array_key_exists; use function array_map; @@ -730,7 +733,23 @@ public function isEnum(): TrinaryLogic return TrinaryLogic::createMaybe(); } - return TrinaryLogic::createFromBoolean($classReflection->isEnum()); + if ( + $classReflection->isEnum() + || $classReflection->is('UnitEnum') + ) { + return TrinaryLogic::createYes(); + } + + if ( + $classReflection->isInterface() + && !$classReflection->is(Stringable::class) // enums cannot have __toString + && !$classReflection->is(Throwable::class) // enums cannot extend Exception/Error + && !$classReflection->is(DateTimeInterface::class) // userland classes cannot extend DateTimeInterface + ) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); } public function canAccessProperties(): TrinaryLogic diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index f17c18ea976..9a1bdf2fead 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -70,6 +70,35 @@ public function testIsIterable(ObjectType $type, TrinaryLogic $expectedResult): ); } + /** + * @return iterable + */ + public function dataIsEnum(): iterable + { + if (PHP_VERSION_ID >= 80000) { + yield [new ObjectType('UnitEnum'), TrinaryLogic::createYes()]; + yield [new ObjectType('BackedEnum'), TrinaryLogic::createYes()]; + } + yield [new ObjectType('Unknown'), TrinaryLogic::createMaybe()]; + yield [new ObjectType('Countable'), TrinaryLogic::createMaybe()]; + yield [new ObjectType('Stringable'), TrinaryLogic::createNo()]; + yield [new ObjectType('Throwable'), TrinaryLogic::createNo()]; + yield [new ObjectType('DateTime'), TrinaryLogic::createNo()]; + } + + /** + * @dataProvider dataIsEnum + */ + public function testIsEnum(ObjectType $type, TrinaryLogic $expectedResult): void + { + $actualResult = $type->isEnum(); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> isEnum()', $type->describe(VerbosityLevel::precise())), + ); + } + public function dataIsCallable(): array { return [ From 375f68eb71490589ec0b1fa810ac190761ef7ba3 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Thu, 10 Apr 2025 17:33:14 +0200 Subject: [PATCH 1248/3097] Allow togging `discoveringSymbols` tip --- conf/config.level0.neon | 50 +++++++++++++++++-- conf/config.level1.neon | 9 +++- conf/config.level2.neon | 3 ++ conf/config.neon | 7 +++ conf/parametersSchema.neon | 1 + src/PhpDoc/StubValidator.php | 15 +++--- .../ExistingClassInClassExtendsRule.php | 13 +++-- .../Classes/ExistingClassInInstanceOfRule.php | 15 ++++-- .../Classes/ExistingClassInTraitUseRule.php | 13 +++-- .../ExistingClassesInClassImplementsRule.php | 13 +++-- .../ExistingClassesInEnumImplementsRule.php | 13 +++-- .../ExistingClassesInInterfaceExtendsRule.php | 13 +++-- src/Rules/Classes/InstantiationRule.php | 13 +++-- src/Rules/Classes/LocalTypeAliasesCheck.php | 13 +++-- src/Rules/Classes/MethodTagCheck.php | 13 +++-- src/Rules/Classes/MixinCheck.php | 13 +++-- src/Rules/Classes/PropertyTagCheck.php | 13 +++-- src/Rules/Constants/ConstantRule.php | 24 ++++++--- .../CaughtExceptionExistenceRule.php | 14 ++++-- .../CallToNonExistentFunctionRule.php | 10 +++- src/Rules/Methods/StaticMethodCallCheck.php | 18 +++++-- .../ExistingNamesInGroupUseRule.php | 25 +++++++--- .../Namespaces/ExistingNamesInUseRule.php | 25 +++++++--- .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 13 +++-- src/Rules/PhpDoc/RequireExtendsCheck.php | 13 +++-- .../RequireImplementsDefinitionTraitRule.php | 13 +++-- .../Properties/AccessStaticPropertiesRule.php | 21 +++++--- .../ExistingClassesInPropertiesRule.php | 12 ++++- src/Rules/RuleLevelHelper.php | 13 +++-- .../Analyser/Bug9307CallMethodsRuleTest.php | 2 +- .../Arrays/ArrayDestructuringRuleTest.php | 2 +- .../Rules/Arrays/ArrayUnpackingRuleTest.php | 2 +- .../InvalidKeyInArrayDimFetchRuleTest.php | 2 +- .../Arrays/IterableInForeachRuleTest.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- .../Arrays/OffsetAccessAssignOpRuleTest.php | 2 +- .../Arrays/OffsetAccessAssignmentRuleTest.php | 2 +- .../OffsetAccessValueAssignmentRuleTest.php | 2 +- .../Arrays/UnpackIterableInArrayRuleTest.php | 2 +- tests/PHPStan/Rules/Cast/EchoRuleTest.php | 2 +- .../Rules/Cast/InvalidCastRuleTest.php | 2 +- .../InvalidPartOfEncapsedStringRuleTest.php | 2 +- tests/PHPStan/Rules/Cast/PrintRuleTest.php | 2 +- .../Rules/Classes/ClassAttributesRuleTest.php | 2 +- .../ClassConstantAttributesRuleTest.php | 2 +- .../Rules/Classes/ClassConstantRuleTest.php | 2 +- .../ExistingClassInClassExtendsRuleTest.php | 1 + .../ExistingClassInInstanceOfRuleTest.php | 1 + .../ExistingClassInTraitUseRuleTest.php | 1 + ...istingClassesInClassImplementsRuleTest.php | 1 + ...xistingClassesInEnumImplementsRuleTest.php | 1 + ...stingClassesInInterfaceExtendsRuleTest.php | 1 + .../ForbiddenNameCheckExtensionRuleTest.php | 3 +- .../Rules/Classes/InstantiationRuleTest.php | 3 +- .../Classes/LocalTypeAliasesRuleTest.php | 1 + .../Classes/LocalTypeTraitAliasesRuleTest.php | 1 + .../LocalTypeTraitUseAliasesRuleTest.php | 1 + .../Rules/Classes/MethodTagRuleTest.php | 1 + .../Rules/Classes/MethodTagTraitRuleTest.php | 1 + .../Classes/MethodTagTraitUseRuleTest.php | 1 + tests/PHPStan/Rules/Classes/MixinRuleTest.php | 1 + .../Rules/Classes/MixinTraitRuleTest.php | 1 + .../Rules/Classes/MixinTraitUseRuleTest.php | 1 + .../Rules/Classes/PropertyTagRuleTest.php | 1 + .../Classes/PropertyTagTraitRuleTest.php | 1 + .../Classes/PropertyTagTraitUseRuleTest.php | 1 + .../Rules/Constants/ConstantRuleTest.php | 2 +- .../DynamicClassConstantFetchRuleTest.php | 2 +- .../EnumCases/EnumCaseAttributesRuleTest.php | 2 +- .../CaughtExceptionExistenceRuleTest.php | 1 + .../Exceptions/ThrowExprTypeRuleTest.php | 2 +- .../ArrowFunctionAttributesRuleTest.php | 2 +- .../ArrowFunctionReturnTypeRuleTest.php | 1 + .../Rules/Functions/CallCallablesRuleTest.php | 2 +- .../CallToFunctionParametersRuleTest.php | 2 +- .../CallToNonExistentFunctionRuleTest.php | 2 +- .../Rules/Functions/CallUserFuncRuleTest.php | 2 +- .../Functions/ClosureAttributesRuleTest.php | 2 +- .../Functions/ClosureReturnTypeRuleTest.php | 2 +- .../Functions/FunctionAttributesRuleTest.php | 2 +- .../Functions/FunctionCallableRuleTest.php | 2 +- ...plodeParameterCastableToStringRuleTest.php | 2 +- .../Functions/ParamAttributesRuleTest.php | 2 +- .../ParameterCastableToNumberRuleTest.php | 2 +- .../ParameterCastableToStringRuleTest.php | 2 +- .../Rules/Functions/ReturnTypeRuleTest.php | 2 +- .../SortParameterCastableToStringRuleTest.php | 2 +- .../Generators/YieldFromTypeRuleTest.php | 2 +- .../Rules/Generators/YieldTypeRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 2 +- .../Methods/CallStaticMethodsRuleTest.php | 3 +- ...hodStatementWithoutSideEffectsRuleTest.php | 2 +- ...hodStatementWithoutSideEffectsRuleTest.php | 2 +- .../Methods/MethodAttributesRuleTest.php | 2 +- .../Rules/Methods/MethodCallableRuleTest.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 2 +- .../Methods/StaticMethodCallableRuleTest.php | 3 +- .../ExistingNamesInGroupUseRuleTest.php | 1 + .../Namespaces/ExistingNamesInUseRuleTest.php | 1 + .../InvalidBinaryOperationRuleTest.php | 2 +- .../InvalidComparisonOperationRuleTest.php | 2 +- .../InvalidIncDecOperationRuleTest.php | 2 +- .../InvalidUnaryOperationRuleTest.php | 2 +- .../InvalidPhpDocVarTagTypeRuleTest.php | 1 + .../RequireExtendsDefinitionClassRuleTest.php | 1 + .../RequireExtendsDefinitionTraitRuleTest.php | 1 + ...quireImplementsDefinitionTraitRuleTest.php | 1 + .../AccessPropertiesInAssignRuleTest.php | 2 +- .../Properties/AccessPropertiesRuleTest.php | 2 +- ...AccessStaticPropertiesInAssignRuleTest.php | 3 +- .../AccessStaticPropertiesRuleTest.php | 3 +- .../PHPStan/Rules/Properties/Bug7074Test.php | 2 +- ...ValueTypesAssignedToPropertiesRuleTest.php | 2 +- .../ExistingClassesInPropertiesRuleTest.php | 1 + .../Properties/PropertyAttributesRuleTest.php | 2 +- .../PropertyHookAttributesRuleTest.php | 2 +- .../ReadingWriteOnlyPropertiesRuleTest.php | 2 +- .../TypesAssignedToPropertiesRuleTest.php | 2 +- .../WritingToReadOnlyPropertiesRuleTest.php | 2 +- .../Rules/Traits/TraitAttributesRuleTest.php | 2 +- .../ParameterOutAssignedTypeRuleTest.php | 2 +- .../ParameterOutExecutionEndTypeRuleTest.php | 2 +- .../Variables/VariableCloningRuleTest.php | 2 +- 123 files changed, 414 insertions(+), 184 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index b5b1f2f793a..084738089e6 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -27,11 +27,6 @@ rules: - PHPStan\Rules\Classes\ClassConstantRule - PHPStan\Rules\Classes\DuplicateDeclarationRule - PHPStan\Rules\Classes\EnumSanityRule - - PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule - - PHPStan\Rules\Classes\ExistingClassesInEnumImplementsRule - - PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule - - PHPStan\Rules\Classes\ExistingClassInTraitUseRule - - PHPStan\Rules\Classes\InstantiationRule - PHPStan\Rules\Classes\InstantiationCallableRule - PHPStan\Rules\Classes\InvalidPromotedPropertiesRule - PHPStan\Rules\Classes\LocalTypeAliasesRule @@ -113,6 +108,22 @@ services: class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule tags: - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% + + - + class: PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% + + - + class: PHPStan\Rules\Classes\ExistingClassesInEnumImplementsRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Classes\ExistingClassInInstanceOfRule @@ -120,6 +131,28 @@ services: - phpstan.rules.rule arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + discoveringSymbolsTip: %tips.discoveringSymbols% + + - + class: PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% + + - + class: PHPStan\Rules\Classes\ExistingClassInTraitUseRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% + + - + class: PHPStan\Rules\Classes\InstantiationRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Exceptions\CaughtExceptionExistenceRule @@ -127,6 +160,7 @@ services: - phpstan.rules.rule arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Functions\CallToNonExistentFunctionRule @@ -134,6 +168,7 @@ services: - phpstan.rules.rule arguments: checkFunctionNameCase: %checkFunctionNameCase% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Constants\OverridingConstantRule @@ -165,6 +200,7 @@ services: - phpstan.rules.rule arguments: checkFunctionNameCase: %checkFunctionNameCase% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Namespaces\ExistingNamesInUseRule @@ -172,6 +208,7 @@ services: - phpstan.rules.rule arguments: checkFunctionNameCase: %checkFunctionNameCase% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Properties\AccessPropertiesRule @@ -182,6 +219,8 @@ services: class: PHPStan\Rules\Properties\AccessStaticPropertiesRule tags: - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Properties\ExistingClassesInPropertiesRule @@ -190,6 +229,7 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Functions\FunctionCallableRule diff --git a/conf/config.level1.neon b/conf/config.level1.neon index 3b5f68d64ac..a1e3872d6bb 100644 --- a/conf/config.level1.neon +++ b/conf/config.level1.neon @@ -9,8 +9,15 @@ parameters: rules: - PHPStan\Rules\Classes\UnusedConstructorParametersRule - - PHPStan\Rules\Constants\ConstantRule - PHPStan\Rules\Functions\UnusedClosureUsesRule - PHPStan\Rules\Variables\EmptyRule - PHPStan\Rules\Variables\IssetRule - PHPStan\Rules\Variables\NullCoalesceRule + +services: + - + class: PHPStan\Rules\Constants\ConstantRule + tags: + - phpstan.rules.rule + arguments: + discoveringSymbolsTip: %tips.discoveringSymbols% diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 9cd92e09e71..2deca2ac0df 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -77,11 +77,13 @@ services: class: PHPStan\Rules\PhpDoc\RequireExtendsCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\PhpDoc\RequireImplementsDefinitionTraitRule arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% + discoveringSymbolsTip: %tips.discoveringSymbols% tags: - phpstan.rules.rule @@ -101,6 +103,7 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkMissingVarTagTypehint: %checkMissingVarTagTypehint% + discoveringSymbolsTip: %tips.discoveringSymbols% tags: - phpstan.rules.rule - diff --git a/conf/config.neon b/conf/config.neon index e764d064eb4..555e7ec53c7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -84,6 +84,7 @@ parameters: usePathConstantsAsConstantString: false rememberPossiblyImpureFunctionValues: true tips: + discoveringSymbols: true treatPhpDocTypesAsCertain: true tipsOfTheDay: true reportMagicMethods: false @@ -888,24 +889,28 @@ services: globalTypeAliases: %typeAliases% checkMissingTypehints: %checkMissingTypehints% checkClassCaseSensitivity: %checkClassCaseSensitivity% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Classes\MethodTagCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkMissingTypehints: %checkMissingTypehints% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Classes\MixinCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkMissingTypehints: %checkMissingTypehints% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Classes\PropertyTagCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkMissingTypehints: %checkMissingTypehints% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper @@ -1005,6 +1010,7 @@ services: class: PHPStan\Rules\Methods\StaticMethodCallCheck arguments: checkFunctionNameCase: %checkFunctionNameCase% + discoveringSymbolsTip: %tips.discoveringSymbols% reportMagicMethods: %reportMagicMethods% - @@ -1096,6 +1102,7 @@ services: checkExplicitMixed: %checkExplicitMixed% checkImplicitMixed: %checkImplicitMixed% checkBenevolentUnionTypes: %checkBenevolentUnionTypes% + discoveringSymbolsTip: %tips.discoveringSymbols% - class: PHPStan\Rules\UnusedFunctionParametersCheck diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3791c293a1c..69fc9380b92 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -63,6 +63,7 @@ parametersSchema: inferPrivatePropertyTypeFromConstructor: bool() tips: structure([ + discoveringSymbols: bool() treatPhpDocTypesAsCertain: bool() ]) tipsOfTheDay: bool() diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 0374aa268f3..b0737cbcc9e 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -194,8 +194,9 @@ private function getRuleRegistry(Container $container): RuleRegistry $genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class); $methodTagTemplateTypeCheck = $container->getByType(MethodTagTemplateTypeCheck::class); $mixinCheck = $container->getByType(MixinCheck::class); - $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); - $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true); + $discoveringSymbolsTip = $container->getParameter('tips')['discoveringSymbols']; + $methodTagCheck = new MethodTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true, $discoveringSymbolsTip); + $propertyTagCheck = new PropertyTagCheck($reflectionProvider, $classNameCheck, $genericObjectTypeCheck, $missingTypehintCheck, $unresolvableTypeHelper, true, true, $discoveringSymbolsTip); $reflector = $container->getService('stubReflector'); $relativePathHelper = $container->getService('simpleRelativePathHelper'); $assertRuleHelper = $container->getByType(AssertRuleHelper::class); @@ -203,13 +204,13 @@ private function getRuleRegistry(Container $container): RuleRegistry $rules = [ // level 0 - new ExistingClassesInClassImplementsRule($classNameCheck, $reflectionProvider), - new ExistingClassesInInterfaceExtendsRule($classNameCheck, $reflectionProvider), - new ExistingClassInClassExtendsRule($classNameCheck, $reflectionProvider), - new ExistingClassInTraitUseRule($classNameCheck, $reflectionProvider), + new ExistingClassesInClassImplementsRule($classNameCheck, $reflectionProvider, $discoveringSymbolsTip), + new ExistingClassesInInterfaceExtendsRule($classNameCheck, $reflectionProvider, $discoveringSymbolsTip), + new ExistingClassInClassExtendsRule($classNameCheck, $reflectionProvider, $discoveringSymbolsTip), + new ExistingClassInTraitUseRule($classNameCheck, $reflectionProvider, $discoveringSymbolsTip), new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), - new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false), + new ExistingClassesInPropertiesRule($reflectionProvider, $classNameCheck, $unresolvableTypeHelper, $phpVersion, true, false, $discoveringSymbolsTip), new OverridingMethodRule( $phpVersion, new MethodSignatureRule($phpClassReflectionExtension, true, true), diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index d1863e19457..8735b220842 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -20,6 +20,7 @@ final class ExistingClassInClassExtendsRule implements Rule public function __construct( private ClassNameCheck $classCheck, private ReflectionProvider $reflectionProvider, + private bool $discoveringSymbolsTip, ) { } @@ -42,15 +43,19 @@ public function processNode(Node $node, Scope $scope): array } if (!$this->reflectionProvider->hasClass($extendedClassName)) { if (!$scope->isInClassExists($extendedClassName)) { - $messages[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( '%s extends unknown class %s.', $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', $extendedClassName, )) ->identifier('class.notFound') - ->nonIgnorable() - ->discoveringSymbolsTip() - ->build(); + ->nonIgnorable(); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $messages[] = $errorBuilder->build(); } } else { $reflection = $this->reflectionProvider->getClass($extendedClassName); diff --git a/src/Rules/Classes/ExistingClassInInstanceOfRule.php b/src/Rules/Classes/ExistingClassInInstanceOfRule.php index 5e408006319..063a5639121 100644 --- a/src/Rules/Classes/ExistingClassInInstanceOfRule.php +++ b/src/Rules/Classes/ExistingClassInInstanceOfRule.php @@ -26,6 +26,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, private bool $checkClassCaseSensitivity, + private bool $discoveringSymbolsTip, ) { } @@ -69,12 +70,16 @@ public function processNode(Node $node, Scope $scope): array return []; } + $errorBuilder = RuleErrorBuilder::message(sprintf('Class %s not found.', $name)) + ->identifier('class.notFound') + ->line($class->getStartLine()); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ - RuleErrorBuilder::message(sprintf('Class %s not found.', $name)) - ->identifier('class.notFound') - ->line($class->getStartLine()) - ->discoveringSymbolsTip() - ->build(), + $errorBuilder->build(), ]; } diff --git a/src/Rules/Classes/ExistingClassInTraitUseRule.php b/src/Rules/Classes/ExistingClassInTraitUseRule.php index 69ecc87fb51..d13aefbd7a5 100644 --- a/src/Rules/Classes/ExistingClassInTraitUseRule.php +++ b/src/Rules/Classes/ExistingClassInTraitUseRule.php @@ -22,6 +22,7 @@ final class ExistingClassInTraitUseRule implements Rule public function __construct( private ClassNameCheck $classCheck, private ReflectionProvider $reflectionProvider, + private bool $discoveringSymbolsTip, ) { } @@ -64,11 +65,15 @@ public function processNode(Node $node, Scope $scope): array foreach ($node->traits as $trait) { $traitName = (string) $trait; if (!$this->reflectionProvider->hasClass($traitName)) { - $messages[] = RuleErrorBuilder::message(sprintf('%s uses unknown trait %s.', $currentName, $traitName)) + $errorBuilder = RuleErrorBuilder::message(sprintf('%s uses unknown trait %s.', $currentName, $traitName)) ->identifier('trait.notFound') - ->nonIgnorable() - ->discoveringSymbolsTip() - ->build(); + ->nonIgnorable(); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $messages[] = $errorBuilder->build(); } else { $reflection = $this->reflectionProvider->getClass($traitName); if ($reflection->isClass()) { diff --git a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php index e0ff7c27ba4..581f45f3bc4 100644 --- a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php @@ -21,6 +21,7 @@ final class ExistingClassesInClassImplementsRule implements Rule public function __construct( private ClassNameCheck $classCheck, private ReflectionProvider $reflectionProvider, + private bool $discoveringSymbolsTip, ) { } @@ -45,15 +46,19 @@ public function processNode(Node $node, Scope $scope): array $implementedClassName = (string) $implements; if (!$this->reflectionProvider->hasClass($implementedClassName)) { if (!$scope->isInClassExists($implementedClassName)) { - $messages[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( '%s implements unknown interface %s.', $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', $implementedClassName, )) ->identifier('interface.notFound') - ->nonIgnorable() - ->discoveringSymbolsTip() - ->build(); + ->nonIgnorable(); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $messages[] = $errorBuilder->build(); } } else { $reflection = $this->reflectionProvider->getClass($implementedClassName); diff --git a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php index aff2164a915..d6caa6a5807 100644 --- a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php @@ -21,6 +21,7 @@ final class ExistingClassesInEnumImplementsRule implements Rule public function __construct( private ClassNameCheck $classCheck, private ReflectionProvider $reflectionProvider, + private bool $discoveringSymbolsTip, ) { } @@ -42,15 +43,19 @@ public function processNode(Node $node, Scope $scope): array $implementedClassName = (string) $implements; if (!$this->reflectionProvider->hasClass($implementedClassName)) { if (!$scope->isInClassExists($implementedClassName)) { - $messages[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( 'Enum %s implements unknown interface %s.', $currentEnumName, $implementedClassName, )) ->identifier('interface.notFound') - ->nonIgnorable() - ->discoveringSymbolsTip() - ->build(); + ->nonIgnorable(); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $messages[] = $errorBuilder->build(); } } else { $reflection = $this->reflectionProvider->getClass($implementedClassName); diff --git a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php index 5fc694a46b7..dc87b36cfae 100644 --- a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php +++ b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php @@ -21,6 +21,7 @@ final class ExistingClassesInInterfaceExtendsRule implements Rule public function __construct( private ClassNameCheck $classCheck, private ReflectionProvider $reflectionProvider, + private bool $discoveringSymbolsTip, ) { } @@ -41,15 +42,19 @@ public function processNode(Node $node, Scope $scope): array $extendedInterfaceName = (string) $extends; if (!$this->reflectionProvider->hasClass($extendedInterfaceName)) { if (!$scope->isInClassExists($extendedInterfaceName)) { - $messages[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( 'Interface %s extends unknown interface %s.', $currentInterfaceName, $extendedInterfaceName, )) ->identifier('interface.notFound') - ->nonIgnorable() - ->discoveringSymbolsTip() - ->build(); + ->nonIgnorable(); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $messages[] = $errorBuilder->build(); } } else { $reflection = $this->reflectionProvider->getClass($extendedInterfaceName); diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 4c0a424c1b1..b8cf691aeb7 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -35,6 +35,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private FunctionCallParametersCheck $check, private ClassNameCheck $classCheck, + private bool $discoveringSymbolsTip, ) { } @@ -122,11 +123,15 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ return []; } + $errorBuilder = RuleErrorBuilder::message(sprintf('Instantiated class %s not found.', $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ - RuleErrorBuilder::message(sprintf('Instantiated class %s not found.', $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(), + $errorBuilder->build(), ]; } diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 28ca78679e8..7351d2a2bbb 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -43,6 +43,7 @@ public function __construct( private GenericObjectTypeCheck $genericObjectTypeCheck, private bool $checkMissingTypehints, private bool $checkClassCaseSensitivity, + private bool $discoveringSymbolsTip, ) { } @@ -254,10 +255,14 @@ public function checkInTraitUseContext( } foreach ($resolvedType->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('Type alias %s contains unknown class %s.', $aliasName, $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf('Type alias %s contains invalid type %s.', $aliasName, $class)) ->identifier('typeAlias.trait') diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index 5730ea3a9bb..a5a420e24c4 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -29,6 +29,7 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, private bool $checkMissingTypehints, + private bool $discoveringSymbolsTip, ) { } @@ -216,10 +217,14 @@ private function checkMethodTypeInTraitUseContext(ClassReflection $classReflecti $errors = []; foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains unknown class %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains unknown class %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @method for method %s::%s() %s contains invalid type %s.', $classReflection->getDisplayName(), $methodName, $description, $class)) ->identifier('methodTag.trait') diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index cff3184a6f7..74df41612bf 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -27,6 +27,7 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, private bool $checkMissingTypehints, + private bool $discoveringSymbolsTip, ) { } @@ -145,10 +146,14 @@ public function checkInTraitUseContext( foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains unknown class %s.', $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @mixin contains invalid type %s.', $class)) ->identifier('mixin.trait') diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index c3e9fda73fb..f45c38cd673 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -31,6 +31,7 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, private bool $checkMissingTypehints, + private bool $discoveringSymbolsTip, ) { } @@ -197,10 +198,14 @@ private function checkPropertyTypeInTraitUseContext(ClassReflection $classReflec $errors = []; foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class)) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains unknown class %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains unknown class %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } elseif ($this->reflectionProvider->getClass($class)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag %s for property %s::$%s contains invalid type %s.', $tagName, $classReflection->getDisplayName(), $propertyName, $class)) ->identifier('propertyTag.trait') diff --git a/src/Rules/Constants/ConstantRule.php b/src/Rules/Constants/ConstantRule.php index d0e52ead39d..2bae50132b2 100644 --- a/src/Rules/Constants/ConstantRule.php +++ b/src/Rules/Constants/ConstantRule.php @@ -14,6 +14,12 @@ final class ConstantRule implements Rule { + public function __construct( + private bool $discoveringSymbolsTip, + ) + { + } + public function getNodeType(): string { return Node\Expr\ConstFetch::class; @@ -22,14 +28,18 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { if (!$scope->hasConstant($node->name)) { + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'Constant %s not found.', + (string) $node->name, + )) + ->identifier('constant.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ - RuleErrorBuilder::message(sprintf( - 'Constant %s not found.', - (string) $node->name, - )) - ->identifier('constant.notFound') - ->discoveringSymbolsTip() - ->build(), + $errorBuilder->build(), ]; } diff --git a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php index 1c826e424c5..f4af37da86e 100644 --- a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php +++ b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php @@ -24,6 +24,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, private bool $checkClassCaseSensitivity, + private bool $discoveringSymbolsTip, ) { } @@ -42,11 +43,16 @@ public function processNode(Node $node, Scope $scope): array if ($scope->isInClassExists($className)) { continue; } - $errors[] = RuleErrorBuilder::message(sprintf('Caught class %s not found.', $className)) + + $errorBuilder = RuleErrorBuilder::message(sprintf('Caught class %s not found.', $className)) ->line($class->getStartLine()) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); continue; } diff --git a/src/Rules/Functions/CallToNonExistentFunctionRule.php b/src/Rules/Functions/CallToNonExistentFunctionRule.php index a97e2caddbb..9805a445b89 100644 --- a/src/Rules/Functions/CallToNonExistentFunctionRule.php +++ b/src/Rules/Functions/CallToNonExistentFunctionRule.php @@ -20,6 +20,7 @@ final class CallToNonExistentFunctionRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, private bool $checkFunctionNameCase, + private bool $discoveringSymbolsTip, ) { } @@ -40,8 +41,15 @@ public function processNode(Node $node, Scope $scope): array return []; } + $errorBuilder = RuleErrorBuilder::message(sprintf('Function %s not found.', (string) $node->name)) + ->identifier('function.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ - RuleErrorBuilder::message(sprintf('Function %s not found.', (string) $node->name))->identifier('function.notFound')->discoveringSymbolsTip()->build(), + $errorBuilder->build(), ]; } diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index b9ffc20442e..63c1de6c2b2 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -40,6 +40,7 @@ public function __construct( private RuleLevelHelper $ruleLevelHelper, private ClassNameCheck $classCheck, private bool $checkFunctionNameCase, + private bool $discoveringSymbolsTip, private bool $reportMagicMethods, ) { @@ -119,13 +120,20 @@ public function check( return [[], null]; } + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'Call to static method %s() on an unknown class %s.', + $methodName, + $className, + )) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ [ - RuleErrorBuilder::message(sprintf( - 'Call to static method %s() on an unknown class %s.', - $methodName, - $className, - ))->identifier('class.notFound')->discoveringSymbolsTip()->build(), + $errorBuilder->build(), ], null, ]; diff --git a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php index 68d8a954655..457ffd29a97 100644 --- a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php @@ -26,6 +26,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, private bool $checkFunctionNameCase, + private bool $discoveringSymbolsTip, ) { } @@ -72,11 +73,15 @@ public function processNode(Node $node, Scope $scope): array private function checkConstant(Node\Name $name): ?IdentifierRuleError { if (!$this->reflectionProvider->hasConstant($name, null)) { - return RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $name)) - ->discoveringSymbolsTip() + $errorBuilder = RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $name)) ->line($name->getStartLine()) - ->identifier('constant.notFound') - ->build(); + ->identifier('constant.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + return $errorBuilder->build(); } return null; @@ -85,11 +90,15 @@ private function checkConstant(Node\Name $name): ?IdentifierRuleError private function checkFunction(Node\Name $name): ?IdentifierRuleError { if (!$this->reflectionProvider->hasFunction($name, null)) { - return RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $name)) - ->discoveringSymbolsTip() + $errorBuilder = RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $name)) ->line($name->getStartLine()) - ->identifier('function.notFound') - ->build(); + ->identifier('function.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + return $errorBuilder->build(); } if ($this->checkFunctionNameCase) { diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index b93db1ea450..3a82facc947 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -25,6 +25,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, private bool $checkFunctionNameCase, + private bool $discoveringSymbolsTip, ) { } @@ -69,11 +70,15 @@ private function checkConstants(array $uses): array continue; } - $errors[] = RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $use->name)) + $errorBuilder = RuleErrorBuilder::message(sprintf('Used constant %s not found.', (string) $use->name)) ->line($use->name->getStartLine()) - ->identifier('constant.notFound') - ->discoveringSymbolsTip() - ->build(); + ->identifier('constant.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } return $errors; @@ -88,11 +93,15 @@ private function checkFunctions(array $uses): array $errors = []; foreach ($uses as $use) { if (!$this->reflectionProvider->hasFunction($use->name, null)) { - $errors[] = RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $use->name)) + $errorBuilder = RuleErrorBuilder::message(sprintf('Used function %s not found.', (string) $use->name)) ->line($use->name->getStartLine()) - ->identifier('function.notFound') - ->discoveringSymbolsTip() - ->build(); + ->identifier('function.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } elseif ($this->checkFunctionNameCase) { $functionReflection = $this->reflectionProvider->getFunction($use->name, null); $realName = $functionReflection->getName(); diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 3815a963aad..375e246f351 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -34,6 +34,7 @@ public function __construct( private UnresolvableTypeHelper $unresolvableTypeHelper, private bool $checkClassCaseSensitivity, private bool $checkMissingVarTagTypehint, + private bool $discoveringSymbolsTip, ) { } @@ -136,13 +137,17 @@ public function processNode(Node $node, Scope $scope): array continue; } - $errors[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( sprintf('%s contains unknown class %%s.', $identifier), $referencedClass, )) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } $errors = array_merge( diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 14f6d436477..f6a602b2a49 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -21,6 +21,7 @@ final class RequireExtendsCheck public function __construct( private ClassNameCheck $classCheck, private bool $checkClassCaseSensitivity, + private bool $discoveringSymbolsTip, ) { } @@ -52,10 +53,14 @@ public function checkExtendsTags(Node $node, array $extendsTags): array $referencedClassReflection = $type->getClassReflection(); if ($referencedClassReflection === null) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) - ->discoveringSymbolsTip() - ->identifier('class.notFound') - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); continue; } diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 076ae342a0c..764c868e532 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -25,6 +25,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, private bool $checkClassCaseSensitivity, + private bool $discoveringSymbolsTip, ) { } @@ -59,10 +60,14 @@ public function processNode(Node $node, Scope $scope): array $class = $type->getClassName(); $referencedClassReflection = $type->getClassReflection(); if ($referencedClassReflection === null) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) - ->discoveringSymbolsTip() - ->identifier('class.notFound') - ->build(); + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); continue; } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 94e526da0ca..80f10341207 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -40,6 +40,7 @@ public function __construct( private ReflectionProvider $reflectionProvider, private RuleLevelHelper $ruleLevelHelper, private ClassNameCheck $classCheck, + private bool $discoveringSymbolsTip, ) { } @@ -114,15 +115,19 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, return []; } + $errorBuilder = RuleErrorBuilder::message(sprintf( + 'Access to static property $%s on an unknown class %s.', + $name, + $class, + )) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + return [ - RuleErrorBuilder::message(sprintf( - 'Access to static property $%s on an unknown class %s.', - $name, - $class, - )) - ->discoveringSymbolsTip() - ->identifier('class.notFound') - ->build(), + $errorBuilder->build(), ]; } diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index 62678e3f141..d2d1dc5095f 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -29,6 +29,7 @@ public function __construct( private PhpVersion $phpVersion, private bool $checkClassCaseSensitivity, private bool $checkThisOnly, + private bool $discoveringSymbolsTip, ) { } @@ -64,12 +65,19 @@ public function processNode(Node $node, Scope $scope): array continue; } - $errors[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( 'Property %s::$%s has unknown class %s as its type.', $propertyReflection->getDeclaringClass()->getDisplayName(), $node->getName(), $referencedClass, - ))->identifier('class.notFound')->discoveringSymbolsTip()->build(); + )) + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } $errors = array_merge( diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 416583546f2..2f9dd979030 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -35,6 +35,7 @@ public function __construct( private bool $checkExplicitMixed, private bool $checkImplicitMixed, private bool $checkBenevolentUnionTypes, + private bool $discoveringSymbolsTip, ) { } @@ -222,11 +223,15 @@ private function findTypeToCheckImplementation( continue; } - $errors[] = RuleErrorBuilder::message(sprintf($unknownClassErrorPattern, $referencedClass)) + $errorBuilder = RuleErrorBuilder::message(sprintf($unknownClassErrorPattern, $referencedClass)) ->line($var->getStartLine()) - ->identifier('class.notFound') - ->discoveringSymbolsTip() - ->build(); + ->identifier('class.notFound'); + + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } + + $errors[] = $errorBuilder->build(); } } diff --git a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php index ff1be831095..278e2d805de 100644 --- a/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php +++ b/tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php @@ -21,7 +21,7 @@ class Bug9307CallMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false, true); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), diff --git a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php index d37f09084b1..3027ce23b7f 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php @@ -15,7 +15,7 @@ class ArrayDestructuringRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true); return new ArrayDestructuringRule( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php index 89366314965..0991d178ccd 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php @@ -22,7 +22,7 @@ protected function getRule(): Rule { return new ArrayUnpackingRule( self::getContainer()->getByType(PhpVersion::class), - new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, $this->checkBenevolentUnions), + new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, $this->checkBenevolentUnions, true), ); } diff --git a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php index df337d9bdf4..757b25cbd7c 100644 --- a/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php @@ -15,7 +15,7 @@ class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true); return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true); } diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index 43c6020744b..c907f46ddd4 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -21,7 +21,7 @@ class IterableInForeachRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); + return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true)); } public function testCheckWithMaybes(): void diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index df2cb2cf269..0198587347d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -23,7 +23,7 @@ class NonexistentOffsetInArrayDimFetchRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true); return new NonexistentOffsetInArrayDimFetchRule( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php index 45750ac2a4a..e131c311a41 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php @@ -17,7 +17,7 @@ class OffsetAccessAssignOpRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnions, false, false, false, true); return new OffsetAccessAssignOpRule($ruleLevelHelper); } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index 9049635db0c..533dd185aed 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -17,7 +17,7 @@ class OffsetAccessAssignmentRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, false, false, false, true); return new OffsetAccessAssignmentRule($ruleLevelHelper); } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php index 38afbe61379..ff9ae587d79 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php @@ -15,7 +15,7 @@ class OffsetAccessValueAssignmentRuleTest extends RuleTestCase protected function getRule(): Rule { - return new OffsetAccessValueAssignmentRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new OffsetAccessValueAssignmentRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php index ba43922b08d..15abfb7cef2 100644 --- a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php @@ -21,7 +21,7 @@ class UnpackIterableInArrayRuleTest extends RuleTestCase protected function getRule(): Rule { - return new UnpackIterableInArrayRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); + return new UnpackIterableInArrayRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Cast/EchoRuleTest.php b/tests/PHPStan/Rules/Cast/EchoRuleTest.php index 5804527769c..f08a205a798 100644 --- a/tests/PHPStan/Rules/Cast/EchoRuleTest.php +++ b/tests/PHPStan/Rules/Cast/EchoRuleTest.php @@ -16,7 +16,7 @@ class EchoRuleTest extends RuleTestCase protected function getRule(): Rule { return new EchoRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index ee36958b199..e5d5f11c2a6 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -22,7 +22,7 @@ class InvalidCastRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false)); + return new InvalidCastRule($broker, new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php index 4503a788e8f..642b296e4fd 100644 --- a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php @@ -19,7 +19,7 @@ protected function getRule(): Rule { return new InvalidPartOfEncapsedStringRule( new ExprPrinter(new Printer()), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Cast/PrintRuleTest.php b/tests/PHPStan/Rules/Cast/PrintRuleTest.php index b3a71307ec9..fb0e7991fdc 100644 --- a/tests/PHPStan/Rules/Cast/PrintRuleTest.php +++ b/tests/PHPStan/Rules/Cast/PrintRuleTest.php @@ -16,7 +16,7 @@ class PrintRuleTest extends RuleTestCase protected function getRule(): Rule { return new PrintRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index d4a9dc96d52..4f0e9ea4cef 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -32,7 +32,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 64b97838771..99b5f29373f 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 838201ffded..e1e48ef78ef 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new ClassConstantRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index fa3ca260c95..ca4132139fe 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), $reflectionProvider, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 0e6d0b066f0..9841cd6ed44 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -25,6 +25,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php index 49b083cd466..d6369b56a26 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), $reflectionProvider, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php index 12183ebc85b..a2533f17d9c 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), $reflectionProvider, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php index f83d296867e..e706e005d97 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php @@ -25,6 +25,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), $reflectionProvider, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php index cadda3b9924..4caaf6f8e51 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), $reflectionProvider, + true, ); } diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 5286e079fc4..b4fb7e23999 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -25,11 +25,12 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + true, ); } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 7852d40555d..bf5e7ab8af1 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -25,11 +25,12 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + true, ); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 47b81f1ea18..9f6ac7b3ec0 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -37,6 +37,7 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 76dc96f81c9..9654660857e 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -36,6 +36,7 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, + true, ), $this->createReflectionProvider(), ); diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php index a84377316b5..e99a521056f 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -36,6 +36,7 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php index ae7ff38ec53..0c221078d22 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index fce38f5d984..5cff88d7072 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index 53179386201..bdacfb3c6ad 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index e05283e71b4..c389d025a07 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -34,6 +34,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index 94657239639..80d1a3e9e13 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index c94636b5e1d..d7920d043ed 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php index 4ec02315e10..826047dbb9a 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index 1266a5a5531..4f822c2a646 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), $reflectionProvider, ); diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php index f45a1b894db..f42aaca316f 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): TRule new UnresolvableTypeHelper(), true, true, + true, ), ); } diff --git a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php index 7b6d03ce194..d30fbcec677 100644 --- a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php +++ b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php @@ -14,7 +14,7 @@ class ConstantRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ConstantRule(); + return new ConstantRule(true); } public function testConstants(): void diff --git a/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php b/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php index 16dbd9dfbfd..4a0b85b34bd 100644 --- a/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php +++ b/tests/PHPStan/Rules/Constants/DynamicClassConstantFetchRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): TRule { return new DynamicClassConstantFetchRule( self::getContainer()->getByType(PhpVersion::class), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), ); } diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 92012f0dbea..83a3a195786 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php index 36346ad255d..e31c3d2faa3 100644 --- a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ); } diff --git a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php index b9ac08e1bde..69f97f9d0ee 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowExprTypeRuleTest.php @@ -15,7 +15,7 @@ class ThrowExprTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ThrowExprTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new ThrowExprTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 1be1cfae69c..b88e13b002a 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php index a3ba642464e..a5f2fa72320 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionReturnTypeRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule false, false, false, + true, ))); } diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index b61120eb27f..26b4a4b906b 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -21,7 +21,7 @@ class CallCallablesRuleTest extends RuleTestCase protected function getRule(): Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false, true); return new CallCallablesRule( new FunctionCallParametersCheck( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 1958ec232a5..0eea3694d1e 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), ); } diff --git a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php index 15734fdf188..4344c32b144 100644 --- a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php @@ -14,7 +14,7 @@ class CallToNonExistentFunctionRuleTest extends RuleTestCase protected function getRule(): Rule { - return new CallToNonExistentFunctionRule($this->createReflectionProvider(), true); + return new CallToNonExistentFunctionRule($this->createReflectionProvider(), true, true); } public function testEmptyFile(): void diff --git a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php index ccff7cb19d4..7ee37a1458a 100644 --- a/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallUserFuncRuleTest.php @@ -20,7 +20,7 @@ class CallUserFuncRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true)); + return new CallUserFuncRule($reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, true, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index 41cc6084579..758c1e00eba 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php index 6ffeadca252..938020a0ba2 100644 --- a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php @@ -15,7 +15,7 @@ class ClosureReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false))); + return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true))); } public function testClosureReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 1961e8e81cd..0a7f95b2704 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php index e13fc184fc0..18bd5ed2060 100644 --- a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new FunctionCallableRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, true, diff --git a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php index 65e4714487d..8f2d3415b61 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeParameterCastableToStringRuleTest.php @@ -17,7 +17,7 @@ class ImplodeParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); + return new ImplodeParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true))); } public function testNamedArguments(): void diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 678738ac509..c5d89a302ae 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php index e4708c5f4c5..e652375414f 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToNumberRuleTest.php @@ -19,7 +19,7 @@ class ParameterCastableToNumberRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); + return new ParameterCastableToNumberRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true))); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php index 0ac63042314..368ce6b2867 100644 --- a/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class ParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); + return new ParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true))); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index 12307bf5bcf..b5ca9817363 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -20,7 +20,7 @@ class ReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), $this->checkNullables, false, true, $this->checkExplicitMixed, false, false))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), $this->checkNullables, false, true, $this->checkExplicitMixed, false, false, true))); } public function testReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php index 2fc82648218..6aee2ae8a3d 100644 --- a/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php +++ b/tests/PHPStan/Rules/Functions/SortParameterCastableToStringRuleTest.php @@ -19,7 +19,7 @@ class SortParameterCastableToStringRuleTest extends RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false))); + return new SortParameterCastableToStringRule($broker, new ParameterCastableToStringCheck(new RuleLevelHelper($broker, true, false, true, true, true, false, true))); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php index 5c84c216a6e..cade4c576b2 100644 --- a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php @@ -14,7 +14,7 @@ class YieldFromTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new YieldFromTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), true); + return new YieldFromTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), true); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php index d658d3aeb41..500199d28e6 100644 --- a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php @@ -14,7 +14,7 @@ class YieldTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new YieldTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new YieldTypeRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 133e6fd6730..bee0436a8db 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -30,7 +30,7 @@ class CallMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true); return new CallMethodsRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 2cfc7891b57..8b1a40dd212 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -31,7 +31,7 @@ class CallStaticMethodsRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true); return new CallStaticMethodsRule( new StaticMethodCallCheck( $reflectionProvider, @@ -42,6 +42,7 @@ protected function getRule(): Rule ), true, true, + true, ), new FunctionCallParametersCheck( $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php index f9ca07781c5..67bd722602d 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php @@ -16,7 +16,7 @@ class CallToMethodStatementWithoutSideEffectsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new CallToMethodStatementWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new CallToMethodStatementWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index 90564ff6d2c..1fa0216870d 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new CallToStaticMethodStatementWithoutSideEffectsRule( - new RuleLevelHelper($broker, true, false, true, false, false, false), + new RuleLevelHelper($broker, true, false, true, false, false, false, true), $broker, ); } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index c87aabde90b..0af7093585e 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php index 5058756f1c1..8b558e4512e 100644 --- a/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php @@ -19,7 +19,7 @@ class MethodCallableRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true); return new MethodCallableRule( new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 0c658ce936d..856a263c2a4 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -22,7 +22,7 @@ class ReturnTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed, false, $this->checkBenevolentUnionTypes))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, $this->checkUnionTypes, $this->checkExplicitMixed, false, $this->checkBenevolentUnionTypes, true))); } public function testReturnTypeRule(): void diff --git a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php index 169acc6693a..8cec36f2369 100644 --- a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php @@ -22,7 +22,7 @@ class StaticMethodCallableRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true); return new StaticMethodCallableRule( new StaticMethodCallCheck( @@ -34,6 +34,7 @@ protected function getRule(): Rule ), true, true, + true, ), new PhpVersion($this->phpVersion), ); diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php index 06a4be03c51..c13cde0b4ea 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ); } diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php index 372a5b311a5..f3e4ad26916 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 135194070b4..cc27629dcf3 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule { return new InvalidBinaryOperationRule( new ExprPrinter(new Printer()), - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php index c6879f2dc17..3305d725c4d 100644 --- a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php @@ -16,7 +16,7 @@ class InvalidComparisonOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidComparisonOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php index 63c757ecc8a..63a9630c09d 100644 --- a/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidIncDecOperationRuleTest.php @@ -20,7 +20,7 @@ class InvalidIncDecOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidIncDecOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), ); } diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index afd0611e89d..5b1b47c41e3 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -20,7 +20,7 @@ class InvalidUnaryOperationRuleTest extends RuleTestCase protected function getRule(): Rule { return new InvalidUnaryOperationRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), ); } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 7b5f476bc96..d2477d1ca66 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -33,6 +33,7 @@ protected function getRule(): Rule new UnresolvableTypeHelper(), true, true, + true, ); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 1518e6bc289..74e50e10f91 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -26,6 +26,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ), ); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index 153f2b31736..d14f249dcc9 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -27,6 +27,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ), ); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php index 6ce92598f61..4a795dd771f 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php @@ -26,6 +26,7 @@ protected function getRule(): Rule new ClassForbiddenNameCheck(self::getContainer()), ), true, + true, ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index d1e43fd0e1e..0640efd8851 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -18,7 +18,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), new PhpVersion(PHP_VERSION_ID), true, true), + new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, true), ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index a0aa4a72f54..3fab4606d8e 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -24,7 +24,7 @@ class AccessPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties)); + return new AccessPropertiesRule(new AccessPropertiesCheck($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false, false, false, true), new PhpVersion(PHP_VERSION_ID), true, $this->checkDynamicProperties)); } public function testAccessProperties(): void diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index ea4b27f4f89..ab157033034 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -21,11 +21,12 @@ protected function getRule(): Rule return new AccessStaticPropertiesInAssignRule( new AccessStaticPropertiesRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + true, ), ); } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 7060aeecea7..88834051bb5 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -20,11 +20,12 @@ protected function getRule(): Rule $reflectionProvider = $this->createReflectionProvider(); return new AccessStaticPropertiesRule( $reflectionProvider, - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), ), + true, ); } diff --git a/tests/PHPStan/Rules/Properties/Bug7074Test.php b/tests/PHPStan/Rules/Properties/Bug7074Test.php index d3398ed7e7f..55df196d56c 100644 --- a/tests/PHPStan/Rules/Properties/Bug7074Test.php +++ b/tests/PHPStan/Rules/Properties/Bug7074Test.php @@ -15,7 +15,7 @@ class Bug7074Test extends RuleTestCase protected function getRule(): Rule { - return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php index eae03f029cf..ad1a9b43a9d 100644 --- a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php @@ -14,7 +14,7 @@ class DefaultValueTypesAssignedToPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new DefaultValueTypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testDefaultValueTypesAssignedToProperties(): void diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php index 97a9f1b1f68..cfe69723414 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php @@ -32,6 +32,7 @@ protected function getRule(): Rule new PhpVersion($this->phpVersion), true, false, + true, ); } diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index 6f4a6121ec2..e9dcd4a4838 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php index 5f627c1902f..fe5d59a5b5e 100644 --- a/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php @@ -27,7 +27,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false), + new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php index 0857934cb5f..83c919af1ff 100644 --- a/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadingWriteOnlyPropertiesRuleTest.php @@ -17,7 +17,7 @@ class ReadingWriteOnlyPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new ReadingWriteOnlyPropertiesRule(new PropertyDescriptor(), new PropertyReflectionFinder(), new RuleLevelHelper($this->createReflectionProvider(), true, $this->checkThisOnly, true, false, false, false), $this->checkThisOnly); + return new ReadingWriteOnlyPropertiesRule(new PropertyDescriptor(), new PropertyReflectionFinder(), new RuleLevelHelper($this->createReflectionProvider(), true, $this->checkThisOnly, true, false, false, false, true), $this->checkThisOnly); } public function testPropertyMustBeReadableInAssignOp(): void diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 2ba73fdc96d..8d050e7636c 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -17,7 +17,7 @@ class TypesAssignedToPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false), new PropertyReflectionFinder()); + return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false, true), new PropertyReflectionFinder()); } public function testTypesAssignedToProperties(): void diff --git a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php index 21c70b7adb2..b11e08f127d 100644 --- a/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/WritingToReadOnlyPropertiesRuleTest.php @@ -17,7 +17,7 @@ class WritingToReadOnlyPropertiesRuleTest extends RuleTestCase protected function getRule(): Rule { - return new WritingToReadOnlyPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); + return new WritingToReadOnlyPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true), new PropertyDescriptor(), new PropertyReflectionFinder(), $this->checkThisOnly); } public function testCheckThisOnlyProperties(): void diff --git a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php index fb35b438a0e..67e500fc634 100644 --- a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php @@ -32,7 +32,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false), + new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), diff --git a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php index 95e94df1687..f8268f8fcd4 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php @@ -15,7 +15,7 @@ class ParameterOutAssignedTypeRuleTest extends RuleTestCase protected function getRule(): TRule { return new ParameterOutAssignedTypeRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php index 8f3b40c7b6a..5929aad03a2 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutExecutionEndTypeRuleTest.php @@ -15,7 +15,7 @@ class ParameterOutExecutionEndTypeRuleTest extends RuleTestCase protected function getRule(): Rule { return new ParameterOutExecutionEndTypeRule( - new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false), + new RuleLevelHelper($this->createReflectionProvider(), true, false, true, true, false, false, true), ); } diff --git a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php index f1c3af3cc09..d26101b421f 100644 --- a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php @@ -15,7 +15,7 @@ class VariableCloningRuleTest extends RuleTestCase protected function getRule(): Rule { - return new VariableCloningRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false)); + return new VariableCloningRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true)); } public function testClone(): void From cd2ca6483689a9805b8706c22897ea5413729b6d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Apr 2025 19:53:55 +0200 Subject: [PATCH 1249/3097] Faster processing of array comparisons with constant offsets --- src/Analyser/MutatingScope.php | 4 + .../Analyser/AnalyserIntegrationTest.php | 6 + tests/PHPStan/Analyser/data/bug-12800.php | 123 ++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12800.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 32b72333440..2d23a406655 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4428,6 +4428,10 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); + if ($newType->isConstantScalarValue()->yes() && $newType->equals($originalExprType)) { + // don't add the same type over and over again to improve performance + return $this; + } return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes()); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e00c33313d6..e8bbb0d1fd1 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1554,6 +1554,12 @@ public function testBug12787(): void $this->assertNoErrors($errors); } + public function testBug12800(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12800.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12800.php b/tests/PHPStan/Analyser/data/bug-12800.php new file mode 100644 index 00000000000..66d362f5271 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12800.php @@ -0,0 +1,123 @@ + $labels + */ + public function b(array $labels, \stdClass $payment): bool + { + $fullData = ' + { + "additionalData": { + "acquirerAccountCode": "TestPmmAcquirerAccount", + "authorisationMid": "1009", + "cvcResult": "1 Matches", + "avsResult": "4 AVS not supported for this card type", + "authCode": "25595", + "acquirerReference": "acquirerReference", + "expiryDate": "8/2018", + "avsResultRaw": "Y", + "cvcResultRaw": "M", + "refusalReasonRaw": "00 : Approved or completed successfully", + "refusalCodeRaw": "00", + "acquirerCode": "TestPmmAcquirer", + "inferredRefusalReason": "3D Secure Mandated", + "networkTxReference": "MCC123456789012", + "cardHolderName": "Test Cardholder", + "issuerCountry": "NL", + "countryCode": "NL", + "cardBin": "411111", + "issuerBin": "41111101", + "cardSchemeCommercial": "true", + "cardPaymentMethod": "visa", + "cardIssuingBank": "Bank of America", + "cardIssuingCountry": "US", + "cardIssuingCurrency": "USD", + "fundingSource": "PREPAID_RELOADABLE", + "cardSummary": "1111", + "isCardCommercial": "true", + "paymentMethodVariant": "visadebit", + "paymentMethod": "visa", + "coBrandedWith": "visa", + "businessTypeIdentifier": "PP", + "cardProductId": "P", + "bankSummary": "1111", + "bankAccount.ownerName": "A. Klaassen", + "bankAccount.iban": "NL13TEST0123456789", + "cavv": "AQIDBAUGBw", + "xid": "ODgxNDc2MDg2", + "cavvAlgorithm": "3", + "eci": "02", + "dsTransID": "f8062b92-66e9-4c5a-979a-f465e66a6e48", + "threeDSVersion": "2.1.0", + "threeDAuthenticatedResponse": "Y", + "liabilityShift": "true", + "threeDOffered": "true", + "threeDAuthenticated": "false", + "challengeCancel": "01", + "fraudResultType": "FRAUD", + "fraudManualReview": "false" + }, + "fraudResult": { + "accountScore": 10, + "result": { + "fraudCheckResult": { + "accountScore": "10", + "checkId": "26", + "name": "ShopperEmailRefCheck" + } + } + }, + "response": "[cancelOrRefund-received]" + }'; + + $result = json_decode($fullData, true); + + $r = $labels['result_code'] === '' + && $labels['merchant_reference'] === $payment->merchant_reference + && $labels['brand_code'] === $payment->brand_code + && $labels['acquirer_account_code'] === $result['additionalData']['acquirerAccountCode'] + && $labels['authorisation_mid'] === $result['additionalData']['authorisationMid'] + && $labels['cvc_result'] === $result['additionalData']['cvcResult'] + && $labels['auth_code'] === $result['additionalData']['authCode'] + && $labels['acquirer_reference'] === $result['additionalData']['acquirerReference'] + && $labels['expiry_date'] === $result['additionalData']['expiryDate'] + && $labels['avs_result_raw'] === $result['additionalData']['avsResultRaw'] + && $labels['cvc_result_raw'] === $result['additionalData']['cvcResultRaw'] + && $labels['acquirer_code'] === $result['additionalData']['acquirerCode'] + && $labels['inferred_refusal_reason'] === $result['additionalData']['inferredRefusalReason'] + && $labels['network_tx_reference'] === $result['additionalData']['networkTxReference'] + && $labels['issuer_country'] === $result['additionalData']['issuerCountry'] + && $labels['country_code'] === $result['additionalData']['countryCode'] + && $labels['card_bin'] === $result['additionalData']['cardBin'] + && $labels['issuer_bin'] === $result['additionalData']['issuerBin'] + && $labels['card_scheme_commercial'] === $result['additionalData']['cardSchemeCommercial'] + && $labels['card_payment_method'] === $result['additionalData']['cardPaymentMethod'] + && $labels['card_issuing_bank'] === $result['additionalData']['cardIssuingBank'] + && $labels['card_issuing_country'] === $result['additionalData']['cardIssuingCountry'] + && $labels['card_issuing_currency'] === $result['additionalData']['cardIssuingCurrency'] + && $labels['card_summary'] === $result['additionalData']['cardSummary'] + && $labels['payment_method_variant'] === $result['additionalData']['paymentMethodVariant'] + && $labels['payment_method'] === $result['additionalData']['paymentMethod'] + && $labels['co_branded_with'] === $result['additionalData']['coBrandedWith'] + && $labels['business_type_identifier'] === $result['additionalData']['businessTypeIdentifier'] + && $labels['card_product_id'] === $result['additionalData']['cardProductId'] + && $labels['bank_summary'] === $result['additionalData']['bankSummary'] + && $labels['cavv'] === $result['additionalData']['cavv'] + && $labels['xid'] === $result['additionalData']['xid'] + && $labels['cavv_algorithm'] === $result['additionalData']['cavvAlgorithm'] + && $labels['eci'] === $result['additionalData']['eci'] + && $labels['ds_trans_id'] === $result['additionalData']['dsTransID'] + && $labels['liability_shift'] === $result['additionalData']['liabilityShift'] + && $labels['fraud_result_type'] === $result['additionalData']['fraudResultType'] + && $labels['fraud_manual_review'] === $result['additionalData']['fraudManualReview'] + && $labels['fraud_result_account_score'] === $result['fraudResult']['accountScore'] + && $labels['fraud_result_check_id'] === $result['fraudResult']['result']['fraudCheckResult']['checkId'] + && $labels['fraud_result_name'] === $result['fraudResult']['result']['fraudCheckResult']['name'] + && $labels['response'] === $result['response']; + return $r; + } +} From da74773df7198bb61a69b65af26dd68186b1f91b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 14 Apr 2025 10:57:59 +0200 Subject: [PATCH 1250/3097] Remember narrowed types from the constructor when analysing other methods --- conf/config.neon | 1 + src/Analyser/MutatingScope.php | 86 ++++++++++++-- src/Analyser/NodeScopeResolver.php | 55 ++++++++- src/Rules/IssetCheck.php | 24 +++- src/Testing/RuleTestCase.php | 6 + src/Testing/TypeInferenceTestCase.php | 1 + tests/PHPStan/Analyser/AnalyserTest.php | 1 + ...er-readonly-constructor-narrowed-hooks.php | 35 ++++++ ...remember-readonly-constructor-narrowed.php | 109 ++++++++++++++++++ .../ExistingClassInInstanceOfRuleTest.php | 30 +++++ ...remember-class-exists-from-constructor.php | 44 +++++++ .../Rules/Constants/ConstantRuleTest.php | 46 ++++++++ .../data/remembered-constructor-scope.php | 100 ++++++++++++++++ .../CallToNonExistentFunctionRuleTest.php | 16 +++ ...ember-function-exists-from-constructor.php | 35 ++++++ .../MissingReadOnlyPropertyAssignRuleTest.php | 27 +++++ .../Rules/Properties/data/bug-10048.php | 26 +++++ .../Rules/Properties/data/bug-11828.php | 31 +++++ .../PHPStan/Rules/Variables/EmptyRuleTest.php | 25 ++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 17 +++ .../Rules/Variables/NullCoalesceRuleTest.php | 17 +++ .../isset-after-remembered-constructor.php | 98 ++++++++++++++++ 22 files changed, 820 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed-hooks.php create mode 100644 tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed.php create mode 100644 tests/PHPStan/Rules/Classes/data/remember-class-exists-from-constructor.php create mode 100644 tests/PHPStan/Rules/Constants/data/remembered-constructor-scope.php create mode 100644 tests/PHPStan/Rules/Functions/data/remember-function-exists-from-constructor.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-10048.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-11828.php create mode 100644 tests/PHPStan/Rules/Variables/data/isset-after-remembered-constructor.php diff --git a/conf/config.neon b/conf/config.neon index 555e7ec53c7..4d18b9552e9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -495,6 +495,7 @@ services: implicitThrows: %exceptions.implicitThrows% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% universalObjectCratesClasses: %universalObjectCratesClasses% + narrowMethodScopeFromConstructor: true - class: PHPStan\Analyser\ConstantResolver diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2d23a406655..42034f0ae3e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -294,6 +294,77 @@ public function enterDeclareStrictTypes(): self ); } + /** + * @param array $currentExpressionTypes + * @return array + */ + private function rememberConstructorExpressions(array $currentExpressionTypes): array + { + $expressionTypes = []; + foreach ($currentExpressionTypes as $exprString => $expressionTypeHolder) { + $expr = $expressionTypeHolder->getExpr(); + if ($expr instanceof FuncCall) { + if ( + !$expr->name instanceof Name + || !in_array($expr->name->name, ['class_exists', 'function_exists'], true) + ) { + continue; + } + } elseif ($expr instanceof PropertyFetch) { + if ( + !$expr->name instanceof Node\Identifier + || !$expr->var instanceof Variable + || $expr->var->name !== 'this' + || !$this->phpVersion->supportsReadOnlyProperties() + ) { + continue; + } + + $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this); + if ($propertyReflection === null) { + continue; + } + + $nativePropertyReflection = $propertyReflection->getNativeReflection(); + if ($nativePropertyReflection === null || !$nativePropertyReflection->isReadOnly()) { + continue; + } + } elseif (!$expr instanceof ConstFetch && !$expr instanceof PropertyInitializationExpr) { + continue; + } + + $expressionTypes[$exprString] = $expressionTypeHolder; + } + + if (array_key_exists('$this', $currentExpressionTypes)) { + $expressionTypes['$this'] = $currentExpressionTypes['$this']; + } + + return $expressionTypes; + } + + public function rememberConstructorScope(): self + { + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->getFunction(), + $this->getNamespace(), + $this->rememberConstructorExpressions($this->expressionTypes), + $this->rememberConstructorExpressions($this->nativeExpressionTypes), + $this->conditionalExpressions, + $this->inClosureBindScopeClasses, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + [], + [], + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope, + $this->nativeTypesPromoted, + ); + } + /** @api */ public function isInClass(): bool { @@ -3286,7 +3357,7 @@ public function enterFunction( private function enterFunctionLike( PhpFunctionFromParserNodeReflection $functionReflection, - bool $preserveThis, + bool $preserveConstructorScope, ): self { $parametersByName = []; @@ -3298,6 +3369,12 @@ private function enterFunctionLike( $expressionTypes = []; $nativeExpressionTypes = []; $conditionalTypes = []; + + if ($preserveConstructorScope) { + $expressionTypes = $this->rememberConstructorExpressions($this->expressionTypes); + $nativeExpressionTypes = $this->rememberConstructorExpressions($this->nativeExpressionTypes); + } + foreach ($functionReflection->getParameters() as $parameter) { $parameterType = $parameter->getType(); @@ -3348,13 +3425,6 @@ private function enterFunctionLike( $nativeExpressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $nativeParameterType); } - if ($preserveThis && array_key_exists('$this', $this->expressionTypes)) { - $expressionTypes['$this'] = $this->expressionTypes['$this']; - } - if ($preserveThis && array_key_exists('$this', $this->nativeExpressionTypes)) { - $nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this']; - } - return $this->scopeFactory->create( $this->context, $this->isDeclareStrictTypes(), diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 668c3aa9bd2..e5cffa85602 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -218,6 +218,7 @@ use function str_starts_with; use function strtolower; use function trim; +use function usort; use const PHP_VERSION_ID; use const SORT_NUMERIC; @@ -271,6 +272,7 @@ public function __construct( private readonly array $universalObjectCratesClasses, private readonly bool $implicitThrows, private readonly bool $treatPhpDocTypesAsCertain, + private readonly bool $narrowMethodScopeFromConstructor, ) { $earlyTerminatingMethodNames = []; @@ -791,6 +793,38 @@ private function processStmtNode( $classReflection, $methodReflection, ), $methodScope); + + if ($isConstructor && $this->narrowMethodScopeFromConstructor) { + $finalScope = null; + + foreach ($executionEnds as $executionEnd) { + if ($executionEnd->getStatementResult()->isAlwaysTerminating()) { + continue; + } + + $endScope = $executionEnd->getStatementResult()->getScope(); + if ($finalScope === null) { + $finalScope = $endScope; + continue; + } + + $finalScope = $finalScope->mergeWith($endScope); + } + + foreach ($gatheredReturnStatements as $statement) { + if ($finalScope === null) { + $finalScope = $statement->getScope(); + continue; + } + + $finalScope = $finalScope->mergeWith($statement->getScope()); + } + + if ($finalScope !== null) { + $scope = $finalScope->rememberConstructorScope(); + } + + } } } elseif ($stmt instanceof Echo_) { $hasYield = false; @@ -925,7 +959,26 @@ private function processStmtNode( $classStatementsGatherer = new ClassStatementsGatherer($classReflection, $nodeCallback); $this->processAttributeGroups($stmt, $stmt->attrGroups, $classScope, $classStatementsGatherer); - $this->processStmtNodes($stmt, $stmt->stmts, $classScope, $classStatementsGatherer, $context); + $classLikeStatements = $stmt->stmts; + if ($this->narrowMethodScopeFromConstructor) { + // analyze static methods first; constructor next; instance methods and property hooks last so we can carry over the scope + usort($classLikeStatements, static function ($a, $b) { + if ($a instanceof Node\Stmt\Property) { + return 1; + } + if ($b instanceof Node\Stmt\Property) { + return -1; + } + + if (!$a instanceof Node\Stmt\ClassMethod || !$b instanceof Node\Stmt\ClassMethod) { + return 0; + } + + return [!$a->isStatic(), $a->name->toLowerString() !== '__construct'] <=> [!$b->isStatic(), $b->name->toLowerString() !== '__construct']; + }); + } + + $this->processStmtNodes($stmt, $classLikeStatements, $classScope, $classStatementsGatherer, $context); $nodeCallback(new ClassPropertiesNode($stmt, $this->readWritePropertiesExtensionProvider, $classStatementsGatherer->getProperties(), $classStatementsGatherer->getPropertyUsages(), $classStatementsGatherer->getMethodCalls(), $classStatementsGatherer->getReturnStatementsNodes(), $classStatementsGatherer->getPropertyAssigns(), $classReflection), $classScope); $nodeCallback(new ClassMethodsNode($stmt, $classStatementsGatherer->getMethods(), $classStatementsGatherer->getMethodCalls(), $classReflection), $classScope); $nodeCallback(new ClassConstantsNode($stmt, $classStatementsGatherer->getConstants(), $classStatementsGatherer->getConstantFetches(), $classReflection), $classScope); diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 528f037f8b0..303f0d2c0de 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; +use PHPStan\Node\Expr\PropertyInitializationExpr; use PHPStan\Rules\Properties\PropertyDescriptor; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\NeverType; @@ -143,6 +144,27 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str } if ($propertyReflection->hasNativeType() && !$propertyReflection->isVirtual()->yes()) { + if ( + $expr instanceof Node\Expr\PropertyFetch + && $expr->name instanceof Node\Identifier + && $expr->var instanceof Expr\Variable + && $expr->var->name === 'this' + && $propertyReflection->getNativeType()->isNull()->no() + && $scope->hasExpressionType(new PropertyInitializationExpr($propertyReflection->getName()))->yes() + ) { + return $this->generateError( + $propertyReflection->getNativeType(), + sprintf( + '%s %s', + $this->propertyDescriptor->describeProperty($propertyReflection, $scope, $expr), + $operatorDescription, + ), + $typeMessageCallback, + $identifier, + 'propertyNeverNullOrUninitialized', + ); + } + if (!$scope->hasExpressionType($expr)->yes()) { if ($expr instanceof Node\Expr\PropertyFetch) { return $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier); @@ -280,7 +302,7 @@ private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescri /** * @param callable(Type): ?string $typeMessageCallback * @param ErrorIdentifier $identifier - * @param 'variable'|'offset'|'property'|'expr' $identifierSecondPart + * @param 'variable'|'offset'|'property'|'expr'|'propertyNeverNullOrUninitialized' $identifierSecondPart */ private function generateError(Type $type, string $message, callable $typeMessageCallback, string $identifier, string $identifierSecondPart): ?IdentifierRuleError { diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index cd0f52534e1..e1e3cdb2006 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -110,6 +110,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getParameter('universalObjectCratesClasses'), self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), + $this->shouldNarrowMethodScopeFromConstructor(), ); $fileAnalyser = new FileAnalyser( $this->createScopeFactory($reflectionProvider, $typeSpecifier), @@ -261,6 +262,11 @@ protected function shouldFailOnPhpErrors(): bool return true; } + protected function shouldNarrowMethodScopeFromConstructor(): bool + { + return false; + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index dc3e884c885..f693c1fe36e 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -90,6 +90,7 @@ public static function processFile( self::getContainer()->getParameter('universalObjectCratesClasses'), self::getContainer()->getParameter('exceptions')['implicitThrows'], self::getContainer()->getParameter('treatPhpDocTypesAsCertain'), + true, ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles()))); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index bacd85a9675..78bfdfa4d75 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -733,6 +733,7 @@ private function createAnalyser(): Analyser [stdClass::class], true, $this->shouldTreatPhpDocTypesAsCertain(), + true, ); $lexer = new Lexer(); $fileAnalyser = new FileAnalyser( diff --git a/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed-hooks.php b/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed-hooks.php new file mode 100644 index 00000000000..8f03858767c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed-hooks.php @@ -0,0 +1,35 @@ += 8.4 + +namespace RememberReadOnlyConstructorInPropertyHookBodies; + +use function PHPStan\Testing\assertType; + +class User +{ + public string $name { + get { + assertType('1|2', $this->type); + return $this->name ; + } + set { + if (strlen($value) === 0) { + throw new ValueError("Name must be non-empty"); + } + assertType('1|2', $this->type); + $this->name = $value; + } + } + + private readonly int $type; + + public function __construct( + string $name + ) { + $this->name = $name; + if (rand(0,1)) { + $this->type = 1; + } else { + $this->type = 2; + } + } +} diff --git a/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed.php b/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed.php new file mode 100644 index 00000000000..7ab2ea364fa --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed.php @@ -0,0 +1,109 @@ += 8.2 + +namespace RememberReadOnlyConstructor; + +use function PHPStan\Testing\assertType; + +class HelloWorldReadonlyProperty { + private readonly int $i; + + public function __construct() + { + if (rand(0,1)) { + $this->i = 4; + } else { + $this->i = 10; + } + } + + public function doFoo() { + assertType('4|10', $this->i); + } +} + +readonly class HelloWorldReadonlyClass { + private int $i; + private string $class; + private string $interface; + private string $enum; + private string $trait; + + public function __construct(string $class, string $interface, string $enum, string $trait) + { + if (rand(0,1)) { + $this->i = 4; + } else { + $this->i = 10; + } + + if (!class_exists($class)) { + throw new \LogicException(); + } + $this->class = $class; + + if (!interface_exists($interface)) { + throw new \LogicException(); + } + $this->interface = $interface; + + if (!enum_exists($enum)) { + throw new \LogicException(); + } + $this->enum = $enum; + + if (!trait_exists($trait)) { + throw new \LogicException(); + } + $this->trait = $trait; + } + + public function doFoo() { + assertType('4|10', $this->i); + assertType('class-string', $this->class); + assertType('class-string', $this->interface); + assertType('class-string', $this->enum); + assertType('class-string', $this->trait); + } +} + + +class HelloWorldRegular { + private int $i; + + public function __construct() + { + if (rand(0,1)) { + $this->i = 4; + } else { + $this->i = 10; + } + } + + public function doFoo() { + assertType('int', $this->i); + } +} + +class HelloWorldReadonlyPropertySometimesThrowing { + private readonly int $i; + + public function __construct() + { + if (rand(0,1)) { + $this->i = 4; + + return; + } elseif (rand(10,100)) { + $this->i = 10; + return; + } else { + $this->i = 20; + } + + throw new \LogicException(); + } + + public function doFoo() { + assertType('4|10', $this->i); + } +} diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 9841cd6ed44..4e026df3a6c 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -15,6 +15,8 @@ class ExistingClassInInstanceOfRuleTest extends RuleTestCase { + private bool $shouldNarrowMethodScopeFromConstructor = true; + protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); @@ -29,6 +31,11 @@ protected function getRule(): Rule ); } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return $this->shouldNarrowMethodScopeFromConstructor; + } + public function testClassDoesNotExist(): void { $this->analyse( @@ -81,4 +88,27 @@ public function testBug7720(): void ]); } + public function testRememberClassExistsFromConstructorDisabled(): void + { + $this->shouldNarrowMethodScopeFromConstructor = false; + + $this->analyse([__DIR__ . '/data/remember-class-exists-from-constructor.php'], [ + [ + 'Class SomeUnknownClass not found.', + 19, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Class SomeUnknownInterface not found.', + 38, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + + public function testRememberClassExistsFromConstructor(): void + { + $this->analyse([__DIR__ . '/data/remember-class-exists-from-constructor.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/remember-class-exists-from-constructor.php b/tests/PHPStan/Rules/Classes/data/remember-class-exists-from-constructor.php new file mode 100644 index 00000000000..6c7b8cecbfa --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/remember-class-exists-from-constructor.php @@ -0,0 +1,44 @@ += 7.4 + +namespace RememberClassExistsFromConstructor; + +use SomeUnknownClass; +use SomeUnknownInterface; + +class UserWithClass +{ + public function __construct( + ) { + if (!class_exists('SomeUnknownClass')) { + throw new \LogicException(); + } + } + + public function doFoo($m): bool + { + if ($m instanceof SomeUnknownClass) { + return false; + } + return true; + } + +} + +class UserWithInterface +{ + public function __construct( + ) { + if (!interface_exists('SomeUnknownInterface')) { + throw new \LogicException(); + } + } + + public function doFoo($m): bool + { + if ($m instanceof SomeUnknownInterface) { + return false; + } + return true; + } + +} diff --git a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php index d30fbcec677..0da9819e08d 100644 --- a/tests/PHPStan/Rules/Constants/ConstantRuleTest.php +++ b/tests/PHPStan/Rules/Constants/ConstantRuleTest.php @@ -17,6 +17,11 @@ protected function getRule(): Rule return new ConstantRule(true); } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + public function testConstants(): void { define('FOO_CONSTANT', 'foo'); @@ -80,4 +85,45 @@ public function testDefinedScopeMerge(): void ]); } + public function testRememberedConstructorScope(): void + { + $this->analyse([__DIR__ . '/data/remembered-constructor-scope.php'], [ + [ + 'Constant REMEMBERED_FOO not found.', + 23, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant REMEMBERED_FOO not found.', + 38, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant REMEMBERED_FOO not found.', + 51, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant REMEMBERED_FOO not found.', + 65, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant XYZ22 not found.', + 87, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant XYZ not found.', + 88, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Constant XYZ33 not found.', + 98, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Constants/data/remembered-constructor-scope.php b/tests/PHPStan/Rules/Constants/data/remembered-constructor-scope.php new file mode 100644 index 00000000000..7be2b0bf7b7 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/remembered-constructor-scope.php @@ -0,0 +1,100 @@ +createReflectionProvider(), true, true); } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + public function testEmptyFile(): void { $this->analyse([__DIR__ . '/data/empty.php'], []); @@ -258,4 +263,15 @@ public function testBug10003(): void ]); } + public function testRememberFunctionExistsFromConstructor(): void + { + $this->analyse([__DIR__ . '/data/remember-function-exists-from-constructor.php'], [ + [ + 'Function another_unknown_function not found.', + 32, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/remember-function-exists-from-constructor.php b/tests/PHPStan/Rules/Functions/data/remember-function-exists-from-constructor.php new file mode 100644 index 00000000000..1d8640a9ae7 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/remember-function-exists-from-constructor.php @@ -0,0 +1,35 @@ += 7.4 + +namespace RememberFunctionExistsFromConstructor; + +class User +{ + public function __construct( + ) { + if (!function_exists('some_unknown_function')) { + throw new \LogicException(); + } + } + + public function doFoo(): void + { + some_unknown_function(); + } + +} + +class FooUser +{ + public function __construct( + ) { + if (!function_exists('another_unknown_function')) { + echo 'Function another_unknown_function does not exist'; + } + } + + public function doFoo(): void + { + another_unknown_function(); + } + +} diff --git a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php index 3b481b8f14d..f389c3f6284 100644 --- a/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php @@ -16,6 +16,8 @@ class MissingReadOnlyPropertyAssignRuleTest extends RuleTestCase { + private bool $shouldNarrowMethodScopeFromConstructor = false; + protected function getRule(): Rule { return new MissingReadOnlyPropertyAssignRule( @@ -31,6 +33,11 @@ protected function getRule(): Rule ); } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return $this->shouldNarrowMethodScopeFromConstructor; + } + protected function getReadWritePropertiesExtensions(): array { return [ @@ -375,6 +382,26 @@ public function testBug9863(): void ]); } + public function testBug10048(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->shouldNarrowMethodScopeFromConstructor = true; + $this->analyse([__DIR__ . '/data/bug-10048.php'], []); + } + + public function testBug11828(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->shouldNarrowMethodScopeFromConstructor = true; + $this->analyse([__DIR__ . '/data/bug-11828.php'], []); + } + public function testBug9864(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/PHPStan/Rules/Properties/data/bug-10048.php b/tests/PHPStan/Rules/Properties/data/bug-10048.php new file mode 100644 index 00000000000..d537fb65278 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-10048.php @@ -0,0 +1,26 @@ += 8.1 + +namespace Bug10048; + +class Foo { + private readonly string $bar; + private readonly \Closure $callback; + public function __construct() { + $this->bar = "hi"; + $this->useBar(); + echo $this->bar; + $this->callback = function() { + $this->useBar(); + }; + } + + private function useBar(): void { + echo $this->bar; + } + + public function useCallback(): void { + call_user_func($this->callback); + } +} + +(new Foo())->useCallback(); diff --git a/tests/PHPStan/Rules/Properties/data/bug-11828.php b/tests/PHPStan/Rules/Properties/data/bug-11828.php new file mode 100644 index 00000000000..0a030d7bd01 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-11828.php @@ -0,0 +1,31 @@ += 8.1 + +namespace Bug11828; + +class Dummy +{ + /** + * @var callable + */ + private $callable; + private readonly int $foo; + + public function __construct(int $foo) + { + $this->foo = $foo; + + $this->callable = function () { + $foo = $this->getFoo(); + }; + } + + public function getFoo(): int + { + return $this->foo; + } + + public function getCallable(): callable + { + return $this->callable; + } +} diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index f45e41b774e..6e7a5f8181e 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -32,6 +32,11 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; @@ -206,4 +211,24 @@ public function testBug12658(): void $this->analyse([__DIR__ . '/data/bug-12658.php'], []); } + public function testIssetAfterRememberedConstructor(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ + [ + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$false in empty() is always falsy.', + 93, + ], + [ + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$true in empty() is not falsy.', + 95, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index f25e40b2d7b..f92076b16a4 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -32,6 +32,11 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; @@ -480,4 +485,16 @@ public function testBug12771(): void $this->analyse([__DIR__ . '/data/bug-12771.php'], []); } + public function testIssetAfterRememberedConstructor(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ + [ + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string in isset() is not nullable.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 0745db66a63..0e849ab84ee 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -32,6 +32,11 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } + public function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + public function testCoalesceRule(): void { $this->treatPhpDocTypesAsCertain = true; @@ -356,4 +361,16 @@ public function testBug12553(): void $this->analyse([__DIR__ . '/data/bug-12553.php'], []); } + public function testIssetAfterRememberedConstructor(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ + [ + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string on left side of ?? is not nullable.', + 46, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/isset-after-remembered-constructor.php b/tests/PHPStan/Rules/Variables/data/isset-after-remembered-constructor.php new file mode 100644 index 00000000000..2c48e6249dc --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/isset-after-remembered-constructor.php @@ -0,0 +1,98 @@ += 8.2 + +namespace IssetOrCoalesceOnNonNullableInitializedProperty; + +class User +{ + private ?string $nullableString; + private string $maybeUninitializedString; + private string $string; + + private $untyped; + + public function __construct() + { + if (rand(0, 1)) { + $this->nullableString = 'hello'; + $this->string = 'world'; + $this->maybeUninitializedString = 'something'; + } else { + $this->nullableString = null; + $this->string = 'world 2'; + $this->untyped = 123; + } + } + + public function doFoo(): void + { + if (isset($this->maybeUninitializedString)) { + echo $this->maybeUninitializedString; + } + if (isset($this->nullableString)) { + echo $this->nullableString; + } + if (isset($this->string)) { + echo $this->string; + } + if (isset($this->untyped)) { + echo $this->untyped; + } + } + + public function doBar(): void + { + echo $this->maybeUninitializedString ?? 'default'; + echo $this->nullableString ?? 'default'; + echo $this->string ?? 'default'; + echo $this->untyped ?? 'default'; + } + + public function doFooBar(): void + { + if (empty($this->maybeUninitializedString)) { + echo $this->maybeUninitializedString; + } + if (empty($this->nullableString)) { + echo $this->nullableString; + } + if (empty($this->string)) { + echo $this->string; + } + if (empty($this->untyped)) { + echo $this->untyped; + } + } +} + +class MoreEmptyCases +{ + private false|string $union; + private false $false; + private true $true; + private bool $bool; + + public function __construct() + { + if (rand(0, 1)) { + $this->union = 'nope'; + $this->bool = true; + } elseif (rand(10, 20)) { + $this->union = false; + $this->bool = false; + } + $this->false = false; + $this->true = true; + } + + public function doFoo(): void + { + if (empty($this->union)) { + } + if (empty($this->bool)) { + } + if (empty($this->false)) { + } + if (empty($this->true)) { + } + } +} From 689f4bb7988f2a8793536a9c2c86164537165013 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 01:53:23 +0000 Subject: [PATCH 1251/3097] Update dependency composer/semver to v3.4.3 --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index bf524029dd3..a219edf40e1 100644 --- a/composer.lock +++ b/composer.lock @@ -227,24 +227,24 @@ }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -288,7 +288,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -304,7 +304,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", From 2fce3f0e1f7ce0199466d6147b475afc03a6f840 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 02:18:30 +0000 Subject: [PATCH 1252/3097] Update dependency composer/ca-bundle to v1.5.6 --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index a219edf40e1..27f5c4cf65b 100644 --- a/composer.lock +++ b/composer.lock @@ -72,16 +72,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.5.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f65c239c970e7f072f067ab78646e9f0b2935175", + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175", "shasum": "" }, "require": { @@ -91,8 +91,8 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", @@ -128,7 +128,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.5.6" }, "funding": [ { @@ -144,7 +144,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2025-03-06T14:30:56+00:00" }, { "name": "composer/pcre", From aa4527b32eae52dd9a96b104fe8a49206b1387f4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 14 Apr 2025 11:16:31 +0200 Subject: [PATCH 1253/3097] Update phpstorm-stubs --- bin/generate-function-metadata.php | 10 +++++++++- composer.json | 2 +- composer.lock | 10 +++++----- resources/functionMetadata.php | 10 ++++++++-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index 80032561d9c..d161d374e46 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -130,13 +130,21 @@ public function enterNode(Node $node) $metadata[$functionName] = ['hasSideEffects' => false]; } foreach ($visitor->impureFunctions as $functionName) { + if (in_array($functionName, [ + 'class_exists', + 'enum_exists', + 'interface_exists', + 'trait_exists', + ], true)) { + continue; + } if (array_key_exists($functionName, $metadata)) { if (in_array($functionName, [ 'ob_get_contents', ], true)) { continue; } - if ($metadata[$functionName]['hasSideEffects']) { + if (!$metadata[$functionName]['hasSideEffects']) { throw new ShouldNotHappenException($functionName); } } diff --git a/composer.json b/composer.json index bef103a992c..db3a405be25 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#7385d3075dc365911c4a3168fa762de6aa4550c9", + "jetbrains/phpstorm-stubs": "dev-master#44f320d4e03204709450e15105536751add593cd", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 27f5c4cf65b..0f87ec80ec1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4ea576b5718d373ded2bcea605f90eba", + "content-hash": "9027944834e6ebe288e6f096d8a9c48c", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "7385d3075dc365911c4a3168fa762de6aa4550c9" + "reference": "44f320d4e03204709450e15105536751add593cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/7385d3075dc365911c4a3168fa762de6aa4550c9", - "reference": "7385d3075dc365911c4a3168fa762de6aa4550c9", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/44f320d4e03204709450e15105536751add593cd", + "reference": "44f320d4e03204709450e15105536751add593cd", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-02-28T14:37:15+00:00" + "time": "2025-04-14T06:11:08+00:00" }, { "name": "nette/bootstrap", diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index 69cbb72a62b..6136b1c068f 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -766,7 +766,7 @@ 'bzerrstr' => ['hasSideEffects' => false], 'bzopen' => ['hasSideEffects' => false], 'ceil' => ['hasSideEffects' => false], - 'checkdate' => ['hasSideEffects' => true], + 'checkdate' => ['hasSideEffects' => false], 'checkdnsrr' => ['hasSideEffects' => false], 'chgrp' => ['hasSideEffects' => true], 'chmod' => ['hasSideEffects' => true], @@ -859,6 +859,7 @@ 'datefmt_get_timezone_id' => ['hasSideEffects' => false], 'datefmt_is_lenient' => ['hasSideEffects' => false], 'dcngettext' => ['hasSideEffects' => false], + 'debug_backtrace' => ['hasSideEffects' => true], 'decbin' => ['hasSideEffects' => false], 'dechex' => ['hasSideEffects' => false], 'decoct' => ['hasSideEffects' => false], @@ -969,10 +970,11 @@ 'get_included_files' => ['hasSideEffects' => true], 'get_loaded_extensions' => ['hasSideEffects' => false], 'get_meta_tags' => ['hasSideEffects' => true], - 'get_object_vars' => ['hasSideEffects' => false], + 'get_object_vars' => ['hasSideEffects' => true], 'get_parent_class' => ['hasSideEffects' => false], 'get_required_files' => ['hasSideEffects' => true], 'get_resource_id' => ['hasSideEffects' => false], + 'get_resource_type' => ['hasSideEffects' => true], 'get_resources' => ['hasSideEffects' => true], 'getallheaders' => ['hasSideEffects' => false], 'getcwd' => ['hasSideEffects' => true], @@ -987,6 +989,7 @@ 'getmyinode' => ['hasSideEffects' => false], 'getmypid' => ['hasSideEffects' => false], 'getmyuid' => ['hasSideEffects' => false], + 'getopt' => ['hasSideEffects' => true], 'getprotobyname' => ['hasSideEffects' => false], 'getprotobynumber' => ['hasSideEffects' => false], 'getrandmax' => ['hasSideEffects' => false], @@ -1388,6 +1391,7 @@ 'ob_flush' => ['hasSideEffects' => true], 'ob_get_clean' => ['hasSideEffects' => true], 'ob_get_contents' => ['hasSideEffects' => true], + 'ob_get_flush' => ['hasSideEffects' => true], 'ob_get_length' => ['hasSideEffects' => true], 'ob_get_level' => ['hasSideEffects' => true], 'ob_get_status' => ['hasSideEffects' => true], @@ -1575,12 +1579,14 @@ 'ucfirst' => ['hasSideEffects' => false], 'ucwords' => ['hasSideEffects' => false], 'umask' => ['hasSideEffects' => true], + 'uniqid' => ['hasSideEffects' => true], 'unlink' => ['hasSideEffects' => true], 'unpack' => ['hasSideEffects' => false], 'urldecode' => ['hasSideEffects' => false], 'urlencode' => ['hasSideEffects' => false], 'utf8_decode' => ['hasSideEffects' => false], 'utf8_encode' => ['hasSideEffects' => false], + 'version_compare' => ['hasSideEffects' => false], 'vsprintf' => ['hasSideEffects' => false], 'wordwrap' => ['hasSideEffects' => false], 'xml_error_string' => ['hasSideEffects' => false], From 752d229852f34a9d7b2f6470a1627ffdb4a3d876 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 14 Apr 2025 15:46:49 +0200 Subject: [PATCH 1254/3097] DeprecationExtensions: allow custom deprecation-marking logic --- build/enums.neon | 4 + conf/config.neon | 3 + src/Analyser/NodeScopeResolver.php | 3 + .../ConditionalTagsExtension.php | 12 + .../BetterReflectionProvider.php | 35 ++- src/Reflection/ClassReflection.php | 56 ++++- .../ClassConstantDeprecationExtension.php | 29 +++ .../Deprecation/ClassDeprecationExtension.php | 30 +++ .../ConstantDeprecationExtension.php | 29 +++ src/Reflection/Deprecation/Deprecation.php | 35 +++ .../Deprecation/DeprecationProvider.php | 144 +++++++++++ .../EnumCaseDeprecationExtension.php | 30 +++ .../FunctionDeprecationExtension.php | 29 +++ .../MethodDeprecationExtension.php | 29 +++ .../PropertyDeprecationExtension.php | 29 +++ src/Reflection/EnumCaseReflection.php | 28 ++- .../Php/PhpClassReflectionExtension.php | 24 +- src/Testing/RuleTestCase.php | 2 + src/Testing/TypeInferenceTestCase.php | 2 + tests/PHPStan/Analyser/AnalyserTest.php | 2 + .../Deprecation/DeprecationProviderTest.php | 224 ++++++++++++++++++ .../Deprecation/data/CustomDeprecated.php | 15 ++ .../data/CustomDeprecationExtension.php | 82 +++++++ .../data/deprecation-provider.neon | 11 + .../Deprecation/data/deprecations-enums.php | 21 ++ .../Deprecation/data/deprecations.php | 151 ++++++++++++ 26 files changed, 1023 insertions(+), 36 deletions(-) create mode 100644 src/Reflection/Deprecation/ClassConstantDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/ClassDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/ConstantDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/Deprecation.php create mode 100644 src/Reflection/Deprecation/DeprecationProvider.php create mode 100644 src/Reflection/Deprecation/EnumCaseDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/FunctionDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/MethodDeprecationExtension.php create mode 100644 src/Reflection/Deprecation/PropertyDeprecationExtension.php create mode 100644 tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php create mode 100644 tests/PHPStan/Reflection/Deprecation/data/CustomDeprecated.php create mode 100644 tests/PHPStan/Reflection/Deprecation/data/CustomDeprecationExtension.php create mode 100644 tests/PHPStan/Reflection/Deprecation/data/deprecation-provider.neon create mode 100644 tests/PHPStan/Reflection/Deprecation/data/deprecations-enums.php create mode 100644 tests/PHPStan/Reflection/Deprecation/data/deprecations.php diff --git a/build/enums.neon b/build/enums.neon index 3ec87ab42e7..44eaccbbd14 100644 --- a/build/enums.neon +++ b/build/enums.neon @@ -13,3 +13,7 @@ parameters: paths: - ../tests/PHPStan/Type/ObjectTypeTest.php - ../tests/PHPStan/Type/IntersectionTypeTest.php + - + message: '#^Class CustomDeprecations\\MyDeprecatedEnum not found\.$#' + paths: + - ../tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php diff --git a/conf/config.neon b/conf/config.neon index 4d18b9552e9..a9a72fdf01a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -2158,6 +2158,9 @@ services: - class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory + - + class: PHPStan\Reflection\Deprecation\DeprecationProvider + php8Lexer: class: PhpParser\Lexer\Emulative factory: @PHPStan\Parser\LexerFactory::createEmulative() diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e5cffa85602..6910dfa0517 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -137,6 +137,7 @@ use PHPStan\Reflection\Callables\SimpleImpurePoint; use PHPStan\Reflection\Callables\SimpleThrowPoint; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; @@ -256,6 +257,7 @@ public function __construct( private readonly StubPhpDocProvider $stubPhpDocProvider, private readonly PhpVersion $phpVersion, private readonly SignatureMapProvider $signatureMapProvider, + private readonly DeprecationProvider $deprecationProvider, private readonly AttributeReflectionFactory $attributeReflectionFactory, private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver, private readonly FileHelper $fileHelper, @@ -2193,6 +2195,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 6e28b549d37..9610d50c9d8 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -16,6 +16,12 @@ use PHPStan\Parser\RichParser; use PHPStan\PhpDoc\StubFilesExtension; use PHPStan\PhpDoc\TypeNodeResolverExtension; +use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension; +use PHPStan\Reflection\Deprecation\ClassDeprecationExtension; +use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension; +use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension; +use PHPStan\Reflection\Deprecation\MethodDeprecationExtension; +use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension; use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; @@ -61,6 +67,12 @@ public function getConfigSchema(): Nette\Schema\Schema LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG => $bool, DiagnoseExtension::EXTENSION_TAG => $bool, ResultCacheMetaExtension::EXTENSION_TAG => $bool, + ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG => $bool, + ClassDeprecationExtension::CLASS_EXTENSION_TAG => $bool, + EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG => $bool, + FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG => $bool, + MethodDeprecationExtension::METHOD_EXTENSION_TAG => $bool, + PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 4029d265546..707aeca28a2 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -36,6 +36,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Constant\RuntimeConstantReflection; use PHPStan\Reflection\ConstantReflection; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\FunctionReflectionFactory; use PHPStan\Reflection\InitializerExprContext; @@ -85,6 +86,7 @@ public function __construct( private Reflector $reflector, private FileTypeMapper $fileTypeMapper, private PhpDocInheritanceResolver $phpDocInheritanceResolver, + private DeprecationProvider $deprecationProvider, private PhpVersion $phpVersion, private NativeFunctionReflectionProvider $nativeFunctionReflectionProvider, private StubPhpDocProvider $stubPhpDocProvider, @@ -148,6 +150,7 @@ public function getClass(string $className): ClassReflection $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), @@ -243,6 +246,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), @@ -305,8 +309,11 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $phpDocParameterTypes = []; $phpDocReturnTag = null; $phpDocThrowsTag = null; - $deprecatedTag = null; - $isDeprecated = false; + + $deprecation = $this->deprecationProvider->getFunctionDeprecation($reflectionFunction); + $deprecationDescription = $deprecation === null ? null : $deprecation->getDescription(); + $isDeprecated = $deprecation !== null; + $isInternal = false; $isPure = null; $asserts = Assertions::createEmpty(); @@ -327,8 +334,10 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $phpDocParameterTypes = array_map(static fn ($tag) => $tag->getType(), $resolvedPhpDoc->getParamTags()); $phpDocReturnTag = $resolvedPhpDoc->getReturnTag(); $phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag(); - $deprecatedTag = $resolvedPhpDoc->getDeprecatedTag(); - $isDeprecated = $resolvedPhpDoc->isDeprecated(); + if (!$isDeprecated) { + $deprecationDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : $deprecationDescription; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } $isInternal = $resolvedPhpDoc->isInternal(); $isPure = $resolvedPhpDoc->isPure(); $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); @@ -347,7 +356,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection $phpDocParameterTypes, $phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null, $phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null, - $deprecatedTag !== null ? $deprecatedTag->getMessage() : null, + $deprecationDescription, $isDeprecated, $isInternal, $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null, @@ -407,13 +416,15 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn $constantValueType = $this->initializerExprTypeResolver->getType($constantReflection->getValueExpression(), InitializerExprContext::fromGlobalConstant($constantReflection)); $docComment = $constantReflection->getDocComment(); - $isDeprecated = TrinaryLogic::createNo(); - $deprecatedDescription = null; - if ($docComment !== null) { + $deprecation = $this->deprecationProvider->getConstantDeprecation($constantReflection); + $isDeprecated = $deprecation !== null; + $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); + + if ($isDeprecated === false && $docComment !== null) { $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, null, $docComment); - $isDeprecated = TrinaryLogic::createFromBoolean($resolvedPhpDoc->isDeprecated()); + $isDeprecated = $resolvedPhpDoc->isDeprecated(); - if ($resolvedPhpDoc->isDeprecated() && $resolvedPhpDoc->getDeprecatedTag() !== null) { + if ($isDeprecated && $resolvedPhpDoc->getDeprecatedTag() !== null) { $deprecatedMessage = $resolvedPhpDoc->getDeprecatedTag()->getMessage(); $matches = Strings::match($deprecatedMessage ?? '', '#^(\d+)\.(\d+)(?:\.(\d+))?$#'); @@ -423,7 +434,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn $patch = $matches[3] ?? 0; $versionId = sprintf('%d%02d%02d', $major, $minor, $patch); - $isDeprecated = TrinaryLogic::createFromBoolean($this->phpVersion->getVersionId() >= $versionId); + $isDeprecated = $this->phpVersion->getVersionId() >= $versionId; } else { // filter raw version number messages like in // https://github.com/JetBrains/phpstorm-stubs/blob/9608c953230b08f07b703ecfe459cc58d5421437/filter/filter.php#L478 @@ -436,7 +447,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn $constantName, $constantValueType, $fileName, - $isDeprecated, + TrinaryLogic::createFromBoolean($isDeprecated), $deprecatedDescription, ); } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 05a9f9b6a6e..6face3bab19 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -26,6 +26,7 @@ use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\PhpDoc\Tag\TypeAliasImportTag; use PHPStan\PhpDoc\Tag\TypeAliasTag; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; @@ -161,6 +162,7 @@ public function __construct( private PhpDocInheritanceResolver $phpDocInheritanceResolver, private PhpVersion $phpVersion, private SignatureMapProvider $signatureMapProvider, + private DeprecationProvider $deprecationProvider, private AttributeReflectionFactory $attributeReflectionFactory, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, @@ -793,7 +795,8 @@ public function getEnumCases(): array $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext); } $caseName = $case->getName(); - $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); + $attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())); + $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider); } return $this->enumCases = $cases; @@ -819,7 +822,9 @@ public function getEnumCase(string $name): EnumCaseReflection $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this)); } - return new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); + $attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())); + + return new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider); } public function isClass(): bool @@ -1079,6 +1084,10 @@ public function getConstant(string $name): ClassConstantReflection throw new MissingConstantFromReflectionException($this->getName(), $name); } + $deprecation = $this->deprecationProvider->getClassConstantDeprecation($reflectionConstant); + $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); + $isDeprecated = $deprecation !== null; + $declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName()); $fileName = $declaringClass->getFileName(); $phpDocType = null; @@ -1099,8 +1108,10 @@ public function getConstant(string $name): ClassConstantReflection ); } - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); + if (!$isDeprecated) { + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); $varTags = $resolvedPhpDoc->getVarTags(); @@ -1210,11 +1221,8 @@ public function getTypeAliases(): array public function getDeprecatedDescription(): ?string { - if ($this->deprecatedDescription === null && $this->isDeprecated()) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) { - $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage(); - } + if ($this->isDeprecated === null) { + $this->resolveDeprecation(); } return $this->deprecatedDescription; @@ -1223,13 +1231,36 @@ public function getDeprecatedDescription(): ?string public function isDeprecated(): bool { if ($this->isDeprecated === null) { - $resolvedPhpDoc = $this->getResolvedPhpDoc(); - $this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated(); + $this->resolveDeprecation(); } return $this->isDeprecated; } + /** + * @phpstan-assert bool $this->isDeprecated + */ + private function resolveDeprecation(): void + { + $deprecation = $this->deprecationProvider->getClassDeprecation($this->reflection); + if ($deprecation !== null) { + $this->isDeprecated = true; + $this->deprecatedDescription = $deprecation->getDescription(); + return; + } + + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + + if ($resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated()) { + $this->isDeprecated = true; + $this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + return; + } + + $this->isDeprecated = false; + $this->deprecatedDescription = null; + } + public function isBuiltin(): bool { return $this->reflection->isInternal(); @@ -1559,6 +1590,7 @@ public function withTypes(array $types): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, @@ -1590,6 +1622,7 @@ public function withVariances(array $variances): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, @@ -1631,6 +1664,7 @@ public function asFinal(): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->deprecationProvider, $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, diff --git a/src/Reflection/Deprecation/ClassConstantDeprecationExtension.php b/src/Reflection/Deprecation/ClassConstantDeprecationExtension.php new file mode 100644 index 00000000000..39e09047ff0 --- /dev/null +++ b/src/Reflection/Deprecation/ClassConstantDeprecationExtension.php @@ -0,0 +1,29 @@ +description; + } + + public static function createWithDescription(string $description): self + { + $clone = new self(); + $clone->description = $description; + + return $clone; + } + +} diff --git a/src/Reflection/Deprecation/DeprecationProvider.php b/src/Reflection/Deprecation/DeprecationProvider.php new file mode 100644 index 00000000000..9c526121e90 --- /dev/null +++ b/src/Reflection/Deprecation/DeprecationProvider.php @@ -0,0 +1,144 @@ + $propertyDeprecationExtensions */ + private ?array $propertyDeprecationExtensions = null; + + /** @var ?array $methodDeprecationExtensions */ + private ?array $methodDeprecationExtensions = null; + + /** @var ?array $classConstantDeprecationExtensions */ + private ?array $classConstantDeprecationExtensions = null; + + /** @var ?array $classDeprecationExtensions */ + private ?array $classDeprecationExtensions = null; + + /** @var ?array $functionDeprecationExtensions */ + private ?array $functionDeprecationExtensions = null; + + /** @var ?array $constantDeprecationExtensions */ + private ?array $constantDeprecationExtensions = null; + + /** @var ?array $enumCaseDeprecationExtensions */ + private ?array $enumCaseDeprecationExtensions = null; + + public function __construct( + private Container $container, + ) + { + } + + public function getPropertyDeprecation(ReflectionProperty $reflectionProperty): ?Deprecation + { + $this->propertyDeprecationExtensions ??= $this->container->getServicesByTag(PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG); + + foreach ($this->propertyDeprecationExtensions as $extension) { + $deprecation = $extension->getPropertyDeprecation($reflectionProperty); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getMethodDeprecation(ReflectionMethod $methodReflection): ?Deprecation + { + $this->methodDeprecationExtensions ??= $this->container->getServicesByTag(MethodDeprecationExtension::METHOD_EXTENSION_TAG); + + foreach ($this->methodDeprecationExtensions as $extension) { + $deprecation = $extension->getMethodDeprecation($methodReflection); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getClassConstantDeprecation(ReflectionClassConstant $reflectionConstant): ?Deprecation + { + $this->classConstantDeprecationExtensions ??= $this->container->getServicesByTag(ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG); + + foreach ($this->classConstantDeprecationExtensions as $extension) { + $deprecation = $extension->getClassConstantDeprecation($reflectionConstant); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getClassDeprecation(ReflectionClass|ReflectionEnum $reflection): ?Deprecation + { + $this->classDeprecationExtensions ??= $this->container->getServicesByTag(ClassDeprecationExtension::CLASS_EXTENSION_TAG); + + foreach ($this->classDeprecationExtensions as $extension) { + $deprecation = $extension->getClassDeprecation($reflection); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getFunctionDeprecation(ReflectionFunction $reflectionFunction): ?Deprecation + { + $this->functionDeprecationExtensions ??= $this->container->getServicesByTag(FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG); + + foreach ($this->functionDeprecationExtensions as $extension) { + $deprecation = $extension->getFunctionDeprecation($reflectionFunction); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getConstantDeprecation(ReflectionConstant $constantReflection): ?Deprecation + { + $this->constantDeprecationExtensions ??= $this->container->getServicesByTag(ConstantDeprecationExtension::CONSTANT_EXTENSION_TAG); + + foreach ($this->constantDeprecationExtensions as $extension) { + $deprecation = $extension->getConstantDeprecation($constantReflection); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + + public function getEnumCaseDeprecation(ReflectionEnumUnitCase|ReflectionEnumBackedCase $enumCaseReflection): ?Deprecation + { + $this->enumCaseDeprecationExtensions ??= $this->container->getServicesByTag(EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG); + + foreach ($this->enumCaseDeprecationExtensions as $extension) { + $deprecation = $extension->getEnumCaseDeprecation($enumCaseReflection); + if ($deprecation !== null) { + return $deprecation; + } + } + + return null; + } + +} diff --git a/src/Reflection/Deprecation/EnumCaseDeprecationExtension.php b/src/Reflection/Deprecation/EnumCaseDeprecationExtension.php new file mode 100644 index 00000000000..af51945d8e0 --- /dev/null +++ b/src/Reflection/Deprecation/EnumCaseDeprecationExtension.php @@ -0,0 +1,30 @@ + $attributes */ @@ -22,8 +27,22 @@ public function __construct( private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, private ?Type $backingValueType, private array $attributes, + DeprecationProvider $deprecationProvider, ) { + $deprecation = $deprecationProvider->getEnumCaseDeprecation($reflection); + if ($deprecation !== null) { + $this->isDeprecated = true; + $this->deprecatedDescription = $deprecation->getDescription(); + + } elseif ($reflection->isDeprecated()) { + $attributes = $this->reflection->getBetterReflection()->getAttributes(); + $this->isDeprecated = true; + $this->deprecatedDescription = DeprecatedAttributeHelper::getDeprecatedDescription($attributes); + } else { + $this->isDeprecated = false; + $this->deprecatedDescription = null; + } } public function getDeclaringEnum(): ClassReflection @@ -43,17 +62,12 @@ public function getBackingValueType(): ?Type public function isDeprecated(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->reflection->isDeprecated()); + return TrinaryLogic::createFromBoolean($this->isDeprecated); } public function getDeprecatedDescription(): ?string { - if ($this->reflection->isDeprecated()) { - $attributes = $this->reflection->getBetterReflection()->getAttributes(); - return DeprecatedAttributeHelper::getDeprecatedDescription($attributes); - } - - return null; + return $this->deprecatedDescription; } /** diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index a0d4d17471f..fc0dd70dbe8 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -22,6 +22,7 @@ use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; @@ -91,6 +92,7 @@ public function __construct( private NodeScopeResolver $nodeScopeResolver, private PhpMethodReflectionFactory $methodReflectionFactory, private PhpDocInheritanceResolver $phpDocInheritanceResolver, + private DeprecationProvider $deprecationProvider, private AnnotationsMethodsClassReflectionExtension $annotationsMethodsClassReflectionExtension, private AnnotationsPropertiesClassReflectionExtension $annotationsPropertiesClassReflectionExtension, private SignatureMapProvider $signatureMapProvider, @@ -220,8 +222,9 @@ private function createProperty( } } - $deprecatedDescription = null; - $isDeprecated = false; + $deprecation = $this->deprecationProvider->getPropertyDeprecation($propertyReflection); + $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); + $isDeprecated = $deprecation !== null; $isInternal = false; $isReadOnlyByPhpDoc = $classReflection->isImmutable(); $isAllowedPrivateMutation = false; @@ -298,8 +301,11 @@ private function createProperty( $phpDocBlockClassReflection->getCallSiteVarianceMap(), TemplateTypeVariance::createInvariant(), ) : null; - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); + + if (!$isDeprecated) { + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } $isInternal = $resolvedPhpDoc->isInternal(); $isReadOnlyByPhpDoc = $isReadOnlyByPhpDoc || $resolvedPhpDoc->isReadOnly(); $isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation(); @@ -699,6 +705,10 @@ private function createMethod( public function createUserlandMethodReflection(ClassReflection $fileDeclaringClass, ClassReflection $actualDeclaringClass, ReflectionMethod $methodReflection, ?string $declaringTraitName): PhpMethodReflection { + $deprecation = $this->deprecationProvider->getMethodDeprecation($methodReflection); + $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); + $isDeprecated = $deprecation !== null; + $resolvedPhpDoc = null; $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($fileDeclaringClass, $fileDeclaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters())); $phpDocBlockClassReflection = $fileDeclaringClass; @@ -821,8 +831,10 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla ); $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); + if (!$isDeprecated) { + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); $isPure = $resolvedPhpDoc->isPure(); diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index e1e3cdb2006..026069146a1 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -24,6 +24,7 @@ use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\AttributeReflectionFactory; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; @@ -94,6 +95,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(DeprecationProvider::class), self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index f693c1fe36e..0d7e0306d13 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -18,6 +18,7 @@ use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\AttributeReflectionFactory; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; @@ -74,6 +75,7 @@ public static function processFile( self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(DeprecationProvider::class), self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 78bfdfa4d75..1b41db9157c 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -22,6 +22,7 @@ use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\AttributeReflectionFactory; +use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\AlwaysFailRule; @@ -717,6 +718,7 @@ private function createAnalyser(): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(DeprecationProvider::class), self::getContainer()->getByType(AttributeReflectionFactory::class), $phpDocInheritanceResolver, $fileHelper, diff --git a/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php b/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php new file mode 100644 index 00000000000..c52796550dd --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/DeprecationProviderTest.php @@ -0,0 +1,224 @@ +getClass(NotDeprecatedClass::class); + $attributeDeprecatedClass = $reflectionProvider->getClass(AttributeDeprecatedClass::class); + $phpDocDeprecatedClass = $reflectionProvider->getClass(PhpDocDeprecatedClass::class); // @phpstan-ignore classConstant.deprecatedClass + $phpDocDeprecatedClassWithMessages = $reflectionProvider->getClass(PhpDocDeprecatedClassWithMessage::class); // @phpstan-ignore classConstant.deprecatedClass + $attributeDeprecatedClassWithMessages = $reflectionProvider->getClass(AttributeDeprecatedClassWithMessage::class); + $doubleDeprecatedClass = $reflectionProvider->getClass(DoubleDeprecatedClass::class); // @phpstan-ignore classConstant.deprecatedClass + $doubleDeprecatedClassOnlyPhpDocMessage = $reflectionProvider->getClass(DoubleDeprecatedClassOnlyPhpDocMessage::class); // @phpstan-ignore classConstant.deprecatedClass + $doubleDeprecatedClassOnlyAttributeMessage = $reflectionProvider->getClass(DoubleDeprecatedClassOnlyAttributeMessage::class); // @phpstan-ignore classConstant.deprecatedClass + + $notDeprecatedFunction = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\notDeprecatedFunction'), null); + $phpDocDeprecatedFunction = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\phpDocDeprecatedFunction'), null); + $phpDocDeprecatedFunctionWithMessage = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\phpDocDeprecatedFunctionWithMessage'), null); + $attributeDeprecatedFunction = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\attributeDeprecatedFunction'), null); + $attributeDeprecatedFunctionWithMessage = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\attributeDeprecatedFunctionWithMessage'), null); + $doubleDeprecatedFunction = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\doubleDeprecatedFunction'), null); + $doubleDeprecatedFunctionOnlyAttributeMessage = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\doubleDeprecatedFunctionOnlyAttributeMessage'), null); + $doubleDeprecatedFunctionOnlyPhpDocMessage = $reflectionProvider->getFunction(new FullyQualified('CustomDeprecations\\doubleDeprecatedFunctionOnlyPhpDocMessage'), null); + + $scopeFactory = self::getContainer()->getByType(ScopeFactory::class); + + $scopeForNotDeprecatedClass = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($notDeprecatedClass)); + $scopeForDeprecatedClass = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($attributeDeprecatedClass)); + $scopeForPhpDocDeprecatedClass = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($phpDocDeprecatedClass)); + $scopeForPhpDocDeprecatedClassWithMessages = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($phpDocDeprecatedClassWithMessages)); + $scopeForAttributeDeprecatedClassWithMessages = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($attributeDeprecatedClassWithMessages)); + $scopeForDoubleDeprecatedClass = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($doubleDeprecatedClass)); + $scopeForDoubleDeprecatedClassOnlyNativeMessage = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($doubleDeprecatedClassOnlyPhpDocMessage)); + $scopeForDoubleDeprecatedClassOnlyCustomMessage = $scopeFactory->create(ScopeContext::create('dummy.php')->enterClass($doubleDeprecatedClassOnlyAttributeMessage)); + + // class + self::assertFalse($notDeprecatedClass->isDeprecated()); + self::assertNull($notDeprecatedClass->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClass->isDeprecated()); + self::assertNull($attributeDeprecatedClass->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClass->isDeprecated()); + self::assertNull($phpDocDeprecatedClass->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClassWithMessages->isDeprecated()); + self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClassWithMessages->isDeprecated()); + self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClass->isDeprecated()); + self::assertSame('attribute', $doubleDeprecatedClass->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->isDeprecated()); + self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->isDeprecated()); + self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getDeprecatedDescription()); + + // class constants + self::assertFalse($notDeprecatedClass->getConstant('FOO')->isDeprecated()->yes()); + self::assertNull($notDeprecatedClass->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClass->getConstant('FOO')->isDeprecated()->yes()); + self::assertNull($attributeDeprecatedClass->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClass->getConstant('FOO')->isDeprecated()->yes()); + self::assertNull($phpDocDeprecatedClass->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClassWithMessages->getConstant('FOO')->isDeprecated()->yes()); + self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClassWithMessages->getConstant('FOO')->isDeprecated()->yes()); + self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClass->getConstant('FOO')->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClass->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->getConstant('FOO')->isDeprecated()->yes()); + self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getConstant('FOO')->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->getConstant('FOO')->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getConstant('FOO')->getDeprecatedDescription()); + + // properties + self::assertFalse($notDeprecatedClass->getProperty('foo', $scopeForNotDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($notDeprecatedClass->getProperty('foo', $scopeForNotDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClass->getProperty('foo', $scopeForDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($attributeDeprecatedClass->getProperty('foo', $scopeForDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClass->getProperty('foo', $scopeForPhpDocDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($phpDocDeprecatedClass->getProperty('foo', $scopeForPhpDocDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClassWithMessages->getProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->isDeprecated()->yes()); + self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getProperty('foo', $scopeForPhpDocDeprecatedClassWithMessages)->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClassWithMessages->getProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->isDeprecated()->yes()); + self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getProperty('foo', $scopeForAttributeDeprecatedClassWithMessages)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClass->getProperty('foo', $scopeForDoubleDeprecatedClass)->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClass->getProperty('foo', $scopeForDoubleDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->isDeprecated()->yes()); + self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getProperty('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->getDeprecatedDescription()); + + // methods + self::assertFalse($notDeprecatedClass->getMethod('foo', $scopeForNotDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($notDeprecatedClass->getMethod('foo', $scopeForNotDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClass->getMethod('foo', $scopeForDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($attributeDeprecatedClass->getMethod('foo', $scopeForDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClass->getMethod('foo', $scopeForPhpDocDeprecatedClass)->isDeprecated()->yes()); + self::assertNull($phpDocDeprecatedClass->getMethod('foo', $scopeForPhpDocDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedClassWithMessages->getMethod('foo', $scopeForPhpDocDeprecatedClassWithMessages)->isDeprecated()->yes()); + self::assertSame('phpdoc', $phpDocDeprecatedClassWithMessages->getMethod('foo', $scopeForPhpDocDeprecatedClassWithMessages)->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedClassWithMessages->getMethod('foo', $scopeForAttributeDeprecatedClassWithMessages)->isDeprecated()->yes()); + self::assertSame('attribute', $attributeDeprecatedClassWithMessages->getMethod('foo', $scopeForAttributeDeprecatedClassWithMessages)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClass->getMethod('foo', $scopeForDoubleDeprecatedClass)->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClass->getMethod('foo', $scopeForDoubleDeprecatedClass)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyPhpDocMessage->getMethod('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->isDeprecated()->yes()); + self::assertNull($doubleDeprecatedClassOnlyPhpDocMessage->getMethod('foo', $scopeForDoubleDeprecatedClassOnlyNativeMessage)->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedClassOnlyAttributeMessage->getMethod('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedClassOnlyAttributeMessage->getMethod('foo', $scopeForDoubleDeprecatedClassOnlyCustomMessage)->getDeprecatedDescription()); + + // functions + self::assertFalse($notDeprecatedFunction->isDeprecated()->yes()); + self::assertNull($notDeprecatedFunction->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedFunction->isDeprecated()->yes()); + self::assertNull($phpDocDeprecatedFunction->getDeprecatedDescription()); + + self::assertTrue($phpDocDeprecatedFunctionWithMessage->isDeprecated()->yes()); + self::assertSame('phpdoc', $phpDocDeprecatedFunctionWithMessage->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedFunction->isDeprecated()->yes()); + self::assertNull($attributeDeprecatedFunction->getDeprecatedDescription()); + + self::assertTrue($attributeDeprecatedFunctionWithMessage->isDeprecated()->yes()); + self::assertSame('attribute', $attributeDeprecatedFunctionWithMessage->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedFunction->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedFunction->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedFunctionOnlyPhpDocMessage->isDeprecated()->yes()); + self::assertNull($doubleDeprecatedFunctionOnlyPhpDocMessage->getDeprecatedDescription()); + + self::assertTrue($doubleDeprecatedFunctionOnlyAttributeMessage->isDeprecated()->yes()); + self::assertSame('attribute', $doubleDeprecatedFunctionOnlyAttributeMessage->getDeprecatedDescription()); + } + + public function testCustomDeprecationsOfEnumCases(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('PHP 8.1+ is required to test enums.'); + } + + require __DIR__ . '/data/deprecations-enums.php'; + + $reflectionProvider = self::createReflectionProvider(); + + $myEnum = $reflectionProvider->getClass(MyDeprecatedEnum::class); + + self::assertTrue($myEnum->isDeprecated()); + self::assertNull($myEnum->getDeprecatedDescription()); + + self::assertTrue($myEnum->getEnumCase('CustomDeprecated')->isDeprecated()->yes()); + self::assertSame('custom', $myEnum->getEnumCase('CustomDeprecated')->getDeprecatedDescription()); + + self::assertTrue($myEnum->getEnumCase('NativeDeprecated')->isDeprecated()->yes()); + self::assertSame('native', $myEnum->getEnumCase('NativeDeprecated')->getDeprecatedDescription()); + + self::assertTrue($myEnum->getEnumCase('PhpDocDeprecated')->isDeprecated()->yes()); + self::assertNull($myEnum->getEnumCase('PhpDocDeprecated')->getDeprecatedDescription()); // this should not be null + + self::assertFalse($myEnum->getEnumCase('NotDeprecated')->isDeprecated()->yes()); + self::assertNull($myEnum->getEnumCase('NotDeprecated')->getDeprecatedDescription()); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/data/deprecation-provider.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecated.php b/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecated.php new file mode 100644 index 00000000000..95997bf046f --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecated.php @@ -0,0 +1,15 @@ += 8.1 + +namespace CustomDeprecations; + +#[\Attribute(\Attribute::TARGET_ALL)] +class CustomDeprecated { + + public ?string $description; + + public function __construct( + ?string $description = null + ) { + $this->description = $description; + } +} diff --git a/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecationExtension.php b/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecationExtension.php new file mode 100644 index 00000000000..dd0ac38c861 --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/data/CustomDeprecationExtension.php @@ -0,0 +1,82 @@ += 8.0 + +declare(strict_types = 1); + +namespace PHPStan\Tests; + +use CustomDeprecations\CustomDeprecated; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; +use PHPStan\BetterReflection\Reflection\ReflectionConstant; +use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension; +use PHPStan\Reflection\Deprecation\ClassDeprecationExtension; +use PHPStan\Reflection\Deprecation\ConstantDeprecationExtension; +use PHPStan\Reflection\Deprecation\Deprecation; +use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension; +use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension; +use PHPStan\Reflection\Deprecation\MethodDeprecationExtension; +use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension; + +class CustomDeprecationExtension implements + ConstantDeprecationExtension, + ClassDeprecationExtension, + ClassConstantDeprecationExtension, + MethodDeprecationExtension, + PropertyDeprecationExtension, + FunctionDeprecationExtension, + EnumCaseDeprecationExtension +{ + + public function getClassDeprecation(ReflectionClass|ReflectionEnum $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getConstantDeprecation(ReflectionConstant $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getFunctionDeprecation(ReflectionFunction $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getMethodDeprecation(ReflectionMethod $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getPropertyDeprecation(ReflectionProperty $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getClassConstantDeprecation(ReflectionClassConstant $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + public function getEnumCaseDeprecation(ReflectionEnumBackedCase|ReflectionEnumUnitCase $reflection): ?Deprecation + { + return $this->buildDeprecation($reflection); + } + + private function buildDeprecation($reflection): ?Deprecation + { + foreach ($reflection->getAttributes(CustomDeprecated::class) as $attribute) { + $description = $attribute->getArguments()[0] ?? $attribute->getArguments()['description'] ?? null; + return $description === null + ? Deprecation::create() + : Deprecation::createWithDescription($description); + } + + return null; + } +} diff --git a/tests/PHPStan/Reflection/Deprecation/data/deprecation-provider.neon b/tests/PHPStan/Reflection/Deprecation/data/deprecation-provider.neon new file mode 100644 index 00000000000..6b79cfe3f2c --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/data/deprecation-provider.neon @@ -0,0 +1,11 @@ +services: + - + class: PHPStan\Tests\CustomDeprecationExtension + tags: + - phpstan.propertyDeprecationExtension + - phpstan.methodDeprecationExtension + - phpstan.classConstantDeprecationExtension + - phpstan.classDeprecationExtension + - phpstan.functionDeprecationExtension + - phpstan.constantDeprecationExtension + - phpstan.enumCaseDeprecationExtension diff --git a/tests/PHPStan/Reflection/Deprecation/data/deprecations-enums.php b/tests/PHPStan/Reflection/Deprecation/data/deprecations-enums.php new file mode 100644 index 00000000000..44710f9e9f9 --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/data/deprecations-enums.php @@ -0,0 +1,21 @@ += 8.1 + +namespace CustomDeprecations; + +#[CustomDeprecated] +enum MyDeprecatedEnum: string +{ + #[CustomDeprecated('custom')] + case CustomDeprecated = '1'; + + /** + * @deprecated phpdoc + */ + case PhpDocDeprecated = '2'; + + #[\Deprecated('native')] + case NativeDeprecated = '3'; + + case NotDeprecated = '4'; + +} diff --git a/tests/PHPStan/Reflection/Deprecation/data/deprecations.php b/tests/PHPStan/Reflection/Deprecation/data/deprecations.php new file mode 100644 index 00000000000..9df05b1b416 --- /dev/null +++ b/tests/PHPStan/Reflection/Deprecation/data/deprecations.php @@ -0,0 +1,151 @@ += 8.1 + +namespace CustomDeprecations; + +class NotDeprecatedClass +{ + const FOO = 'foo'; + + private $foo; + + public function foo() {} + +} + + +/** @deprecated */ +class PhpDocDeprecatedClass +{ + + /** @deprecated */ + const FOO = 'foo'; + + /** @deprecated */ + private $foo; + + /** @deprecated */ + public function foo() {} + +} +/** @deprecated phpdoc */ +class PhpDocDeprecatedClassWithMessage +{ + + /** @deprecated phpdoc */ + const FOO = 'foo'; + + /** @deprecated phpdoc */ + private $foo; + + /** @deprecated phpdoc */ + public function foo() {} + +} + +#[CustomDeprecated] +class AttributeDeprecatedClass { + #[CustomDeprecated] + public const FOO = 'foo'; + + #[CustomDeprecated] + private $foo; + + #[CustomDeprecated] + public function foo() {} +} + +#[CustomDeprecated('attribute')] +class AttributeDeprecatedClassWithMessage { + #[CustomDeprecated('attribute')] + const FOO = 'foo'; + + #[CustomDeprecated('attribute')] + private $foo; + + #[CustomDeprecated(description: 'attribute')] + public function foo() {} +} + +/** @deprecated phpdoc */ +#[CustomDeprecated('attribute')] +class DoubleDeprecatedClass +{ + + /** @deprecated phpdoc */ + #[CustomDeprecated('attribute')] + const FOO = 'foo'; + + /** @deprecated phpdoc */ + #[CustomDeprecated('attribute')] + private $foo; + + /** @deprecated phpdoc */ + #[CustomDeprecated('attribute')] + public function foo() {} + +} + +/** @deprecated */ +#[CustomDeprecated('attribute')] +class DoubleDeprecatedClassOnlyAttributeMessage +{ + + /** @deprecated */ + #[CustomDeprecated('attribute')] + const FOO = 'foo'; + + /** @deprecated */ + #[CustomDeprecated('attribute')] + private $foo; + + /** @deprecated */ + #[CustomDeprecated('attribute')] + public function foo() {} + +} + +/** @deprecated phpdoc */ +#[CustomDeprecated()] +class DoubleDeprecatedClassOnlyPhpDocMessage +{ + + /** @deprecated phpdoc */ + #[CustomDeprecated()] + const FOO = 'foo'; + + /** @deprecated phpdoc */ + #[CustomDeprecated()] + private $foo; + + /** @deprecated phpdoc */ + #[CustomDeprecated()] + public function foo() {} + +} + + +function notDeprecatedFunction() {} + +/** @deprecated */ +function phpDocDeprecatedFunction() {} + +/** @deprecated phpdoc */ +function phpDocDeprecatedFunctionWithMessage() {} + +#[CustomDeprecated] +function attributeDeprecatedFunction() {} + +#[CustomDeprecated('attribute')] +function attributeDeprecatedFunctionWithMessage() {} + +/** @deprecated phpdoc */ +#[CustomDeprecated('attribute')] +function doubleDeprecatedFunction() {} + +/** @deprecated */ +#[CustomDeprecated('attribute')] +function doubleDeprecatedFunctionOnlyAttributeMessage() {} + +/** @deprecated phpdoc */ +#[CustomDeprecated()] +function doubleDeprecatedFunctionOnlyPhpDocMessage() {} From ed7dae51754437a0515d595ed2757fd5c695391f Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 15 Apr 2025 00:22:36 +0000 Subject: [PATCH 1255/3097] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index db3a405be25..42efad3eca3 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", - "phpstan/php-8-stubs": "0.4.11", + "phpstan/php-8-stubs": "0.4.12", "phpstan/phpdoc-parser": "2.1.0", "psr/http-message": "^1.1", "react/async": "^3", diff --git a/composer.lock b/composer.lock index 0f87ec80ec1..bdeaa98f576 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9027944834e6ebe288e6f096d8a9c48c", + "content-hash": "57704972deb09985fa7fc347f052e075", "packages": [ { "name": "clue/ndjson-react", @@ -2258,16 +2258,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.4.11", + "version": "0.4.12", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "8b29105305d85fa440ae0bce1d4d83fcdf5b47ca" + "reference": "d8f8290313e4fd1b4840c553a8492eff31ad54eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/8b29105305d85fa440ae0bce1d4d83fcdf5b47ca", - "reference": "8b29105305d85fa440ae0bce1d4d83fcdf5b47ca", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/d8f8290313e4fd1b4840c553a8492eff31ad54eb", + "reference": "d8f8290313e4fd1b4840c553a8492eff31ad54eb", "shasum": "" }, "type": "library", @@ -2284,9 +2284,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.11" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.4.12" }, - "time": "2025-02-12T00:19:27+00:00" + "time": "2025-04-15T00:22:00+00:00" }, { "name": "phpstan/phpdoc-parser", From 99299a27f407afe34c2ef8037c84bbe4a6f90e9b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 15 Apr 2025 14:21:58 +0200 Subject: [PATCH 1256/3097] Improve `getopt()` function stub --- stubs/core.stub | 9 +++++++++ tests/PHPStan/Analyser/nsrt/getopt.php | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/stubs/core.stub b/stubs/core.stub index f2b72c235b7..de4c9044234 100644 --- a/stubs/core.stub +++ b/stubs/core.stub @@ -318,3 +318,12 @@ function abs($num) {} * @return ($categorize is true ? array> : array) */ function get_defined_constants(bool $categorize = false): array {} + +/** + * @param array $long_options + * @param mixed $rest_index + * @param-out positive-int $rest_index + * @return __benevolent|array|array>|false> + */ +function getopt(string $short_options, array $long_options = [], &$rest_index = null) {} + diff --git a/tests/PHPStan/Analyser/nsrt/getopt.php b/tests/PHPStan/Analyser/nsrt/getopt.php index 453667cd515..aae0e128e54 100644 --- a/tests/PHPStan/Analyser/nsrt/getopt.php +++ b/tests/PHPStan/Analyser/nsrt/getopt.php @@ -5,5 +5,6 @@ use function getopt; use function PHPStan\Testing\assertType; -$opts = getopt("ab:c::", ["longopt1", "longopt2:", "longopt3::"]); +$opts = getopt("ab:c::", ["longopt1", "longopt2:", "longopt3::"], $restIndex); assertType('(array|string|false>|false)', $opts); +assertType('int<1, max>', $restIndex); From 5df46172237c9966b51ada815a31404fa5729878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Tue, 15 Apr 2025 14:31:47 +0200 Subject: [PATCH 1257/3097] Fix `matches[0]` type for regexes containing `\K` --- src/Type/Regex/RegexGroupParser.php | 17 +++++- .../Analyser/nsrt/preg_match_shapes.php | 60 ++++++++++++++++++- .../nsrt/preg_replace_callback_shapes.php | 11 ++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 0383ea4c5ae..2f90e08c222 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -110,7 +110,7 @@ public function parseGroups(string $regex): ?RegexAstWalkResult RegexGroupWalkResult::createEmpty(), ); - if (!$subjectAsGroupResult->mightContainEmptyStringLiteral()) { + if (!$subjectAsGroupResult->mightContainEmptyStringLiteral() && !$this->containsEscapeK($ast)) { // we could handle numeric-string, in case we know the regex is delimited by ^ and $ if ($subjectAsGroupResult->isNonFalsy()->yes()) { $astWalkResult = $astWalkResult->withSubjectBaseType( @@ -171,6 +171,21 @@ private function updateCapturingAstAddEmptyToken(TreeNode $ast): void $ast->setChildren([$emptyAlternationAst]); } + private function containsEscapeK(TreeNode $ast): bool + { + if ($ast->getId() === 'token' && $ast->getValueToken() === 'match_point_reset') { + return true; + } + + foreach ($ast->getChildren() as $child) { + if ($this->containsEscapeK($child)) { + return true; + } + } + + return false; + } + private function walkRegexAst( TreeNode $ast, ?RegexAlternation $alternation, diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index 5e87970c8e7..545fd191f11 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -1011,7 +1011,65 @@ function bug12749f(string $str): void } } -function bug12397(string $string) : array { +function bug12397(string $string): void { $m = preg_match('#\b([A-Z]{2,})-(\d+)#', $string, $match); assertType('list{0?: string, 1?: non-falsy-string, 2?: numeric-string}', $match); } + +function bug12792(string $string): void { + if (preg_match('~a\Kb~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'b'} + } + + if (preg_match('~a\K~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a\K.+~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{non-empty-string} + } + + if (preg_match('~a\K.*~', $string, $match) === 1) { + assertType('array{string}', $match); + } + + if (preg_match('~a\K(.+)~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{non-empty-string, non-empty-string} + } + + if (preg_match('~a\K(.*)~', $string, $match) === 1) { + assertType('array{string, string}', $match); + } + + if (preg_match('~a\K(.+?)~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{non-empty-string, non-empty-string} + } + + if (preg_match('~a\K(.*?)~', $string, $match) === 1) { + assertType('array{string, string}', $match); + } + + if (preg_match('~a\K(?=.+)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a\K(?=.*)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{''} + } + + if (preg_match('~a(?:x\Kb|c)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'ac'|'b'} + } + + if (preg_match('~a(?:c|x\Kb)~', $string, $match) === 1) { + assertType('array{string}', $match); // could be array{'ac'|'b'} + } + + if (preg_match('~a(y|(?:x\Kb|c))d~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{'acd'|'ayd'|'bd', 'c'|'xb'|'y'} + } + + if (preg_match('~a((?:c|x\Kb)|y)d~', $string, $match) === 1) { + assertType('array{string, non-empty-string}', $match); // could be array{'acd'|'ayd'|'bd', 'c'|'xb'|'y'} + } +} diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php index c6ba4824c2e..7bd70492ee0 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php @@ -45,3 +45,14 @@ function ($matches) { PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL ); }; + +function bug12792(string $string) : void { + preg_replace_callback( + '~\'(?:[^\']+|\'\')*+\'\K|\[(\w*)\]~', + function ($matches) { + assertType("array{0: string, 1?: string}", $matches); + return ''; + }, + $string + ); +} From 7fd50229d01d0455029667cc6a6ed876b6549648 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 15 Apr 2025 15:59:00 +0200 Subject: [PATCH 1258/3097] TypeCombinator returns `non-empty-array` for union of `isIterableAtLeastOnce()->yes()` --- .../ArrayFlipFunctionReturnTypeExtension.php | 8 +++++++- src/Type/TypeCombinator.php | 8 ++++++++ tests/PHPStan/Analyser/TypeSpecifierTest.php | 8 ++++---- tests/PHPStan/Analyser/nsrt/array-flip.php | 4 ++-- tests/PHPStan/Analyser/nsrt/bug-4565.php | 2 +- .../PHPStan/Analyser/nsrt/conditional-vars.php | 14 ++++++++------ .../Rules/Methods/ReturnTypeRuleTest.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 18 ++++++++++++++++-- 8 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php index 0d6342e5842..b5b0eb1df89 100644 --- a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php @@ -6,10 +6,12 @@ use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; final class ArrayFlipFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -35,7 +37,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - return $arrayType->flipArray(); + $flipped = $arrayType->flipArray(); + if ($arrayType->isIterableAtLeastOnce()->yes()) { + return TypeCombinator::intersect($flipped, new NonEmptyArrayType()); + } + return $flipped; } } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 29b57c15937..e6cf82bf5f9 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -639,8 +640,11 @@ private static function intersectWithSubtractedType( */ private static function processArrayAccessoryTypes(array $arrayTypes): array { + $isIterableAtLeastOnce = []; $accessoryTypes = []; foreach ($arrayTypes as $i => $arrayType) { + $isIterableAtLeastOnce[] = $arrayType->isIterableAtLeastOnce(); + if ($arrayType instanceof IntersectionType) { foreach ($arrayType->getTypes() as $innerType) { if ($innerType instanceof TemplateType) { @@ -703,6 +707,10 @@ private static function processArrayAccessoryTypes(array $arrayTypes): array $commonAccessoryTypes[] = $accessoryType[0]; } + if (TrinaryLogic::createYes()->and(...$isIterableAtLeastOnce)->yes()) { + $commonAccessoryTypes[] = new NonEmptyArrayType(); + } + return $commonAccessoryTypes; } diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index ac9c8aa88f5..64734d276fc 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1034,7 +1034,7 @@ public function dataCondition(): iterable ]), ), [ - '$array' => 'array', + '$array' => 'non-empty-array', ], [ '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', @@ -1055,7 +1055,7 @@ public function dataCondition(): iterable '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', ], [ - '$array' => 'array', + '$array' => 'non-empty-array', ], ], [ @@ -1082,7 +1082,7 @@ public function dataCondition(): iterable ]), ), [ - '$array' => 'array', + '$array' => 'non-empty-array', ], [ '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', @@ -1103,7 +1103,7 @@ public function dataCondition(): iterable '$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')', ], [ - '$array' => 'array', + '$array' => 'non-empty-array', ], ], [ diff --git a/tests/PHPStan/Analyser/nsrt/array-flip.php b/tests/PHPStan/Analyser/nsrt/array-flip.php index 090fcde8920..9ec89f5c1d0 100644 --- a/tests/PHPStan/Analyser/nsrt/array-flip.php +++ b/tests/PHPStan/Analyser/nsrt/array-flip.php @@ -72,12 +72,12 @@ function foo10(array $array) { if (array_key_exists('foo', $array)) { assertType('non-empty-array&hasOffset(\'foo\')', $array); - assertType('array', array_flip($array)); + assertType('non-empty-array', array_flip($array)); } if (array_key_exists('foo', $array) && is_int($array['foo'])) { assertType("non-empty-array&hasOffsetValue('foo', int)", $array); - assertType('array', array_flip($array)); + assertType('non-empty-array', array_flip($array)); } if (array_key_exists('foo', $array) && $array['foo'] === 17) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-4565.php b/tests/PHPStan/Analyser/nsrt/bug-4565.php index 55a1c372a58..48ab02dd928 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4565.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4565.php @@ -14,6 +14,6 @@ function test(array $variables) { unset($attributes['href']); assertType("non-empty-array&hasOffsetValue('type', 'button')", $attributes); } - assertType('array', $attributes); + assertType('non-empty-array', $attributes); return $attributes; } diff --git a/tests/PHPStan/Analyser/nsrt/conditional-vars.php b/tests/PHPStan/Analyser/nsrt/conditional-vars.php index e76b112f4de..568c6a8b7f6 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-vars.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-vars.php @@ -10,27 +10,29 @@ class HelloWorld public function conditionalVarInTernary(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('array', $innerHits); + assertType('non-empty-array', $innerHits); $x = array_key_exists('nearest_premise', $innerHits) ? assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits) - : assertType('array', $innerHits); + : assertType('non-empty-array', $innerHits); - assertType('array', $innerHits); + assertType('non-empty-array', $innerHits); } + assertType('array', $innerHits); } /** @param array $innerHits */ public function conditionalVarInIf(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('array', $innerHits); + assertType('non-empty-array', $innerHits); if (array_key_exists('nearest_premise', $innerHits)) { assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits); } else { - assertType('array', $innerHits); + assertType('non-empty-array', $innerHits); } - assertType('array', $innerHits); + assertType('non-empty-array', $innerHits); } + assertType('array', $innerHits); } } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 856a263c2a4..1c62efa0c07 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -870,7 +870,7 @@ public function testBug8146bErrors(): void $this->checkBenevolentUnionTypes = true; $this->analyse([__DIR__ . '/data/bug-8146b-errors.php'], [ [ - "Method Bug8146bError\LocationFixtures::getData() should return array, coordinates: array{lat: float, lng: float}}>> but returns array{Bács-Kiskun: array{Ágasegyháza: array{constituencies: array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}, coordinates: array{lat: 46.8386043, lng: 19.4502899}}, Akasztó: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.6898175, lng: 19.205086}}, Apostag: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.8812652, lng: 18.9648478}}, Bácsalmás: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1250396, lng: 19.3357509}}, Bácsbokod: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.1234737, lng: 19.155708}}, Bácsborsód: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.0989373, lng: 19.1566725}}, Bácsszentgyörgy: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 45.9746039, lng: 19.0398066}}, Bácsszőlős: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1352003, lng: 19.4215997}}, ...}, Baranya: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Békés: array{Almáskamarás: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4617785, lng: 21.092448}}, Battonya: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.2902462, lng: 21.0199215}}, Békés: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.6704899, lng: 21.0434996}}, Békéscsaba: array{constituencies: array{'Békés 1.'}, coordinates: array{lat: 46.6735939, lng: 21.0877309}}, Békéssámson: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4208677, lng: 20.6176498}}, Békésszentandrás: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.8715996, lng: 20.48336}}, Bélmegyer: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.8726019, lng: 21.1832832}}, Biharugra: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.9691009, lng: 21.5987651}}, ...}, Borsod-Abaúj-Zemplén: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Budapest: array{Budapest I. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.4968219, lng: 19.037458}}, Budapest II. ker.: array{constituencies: array{'Budapest 03.', 'Budapest 04.'}, coordinates: array{lat: 47.5393329, lng: 18.986934}}, Budapest III. ker.: array{constituencies: array{'Budapest 04.', 'Budapest 10.'}, coordinates: array{lat: 47.5671768, lng: 19.0368517}}, Budapest IV. ker.: array{constituencies: array{'Budapest 11.', 'Budapest 12.'}, coordinates: array{lat: 47.5648915, lng: 19.0913149}}, Budapest V. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.5002319, lng: 19.0520181}}, Budapest VI. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.509863, lng: 19.0625813}}, Budapest VII. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.5027289, lng: 19.073376}}, Budapest VIII. ker.: array{constituencies: array{'Budapest 01.', 'Budapest 06.'}, coordinates: array{lat: 47.4894184, lng: 19.070668}}, ...}, Csongrád-Csanád: array{Algyő: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3329625, lng: 20.207889}}, Ambrózfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3501417, lng: 20.7313995}}, Apátfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.173317, lng: 20.5800472}}, Árpádhalom: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.6158286, lng: 20.547733}}, Ásotthalom: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.1995983, lng: 19.7833756}}, Baks: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.5518708, lng: 20.1064166}}, Balástya: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.4261828, lng: 20.004933}}, Bordány: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.3194213, lng: 19.9227063}}, ...}, Fejér: array{Aba: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 47.0328193, lng: 18.522359}}, Adony: array{constituencies: array{'Fejér 4.'}, coordinates: array{lat: 47.119831, lng: 18.8612469}}, Alap: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.8075763, lng: 18.684028}}, Alcsútdoboz: array{constituencies: array{'Fejér 3.'}, coordinates: array{lat: 47.4277067, lng: 18.6030325}}, Alsószentiván: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.7910573, lng: 18.732161}}, Bakonycsernye: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.321719, lng: 18.0907379}}, Bakonykúti: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.2458464, lng: 18.195769}}, Balinka: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.3135736, lng: 18.1907168}}, ...}, Győr-Moson-Sopron: array{Abda: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.6962149, lng: 17.5445786}}, Acsalag: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.676095, lng: 17.1977771}}, Ágfalva: array{constituencies: array{'Győr-Moson-Sopron 4.'}, coordinates: array{lat: 47.688862, lng: 16.5110233}}, Agyagosszergény: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.608545, lng: 16.9409912}}, Árpás: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5134127, lng: 17.3931579}}, Ásványráró: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.8287695, lng: 17.499195}}, Babót: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5752269, lng: 17.0758604}}, Bágyogszovát: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5866036, lng: 17.3617273}}, ...}, ...}.", + "Method Bug8146bError\LocationFixtures::getData() should return array, coordinates: array{lat: float, lng: float}}>> but returns array{Bács-Kiskun: array{Ágasegyháza: array{constituencies: array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}, coordinates: array{lat: 46.8386043, lng: 19.4502899}}, Akasztó: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.6898175, lng: 19.205086}}, Apostag: array{constituencies: array{'Bács-Kiskun 3.'}, coordinates: array{lat: 46.8812652, lng: 18.9648478}}, Bácsalmás: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1250396, lng: 19.3357509}}, Bácsbokod: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.1234737, lng: 19.155708}}, Bácsborsód: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 46.0989373, lng: 19.1566725}}, Bácsszentgyörgy: array{constituencies: array{'Bács-Kiskun 6.'}, coordinates: array{lat: 45.9746039, lng: 19.0398066}}, Bácsszőlős: array{constituencies: array{'Bács-Kiskun 5.'}, coordinates: array{lat: 46.1352003, lng: 19.4215997}}, ...}, Baranya: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Békés: array{Almáskamarás: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4617785, lng: 21.092448}}, Battonya: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.2902462, lng: 21.0199215}}, Békés: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.6704899, lng: 21.0434996}}, Békéscsaba: array{constituencies: array{'Békés 1.'}, coordinates: array{lat: 46.6735939, lng: 21.0877309}}, Békéssámson: array{constituencies: array{'Békés 4.'}, coordinates: array{lat: 46.4208677, lng: 20.6176498}}, Békésszentandrás: array{constituencies: array{'Békés 2.'}, coordinates: array{lat: 46.8715996, lng: 20.48336}}, Bélmegyer: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.8726019, lng: 21.1832832}}, Biharugra: array{constituencies: array{'Békés 3.'}, coordinates: array{lat: 46.9691009, lng: 21.5987651}}, ...}, Borsod-Abaúj-Zemplén: non-empty-array|(literal-string&non-falsy-string), float|(literal-string&non-falsy-string)>>>, Budapest: array{Budapest I. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.4968219, lng: 19.037458}}, Budapest II. ker.: array{constituencies: array{'Budapest 03.', 'Budapest 04.'}, coordinates: array{lat: 47.5393329, lng: 18.986934}}, Budapest III. ker.: array{constituencies: array{'Budapest 04.', 'Budapest 10.'}, coordinates: array{lat: 47.5671768, lng: 19.0368517}}, Budapest IV. ker.: array{constituencies: array{'Budapest 11.', 'Budapest 12.'}, coordinates: array{lat: 47.5648915, lng: 19.0913149}}, Budapest V. ker.: array{constituencies: array{'Budapest 01.'}, coordinates: array{lat: 47.5002319, lng: 19.0520181}}, Budapest VI. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.509863, lng: 19.0625813}}, Budapest VII. ker.: array{constituencies: array{'Budapest 05.'}, coordinates: array{lat: 47.5027289, lng: 19.073376}}, Budapest VIII. ker.: array{constituencies: array{'Budapest 01.', 'Budapest 06.'}, coordinates: array{lat: 47.4894184, lng: 19.070668}}, ...}, Csongrád-Csanád: array{Algyő: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3329625, lng: 20.207889}}, Ambrózfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.3501417, lng: 20.7313995}}, Apátfalva: array{constituencies: array{'Csongrád-Csanád 4.'}, coordinates: array{lat: 46.173317, lng: 20.5800472}}, Árpádhalom: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.6158286, lng: 20.547733}}, Ásotthalom: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.1995983, lng: 19.7833756}}, Baks: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.5518708, lng: 20.1064166}}, Balástya: array{constituencies: array{'Csongrád-Csanád 3.'}, coordinates: array{lat: 46.4261828, lng: 20.004933}}, Bordány: array{constituencies: array{'Csongrád-Csanád 2.'}, coordinates: array{lat: 46.3194213, lng: 19.9227063}}, ...}, Fejér: array{Aba: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 47.0328193, lng: 18.522359}}, Adony: array{constituencies: array{'Fejér 4.'}, coordinates: array{lat: 47.119831, lng: 18.8612469}}, Alap: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.8075763, lng: 18.684028}}, Alcsútdoboz: array{constituencies: array{'Fejér 3.'}, coordinates: array{lat: 47.4277067, lng: 18.6030325}}, Alsószentiván: array{constituencies: array{'Fejér 5.'}, coordinates: array{lat: 46.7910573, lng: 18.732161}}, Bakonycsernye: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.321719, lng: 18.0907379}}, Bakonykúti: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.2458464, lng: 18.195769}}, Balinka: array{constituencies: array{'Fejér 2.'}, coordinates: array{lat: 47.3135736, lng: 18.1907168}}, ...}, Győr-Moson-Sopron: array{Abda: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.6962149, lng: 17.5445786}}, Acsalag: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.676095, lng: 17.1977771}}, Ágfalva: array{constituencies: array{'Győr-Moson-Sopron 4.'}, coordinates: array{lat: 47.688862, lng: 16.5110233}}, Agyagosszergény: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.608545, lng: 16.9409912}}, Árpás: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5134127, lng: 17.3931579}}, Ásványráró: array{constituencies: array{'Győr-Moson-Sopron 5.'}, coordinates: array{lat: 47.8287695, lng: 17.499195}}, Babót: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5752269, lng: 17.0758604}}, Bágyogszovát: array{constituencies: array{'Győr-Moson-Sopron 3.'}, coordinates: array{lat: 47.5866036, lng: 17.3617273}}, ...}, ...}.", 12, "Offset 'constituencies' (non-empty-list) does not accept type array{'Bács-Kiskun 4.', true, false, Bug8146bError\X, null}.", ], diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index a62073a40c7..a85b6a47093 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -953,8 +953,8 @@ public function dataUnion(): iterable new HasOffsetType(new ConstantStringType('bar')), ]), ], - ArrayType::class, - 'array', + IntersectionType::class, + 'non-empty-array', ], [ [ @@ -971,6 +971,20 @@ public function dataUnion(): iterable IntersectionType::class, 'non-empty-array&hasOffsetValue(\'foo\', mixed)', ], + [ + [ + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetType(new ConstantStringType('foo')), + ]), + new IntersectionType([ + new ArrayType(new MixedType(), new MixedType()), + new HasOffsetValueType(new ConstantIntegerType(2), new ConstantStringType('foo')), + ]), + ], + IntersectionType::class, + 'non-empty-array', + ], [ [ new BenevolentUnionType([new IntegerType(), new StringType()]), From 596696fefecaeb846547490e3271a78e32c0ad47 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 15 Apr 2025 17:24:28 +0200 Subject: [PATCH 1259/3097] ClassReflection: narrow getNativeReflection after isEnum is true --- build/more-enum-adapter-errors.neon | 7 +++---- src/Reflection/ClassReflection.php | 1 + tests/PHPStan/Reflection/ClassReflectionTest.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/more-enum-adapter-errors.neon b/build/more-enum-adapter-errors.neon index 69882bcfc5b..4e7e2ee0838 100644 --- a/build/more-enum-adapter-errors.neon +++ b/build/more-enum-adapter-errors.neon @@ -1,9 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#1 \\$expected of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) expects class\\-string\\, string given\\.$#" - count: 1 - path: ../tests/PHPStan/Reflection/ClassReflectionTest.php - message: "#^Strict comparison using \\!\\=\\= between class\\-string and 'UnitEnum' will always evaluate to true\\.$#" count: 1 @@ -23,3 +19,6 @@ parameters: message: "#^Class BackedEnum not found\\.$#" count: 1 path: ../src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php + + - + message: "#^Call to method PHPStan\\\\Reflection\\\\ClassReflection::isEnum\\(\\) will always evaluate to false\\.$#" diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 6face3bab19..f6e3da43e9e 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -716,6 +716,7 @@ public function isTrait(): bool /** * @phpstan-assert-if-true ReflectionEnum $this->reflection + * @phpstan-assert-if-true ReflectionEnum $this->getNativeReflection() */ public function isEnum(): bool { diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 7058b8b5105..b4d2fa5eb4c 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -296,7 +296,7 @@ public function testEnumIsFinal(): void $reflectionProvider = $this->createReflectionProvider(); $enum = $reflectionProvider->getClass('PHPStan\Fixture\TestEnum'); $this->assertTrue($enum->isEnum()); - $this->assertInstanceOf('ReflectionEnum', $enum->getNativeReflection()); + $this->assertInstanceOf('ReflectionEnum', $enum->getNativeReflection()); // @phpstan-ignore-line Exact error differs on PHP 7.4 and others $this->assertTrue($enum->isFinal()); $this->assertTrue($enum->isFinalByKeyword()); } From 966aea167b801cc4e9f16a575e076bfb71a913ea Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 15 Apr 2025 17:18:28 +0200 Subject: [PATCH 1260/3097] ResultCacheManager: support dots in parametersNotInvalidatingCache --- conf/config.neon | 22 +++++++++---------- conf/parametersSchema.neon | 5 ++++- .../ResultCache/ResultCacheManager.php | 5 +++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index a9a72fdf01a..6b64f0d1b19 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -191,17 +191,17 @@ parameters: tmpDir: %sysGetTempDir%/phpstan-fixer __validate: true parametersNotInvalidatingCache: - - parameters.editorUrl - - parameters.editorUrlTitle - - parameters.errorFormat - - parameters.ignoreErrors - - parameters.reportUnmatchedIgnoredErrors - - parameters.tipsOfTheDay - - parameters.parallel - - parameters.internalErrorsCountLimit - - parameters.cache - - parameters.memoryLimitFile - - parameters.pro + - [parameters, editorUrl] + - [parameters, editorUrlTitle] + - [parameters, errorFormat] + - [parameters, ignoreErrors] + - [parameters, reportUnmatchedIgnoredErrors] + - [parameters, tipsOfTheDay] + - [parameters, parallel] + - [parameters, internalErrorsCountLimit] + - [parameters, cache] + - [parameters, memoryLimitFile] + - [parameters, pro] - parametersSchema extensions: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 69fc9380b92..abedcd1e624 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -167,7 +167,10 @@ parametersSchema: ]) env: arrayOf(string(), anyOf(int(), string())) sysGetTempDir: string() - parametersNotInvalidatingCache: listOf(string()) + parametersNotInvalidatingCache: listOf(schema(anyOf( + string(), + listOf(string()), + ))) # playground mode sourceLocatorPlaygroundMode: bool() diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index b40e3bb83ed..5335008c348 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -67,7 +67,7 @@ final class ResultCacheManager * @param string[] $bootstrapFiles * @param string[] $scanFiles * @param string[] $scanDirectories - * @param list $parametersNotInvalidatingCache + * @param list> $parametersNotInvalidatingCache */ public function __construct( private Container $container, @@ -886,7 +886,8 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a if ($projectConfigArray !== null) { foreach ($this->parametersNotInvalidatingCache as $parameterPath) { - ArrayHelper::unsetKeyAtPath($projectConfigArray, explode('.', $parameterPath)); + $pathAsArray = is_array($parameterPath) ? $parameterPath : explode('.', $parameterPath); + ArrayHelper::unsetKeyAtPath($projectConfigArray, $pathAsArray); } ksort($projectConfigArray); From 3cdaab0f604352161b9b276aa02bfcea1bc64240 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 15 Apr 2025 17:27:22 +0200 Subject: [PATCH 1261/3097] Regression test --- .../CallToFunctionParametersRuleTest.php | 31 +++++++++ .../Rules/Functions/data/bug-12847.php | 69 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-12847.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 0eea3694d1e..c5fce4e73a9 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2053,4 +2053,35 @@ public function testBug7522(): void $this->analyse([__DIR__ . '/data/bug-7522.php'], []); } + public function testBug12847(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12847.php'], [ + [ + 'Parameter #1 $array of function Bug12847\doSomething expects non-empty-array, mixed given.', + 32, + 'mixed is empty.', + ], + [ + 'Parameter #1 $array of function Bug12847\doSomething expects non-empty-array, mixed given.', + 39, + 'mixed is empty.', + ], + [ + 'Parameter #1 $array of function Bug12847\doSomethingWithInt expects non-empty-array, non-empty-array given.', + 61, + ], + [ + 'Parameter #1 $array of function Bug12847\doSomethingWithInt expects non-empty-array, non-empty-array given.', + 67, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12847.php b/tests/PHPStan/Rules/Functions/data/bug-12847.php new file mode 100644 index 00000000000..c4880d83f6a --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12847.php @@ -0,0 +1,69 @@ + $array + */ + $array = [ + 'abc' => 'def' + ]; + + if (isset($array['def'])) { + doSomething($array); + } +} + +function doFoo(array $array):void { + if (isset($array['def'])) { + doSomething($array); + } +} + +function doFooBar(array $array):void { + if (array_key_exists('foo', $array) && $array['foo'] === 17) { + doSomething($array); + } +} + +function doImplicitMixed($mixed):void { + if (isset($mixed['def'])) { + doSomething($mixed); + } +} + +function doExplicitMixed(mixed $mixed): void +{ + if (isset($mixed['def'])) { + doSomething($mixed); + } +} + +/** + * @param non-empty-array $array + */ +function doSomething(array $array): void +{ + +} + +/** + * @param non-empty-array $array + */ +function doSomethingWithInt(array $array): void +{ + +} + +function doFooBarInt(array $array):void { + if (array_key_exists('foo', $array) && $array['foo'] === 17) { + doSomethingWithInt($array); // expect error, because our array is not sealed + } +} + +function doFooBarString(array $array):void { + if (array_key_exists('foo', $array) && $array['foo'] === "hello") { + doSomethingWithInt($array); // expect error, because our array is not sealed + } +} From 29cb09bf12da8e3b15af5c6214667c207dcd5c4a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 15 Apr 2025 18:28:13 +0200 Subject: [PATCH 1262/3097] Cosmetic tweaks to propertyNeverNullOrUninitialized --- src/Rules/IssetCheck.php | 19 +++++++++++++++---- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 4 ++-- .../PHPStan/Rules/Variables/IssetRuleTest.php | 2 +- .../Rules/Variables/NullCoalesceRuleTest.php | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 303f0d2c0de..9669fccc8a2 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -13,6 +13,7 @@ use PHPStan\Type\VerbosityLevel; use function is_string; use function sprintf; +use function str_starts_with; /** * @phpstan-type ErrorIdentifier = 'empty'|'isset'|'nullCoalesce' @@ -149,7 +150,6 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str && $expr->name instanceof Node\Identifier && $expr->var instanceof Expr\Variable && $expr->var->name === 'this' - && $propertyReflection->getNativeType()->isNull()->no() && $scope->hasExpressionType(new PropertyInitializationExpr($propertyReflection->getName()))->yes() ) { return $this->generateError( @@ -159,9 +159,20 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str $this->propertyDescriptor->describeProperty($propertyReflection, $scope, $expr), $operatorDescription, ), - $typeMessageCallback, + static function (Type $type) use ($typeMessageCallback): ?string { + $originalMessage = $typeMessageCallback($type); + if ($originalMessage === null) { + return null; + } + + if (str_starts_with($originalMessage, 'is not')) { + return sprintf('%s nor uninitialized', $originalMessage); + } + + return sprintf('%s and initialized', $originalMessage); + }, $identifier, - 'propertyNeverNullOrUninitialized', + 'initializedProperty', ); } @@ -302,7 +313,7 @@ private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescri /** * @param callable(Type): ?string $typeMessageCallback * @param ErrorIdentifier $identifier - * @param 'variable'|'offset'|'property'|'expr'|'propertyNeverNullOrUninitialized' $identifierSecondPart + * @param 'variable'|'offset'|'property'|'expr'|'initializedProperty' $identifierSecondPart */ private function generateError(Type $type, string $message, callable $typeMessageCallback, string $identifier, string $identifierSecondPart): ?IdentifierRuleError { diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 6e7a5f8181e..178101b9517 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -221,11 +221,11 @@ public function testIssetAfterRememberedConstructor(): void $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ [ - 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$false in empty() is always falsy.', + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$false in empty() is always falsy and initialized.', 93, ], [ - 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$true in empty() is not falsy.', + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$true in empty() is not falsy nor uninitialized.', 95, ], ]); diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index f92076b16a4..fb3dbb66ee1 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -491,7 +491,7 @@ public function testIssetAfterRememberedConstructor(): void $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ [ - 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string in isset() is not nullable.', + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string in isset() is not nullable nor uninitialized.', 34, ], ]); diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 0e849ab84ee..ba73fbe2fb5 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -367,7 +367,7 @@ public function testIssetAfterRememberedConstructor(): void $this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [ [ - 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string on left side of ?? is not nullable.', + 'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string on left side of ?? is not nullable nor uninitialized.', 46, ], ]); From 7c0c857f7a315455b3b3cbfc9ac237e286fb585e Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Tue, 15 Apr 2025 21:23:27 +0200 Subject: [PATCH 1263/3097] Fix specifying types on nullsafe true/false comparison --- src/Analyser/TypeSpecifier.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-12866.php | 65 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12866.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f7d01f2da68..ff08ce45034 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1134,7 +1134,7 @@ private function specifyTypesForConstantBinaryExpression( { if (!$context->null() && $constantType->isFalse()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { + if (!$context->true() && ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch)) { return $types; } @@ -1148,7 +1148,7 @@ private function specifyTypesForConstantBinaryExpression( if (!$context->null() && $constantType->isTrue()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { + if (!$context->true() && ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch)) { return $types; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12866.php b/tests/PHPStan/Analyser/nsrt/bug-12866.php new file mode 100644 index 00000000000..4360d8bdd16 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12866.php @@ -0,0 +1,65 @@ += 8.0 + +namespace Bug12866; + +use function PHPStan\Testing\assertType; + +interface I +{ + /** + * @phpstan-assert-if-true A $this + */ + public function isA(): bool; +} + +class A implements I +{ + public function isA(): bool + { + return true; + } +} + +class B implements I +{ + public function isA(): bool + { + return false; + } +} + +function takesI(I $i): void +{ + if (!$i->isA()) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesIStrictComparison(I $i): void +{ + if ($i->isA() !== true) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesNullableI(?I $i): void +{ + if (!$i?->isA()) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesNullableIStrictComparison(?I $i): void +{ + if ($i?->isA() !== true) { + return; + } + + assertType('Bug12866\\A', $i); +} From 740ec2900270675c9c70e583dcc482443e8bcc99 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 16 Apr 2025 13:23:44 +0200 Subject: [PATCH 1264/3097] ResultCache: configurable days causing cache skip --- conf/config.neon | 2 ++ conf/parametersSchema.neon | 1 + src/Analyser/ResultCache/ResultCacheManager.php | 8 +++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 6b64f0d1b19..7e11398a3e9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -125,6 +125,7 @@ parameters: earlyTerminatingMethodCalls: [] earlyTerminatingFunctionCalls: [] resultCachePath: %tmpDir%/resultCache.php + resultCacheSkipIfOlderThanDays: 7 resultCacheChecksProjectExtensionFilesDependencies: false dynamicConstantNames: - ICONV_IMPL @@ -518,6 +519,7 @@ services: scanDirectories: %scanDirectories% checkDependenciesOfProjectExtensionFiles: %resultCacheChecksProjectExtensionFilesDependencies% parametersNotInvalidatingCache: %parametersNotInvalidatingCache% + skipResultCacheIfOlderThanDays: %resultCacheSkipIfOlderThanDays% - class: PHPStan\Analyser\ResultCache\ResultCacheClearer diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index abedcd1e624..a148f6fc924 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -148,6 +148,7 @@ parametersSchema: earlyTerminatingMethodCalls: arrayOf(listOf(string())) earlyTerminatingFunctionCalls: listOf(string()) resultCachePath: string() + resultCacheSkipIfOlderThanDays: int() resultCacheChecksProjectExtensionFilesDependencies: bool() dynamicConstantNames: listOf(string()) customRulesetUsed: schema(bool(), nullable()) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 5335008c348..e2ad34e8178 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -86,6 +86,7 @@ public function __construct( private array $scanDirectories, private bool $checkDependenciesOfProjectExtensionFiles, private array $parametersNotInvalidatingCache, + private int $skipResultCacheIfOlderThanDays, ) { } @@ -148,11 +149,12 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); } - if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { + $daysOldForSkip = $this->skipResultCacheIfOlderThanDays; + if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * $daysOldForSkip) { if ($output->isVeryVerbose()) { - $output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.'); + $output->writeLineFormatted(sprintf("Result cache not used because it's more than %d days since last full analysis.", $daysOldForSkip)); } - // run full analysis if the result cache is older than 7 days + // run full analysis if the result cache is older than X days return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); } From a2c09465133b35bab1b83ba4b33c71760a151b5c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 16 Apr 2025 17:56:33 +0200 Subject: [PATCH 1265/3097] Consider comparison as strict when type is the same --- ...InArrayFunctionTypeSpecifyingExtension.php | 6 ++- .../Rules/Methods/CallMethodsRuleTest.php | 10 +++++ .../PHPStan/Rules/Methods/data/bug-12884.php | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12884.php diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index df1bf3499a2..a13e7876669 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -60,7 +60,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $isStrictComparison = $isStrictComparison || $needleType->isEnum()->yes() - || $arrayValueType->isEnum()->yes(); + || $arrayValueType->isEnum()->yes() + || ($needleType->isString()->yes() && $arrayValueType->isString()->yes()) + || ($needleType->isInteger()->yes() && $arrayValueType->isInteger()->yes()) + || ($needleType->isFloat()->yes() && $arrayValueType->isFloat()->yes()) + || ($needleType->isBoolean()->yes() && $arrayValueType->isBoolean()->yes()); if ($arrayExpr instanceof Array_) { $types = null; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index ea34875cd11..726542e93df 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3442,4 +3442,14 @@ public function testBug6828(): void $this->analyse([__DIR__ . '/data/bug-6828.php'], []); } + public function testBug12884(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12884.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12884.php b/tests/PHPStan/Rules/Methods/data/bug-12884.php new file mode 100644 index 00000000000..20ec2cac5fb --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12884.php @@ -0,0 +1,40 @@ + $levels */ + public function __construct( + private LoggerInterface $logger, + public array $levels = [] + ) {} + + public function log(string $level, string $message): void + { + if (!in_array($level, $this->levels, true)) { + $level = LogLevel::INFO; + } + $this->logger->log($level, $message); + } +} + +interface LoggerInterface +{ + /** + * @param 'emergency'|'alert'|'critical'|'error'|'warning'|'notice'|'info'|'debug' $level + */ + public function log($level, string|\Stringable $message): void; +} From 7c1ee34aa09798c2bb5ba182b078e436f0b7820c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Apr 2025 09:33:28 +0200 Subject: [PATCH 1266/3097] Fix wrong property type after assigning iterable type --- src/Type/IterableType.php | 18 +++++++++++++++++- tests/PHPStan/Analyser/nsrt/bug-12891.php | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12891.php diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index e2864494e90..2e6d26a3813 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -236,7 +236,23 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { - return TypeCombinator::union($this, new ArrayType(new MixedType(true), new MixedType(true)), new ObjectType(Traversable::class)); + return TypeCombinator::union( + $this, + new ArrayType( + TypeCombinator::intersect( + $this->keyType->toArrayKey(), + new UnionType([ + new IntegerType(), + new StringType(), + ]), + ), + $this->itemType, + ), + new GenericObjectType(Traversable::class, [ + $this->keyType, + $this->itemType, + ]), + ); } public function isOffsetAccessLegal(): TrinaryLogic diff --git a/tests/PHPStan/Analyser/nsrt/bug-12891.php b/tests/PHPStan/Analyser/nsrt/bug-12891.php new file mode 100644 index 00000000000..a932a97491d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12891.php @@ -0,0 +1,22 @@ + */ + private iterable $builders; + + /** + * @param iterable $builders + */ + public function __construct(iterable $builders) { + $this->builders = $builders; + assertType('iterable<(int|string), string>', $builders); + assertType('iterable<(int|string), string>', $this->builders); + } +} From e31ab6930a61e91ae2eb5192dfbebcc4cfe4d141 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 09:54:46 +0200 Subject: [PATCH 1267/3097] MutatingScope: remove unnecessary callback functions --- src/Analyser/MutatingScope.php | 154 ++++++++++++++------------------- 1 file changed, 67 insertions(+), 87 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 42034f0ae3e..b15018fbc87 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2113,34 +2113,28 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($node instanceof MethodCall) { if ($node->name instanceof Node\Identifier) { if ($this->nativeTypesPromoted) { - $typeCallback = function () use ($node): Type { - $methodReflection = $this->getMethodReflection( - $this->getNativeType($node->var), - $node->name->name, - ); - if ($methodReflection === null) { - return new ErrorType(); - } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); - }; - - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); - } - - $typeCallback = function () use ($node): Type { - $returnType = $this->methodCallReturnType( - $this->getType($node->var), + $methodReflection = $this->getMethodReflection( + $this->getNativeType($node->var), $node->name->name, - $node, ); - if ($returnType === null) { - return new ErrorType(); + if ($methodReflection === null) { + $returnType = new ErrorType(); + } else { + $returnType = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); } - return $returnType; - }; - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + return $this->getNullsafeShortCircuitingType($node->var, $returnType); + } + + $returnType = $this->methodCallReturnType( + $this->getType($node->var), + $node->name->name, + $node, + ); + if ($returnType === null) { + $returnType = new ErrorType(); + } + return $this->getNullsafeShortCircuitingType($node->var, $returnType); } $nameType = $this->getType($node->name); @@ -2172,24 +2166,21 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($node instanceof Expr\StaticCall) { if ($node->name instanceof Node\Identifier) { if ($this->nativeTypesPromoted) { - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); - } else { - $staticMethodCalledOnType = $this->getNativeType($node->class); - } - $methodReflection = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflection === null) { - return new ErrorType(); - } - - return ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); - }; + if ($node->class instanceof Name) { + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); + } else { + $staticMethodCalledOnType = $this->getNativeType($node->class); + } + $methodReflection = $this->getMethodReflection( + $staticMethodCalledOnType, + $node->name->name, + ); + if ($methodReflection === null) { + $callType = new ErrorType(); + } else { + $callType = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants())->getNativeReturnType(); + } - $callType = $typeCallback(); if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $callType); } @@ -2197,25 +2188,21 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return $callType; } - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); - } else { - $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); - } + if ($node->class instanceof Name) { + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); + } else { + $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); + } - $returnType = $this->methodCallReturnType( - $staticMethodCalledOnType, - $node->name->toString(), - $node, - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; + $callType = $this->methodCallReturnType( + $staticMethodCalledOnType, + $node->name->toString(), + $node, + ); + if ($callType === null) { + $callType = new ErrorType(); + } - $callType = $typeCallback(); if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $callType); } @@ -2250,19 +2237,16 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return $this->getNullsafeShortCircuitingType($node->var, $nativeType); } - $typeCallback = function () use ($node): Type { - $returnType = $this->propertyFetchType( - $this->getType($node->var), - $node->name->name, - $node, - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; + $returnType = $this->propertyFetchType( + $this->getType($node->var), + $node->name->name, + $node, + ); + if ($returnType === null) { + $returnType = new ErrorType(); + } - return $this->getNullsafeShortCircuitingType($node->var, $typeCallback()); + return $this->getNullsafeShortCircuitingType($node->var, $returnType); } $nameType = $this->getType($node->name); @@ -2313,25 +2297,21 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu return $nativeType; } - $typeCallback = function () use ($node): Type { - if ($node->class instanceof Name) { - $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); - } else { - $staticPropertyFetchedOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); - } + if ($node->class instanceof Name) { + $staticPropertyFetchedOnType = $this->resolveTypeByName($node->class); + } else { + $staticPropertyFetchedOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); + } - $returnType = $this->propertyFetchType( - $staticPropertyFetchedOnType, - $node->name->toString(), - $node, - ); - if ($returnType === null) { - return new ErrorType(); - } - return $returnType; - }; + $fetchType = $this->propertyFetchType( + $staticPropertyFetchedOnType, + $node->name->toString(), + $node, + ); + if ($fetchType === null) { + $fetchType = new ErrorType(); + } - $fetchType = $typeCallback(); if ($node->class instanceof Expr) { return $this->getNullsafeShortCircuitingType($node->class, $fetchType); } From 4f5a63a5f577dbf9fecca53e8d6ea4ac64542f7a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Apr 2025 10:06:41 +0200 Subject: [PATCH 1268/3097] Fix occasional failure in ParallelAnalyserIntegrationTest --- .../PHPStan/Parallel/ParallelAnalyserIntegrationTest.php | 8 ++++++++ tests/PHPStan/Parallel/parallel-analyser.neon | 1 + 2 files changed, 9 insertions(+) diff --git a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php index 3ae92e09dcb..3565a312ce6 100644 --- a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php +++ b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Parallel; +use Nette\Utils\FileSystem; use Nette\Utils\Json; use PHPStan\File\FileHelper; use PHPStan\ShouldNotHappenException; @@ -10,7 +11,10 @@ use function escapeshellarg; use function exec; use function implode; +use function putenv; use function sprintf; +use function sys_get_temp_dir; +use function uniqid; use const PHP_BINARY; /** @@ -32,6 +36,8 @@ public function dataRun(): array */ public function testRun(string $command): void { + $tmpDir = sys_get_temp_dir() . '/' . md5(uniqid()); + putenv('PHPSTAN_TMP_DIR=' . $tmpDir); exec(sprintf('%s %s clear-result-cache --configuration %s -q', escapeshellarg(PHP_BINARY), escapeshellarg(__DIR__ . '/../../../bin/phpstan'), escapeshellarg(__DIR__ . '/parallel-analyser.neon')), $clearResultCacheOutputLines, $clearResultCacheExitCode); if ($clearResultCacheExitCode !== 0) { throw new ShouldNotHappenException('Could not clear result cache.'); @@ -50,6 +56,8 @@ public function testRun(string $command): void ), $outputLines, $exitCode); $output = implode("\n", $outputLines); + FileSystem::delete($tmpDir); + $fileHelper = new FileHelper(__DIR__); $filePath = $fileHelper->normalizePath(__DIR__ . '/data/trait-definition.php'); $this->assertJsonStringEqualsJsonString(Json::encode([ diff --git a/tests/PHPStan/Parallel/parallel-analyser.neon b/tests/PHPStan/Parallel/parallel-analyser.neon index f942a62afa0..a2f7f009806 100644 --- a/tests/PHPStan/Parallel/parallel-analyser.neon +++ b/tests/PHPStan/Parallel/parallel-analyser.neon @@ -1,3 +1,4 @@ parameters: parallel: jobSize: 1 + tmpDir: %env.PHPSTAN_TMP_DIR% From b3d338639017ec792134815cec3f5bcb24ca6dc9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 18 Apr 2025 10:13:46 +0200 Subject: [PATCH 1269/3097] Fix CS --- tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php index 3565a312ce6..91f0e9544c7 100644 --- a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php +++ b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php @@ -11,6 +11,7 @@ use function escapeshellarg; use function exec; use function implode; +use function md5; use function putenv; use function sprintf; use function sys_get_temp_dir; From 488b65ff9b31754348e2dfc53cd8e50b3048d381 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 15:31:27 +0200 Subject: [PATCH 1270/3097] `Scope->rememberConstructorScope` should not remember the function scope --- src/Analyser/MutatingScope.php | 2 +- ...tanceMethodsParameterScopeFunctionRule.php | 34 +++++++++++++++ ...tanceMethodsParameterScopeFunctionTest.php | 43 +++++++++++++++++++ .../data/instance-methods-parameter-scope.php | 19 ++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionRule.php create mode 100644 tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionTest.php create mode 100644 tests/PHPStan/Analyser/data/instance-methods-parameter-scope.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b15018fbc87..22a895e03c3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -348,7 +348,7 @@ public function rememberConstructorScope(): self return $this->scopeFactory->create( $this->context, $this->isDeclareStrictTypes(), - $this->getFunction(), + null, $this->getNamespace(), $this->rememberConstructorExpressions($this->expressionTypes), $this->rememberConstructorExpressions($this->nativeExpressionTypes), diff --git a/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionRule.php b/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionRule.php new file mode 100644 index 00000000000..f3fce8fd9de --- /dev/null +++ b/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionRule.php @@ -0,0 +1,34 @@ + + */ +class InstanceMethodsParameterScopeFunctionRule implements Rule +{ + + public function getNodeType(): string + { + return FullyQualified::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($scope->getFunction() !== null) { + throw new ShouldNotHappenException('All names in the tests should not have a function scope.'); + } + + return [ + RuleErrorBuilder::message(sprintf('Name %s found in function scope null', $node->toString()))->identifier('test.instanceOfMethodsParameterRule')->build(), + ]; + } + +} diff --git a/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionTest.php b/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionTest.php new file mode 100644 index 00000000000..7cbae1c8be7 --- /dev/null +++ b/tests/PHPStan/Analyser/InstanceMethodsParameterScopeFunctionTest.php @@ -0,0 +1,43 @@ + + */ +class InstanceMethodsParameterScopeFunctionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new InstanceMethodsParameterScopeFunctionRule(); + } + + protected function shouldNarrowMethodScopeFromConstructor(): bool + { + return true; + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/instance-methods-parameter-scope.php'], [ + [ + 'Name DateTime found in function scope null', + 12, + ], + [ + 'Name Baz\Waldo found in function scope null', + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Analyser/data/instance-methods-parameter-scope.php b/tests/PHPStan/Analyser/data/instance-methods-parameter-scope.php new file mode 100644 index 00000000000..a861ac8eddd --- /dev/null +++ b/tests/PHPStan/Analyser/data/instance-methods-parameter-scope.php @@ -0,0 +1,19 @@ + Date: Sat, 19 Apr 2025 09:42:00 +0200 Subject: [PATCH 1271/3097] RestrictedMethodUsageExtension (more extensions for properties etc. are coming) --- conf/config.neon | 1 + .../ConditionalTagsExtension.php | 2 + .../RestrictedMethodUsageExtension.php | 37 +++++++++ .../RestrictedMethodUsageRule.php | 76 +++++++++++++++++++ src/Rules/RestrictedUsage/RestrictedUsage.php | 26 +++++++ .../RestrictedMethodUsageRuleTest.php | 40 ++++++++++ .../RestrictedUsage/data/MethodExtension.php | 25 ++++++ .../data/restricted-method.php | 26 +++++++ .../RestrictedUsage/restricted-usage.neon | 5 ++ 9 files changed, 238 insertions(+) create mode 100644 src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php create mode 100644 src/Rules/RestrictedUsage/RestrictedUsage.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedMethodUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon diff --git a/conf/config.neon b/conf/config.neon index 7e11398a3e9..faf1ce641c8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -217,6 +217,7 @@ rules: - PHPStan\Rules\Debug\DumpPhpDocTypeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule + - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 9610d50c9d8..18a2e766d38 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -25,6 +25,7 @@ use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; use PHPStan\ShouldNotHappenException; use function array_reduce; use function count; @@ -73,6 +74,7 @@ public function getConfigSchema(): Nette\Schema\Schema FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG => $bool, MethodDeprecationExtension::METHOD_EXTENSION_TAG => $bool, PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool, + RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php new file mode 100644 index 00000000000..275314a9982 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php @@ -0,0 +1,37 @@ + + */ +final class RestrictedMethodUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + /** @var RestrictedMethodUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $methodName = $node->name->name; + $methodCalledOnType = $scope->getType($node->var); + $referencedClasses = $methodCalledOnType->getObjectClassNames(); + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasMethod($methodName)) { + continue; + } + + $methodReflection = $classReflection->getMethod($methodName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedUsage.php b/src/Rules/RestrictedUsage/RestrictedUsage.php new file mode 100644 index 00000000000..fff3904d5e2 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedUsage.php @@ -0,0 +1,26 @@ + + */ +class RestrictedMethodUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new RestrictedMethodUsageRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-method.php'], [ + [ + 'Cannot call doFoo', + 13, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php new file mode 100644 index 00000000000..aac69f9a4c7 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php @@ -0,0 +1,25 @@ +getName() !== 'doFoo') { + return null; + } + + return RestrictedUsage::create('Cannot call doFoo', 'restrictedUsage.doFoo'); + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php new file mode 100644 index 00000000000..02b6e3e1681 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php @@ -0,0 +1,26 @@ +test(); + $this->doNonexistent(); + $this->doBar(); + $this->doFoo(); + } + + public function doBar(): void + { + + } + + public function doFoo(): void + { + + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon b/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon new file mode 100644 index 00000000000..46c4bc8f353 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon @@ -0,0 +1,5 @@ +services: + - + class: RestrictedUsage\MethodExtension + tags: + - phpstan.restrictedMethodUsageExtension From 14413542253768ab876679b6be4ecd5e33237e87 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 10:18:01 +0200 Subject: [PATCH 1272/3097] Do not complain about accessing `RestrictedMethodUsageRule::class` --- src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php b/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php index bff45debefe..1dcd4708250 100644 --- a/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php @@ -29,6 +29,9 @@ public function getNodeType(): string return MethodCall::class; } + /** + * @api + */ public function processNode(Node $node, Scope $scope): array { if (!$node->name instanceof Identifier) { From d831c93c6107839b0c85cec981796ff154b15d11 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 10:18:53 +0200 Subject: [PATCH 1273/3097] Fix build --- tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php index aac69f9a4c7..4fee1f8eef5 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php +++ b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php @@ -12,7 +12,7 @@ class MethodExtension implements RestrictedMethodUsageExtension public function isRestrictedMethodUsage( ExtendedMethodReflection $methodReflection, - Scope $scope, + Scope $scope ): ?RestrictedUsage { if ($methodReflection->getName() !== 'doFoo') { From 67fb7a642faa23cc012b4de971f3dab3f34415d5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 19 Apr 2025 10:57:07 +0200 Subject: [PATCH 1274/3097] Fix imprecise property native types after assignment --- src/Analyser/NodeScopeResolver.php | 42 ++++++++- .../Analyser/nsrt/bug-12902-non-strict.php | 90 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-12902.php | 90 +++++++++++++++++++ ...ember-non-nullable-property-non-strict.php | 88 ++++++++++++++++++ 4 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12902.php create mode 100644 tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6910dfa0517..756ea8db317 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5625,10 +5625,27 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { + if ($propertyReflection->hasNativeType()) { + $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); + $assignedTypeIsCompatible = false; + foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { + if ($type->isSuperTypeOf($assignedNativeType)->yes()) { + $assignedTypeIsCompatible = true; + break; + } + } + + if ($assignedTypeIsCompatible) { + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); + } elseif ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression( + $var, + TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), + TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType), + ); + } } else { $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } @@ -5696,10 +5713,27 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { + if ($propertyReflection->hasNativeType()) { + $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); + $assignedTypeIsCompatible = false; + foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { + if ($type->isSuperTypeOf($assignedNativeType)->yes()) { + $assignedTypeIsCompatible = true; + break; + } + } + + if ($assignedTypeIsCompatible) { + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); + } elseif ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression( + $var, + TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), + TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType), + ); + } } else { $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php new file mode 100644 index 00000000000..d294016ec5a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php @@ -0,0 +1,90 @@ += 8.1 + +declare(strict_types = 0); + +namespace Bug12902NonStrict; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class NarrowsNativeConstantValue +{ + private readonly int|float $i; + + public function __construct() + { + $this->i = 1; + } + + public function doFoo(): void + { + assertType('1', $this->i); + assertNativeType('1', $this->i); + } +} + +class NarrowsNativeReadonlyUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + assertType('int', $this->i); + assertNativeType('int', $this->i); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class NarrowsNativeUnion { + private int|float $i; + + public function __construct() + { + $this->i = getInt(); + assertType('int', $this->i); + assertNativeType('int', $this->i); + + $this->impureCall(); + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + +class NarrowsStaticNativeUnion { + private static int|float $i; + + public function __construct() + { + self::$i = getInt(); + assertType('int', self::$i); + assertNativeType('int', self::$i); + + $this->impureCall(); + assertType('int', self::$i); // should be float|int + assertNativeType('int', self::$i); // should be float|int + } + + public function doFoo(): void { + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + +function getInt(): int { + return 1; +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902.php b/tests/PHPStan/Analyser/nsrt/bug-12902.php new file mode 100644 index 00000000000..cbdc8160743 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12902.php @@ -0,0 +1,90 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug12902; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class NarrowsNativeConstantValue +{ + private readonly int|float $i; + + public function __construct() + { + $this->i = 1; + } + + public function doFoo(): void + { + assertType('1', $this->i); + assertNativeType('1', $this->i); + } +} + +class NarrowsNativeReadonlyUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + assertType('int', $this->i); + assertNativeType('int', $this->i); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class NarrowsNativeUnion { + private int|float $i; + + public function __construct() + { + $this->i = getInt(); + assertType('int', $this->i); + assertNativeType('int', $this->i); + + $this->impureCall(); + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + +class NarrowsStaticNativeUnion { + private static int|float $i; + + public function __construct() + { + self::$i = getInt(); + assertType('int', self::$i); + assertNativeType('int', self::$i); + + $this->impureCall(); + assertType('int', self::$i); // should be float|int + assertNativeType('int', self::$i); // should be float|int + } + + public function doFoo(): void { + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + +function getInt(): int { + return 1; +} diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php new file mode 100644 index 00000000000..ed949a846f6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php @@ -0,0 +1,88 @@ += 8.1 + +declare(strict_types = 0); + +namespace RememberNonNullablePropertyWhenStrictTypesDisabled; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class KeepsPropertyNonNullable { + private readonly int $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class DontCoercePhpdocType { + /** @var int */ + private $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('mixed', $this->i); + } +} + +function getIntOrNull(): ?int { + if (rand(0, 1) === 0) { + return null; + } + return 1; +} + + +class KeepsPropertyNonNullable2 { + private int|float $i; + + public function __construct() + { + $this->i = getIntOrFloatOrNull(); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } +} + +function getIntOrFloatOrNull(): null|int|float { + if (rand(0, 1) === 0) { + return null; + } + + if (rand(0, 10) === 0) { + return 1.0; + } + return 1; +} + +class NarrowsNativeUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +function getInt(): int { + return 1; +} From 3024c02e844d430759ac950261ba5c5aea52eb1b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 11:28:50 +0200 Subject: [PATCH 1275/3097] MethodReflection - fix isInternal vs. isBuiltin --- .../Annotations/AnnotationMethodReflection.php | 5 +++++ .../Dummy/ChangedTypeMethodReflection.php | 10 ++++++++++ .../Dummy/DummyConstructorReflection.php | 5 +++++ src/Reflection/Dummy/DummyMethodReflection.php | 5 +++++ src/Reflection/ExtendedMethodReflection.php | 2 ++ src/Reflection/Native/NativeMethodReflection.php | 5 +++++ src/Reflection/Php/ClosureCallMethodReflection.php | 10 ++++++++++ src/Reflection/Php/EnumCasesMethodReflection.php | 5 +++++ src/Reflection/Php/PhpMethodReflection.php | 7 ++++++- src/Reflection/ResolvedMethodReflection.php | 10 ++++++++++ .../Type/IntersectionTypeMethodReflection.php | 5 +++++ src/Reflection/Type/UnionTypeMethodReflection.php | 7 ++++++- src/Reflection/WrappedExtendedMethodReflection.php | 5 +++++ src/Rules/Methods/OverridingMethodRule.php | 14 +++++++++++--- .../Reflection/ReflectionProviderGoldenTest.php | 9 +++++++++ 15 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index b7aab264ff5..696e0e5b08d 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -119,6 +119,11 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function getThrowType(): ?Type { return $this->throwType; diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 9a309e3189f..ade4e1c4aa8 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -110,6 +110,16 @@ public function isInternal(): TrinaryLogic return $this->reflection->isInternal(); } + public function isBuiltin(): TrinaryLogic + { + $builtin = $this->reflection->isBuiltin(); + if (is_bool($builtin)) { + return TrinaryLogic::createFromBoolean($builtin); + } + + return $builtin; + } + public function getThrowType(): ?Type { return $this->reflection->getThrowType(); diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 4c2efda773e..c48d6904ce3 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -97,6 +97,11 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function getThrowType(): ?Type { return null; diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index a67f79e00b0..dced9b6206b 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -94,6 +94,11 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function getThrowType(): ?Type { return null; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 03a9a2b2eab..5cea3927542 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -49,6 +49,8 @@ public function isFinalByKeyword(): TrinaryLogic; public function isAbstract(): TrinaryLogic|bool; + public function isBuiltin(): TrinaryLogic|bool; + /** * This indicates whether the method has phpstan-pure * or phpstan-impure annotation above it. diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index a9ec6063d8e..34ec4e3e51f 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -140,6 +140,11 @@ public function isDeprecated(): TrinaryLogic } public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function isBuiltin(): TrinaryLogic { return TrinaryLogic::createFromBoolean($this->reflection->isInternal()); } diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index ae8292e32df..aafd7b658eb 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -142,6 +142,16 @@ public function isInternal(): TrinaryLogic return $this->nativeMethodReflection->isInternal(); } + public function isBuiltin(): TrinaryLogic + { + $builtin = $this->nativeMethodReflection->isBuiltin(); + if (is_bool($builtin)) { + return TrinaryLogic::createFromBoolean($builtin); + } + + return $builtin; + } + public function getThrowType(): ?Type { return $this->nativeMethodReflection->getThrowType(); diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index 61ee5d767b6..ecf72e435db 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -111,6 +111,11 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function getThrowType(): ?Type { return null; diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index b2b45932a39..432fd693504 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -379,7 +379,12 @@ public function isDeprecated(): TrinaryLogic public function isInternal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->isInternal || $this->reflection->isInternal()); + return TrinaryLogic::createFromBoolean($this->isInternal); + } + + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->reflection->isInternal()); } public function isFinal(): TrinaryLogic diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index bd7ef46f2bc..134e566eca5 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -150,6 +150,16 @@ public function isInternal(): TrinaryLogic return $this->reflection->isInternal(); } + public function isBuiltin(): TrinaryLogic + { + $builtin = $this->reflection->isBuiltin(); + if (is_bool($builtin)) { + return TrinaryLogic::createFromBoolean($builtin); + } + + return $builtin; + } + public function getThrowType(): ?Type { return $this->reflection->getThrowType(); diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index f0ce213ad79..eafd3141576 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -152,6 +152,11 @@ public function isInternal(): TrinaryLogic return TrinaryLogic::lazyMaxMin($this->methods, static fn (MethodReflection $method): TrinaryLogic => $method->isInternal()); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isBuiltin()) ? TrinaryLogic::createFromBoolean($method->isBuiltin()) : $method->isBuiltin()); + } + public function getThrowType(): ?Type { $types = []; diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 3d8015ddaf6..b330b6fdada 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -132,7 +132,12 @@ public function isFinalByKeyword(): TrinaryLogic public function isInternal(): TrinaryLogic { - return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (MethodReflection $method): TrinaryLogic => $method->isInternal()); + return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->isInternal()); + } + + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isBuiltin()) ? TrinaryLogic::createFromBoolean($method->isBuiltin()) : $method->isBuiltin()); } public function getThrowType(): ?Type diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index 42bc13b430c..5a9ea238cf1 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -123,6 +123,11 @@ public function isInternal(): TrinaryLogic return $this->method->isInternal(); } + public function isBuiltin(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function getThrowType(): ?Type { return $this->method->getThrowType(); diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 7aa841be155..2dcb9d3cc3b 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -214,10 +214,15 @@ public function processNode(Node $node, Scope $scope): array $prototypeReturnType = $prototypeVariant->getNativeReturnType(); $reportReturnType = true; if ($this->phpVersion->hasTentativeReturnTypes()) { - $reportReturnType = !$realPrototype instanceof MethodPrototypeReflection || $realPrototype->getTentativeReturnType() === null || $prototype->isInternal()->no(); + $reportReturnType = !$realPrototype instanceof MethodPrototypeReflection + || $realPrototype->getTentativeReturnType() === null + || (is_bool($prototype->isBuiltin()) ? !$prototype->isBuiltin() : $prototype->isBuiltin()->no()); } else { if ($realPrototype instanceof MethodPrototypeReflection && $realPrototype->isInternal()) { - if ($prototype->isInternal()->yes() && $prototypeDeclaringClass->getName() !== $realPrototype->getDeclaringClass()->getName()) { + if ( + (is_bool($prototype->isBuiltin()) ? $prototype->isBuiltin() : $prototype->isBuiltin()->yes()) + && $prototypeDeclaringClass->getName() !== $realPrototype->getDeclaringClass()->getName() + ) { $realPrototypeVariant = $realPrototype->getVariants()[0]; if ( $prototypeReturnType instanceof MixedType @@ -228,7 +233,10 @@ public function processNode(Node $node, Scope $scope): array } } - if ($reportReturnType && $prototype->isInternal()->yes()) { + if ( + $reportReturnType + && (is_bool($prototype->isBuiltin()) ? $prototype->isBuiltin() : $prototype->isBuiltin()->yes()) + ) { $reportReturnType = !$this->hasReturnTypeWillChangeAttribute($node->getOriginalNode()); } } diff --git a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php index 870d185d59b..b26eda58497 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php @@ -28,6 +28,7 @@ use function get_defined_functions; use function getenv; use function implode; +use function is_bool; use function mkdir; use function sort; use function strpos; @@ -349,6 +350,14 @@ private static function generateFunctionMethodBaseDescription($reflection): stri $result .= 'Is internal: ' . $reflection->isInternal()->describe() . "\n"; } + if (is_bool($reflection->isBuiltin()) && $reflection->isBuiltin()) { + $result .= 'Is built-in' . "\n"; + } + + if (!is_bool($reflection->isBuiltin()) && !$reflection->isBuiltin()->no()) { + $result .= 'Is built-in: ' . $reflection->isBuiltin()->describe() . "\n"; + } + if (! $reflection->returnsByReference()->no()) { $result .= 'Returns by reference: ' . $reflection->returnsByReference()->describe() . "\n"; } From b9eb832e8758a5695cf9f4e295e5fca581e6beea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 10:49:49 +0200 Subject: [PATCH 1276/3097] Bleeding edge (level 2) - report `@internal` method call --- conf/bleedingEdge.neon | 1 + conf/config.level2.neon | 7 + conf/config.neon | 1 + conf/parametersSchema.neon | 1 + phpstan-baseline.neon | 6 + src/Reflection/Php/PhpMethodReflection.php | 2 +- ...RestrictedInternalMethodUsageExtension.php | 55 ++++++++ ...rictedInternalMethodUsageExtensionTest.php | 59 ++++++++ .../InternalTag/data/method-internal-tag.php | 128 ++++++++++++++++++ 9 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/method-internal-tag.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 76dd0d89041..22487e357c1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -5,3 +5,4 @@ parameters: skipCheckGenericClasses!: [] stricterFunctionMap: true reportPreciseLineForUnusedFunctionParameter: true + internalTag: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 2deca2ac0df..aaba4b93656 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -67,6 +67,10 @@ rules: - PHPStan\Rules\Pure\PureFunctionRule - PHPStan\Rules\Pure\PureMethodRule +conditionalTags: + PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension: + phpstan.restrictedMethodUsageExtension: %featureToggles.internalTag% + services: - class: PHPStan\Rules\Classes\MixinRule @@ -110,3 +114,6 @@ services: class: PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension diff --git a/conf/config.neon b/conf/config.neon index faf1ce641c8..d3c6f958ecf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -26,6 +26,7 @@ parameters: skipCheckGenericClasses: [] stricterFunctionMap: false reportPreciseLineForUnusedFunctionParameter: false + internalTag: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index a148f6fc924..d18df776e35 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -32,6 +32,7 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), stricterFunctionMap: bool() reportPreciseLineForUnusedFunctionParameter: bool() + internalTag: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d252aaef595..c14025a4a48 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1920,6 +1920,12 @@ parameters: count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php + - + message: '#^Call to internal method PHPUnit\\Framework\\ExpectationFailedException\:\:getComparisonFailure\(\) from outside its root namespace PHPUnit\.$#' + identifier: method.internal + count: 2 + path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php + - message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#' identifier: phpstanApi.varTagAssumption diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 432fd693504..c4915edddb1 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -379,7 +379,7 @@ public function isDeprecated(): TrinaryLogic public function isInternal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->isInternal); + return TrinaryLogic::createFromBoolean($this->isInternal || $this->declaringClass->isInternal()); } public function isBuiltin(): TrinaryLogic diff --git a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php new file mode 100644 index 00000000000..98e7f9ea358 --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php @@ -0,0 +1,55 @@ +isInternal()->yes()) { + return null; + } + + $currentNamespace = $scope->getNamespace(); + $declaringClassName = $methodReflection->getDeclaringClass()->getName(); + $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; + if ($currentNamespace === null) { + return $this->buildRestrictedUsage($methodReflection, $namespace); + } + + $currentNamespace = explode('\\', $currentNamespace)[0]; + if (str_starts_with($namespace . '\\', $currentNamespace . '\\')) { + return null; + } + + return $this->buildRestrictedUsage($methodReflection, $namespace); + } + + private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection, ?string $namespace): RestrictedUsage + { + if ($namespace === null) { + return RestrictedUsage::create( + sprintf('Call to internal method %s::%s().', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName()), + 'method.internal', + ); + } + return RestrictedUsage::create( + sprintf('Call to internal method %s::%s() from outside its root namespace %s.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $namespace), + 'method.internal', + ); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php new file mode 100644 index 00000000000..37d53e7af98 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php @@ -0,0 +1,59 @@ + + */ +class RestrictedInternalMethodUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedMethodUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/method-internal-tag.php'], [ + [ + 'Call to internal method MethodInternalTagOne\Foo::doInternal() from outside its root namespace MethodInternalTagOne.', + 58, + ], + [ + 'Call to internal method MethodInternalTagOne\FooInternal::doFoo() from outside its root namespace MethodInternalTagOne.', + 63, + ], + [ + 'Call to internal method MethodInternalTagOne\Foo::doInternal() from outside its root namespace MethodInternalTagOne.', + 71, + ], + + [ + 'Call to internal method MethodInternalTagOne\FooInternal::doFoo() from outside its root namespace MethodInternalTagOne.', + 76, + ], + [ + 'Call to internal method FooWithInternalMethodWithoutNamespace::doInternal().', + 107, + ], + [ + 'Call to internal method FooInternalWithoutNamespace::doFoo().', + 112, + ], + [ + 'Call to internal method FooWithInternalMethodWithoutNamespace::doInternal().', + 120, + ], + [ + 'Call to internal method FooInternalWithoutNamespace::doFoo().', + 125, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/method-internal-tag.php b/tests/PHPStan/Rules/InternalTag/data/method-internal-tag.php new file mode 100644 index 00000000000..c33e3ab9299 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/method-internal-tag.php @@ -0,0 +1,128 @@ +doInternal(); + $foo->doNotInternal(); + }; + + function (FooInternal $foo): void { + $foo->doFoo(); + }; + +} + +namespace MethodInternalTagOne\Test { + + function (\MethodInternalTagOne\Foo $foo): void { + $foo->doInternal(); + $foo->doNotInternal(); + }; + + function (\MethodInternalTagOne\FooInternal $foo): void { + $foo->doFoo(); + }; +} + +namespace MethodInternalTagTwo { + + function (\MethodInternalTagOne\Foo $foo): void { + $foo->doInternal(); + $foo->doNotInternal(); + }; + + function (\MethodInternalTagOne\FooInternal $foo): void { + $foo->doFoo(); + }; + +} + +namespace { + + function (\MethodInternalTagOne\Foo $foo): void { + $foo->doInternal(); + $foo->doNotInternal(); + }; + + function (\MethodInternalTagOne\FooInternal $foo): void { + $foo->doFoo(); + }; + + class FooWithInternalMethodWithoutNamespace + { + /** @internal */ + public function doInternal() + { + + } + + public function doNotInternal() + { + + } + } + + /** + * @internal + */ + class FooInternalWithoutNamespace + { + + public function doFoo(): void + { + + } + + } + + function (FooWithInternalMethodWithoutNamespace $foo): void { + $foo->doInternal(); + $foo->doNotInternal(); + }; + + function (FooInternalWithoutNamespace $foo): void { + $foo->doFoo(); + }; + +} + +namespace SomeNamespace { + + function (\FooWithInternalMethodWithoutNamespace $foo): void { + $foo->doInternal(); + $foo->doNotInternal(); + }; + + function (\FooInternalWithoutNamespace $foo): void { + $foo->doFoo(); + }; + +} From 2f282efcac71b15cf78c89ef6c0372bee4a95a5e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 14:00:25 +0200 Subject: [PATCH 1277/3097] Fix --- src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php index 275314a9982..8de9bf5ffc1 100644 --- a/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php +++ b/src/Rules/RestrictedUsage/RestrictedMethodUsageExtension.php @@ -15,6 +15,7 @@ * * To register it in the configuration file use the following tag: * + * ``` * services: * - * class: App\PHPStan\MyExtension From 325dcf029102627e72597185b2da4a05a5aeac38 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Apr 2025 21:17:59 +0200 Subject: [PATCH 1278/3097] Simplify degradation to general array in `ConstantArrayType::shuffle()` --- src/Type/Constant/ConstantArrayType.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 76fdf42a5d4..a44135424c3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -913,23 +913,10 @@ public function shiftArray(): Type public function shuffleArray(): Type { - $valuesArray = $this->getValuesArray(); + $builder = ConstantArrayTypeBuilder::createFromConstantArray($this->getValuesArray()); + $builder->degradeToGeneralArray(); - $isIterableAtLeastOnce = $valuesArray->isIterableAtLeastOnce(); - if ($isIterableAtLeastOnce->no()) { - return $valuesArray; - } - - $generalizedArray = new ArrayType($valuesArray->getIterableKeyType(), $valuesArray->getIterableValueType()); - - if ($isIterableAtLeastOnce->yes()) { - $generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType()); - } - if ($valuesArray->isList->yes()) { - $generalizedArray = TypeCombinator::intersect($generalizedArray, new AccessoryArrayListType()); - } - - return $generalizedArray; + return $builder->getArray(); } public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type From 0d9c974aeda2065d1633d59d0e5dae384c2a38e8 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Apr 2025 16:46:40 +0200 Subject: [PATCH 1279/3097] Improve return type of `array_splice()` --- ...ArraySpliceFunctionReturnTypeExtension.php | 23 +++++++++++++++---- tests/PHPStan/Analyser/nsrt/bug-5017.php | 6 ++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php index bf4c3abc6b9..85def351d28 100644 --- a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php @@ -4,14 +4,22 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\ArrayType; +use PHPStan\TrinaryLogic; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; +use function count; final class ArraySpliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + public function __construct(private PhpVersion $phpVersion) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'array_splice'; @@ -23,13 +31,20 @@ public function getTypeFromFunctionCall( Scope $scope, ): ?Type { - if (!isset($functionCall->getArgs()[0])) { + $args = $functionCall->getArgs(); + if (count($args) < 2) { return null; } - $arrayArg = $scope->getType($functionCall->getArgs()[0]->value); + $arrayType = $scope->getType($args[0]->value); + if ($arrayType->isArray()->no()) { + return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); + } + + $offsetType = $scope->getType($args[1]->value); + $lengthType = isset($args[2]) ? $scope->getType($args[2]->value) : new NullType(); - return new ArrayType($arrayArg->getIterableKeyType(), $arrayArg->getIterableValueType()); + return $arrayType->sliceArray($offsetType, $lengthType, TrinaryLogic::createNo()); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-5017.php b/tests/PHPStan/Analyser/nsrt/bug-5017.php index fa1abc5b463..9c9a3d3abfb 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5017.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5017.php @@ -15,7 +15,7 @@ public function doFoo() assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); - assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); + assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $batch); } } @@ -28,7 +28,7 @@ public function doBar($items) assertType('non-empty-array', $items); $batch = array_splice($items, 0, 2); assertType('array', $items); - assertType('array', $batch); + assertType('non-empty-array', $batch); } } @@ -38,7 +38,7 @@ public function doBar2() assertType('array{0, 1, 2, 3, 4}', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); - assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); + assertType('array{0, 1}', $batch); } /** From 9015e3b723dcc750f5c31cada3c07ca1e0816e16 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Apr 2025 16:01:46 +0200 Subject: [PATCH 1280/3097] Handle preserve_keys in `array_slice()` for normal arrays --- src/Type/ArrayType.php | 4 +++ tests/PHPStan/Analyser/nsrt/array-slice.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-10721.php | 12 +++---- .../Rules/Methods/CallMethodsRuleTest.php | 10 ++++++ .../PHPStan/Rules/Methods/data/bug-12880.php | 31 +++++++++++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12880.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 68224a0f9bd..e68a6a61d3f 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -442,6 +442,10 @@ public function shuffleArray(): Type public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type { + if ($preserveKeys->no() && $this->keyType->isInteger()->yes()) { + return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType()); + } + return $this; } diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index 847f535df1f..79deb5576e9 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -30,7 +30,7 @@ public function fromMixed($arr): void public function normalArrays(array $arr): void { /** @var array $arr */ - assertType('array', array_slice($arr, 1, 2)); + assertType('list', array_slice($arr, 1, 2)); assertType('array', array_slice($arr, 1, 2, true)); /** @var array $arr */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-10721.php b/tests/PHPStan/Analyser/nsrt/bug-10721.php index 52d511c163a..c82d2298f2e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10721.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10721.php @@ -86,15 +86,15 @@ public function listVariants(): void public function arrayVariants(array $strings, $maybeZero): void { assertType("array", $strings); - assertType("array", array_slice($strings, 0)); - assertType("array", array_slice($strings, 1)); - assertType("array", array_slice($strings, $maybeZero)); + assertType("list", array_slice($strings, 0)); + assertType("list", array_slice($strings, 1)); + assertType("list", array_slice($strings, $maybeZero)); if (count($strings) > 0) { assertType("non-empty-array", $strings); - assertType("non-empty-array", array_slice($strings, 0)); - assertType("array", array_slice($strings, 1)); - assertType("array", array_slice($strings, $maybeZero)); + assertType("non-empty-list", array_slice($strings, 0)); + assertType("list", array_slice($strings, 1)); + assertType("list", array_slice($strings, $maybeZero)); } } } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 23a8d6194e4..bc177e9d0b4 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3606,4 +3606,14 @@ public function testBu12793(): void $this->analyse([__DIR__ . '/data/bug-12793.php'], []); } + public function testBug12880(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12880.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12880.php b/tests/PHPStan/Rules/Methods/data/bug-12880.php new file mode 100644 index 00000000000..b82ecb820e2 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12880.php @@ -0,0 +1,31 @@ +test([1, 2, 3, true]); + } + + /** + * @param list $ids + */ + private function test(array $ids): void + { + $ids = array_unique($ids); + \PHPStan\dumpType($ids); + $ids = array_slice($ids, 0, 5); + \PHPStan\dumpType($ids); + $this->expectList($ids); + } + + /** + * @param list $ids + */ + private function expectList(array $ids): void + { + var_dump($ids); + } +} From f78547c853594267738e9ab54b374b0227ba46b3 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 18 Apr 2025 21:21:13 +0200 Subject: [PATCH 1281/3097] Improve `ConstantArrayType::sliceArray()` with non constant integer args --- src/Type/Constant/ConstantArrayType.php | 19 +++++++++++++++++-- tests/PHPStan/Analyser/nsrt/array-slice.php | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index a44135424c3..8e76f0d08f0 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -926,8 +926,23 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre return $this; } - $offset = $offsetType instanceof ConstantIntegerType ? $offsetType->getValue() : 0; - $length = $lengthType instanceof ConstantIntegerType ? $lengthType->getValue() : $keyTypesCount; + $offset = $offsetType instanceof ConstantIntegerType ? $offsetType->getValue() : null; + + if ($lengthType instanceof ConstantIntegerType) { + $length = $lengthType->getValue(); + } elseif ($lengthType->isNull()->yes()) { + $length = $keyTypesCount; + } else { + $length = null; + } + + if ($offset === null || $length === null) { + $builder = ConstantArrayTypeBuilder::createFromConstantArray($this); + $builder->degradeToGeneralArray(); + + return $builder->getArray() + ->sliceArray($offsetType, $lengthType, $preserveKeys); + } if ($length < 0) { // Negative lengths prevent access to the most right n elements diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index 79deb5576e9..12caf4fdbf9 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -43,6 +43,7 @@ public function constantArrays(array $arr): void /** @var array{17: 'foo', b: 'bar', 19: 'baz'} $arr */ assertType('array{b: \'bar\', 0: \'baz\'}', array_slice($arr, 1, 2)); assertType('array{b: \'bar\', 19: \'baz\'}', array_slice($arr, 1, 2, true)); + assertType('array<17|19|\'b\', \'bar\'|\'baz\'|\'foo\'>', array_slice($arr, rand(0, 1) ? 0 : 1, rand(0, 1) ? 0 : 1)); /** @var array{17: 'foo', 19: 'bar', 21: 'baz'}|array{foo: 17, bar: 19, baz: 21} $arr */ assertType('array{\'bar\', \'baz\'}|array{bar: 19, baz: 21}', array_slice($arr, 1, 2)); From c7f870adbb2cf29073d86977066398bfe0516624 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 14:16:09 +0200 Subject: [PATCH 1282/3097] Fix text --- tests/PHPStan/Analyser/nsrt/bug-5017.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-5017.php b/tests/PHPStan/Analyser/nsrt/bug-5017.php index 9c9a3d3abfb..918b56e6249 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5017.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5017.php @@ -15,7 +15,7 @@ public function doFoo() assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); - assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $batch); + assertType('non-empty-list<0|1|2|3|4>', $batch); } } From f7027e9cf39f07b19fc0211174418cf36dd7c7d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 16:48:18 +0200 Subject: [PATCH 1283/3097] RestrictedInternalMethodUsageExtension - differentiate between internal method and internal declaring classlike --- phpstan-baseline.neon | 4 +- src/Reflection/Php/PhpMethodReflection.php | 2 +- ...RestrictedInternalMethodUsageExtension.php | 37 +++++++++++++++++-- ...rictedInternalMethodUsageExtensionTest.php | 8 ++-- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c14025a4a48..24299aeaaa3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1921,8 +1921,8 @@ parameters: path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php - - message: '#^Call to internal method PHPUnit\\Framework\\ExpectationFailedException\:\:getComparisonFailure\(\) from outside its root namespace PHPUnit\.$#' - identifier: method.internal + message: '#^Call to method getComparisonFailure\(\) of internal class PHPUnit\\Framework\\ExpectationFailedException from outside its root namespace PHPUnit\.$#' + identifier: method.internalClass count: 2 path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index c4915edddb1..432fd693504 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -379,7 +379,7 @@ public function isDeprecated(): TrinaryLogic public function isInternal(): TrinaryLogic { - return TrinaryLogic::createFromBoolean($this->isInternal || $this->declaringClass->isInternal()); + return TrinaryLogic::createFromBoolean($this->isInternal); } public function isBuiltin(): TrinaryLogic diff --git a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php index 98e7f9ea358..fb54889d945 100644 --- a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php @@ -10,6 +10,7 @@ use function explode; use function sprintf; use function str_starts_with; +use function strtolower; final class RestrictedInternalMethodUsageExtension implements RestrictedMethodUsageExtension { @@ -19,7 +20,9 @@ public function isRestrictedMethodUsage( Scope $scope, ): ?RestrictedUsage { - if (!$methodReflection->isInternal()->yes()) { + $isMethodInternal = $methodReflection->isInternal()->yes(); + $isDeclaringClassInternal = $methodReflection->getDeclaringClass()->isInternal(); + if (!$isMethodInternal && !$isDeclaringClassInternal) { return null; } @@ -27,7 +30,7 @@ public function isRestrictedMethodUsage( $declaringClassName = $methodReflection->getDeclaringClass()->getName(); $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; if ($currentNamespace === null) { - return $this->buildRestrictedUsage($methodReflection, $namespace); + return $this->buildRestrictedUsage($methodReflection, $namespace, $isMethodInternal); } $currentNamespace = explode('\\', $currentNamespace)[0]; @@ -35,17 +38,43 @@ public function isRestrictedMethodUsage( return null; } - return $this->buildRestrictedUsage($methodReflection, $namespace); + return $this->buildRestrictedUsage($methodReflection, $namespace, $isMethodInternal); } - private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection, ?string $namespace): RestrictedUsage + private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection, ?string $namespace, bool $isMethodInternal): RestrictedUsage { if ($namespace === null) { + if (!$isMethodInternal) { + return RestrictedUsage::create( + sprintf( + 'Call to method %s() of internal %s %s.', + $methodReflection->getName(), + strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()), + $methodReflection->getDeclaringClass()->getDisplayName(), + ), + sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()), + ); + } + return RestrictedUsage::create( sprintf('Call to internal method %s::%s().', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName()), 'method.internal', ); } + + if (!$isMethodInternal) { + return RestrictedUsage::create( + sprintf( + 'Call to method %s() of internal %s %s from outside its root namespace %s.', + $methodReflection->getName(), + strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()), + $methodReflection->getDeclaringClass()->getDisplayName(), + $namespace, + ), + sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()), + ); + } + return RestrictedUsage::create( sprintf('Call to internal method %s::%s() from outside its root namespace %s.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $namespace), 'method.internal', diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php index 37d53e7af98..8605c44c9bd 100644 --- a/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalMethodUsageExtensionTest.php @@ -25,7 +25,7 @@ public function testRule(): void 58, ], [ - 'Call to internal method MethodInternalTagOne\FooInternal::doFoo() from outside its root namespace MethodInternalTagOne.', + 'Call to method doFoo() of internal class MethodInternalTagOne\FooInternal from outside its root namespace MethodInternalTagOne.', 63, ], [ @@ -34,7 +34,7 @@ public function testRule(): void ], [ - 'Call to internal method MethodInternalTagOne\FooInternal::doFoo() from outside its root namespace MethodInternalTagOne.', + 'Call to method doFoo() of internal class MethodInternalTagOne\FooInternal from outside its root namespace MethodInternalTagOne.', 76, ], [ @@ -42,7 +42,7 @@ public function testRule(): void 107, ], [ - 'Call to internal method FooInternalWithoutNamespace::doFoo().', + 'Call to method doFoo() of internal class FooInternalWithoutNamespace.', 112, ], [ @@ -50,7 +50,7 @@ public function testRule(): void 120, ], [ - 'Call to internal method FooInternalWithoutNamespace::doFoo().', + 'Call to method doFoo() of internal class FooInternalWithoutNamespace.', 125, ], ]); From cf0771d95047fad9a1f2d0be6599e009711ba56c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 16:57:23 +0200 Subject: [PATCH 1284/3097] Call RestrictedMethodUsageExtension for static method calls --- conf/config.neon | 1 + phpstan-baseline.neon | 30 ++++ ...RestrictedInternalMethodUsageExtension.php | 37 +++-- .../RestrictedStaticMethodUsageRule.php | 98 ++++++++++++++ ...InternalStaticMethodUsageExtensionTest.php | 59 ++++++++ .../data/static-method-internal-tag.php | 128 ++++++++++++++++++ .../RestrictedStaticMethodUsageRuleTest.php | 43 ++++++ .../data/restricted-method.php | 23 ++++ 8 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/static-method-internal-tag.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodUsageRuleTest.php diff --git a/conf/config.neon b/conf/config.neon index d3c6f958ecf..db327fbfcc9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -219,6 +219,7 @@ rules: - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 24299aeaaa3..19bd8da64d7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -108,6 +108,12 @@ parameters: count: 1 path: src/Command/CommandHelper.php + - + message: '#^Call to static method expand\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#' + identifier: staticMethod.internalClass + count: 2 + path: src/Command/CommandHelper.php + - message: '#^Parameter \#1 \$path of function dirname expects string, string\|false given\.$#' identifier: argument.type @@ -120,6 +126,18 @@ parameters: count: 1 path: src/Command/CommandHelper.php + - + message: '#^Call to static method escape\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#' + identifier: staticMethod.internalClass + count: 4 + path: src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php + + - + message: '#^Call to static method escape\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#' + identifier: staticMethod.internalClass + count: 5 + path: src/Command/ErrorFormatter/BaselinePhpErrorFormatter.php + - message: '#^Parameter \#1 \$headers \(array\\) of method PHPStan\\Command\\ErrorsConsoleStyle\:\:table\(\) should be contravariant with parameter \$headers \(array\) of method Symfony\\Component\\Console\\Style\\StyleInterface\:\:table\(\)$#' identifier: method.childParameterType @@ -144,6 +162,18 @@ parameters: count: 1 path: src/Command/ErrorsConsoleStyle.php + - + message: '#^Call to static method expand\(\) of internal class Nette\\DI\\Helpers from outside its root namespace Nette\.$#' + identifier: staticMethod.internalClass + count: 1 + path: src/DependencyInjection/ContainerFactory.php + + - + message: '#^Call to static method merge\(\) of internal class Nette\\Schema\\Helpers from outside its root namespace Nette\.$#' + identifier: staticMethod.internalClass + count: 2 + path: src/DependencyInjection/ContainerFactory.php + - message: '#^Variable method call on Nette\\Schema\\Elements\\AnyOf\|Nette\\Schema\\Elements\\Structure\|Nette\\Schema\\Elements\\Type\.$#' identifier: method.dynamicName diff --git a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php index fb54889d945..441d9f8f517 100644 --- a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php @@ -47,37 +47,58 @@ private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection if (!$isMethodInternal) { return RestrictedUsage::create( sprintf( - 'Call to method %s() of internal %s %s.', + 'Call to %smethod %s() of internal %s %s.', + $methodReflection->isStatic() ? 'static ' : '', $methodReflection->getName(), strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()), $methodReflection->getDeclaringClass()->getDisplayName(), ), - sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()), + sprintf( + '%s.internal%s', + $methodReflection->isStatic() ? 'staticMethod' : 'method', + $methodReflection->getDeclaringClass()->getClassTypeDescription(), + ), ); } return RestrictedUsage::create( - sprintf('Call to internal method %s::%s().', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName()), - 'method.internal', + sprintf( + 'Call to internal %smethod %s::%s().', + $methodReflection->isStatic() ? 'static ' : '', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + ), + sprintf('%s.internal', $methodReflection->isStatic() ? 'staticMethod' : 'method'), ); } if (!$isMethodInternal) { return RestrictedUsage::create( sprintf( - 'Call to method %s() of internal %s %s from outside its root namespace %s.', + 'Call to %smethod %s() of internal %s %s from outside its root namespace %s.', + $methodReflection->isStatic() ? 'static ' : '', $methodReflection->getName(), strtolower($methodReflection->getDeclaringClass()->getClassTypeDescription()), $methodReflection->getDeclaringClass()->getDisplayName(), $namespace, ), - sprintf('method.internal%s', $methodReflection->getDeclaringClass()->getClassTypeDescription()), + sprintf( + '%s.internal%s', + $methodReflection->isStatic() ? 'staticMethod' : 'method', + $methodReflection->getDeclaringClass()->getClassTypeDescription(), + ), ); } return RestrictedUsage::create( - sprintf('Call to internal method %s::%s() from outside its root namespace %s.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $namespace), - 'method.internal', + sprintf( + 'Call to internal %smethod %s::%s() from outside its root namespace %s.', + $methodReflection->isStatic() ? 'static ' : '', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $namespace, + ), + sprintf('%s.internal', $methodReflection->isStatic() ? 'staticMethod' : 'method'), ); } diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php new file mode 100644 index 00000000000..b9f061bc3b4 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php @@ -0,0 +1,98 @@ + + */ +final class RestrictedStaticMethodUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\StaticCall::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + /** @var RestrictedMethodUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $methodName = $node->name->name; + $referencedClasses = []; + + if ($node->class instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->class); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', // We don't care about the error message + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(), + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasMethod($methodName)) { + continue; + } + + $methodReflection = $classReflection->getMethod($methodName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php new file mode 100644 index 00000000000..f03478cfb53 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php @@ -0,0 +1,59 @@ + + */ +class RestrictedInternalStaticMethodUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedStaticMethodUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/static-method-internal-tag.php'], [ + [ + 'Call to internal static method StaticMethodInternalTagOne\Foo::doInternal() from outside its root namespace StaticMethodInternalTagOne.', + 58, + ], + [ + 'Call to static method doFoo() of internal class StaticMethodInternalTagOne\FooInternal from outside its root namespace StaticMethodInternalTagOne.', + 63, + ], + [ + 'Call to internal static method StaticMethodInternalTagOne\Foo::doInternal() from outside its root namespace StaticMethodInternalTagOne.', + 71, + ], + + [ + 'Call to static method doFoo() of internal class StaticMethodInternalTagOne\FooInternal from outside its root namespace StaticMethodInternalTagOne.', + 76, + ], + [ + 'Call to internal static method FooWithInternalStaticMethodWithoutNamespace::doInternal().', + 107, + ], + [ + 'Call to static method doFoo() of internal class FooInternalStaticWithoutNamespace.', + 112, + ], + [ + 'Call to internal static method FooWithInternalStaticMethodWithoutNamespace::doInternal().', + 120, + ], + [ + 'Call to static method doFoo() of internal class FooInternalStaticWithoutNamespace.', + 125, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/static-method-internal-tag.php b/tests/PHPStan/Rules/InternalTag/data/static-method-internal-tag.php new file mode 100644 index 00000000000..a2e3ad5909c --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/static-method-internal-tag.php @@ -0,0 +1,128 @@ + + */ +class RestrictedStaticMethodUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + return new RestrictedStaticMethodUsageRule( + self::getContainer(), + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, true, true, false, true), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-method.php'], [ + [ + 'Cannot call doFoo', + 36, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php index 02b6e3e1681..70e14b0e036 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method.php @@ -24,3 +24,26 @@ public function doFoo(): void } } + +class FooStatic +{ + + public static function doTest(): void + { + Nonexistent::test(); + self::doNonexistent(); + self::doBar(); + self::doFoo(); + } + + public static function doBar(): void + { + + } + + public static function doFoo(): void + { + + } + +} From e6d9f0c3ec251ea6fad604d3f97e02bedb4d7f42 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 19 Apr 2025 18:07:03 +0200 Subject: [PATCH 1285/3097] Update phpstan-deprecation-rules --- composer.json | 2 +- composer.lock | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 42efad3eca3..1b5ef4e5152 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "cweagans/composer-patches": "^1.7.3", "ondrejmirtes/simple-downgrader": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.2", "phpstan/phpstan-nette": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", diff --git a/composer.lock b/composer.lock index bdeaa98f576..fbe52cba3b2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57704972deb09985fa7fc347f052e075", + "content-hash": "a1dba49658a71b1032e5a3ad804f2936", "packages": [ { "name": "clue/ndjson-react", @@ -4672,27 +4672,28 @@ }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2" + "reference": "15f1d89bd70d9d05c9c99f7698ab8724e5a8431b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/81833b5787e2e8f451b31218875e29e4ed600ab2", - "reference": "81833b5787e2e8f451b31218875e29e4ed600ab2", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/15f1d89bd70d9d05c9c99f7698ab8724e5a8431b", + "reference": "15f1d89bd70d9d05c9c99f7698ab8724e5a8431b", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.13" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^9.6" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "phpstan": { @@ -4713,9 +4714,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.x" }, - "time": "2024-10-26T16:04:11+00:00" + "time": "2025-04-19T16:06:02+00:00" }, { "name": "phpstan/phpstan-nette", From 7489a093217ccf26e32ccf813cab350cf64b88a3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 20 Apr 2025 14:46:42 +0200 Subject: [PATCH 1286/3097] Fix invalidating static property access after impure call --- src/Analyser/MutatingScope.php | 11 ++++++ src/Analyser/NodeScopeResolver.php | 15 +++++--- .../Analyser/nsrt/bug-12902-non-strict.php | 4 +-- tests/PHPStan/Analyser/nsrt/bug-12902.php | 31 ++++++++++++++-- ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 +++ tests/PHPStan/Rules/Arrays/data/bug-3747.php | 27 ++++++++++++++ .../IfConstantConditionRuleTest.php | 12 +++++++ ...rictComparisonOfDifferentTypesRuleTest.php | 5 +++ .../Rules/Comparison/data/bug-11019.php | 21 +++++++++++ .../Rules/Comparison/data/bug-4864.php | 25 +++++++++++++ .../Rules/Comparison/data/bug-8926.php | 32 +++++++++++++++++ .../Methods/NullsafeMethodCallRuleTest.php | 15 ++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 14 ++++++++ tests/PHPStan/Rules/Methods/data/bug-4443.php | 26 ++++++++++++++ tests/PHPStan/Rules/Methods/data/bug-8523.php | 36 +++++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-8523b.php | 24 +++++++++++++ .../PHPStan/Rules/Methods/data/bug-8523c.php | 26 ++++++++++++++ 17 files changed, 321 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-3747.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-11019.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-4864.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8926.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4443.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-8523.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-8523b.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-8523c.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 22a895e03c3..16a9c15f053 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4379,6 +4379,17 @@ private function shouldInvalidateExpression(string $exprStringToInvalidate, Expr $nodeFinder = new NodeFinder(); $expressionToInvalidateClass = get_class($exprToInvalidate); $found = $nodeFinder->findFirst([$expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool { + if ( + $exprStringToInvalidate === '$this' + && $node instanceof Name + && ( + in_array($node->toLowerString(), ['self', 'static', 'parent'], true) + || ($this->getClassReflection() !== null && $this->getClassReflection()->is($this->resolveName($node))) + ) + ) { + return true; + } + if (!$node instanceof $expressionToInvalidateClass) { return false; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 756ea8db317..b43f0298cc5 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2597,6 +2597,13 @@ static function (): void { $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } + if ( + $parametersAcceptor instanceof ClosureType && count($parametersAcceptor->getImpurePoints()) > 0 + && $scope->isInClass() + ) { + $scope = $scope->invalidateExpression(new Variable('this'), true); + } + if ( $functionReflection !== null && in_array($functionReflection->getName(), ['json_encode', 'json_decode'], true) @@ -3022,13 +3029,13 @@ static function (): void { if ( $methodReflection !== null - && !$methodReflection->isStatic() && ( $methodReflection->hasSideEffects()->yes() - || $methodReflection->getName() === '__construct' + || ( + !$methodReflection->isStatic() + && $methodReflection->getName() === '__construct' + ) ) - && $scopeFunction instanceof MethodReflection - && !$scopeFunction->isStatic() && $scope->isInClass() && $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) ) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php index d294016ec5a..33f8a11e264 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12902-non-strict.php @@ -72,8 +72,8 @@ public function __construct() assertNativeType('int', self::$i); $this->impureCall(); - assertType('int', self::$i); // should be float|int - assertNativeType('int', self::$i); // should be float|int + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); } public function doFoo(): void { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12902.php b/tests/PHPStan/Analyser/nsrt/bug-12902.php index cbdc8160743..2330c0c130b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12902.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12902.php @@ -72,8 +72,8 @@ public function __construct() assertNativeType('int', self::$i); $this->impureCall(); - assertType('int', self::$i); // should be float|int - assertNativeType('int', self::$i); // should be float|int + assertType('float|int', self::$i); + assertNativeType('float|int', self::$i); } public function doFoo(): void { @@ -85,6 +85,33 @@ public function doFoo(): void { public function impureCall(): void {} } +class BaseClass +{ + static protected int|float $i; +} + +class UsesBaseClass extends BaseClass +{ + public function __construct() + { + parent::$i = getInt(); + assertType('int', parent::$i); + assertNativeType('int', parent::$i); + + $this->impureCall(); + assertType('float|int', parent::$i); + assertNativeType('float|int', parent::$i); + } + + public function doFoo(): void { + assertType('float|int', parent::$i); + assertNativeType('float|int', parent::$i); + } + + /** @phpstan-impure */ + public function impureCall(): void {} +} + function getInt(): int { return 1; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 0198587347d..941829a685c 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -930,4 +930,9 @@ public function testBug12593(): void $this->analyse([__DIR__ . '/data/bug-12593.php'], []); } + public function testBug3747(): void + { + $this->analyse([__DIR__ . '/data/bug-3747.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-3747.php b/tests/PHPStan/Rules/Arrays/data/bug-3747.php new file mode 100644 index 00000000000..d1dea20e05b --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-3747.php @@ -0,0 +1,27 @@ + $x */ + private static array $x; + + public function y(): void { + + self::$x = []; + + $this->z(); + + echo self::$x['foo']; + + } + + private function z(): void { + self::$x['foo'] = 'bar'; + } + +} + +$x = new X(); +$x->y(); diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 1f03a122bdb..25e362f6cc4 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -173,4 +173,16 @@ public function testBug4912(): void $this->analyse([__DIR__ . '/data/bug-4912.php'], []); } + public function testBug4864(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4864.php'], []); + } + + public function testBug8926(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8926.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 4c72f04c610..68cd2cc0590 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1011,4 +1011,9 @@ public function testBug12748(): void $this->analyse([__DIR__ . '/data/bug-12748.php'], []); } + public function testBug11019(): void + { + $this->analyse([__DIR__ . '/data/bug-11019.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11019.php b/tests/PHPStan/Rules/Comparison/data/bug-11019.php new file mode 100644 index 00000000000..c6a64cec05e --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11019.php @@ -0,0 +1,21 @@ +reset(); + assert(static::$a === 1); + $this->reset(); + assert(static::$a === 1); + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4864.php b/tests/PHPStan/Rules/Comparison/data/bug-4864.php new file mode 100644 index 00000000000..288e19c21f3 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-4864.php @@ -0,0 +1,25 @@ +isHandled = false; + $this->value = null; + + (function () { + $this->isHandled = true; + $this->value = 'value'; + })(); + + if ($this->isHandled) { + $f($this->value); + } + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8926.php b/tests/PHPStan/Rules/Comparison/data/bug-8926.php new file mode 100644 index 00000000000..c5d92bd1e0f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8926.php @@ -0,0 +1,32 @@ +test = false; + (function($arr) { + $this->test = count($arr) == 1; + })($arr); + + + if ($this->test) { + echo "...\n"; + } + } +} diff --git a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php index b954794c6e5..5c308ea5ade 100644 --- a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php @@ -55,4 +55,19 @@ public function testBug6922b(): void $this->analyse([__DIR__ . '/data/bug-6922b.php'], []); } + public function testBug8523(): void + { + $this->analyse([__DIR__ . '/data/bug-8523.php'], []); + } + + public function testBug8523b(): void + { + $this->analyse([__DIR__ . '/data/bug-8523b.php'], []); + } + + public function testBug8523c(): void + { + $this->analyse([__DIR__ . '/data/bug-8523c.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 1c62efa0c07..bc3a1b1fae7 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1232,4 +1232,18 @@ public function testBug1O580(): void ]); } + public function testBug4443(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/bug-4443.php'], [ + [ + 'Method Bug4443\HelloWorld::getArray() should return array but returns array|null.', + 22, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4443.php b/tests/PHPStan/Rules/Methods/data/bug-4443.php new file mode 100644 index 00000000000..9f7ff6a28ec --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4443.php @@ -0,0 +1,26 @@ + */ + private static ?array $arr = null; + + private static function setup(): void + { + self::$arr = null; + } + + /** @return array */ + public static function getArray(): array + { + if (self::$arr === null) { + self::$arr = []; + self::setup(); + } + return self::$arr; + } +} + +HelloWorld::getArray(); diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523.php b/tests/PHPStan/Rules/Methods/data/bug-8523.php new file mode 100644 index 00000000000..0cc8b3ad0a4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523.php @@ -0,0 +1,36 @@ +foo(); + } + + public function bar(): void + { + self::$instance = null; + } + + public function baz(): void + { + self::$instance = new HelloWorld(); + + $this->bar(); + + self::$instance?->foo(); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523b.php b/tests/PHPStan/Rules/Methods/data/bug-8523b.php new file mode 100644 index 00000000000..a007fd26616 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523b.php @@ -0,0 +1,24 @@ +save(); + } +} + +(new HelloWorld())->save(); diff --git a/tests/PHPStan/Rules/Methods/data/bug-8523c.php b/tests/PHPStan/Rules/Methods/data/bug-8523c.php new file mode 100644 index 00000000000..ea889404464 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-8523c.php @@ -0,0 +1,26 @@ +save(); + } +} + +(new HelloWorld())->save(); From 97281a7e2ed3debb8b947d32013096898f16f300 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 21 Apr 2025 10:34:07 +0200 Subject: [PATCH 1287/3097] TableErrorFormatter: visually differentiate phpstan assertion errors from rule errors --- src/Command/ErrorFormatter/TableErrorFormatter.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index 7da56bdff1b..dc0ce7e244e 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -13,6 +13,7 @@ use function count; use function explode; use function getenv; +use function in_array; use function is_string; use function ltrim; use function sprintf; @@ -117,6 +118,14 @@ public function formatErrors( $message .= "\n✏️ ' . $title . ''; } + + if ( + $error->getIdentifier() !== null + && in_array($error->getIdentifier(), ['phpstan.type', 'phpstan.nativeType', 'phpstan.variable', 'phpstan.dumpType', 'phpstan.unknownExpectation'], true) + ) { + $message = '' . $message . ''; + } + $rows[] = [ $this->formatLineNumber($error->getLine()), $message, From 23bf4d360afca88d2f25b37b68411028a8e62ade Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 02:16:41 +0000 Subject: [PATCH 1288/3097] Update dependency symfony/service-contracts to v2.5.4 --- composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index fbe52cba3b2..5d0ec9adcd1 100644 --- a/composer.lock +++ b/composer.lock @@ -3294,12 +3294,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3943,16 +3943,16 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { @@ -3968,12 +3968,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4006,7 +4006,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -4022,7 +4022,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:04:16+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/string", From 085066843e2658893722fe55caedb24fdd1fd39d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 02:16:33 +0000 Subject: [PATCH 1289/3097] Update dependency phpunit/phpunit to v9.6.22 --- composer.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index 5d0ec9adcd1..e25e48c8d2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4384,16 +4384,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -4432,7 +4432,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -4440,7 +4440,7 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "ondrejmirtes/simple-downgrader", @@ -5199,16 +5199,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "shasum": "" }, "require": { @@ -5219,11 +5219,11 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -5282,7 +5282,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" }, "funding": [ { @@ -5298,7 +5298,7 @@ "type": "tidelift" } ], - "time": "2024-07-10T11:45:39+00:00" + "time": "2024-12-05T13:48:26+00:00" }, { "name": "sebastian/cli-parser", From 4c41073c1dcdf44b6c3c90768c4430e45238b522 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 21 Apr 2025 11:15:51 +0200 Subject: [PATCH 1290/3097] Call RestrictedMethodUsageExtension for first-class callables --- conf/config.neon | 2 + .../RestrictedMethodCallableUsageRule.php | 79 +++++++++++++++ ...estrictedStaticMethodCallableUsageRule.php | 99 +++++++++++++++++++ .../RestrictedMethodCallableUsageRuleTest.php | 45 +++++++++ ...ictedStaticMethodCallableUsageRuleTest.php | 48 +++++++++ .../data/restricted-method-callable.php | 49 +++++++++ 6 files changed, 322 insertions(+) create mode 100644 src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php create mode 100644 src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedMethodCallableUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-method-callable.php diff --git a/conf/config.neon b/conf/config.neon index db327fbfcc9..e4b43fe9ecf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -219,7 +219,9 @@ rules: - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedMethodCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodCallableUsageRule conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php new file mode 100644 index 00000000000..66eb85f906a --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php @@ -0,0 +1,79 @@ + + */ +final class RestrictedMethodCallableUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getName() instanceof Identifier) { + return []; + } + + /** @var RestrictedMethodUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $methodName = $node->getName()->name; + $methodCalledOnType = $scope->getType($node->getVar()); + $referencedClasses = $methodCalledOnType->getObjectClassNames(); + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasMethod($methodName)) { + continue; + } + + $methodReflection = $classReflection->getMethod($methodName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php new file mode 100644 index 00000000000..a6172e69dd4 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php @@ -0,0 +1,99 @@ + + */ +final class RestrictedStaticMethodCallableUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return StaticMethodCallableNode::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getName() instanceof Identifier) { + return []; + } + + /** @var RestrictedMethodUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $methodName = $node->getName()->name; + $referencedClasses = []; + + if ($node->getClass() instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->getClass()); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->getClass(), + '', // We don't care about the error message + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(), + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasMethod($methodName)) { + continue; + } + + $methodReflection = $classReflection->getMethod($methodName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedMethodCallableUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedMethodCallableUsageRuleTest.php new file mode 100644 index 00000000000..c5de137c291 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedMethodCallableUsageRuleTest.php @@ -0,0 +1,45 @@ + + */ +class RestrictedMethodCallableUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new RestrictedMethodCallableUsageRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/restricted-method-callable.php'], [ + [ + 'Cannot call doFoo', + 13, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRuleTest.php new file mode 100644 index 00000000000..804042289a7 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRuleTest.php @@ -0,0 +1,48 @@ + + */ +class RestrictedStaticMethodCallableUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + return new RestrictedStaticMethodCallableUsageRule( + self::getContainer(), + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, true, true, false, true), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/restricted-method-callable.php'], [ + [ + 'Cannot call doFoo', + 36, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method-callable.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method-callable.php new file mode 100644 index 00000000000..eefc896faad --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-method-callable.php @@ -0,0 +1,49 @@ += 8.1 + +namespace RestrictedMethodCallableUsage; + +class Foo +{ + + public function doTest(Nonexistent $c): void + { + $c->test(...); + $this->doNonexistent(...); + $this->doBar(...); + $this->doFoo(...); + } + + public function doBar(): void + { + + } + + public function doFoo(): void + { + + } + +} + +class FooStatic +{ + + public static function doTest(): void + { + Nonexistent::test(...); + self::doNonexistent(...); + self::doBar(...); + self::doFoo(...); + } + + public static function doBar(): void + { + + } + + public static function doFoo(): void + { + + } + +} From e1e98fc4ff6306781d56b901bfa5b3b9dec423d3 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 22 Apr 2025 00:04:15 +0000 Subject: [PATCH 1291/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 1b5ef4e5152..a184310b1ba 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#44f320d4e03204709450e15105536751add593cd", + "jetbrains/phpstorm-stubs": "dev-master#4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index e25e48c8d2b..07344c7cd81 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a1dba49658a71b1032e5a3ad804f2936", + "content-hash": "f2523c1a5da0b0b5802408bf7969ec24", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "44f320d4e03204709450e15105536751add593cd" + "reference": "4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/44f320d4e03204709450e15105536751add593cd", - "reference": "44f320d4e03204709450e15105536751add593cd", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", + "reference": "4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-04-14T06:11:08+00:00" + "time": "2025-04-16T09:26:41+00:00" }, { "name": "nette/bootstrap", From 5de0b2c7e20da87264cb01a768967e33939b325a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 21 Apr 2025 11:27:36 +0200 Subject: [PATCH 1292/3097] ClassNameCheck - ClassNameUsageLocation (for later usage in RestrictedUsage extensions) --- .../ConditionalTagsExtension.php | 2 + src/Rules/AttributesCheck.php | 2 +- src/Rules/ClassNameCheck.php | 9 ++- src/Rules/ClassNameUsageLocation.php | 60 +++++++++++++++++++ src/Rules/Classes/ClassConstantRule.php | 7 ++- .../ExistingClassInClassExtendsRule.php | 7 ++- .../Classes/ExistingClassInInstanceOfRule.php | 3 + .../Classes/ExistingClassInTraitUseRule.php | 3 + .../ExistingClassesInClassImplementsRule.php | 3 + .../ExistingClassesInEnumImplementsRule.php | 3 + .../ExistingClassesInInterfaceExtendsRule.php | 3 + src/Rules/Classes/InstantiationRule.php | 5 +- src/Rules/Classes/LocalTypeAliasesCheck.php | 11 ++-- src/Rules/Classes/LocalTypeAliasesRule.php | 2 +- .../Classes/LocalTypeTraitUseAliasesRule.php | 1 + src/Rules/Classes/MethodTagCheck.php | 22 ++++--- src/Rules/Classes/MethodTagRule.php | 1 + src/Rules/Classes/MethodTagTraitUseRule.php | 1 + src/Rules/Classes/MixinCheck.php | 11 ++-- src/Rules/Classes/MixinRule.php | 2 +- src/Rules/Classes/MixinTraitUseRule.php | 1 + src/Rules/Classes/PropertyTagCheck.php | 14 +++-- src/Rules/Classes/PropertyTagRule.php | 2 +- src/Rules/Classes/PropertyTagTraitUseRule.php | 1 + .../CaughtExceptionExistenceRule.php | 3 + src/Rules/FunctionDefinitionCheck.php | 19 ++++-- .../ExistingClassesInTypehintsRule.php | 1 + src/Rules/Generics/TemplateTypeCheck.php | 5 +- .../ExistingClassesInTypehintsRule.php | 1 + src/Rules/Methods/StaticMethodCallCheck.php | 7 ++- .../ExistingNamesInGroupUseRule.php | 9 +-- .../Namespaces/ExistingNamesInUseRule.php | 7 ++- src/Rules/PhpDoc/AssertRuleHelper.php | 7 ++- src/Rules/PhpDoc/FunctionAssertRule.php | 2 +- .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 3 + src/Rules/PhpDoc/MethodAssertRule.php | 2 +- src/Rules/PhpDoc/RequireExtendsCheck.php | 8 ++- .../RequireExtendsDefinitionClassRule.php | 2 +- .../RequireExtendsDefinitionTraitRule.php | 2 +- .../RequireImplementsDefinitionTraitRule.php | 5 +- .../Properties/AccessStaticPropertiesRule.php | 7 ++- .../ExistingClassesInPropertiesRule.php | 3 + ...tingClassesInPropertyHookTypehintsRule.php | 1 + 43 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 src/Rules/ClassNameUsageLocation.php diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 18a2e766d38..8a4fe377c0e 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -25,6 +25,7 @@ use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; use PHPStan\ShouldNotHappenException; use function array_reduce; @@ -75,6 +76,7 @@ public function getConfigSchema(): Nette\Schema\Schema MethodDeprecationExtension::METHOD_EXTENSION_TAG => $bool, PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool, RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG => $bool, + RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index df104b3a20f..c391d69123a 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -67,7 +67,7 @@ public function check( ->build(); } - foreach ($this->classCheck->checkClassNames([new ClassNameNodePair($name, $attribute)]) as $caseSensitivityError) { + foreach ($this->classCheck->checkClassNames($scope, [new ClassNameNodePair($name, $attribute)], ClassNameUsageLocation::from(ClassNameUsageLocation::ATTRIBUTE)) as $caseSensitivityError) { $errors[] = $caseSensitivityError; } diff --git a/src/Rules/ClassNameCheck.php b/src/Rules/ClassNameCheck.php index 80d3af0f778..c2f8d5f00a4 100644 --- a/src/Rules/ClassNameCheck.php +++ b/src/Rules/ClassNameCheck.php @@ -2,6 +2,8 @@ namespace PHPStan\Rules; +use PHPStan\Analyser\Scope; + final class ClassNameCheck { @@ -16,7 +18,12 @@ public function __construct( * @param ClassNameNodePair[] $pairs * @return list */ - public function checkClassNames(array $pairs, bool $checkClassCaseSensitivity = true): array + public function checkClassNames( + Scope $scope, + array $pairs, + ClassNameUsageLocation $location, + bool $checkClassCaseSensitivity = true, + ): array { $errors = []; diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php new file mode 100644 index 00000000000..6e3bf8cd17c --- /dev/null +++ b/src/Rules/ClassNameUsageLocation.php @@ -0,0 +1,60 @@ + */ + public static array $registry = []; + + /** + * @param self::* $value + */ + private function __construct(string $value) // @phpstan-ignore constructor.unusedParameter + { + } + + /** + * @param self::* $value + */ + public static function from(string $value): self + { + if (array_key_exists($value, self::$registry)) { + return self::$registry[$value]; + } + + return self::$registry[$value] = new self($value); + } + +} diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 23c3aafdaa1..5f2b8640368 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -150,7 +151,11 @@ private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $nod } } - $messages = $this->classCheck->checkClassNames([new ClassNameNodePair($className, $class)]); + $messages = $this->classCheck->checkClassNames( + $scope, + [new ClassNameNodePair($className, $class)], + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_CONSTANT_ACCESS), + ); } if (strtolower($constantName) === 'class') { diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index 8735b220842..60a542ff17c 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; @@ -36,7 +37,11 @@ public function processNode(Node $node, Scope $scope): array return []; } $extendedClassName = (string) $node->extends; - $messages = $this->classCheck->checkClassNames([new ClassNameNodePair($extendedClassName, $node->extends)]); + $messages = $this->classCheck->checkClassNames( + $scope, + [new ClassNameNodePair($extendedClassName, $node->extends)], + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_EXTENDS), + ); $currentClassName = null; if (isset($node->namespacedName)) { $currentClassName = (string) $node->namespacedName; diff --git a/src/Rules/Classes/ExistingClassInInstanceOfRule.php b/src/Rules/Classes/ExistingClassInInstanceOfRule.php index 063a5639121..7a4d4d21ebd 100644 --- a/src/Rules/Classes/ExistingClassInInstanceOfRule.php +++ b/src/Rules/Classes/ExistingClassInInstanceOfRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; @@ -86,7 +87,9 @@ public function processNode(Node $node, Scope $scope): array $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, [new ClassNameNodePair($name, $class)], + ClassNameUsageLocation::from(ClassNameUsageLocation::INSTANCEOF), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/Classes/ExistingClassInTraitUseRule.php b/src/Rules/Classes/ExistingClassInTraitUseRule.php index d13aefbd7a5..328e2387620 100644 --- a/src/Rules/Classes/ExistingClassInTraitUseRule.php +++ b/src/Rules/Classes/ExistingClassInTraitUseRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -35,7 +36,9 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $messages = $this->classCheck->checkClassNames( + $scope, array_map(static fn (Node\Name $traitName): ClassNameNodePair => new ClassNameNodePair((string) $traitName, $traitName), $node->traits), + ClassNameUsageLocation::from(ClassNameUsageLocation::TRAIT_USE), ); if (!$scope->isInClass()) { diff --git a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php index 581f45f3bc4..6101523b07f 100644 --- a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function array_map; @@ -34,7 +35,9 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $messages = $this->classCheck->checkClassNames( + $scope, array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->implements), + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_IMPLEMENTS), ); $currentClassName = null; diff --git a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php index d6caa6a5807..35d3f088fe2 100644 --- a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function array_map; @@ -34,7 +35,9 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $messages = $this->classCheck->checkClassNames( + $scope, array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->implements), + ClassNameUsageLocation::from(ClassNameUsageLocation::ENUM_IMPLEMENTS), ); $currentEnumName = (string) $node->namespacedName; diff --git a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php index dc87b36cfae..97f541b5a9c 100644 --- a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php +++ b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function array_map; @@ -34,7 +35,9 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $messages = $this->classCheck->checkClassNames( + $scope, array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->extends), + ClassNameUsageLocation::from(ClassNameUsageLocation::INTERFACE_EXTENDS), ); $currentInterfaceName = (string) $node->namespacedName; diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index b8cf691aeb7..6c5ef87c205 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -135,9 +136,9 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ ]; } - $messages = $this->classCheck->checkClassNames([ + $messages = $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node->class), - ]); + ], ClassNameUsageLocation::from(ClassNameUsageLocation::INSTANTIATION)); $classReflection = $this->reflectionProvider->getClass($class); } diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 7351d2a2bbb..13f384974ad 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\NameScope; +use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -11,6 +12,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; @@ -51,13 +53,13 @@ public function __construct( /** * @return list */ - public function check(ClassReflection $reflection, ClassLike $node): array + public function check(Scope $scope, ClassReflection $reflection, ClassLike $node): array { $errors = []; foreach ($this->checkInTraitDefinitionContext($reflection) as $error) { $errors[] = $error; } - foreach ($this->checkInTraitUseContext($reflection, $reflection, $node) as $error) { + foreach ($this->checkInTraitUseContext($scope, $reflection, $reflection, $node) as $error) { $errors[] = $error; } @@ -230,6 +232,7 @@ public function checkInTraitDefinitionContext(ClassReflection $reflection): arra * @return list */ public function checkInTraitUseContext( + Scope $scope, ClassReflection $reflection, ClassReflection $implementingClassReflection, ClassLike $node, @@ -270,9 +273,9 @@ public function checkInTraitUseContext( } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::TYPE_ALIAS), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index cfb270cadcb..9621d3c9ec0 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + return $this->check->check($scope, $node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php b/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php index 2523e3a9b44..8c8eb5cfc19 100644 --- a/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php +++ b/src/Rules/Classes/LocalTypeTraitUseAliasesRule.php @@ -25,6 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { return $this->check->checkInTraitUseContext( + $scope, $node->getTraitReflection(), $node->getImplementingClassReflection(), $node->getOriginalNode(), diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index a5a420e24c4..d0fcaace7ad 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -3,11 +3,13 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node\Stmt\ClassLike; +use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; @@ -38,6 +40,7 @@ public function __construct( * @return list */ public function check( + Scope $scope, ClassReflection $classReflection, ClassLike $node, ): array @@ -51,7 +54,7 @@ public function check( foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType()) as $error) { $errors[] = $error; } - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { $errors[] = $error; } @@ -63,7 +66,7 @@ public function check( foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue()) as $error) { $errors[] = $error; } - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { $errors[] = $error; } } @@ -72,7 +75,7 @@ public function check( foreach ($this->checkMethodTypeInTraitDefinitionContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType()) as $error) { $errors[] = $error; } - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { $errors[] = $error; } } @@ -118,6 +121,7 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): * @return list */ public function checkInTraitUseContext( + Scope $scope, ClassReflection $classReflection, ClassReflection $implementingClass, ClassLike $node, @@ -134,7 +138,7 @@ public function checkInTraitUseContext( foreach ($methodTag->getParameters() as $parameterName => $parameterTag) { $i++; $parameterDescription = sprintf('parameter #%d $%s', $i, $parameterName); - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $parameterDescription, $parameterTag->getType(), $node) as $error) { $errors[] = $error; } @@ -143,13 +147,13 @@ public function checkInTraitUseContext( } $defaultValueDescription = sprintf('%s default value', $parameterDescription); - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $defaultValueDescription, $parameterTag->getDefaultValue(), $node) as $error) { $errors[] = $error; } } $returnTypeDescription = 'return type'; - foreach ($this->checkMethodTypeInTraitUseContext($classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { + foreach ($this->checkMethodTypeInTraitUseContext($scope, $classReflection, $methodName, $returnTypeDescription, $methodTag->getReturnType(), $node) as $error) { $errors[] = $error; } } @@ -212,7 +216,7 @@ private function checkMethodTypeInTraitDefinitionContext(ClassReflection $classR /** * @return list */ - private function checkMethodTypeInTraitUseContext(ClassReflection $classReflection, string $methodName, string $description, Type $type, ClassLike $node): array + private function checkMethodTypeInTraitUseContext(Scope $scope, ClassReflection $classReflection, string $methodName, string $description, Type $type, ClassLike $node): array { $errors = []; foreach ($type->getReferencedClasses() as $class) { @@ -232,9 +236,9 @@ private function checkMethodTypeInTraitUseContext(ClassReflection $classReflecti } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_METHOD), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/MethodTagRule.php b/src/Rules/Classes/MethodTagRule.php index ddb3cf254db..20b0c004d18 100644 --- a/src/Rules/Classes/MethodTagRule.php +++ b/src/Rules/Classes/MethodTagRule.php @@ -25,6 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { return $this->check->check( + $scope, $node->getClassReflection(), $node->getOriginalNode(), ); diff --git a/src/Rules/Classes/MethodTagTraitUseRule.php b/src/Rules/Classes/MethodTagTraitUseRule.php index 1f6d6f1f7c1..aecce7bdcf4 100644 --- a/src/Rules/Classes/MethodTagTraitUseRule.php +++ b/src/Rules/Classes/MethodTagTraitUseRule.php @@ -25,6 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { return $this->check->checkInTraitUseContext( + $scope, $node->getTraitReflection(), $node->getImplementingClassReflection(), $node->getOriginalNode(), diff --git a/src/Rules/Classes/MixinCheck.php b/src/Rules/Classes/MixinCheck.php index 74df41612bf..2c6417edddc 100644 --- a/src/Rules/Classes/MixinCheck.php +++ b/src/Rules/Classes/MixinCheck.php @@ -3,10 +3,12 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node\Stmt\ClassLike; +use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; @@ -35,14 +37,14 @@ public function __construct( /** * @return list */ - public function check(ClassReflection $classReflection, ClassLike $node): array + public function check(Scope $scope, ClassReflection $classReflection, ClassLike $node): array { $errors = []; foreach ($this->checkInTraitDefinitionContext($classReflection) as $error) { $errors[] = $error; } - foreach ($this->checkInTraitUseContext($classReflection, $classReflection, $node) as $error) { + foreach ($this->checkInTraitUseContext($scope, $classReflection, $classReflection, $node) as $error) { $errors[] = $error; } @@ -108,6 +110,7 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): * @return list */ public function checkInTraitUseContext( + Scope $scope, ClassReflection $reflection, ClassReflection $implementingClassReflection, ClassLike $node, @@ -161,9 +164,9 @@ public function checkInTraitUseContext( } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_MIXIN), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/MixinRule.php b/src/Rules/Classes/MixinRule.php index 8fb28e0888f..de75fc13a61 100644 --- a/src/Rules/Classes/MixinRule.php +++ b/src/Rules/Classes/MixinRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + return $this->check->check($scope, $node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/src/Rules/Classes/MixinTraitUseRule.php b/src/Rules/Classes/MixinTraitUseRule.php index 33a3e807808..7ef205cbaf6 100644 --- a/src/Rules/Classes/MixinTraitUseRule.php +++ b/src/Rules/Classes/MixinTraitUseRule.php @@ -25,6 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { return $this->check->checkInTraitUseContext( + $scope, $node->getTraitReflection(), $node->getImplementingClassReflection(), $node->getOriginalNode(), diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index f45c38cd673..e499f38ed8f 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -3,12 +3,14 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node\Stmt\ClassLike; +use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\PhpDoc\Tag\PropertyTag; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; @@ -40,6 +42,7 @@ public function __construct( * @return list */ public function check( + Scope $scope, ClassReflection $classReflection, ClassLike $node, ): array @@ -51,7 +54,7 @@ public function check( foreach ($this->checkPropertyTypeInTraitDefinitionContext($classReflection, $propertyName, $tagName, $type) as $error) { $errors[] = $error; } - foreach ($this->checkPropertyTypeInTraitUseContext($classReflection, $propertyName, $tagName, $type, $node) as $error) { + foreach ($this->checkPropertyTypeInTraitUseContext($scope, $classReflection, $propertyName, $tagName, $type, $node) as $error) { $errors[] = $error; } } @@ -82,6 +85,7 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection): * @return list */ public function checkInTraitUseContext( + Scope $scope, ClassReflection $classReflection, ClassReflection $implementingClass, ClassLike $node, @@ -96,7 +100,7 @@ public function checkInTraitUseContext( foreach ($phpDoc->getPropertyTags() as $propertyName => $propertyTag) { [$types, $tagName] = $this->getTypesAndTagName($propertyTag); foreach ($types as $type) { - foreach ($this->checkPropertyTypeInTraitUseContext($classReflection, $propertyName, $tagName, $type, $node) as $error) { + foreach ($this->checkPropertyTypeInTraitUseContext($scope, $classReflection, $propertyName, $tagName, $type, $node) as $error) { $errors[] = $error; } } @@ -193,7 +197,7 @@ private function checkPropertyTypeInTraitDefinitionContext(ClassReflection $clas /** * @return list */ - private function checkPropertyTypeInTraitUseContext(ClassReflection $classReflection, string $propertyName, string $tagName, Type $type, ClassLike $node): array + private function checkPropertyTypeInTraitUseContext(Scope $scope, ClassReflection $classReflection, string $propertyName, string $tagName, Type $type, ClassLike $node): array { $errors = []; foreach ($type->getReferencedClasses() as $class) { @@ -213,9 +217,9 @@ private function checkPropertyTypeInTraitUseContext(ClassReflection $classReflec } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_PROPERTY), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/PropertyTagRule.php b/src/Rules/Classes/PropertyTagRule.php index c1f002c3b3d..0ed1a2918ae 100644 --- a/src/Rules/Classes/PropertyTagRule.php +++ b/src/Rules/Classes/PropertyTagRule.php @@ -24,7 +24,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - return $this->check->check($node->getClassReflection(), $node->getOriginalNode()); + return $this->check->check($scope, $node->getClassReflection(), $node->getOriginalNode()); } } diff --git a/src/Rules/Classes/PropertyTagTraitUseRule.php b/src/Rules/Classes/PropertyTagTraitUseRule.php index f381cd0dfd2..4ac620fbc22 100644 --- a/src/Rules/Classes/PropertyTagTraitUseRule.php +++ b/src/Rules/Classes/PropertyTagTraitUseRule.php @@ -25,6 +25,7 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { return $this->check->checkInTraitUseContext( + $scope, $node->getTraitReflection(), $node->getImplementingClassReflection(), $node->getOriginalNode(), diff --git a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php index f4af37da86e..d873394c20b 100644 --- a/src/Rules/Exceptions/CaughtExceptionExistenceRule.php +++ b/src/Rules/Exceptions/CaughtExceptionExistenceRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use Throwable; @@ -67,7 +68,9 @@ public function processNode(Node $node, Scope $scope): array $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, [new ClassNameNodePair($className, $class)], + ClassNameUsageLocation::from(ClassNameUsageLocation::EXCEPTION_CATCH), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index f855124285c..e4cb31e36d2 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -62,6 +62,7 @@ public function __construct( * @return list */ public function checkFunction( + Scope $scope, Function_ $function, PhpFunctionFromParserNodeReflection $functionReflection, string $parameterMessage, @@ -73,6 +74,7 @@ public function checkFunction( ): array { return $this->checkParametersAcceptor( + $scope, $functionReflection, $function, $parameterMessage, @@ -173,9 +175,9 @@ public function checkAnonymousFunction( $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $param->type), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE), $this->checkClassCaseSensitivity), ); } } @@ -231,9 +233,9 @@ public function checkAnonymousFunction( $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($returnTypeClass, $returnTypeNode), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE), $this->checkClassCaseSensitivity), ); } @@ -244,6 +246,7 @@ public function checkAnonymousFunction( * @return list */ public function checkClassMethod( + Scope $scope, PhpMethodFromParserNodeReflection $methodReflection, ClassMethod|Node\PropertyHook $methodNode, string $parameterMessage, @@ -256,6 +259,7 @@ public function checkClassMethod( ): array { $errors = $this->checkParametersAcceptor( + $scope, $methodReflection, $methodNode, $parameterMessage, @@ -291,7 +295,9 @@ public function checkClassMethod( $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $methodNode), $selfOutTypeReferencedClasses), + ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_SELF_OUT), $this->checkClassCaseSensitivity, ), ); @@ -304,6 +310,7 @@ public function checkClassMethod( * @return list */ private function checkParametersAcceptor( + Scope $scope, ParametersAcceptor $parametersAcceptor, FunctionLike $functionNode, string $parameterMessage, @@ -420,7 +427,9 @@ private function checkParametersAcceptor( $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $parameterNodeCallback()), $referencedClasses), + ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE), $this->checkClassCaseSensitivity, ), ); @@ -468,7 +477,9 @@ private function checkParametersAcceptor( $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $returnTypeNode), $returnTypeReferencedClasses), + ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index 5df0e84af38..7f83eea1932 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -30,6 +30,7 @@ public function processNode(Node $node, Scope $scope): array $functionName = SprintfHelper::escapeFormatString($node->getFunctionReflection()->getName()); return $this->check->checkFunction( + $scope, $node->getOriginalNode(), $node->getFunctionReflection(), sprintf( diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index dda84c8629e..2dbd279da26 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -9,6 +9,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; @@ -105,7 +106,7 @@ public function check( } $classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $boundType->getReferencedClasses()); - $messages = array_merge($messages, $this->classCheck->checkClassNames($classNameNodePairs, $this->checkClassCaseSensitivity)); + $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_BOUND), $this->checkClassCaseSensitivity)); $boundTypeClass = get_class($boundType); if ( @@ -182,7 +183,7 @@ public function check( } $classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $defaultType->getReferencedClasses()); - $messages = array_merge($messages, $this->classCheck->checkClassNames($classNameNodePairs, $this->checkClassCaseSensitivity)); + $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_DEFAULT), $this->checkClassCaseSensitivity)); $genericDefaultErrors = $this->genericObjectTypeCheck->check( $defaultType, diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index fcc46717085..6127bac985c 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -32,6 +32,7 @@ public function processNode(Node $node, Scope $scope): array $methodName = SprintfHelper::escapeFormatString($methodReflection->getName()); return $this->check->checkClassMethod( + $scope, $methodReflection, $node->getOriginalNode(), sprintf( diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index 63c1de6c2b2..df72cfb6bf7 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -15,6 +15,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -139,7 +140,11 @@ public function check( ]; } - $errors = $this->classCheck->checkClassNames([new ClassNameNodePair($className, $class)]); + $errors = $this->classCheck->checkClassNames( + $scope, + [new ClassNameNodePair($className, $class)], + ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_METHOD_CALL), + ); $classType = $scope->resolveTypeByName($class); } diff --git a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php index 457ffd29a97..6e246de285c 100644 --- a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -55,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array ) { $error = $this->checkFunction($name); } elseif ($use->type === Use_::TYPE_NORMAL) { - $error = $this->checkClass($name); + $error = $this->checkClass($scope, $name); } else { throw new ShouldNotHappenException(); } @@ -123,11 +124,11 @@ private function checkFunction(Node\Name $name): ?IdentifierRuleError return null; } - private function checkClass(Node\Name $name): ?IdentifierRuleError + private function checkClass(Scope $scope, Node\Name $name): ?IdentifierRuleError { - $errors = $this->classCheck->checkClassNames([ + $errors = $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair((string) $name, $name), - ]); + ], ClassNameUsageLocation::from(ClassNameUsageLocation::USE_STATEMENT)); if (count($errors) === 0) { return null; } elseif (count($errors) === 1) { diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index 3a82facc947..41407def845 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -55,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array return $this->checkFunctions($node->uses); } - return $this->checkClasses($node->uses); + return $this->checkClasses($scope, $node->uses); } /** @@ -129,10 +130,12 @@ private function checkFunctions(array $uses): array * @param Node\UseItem[] $uses * @return list */ - private function checkClasses(array $uses): array + private function checkClasses(Scope $scope, array $uses): array { return $this->classCheck->checkClassNames( + $scope, array_map(static fn (Node\UseItem $use): ClassNameNodePair => new ClassNameNodePair((string) $use->name, $use->name), $uses), + ClassNameUsageLocation::from(ClassNameUsageLocation::USE_STATEMENT), ); } diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 1fecf4ffa3c..40824275575 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\TypeExpr; use PHPStan\PhpDoc\Tag\AssertTag; use PHPStan\Reflection\ExtendedMethodReflection; @@ -14,6 +15,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\MissingTypehintCheck; @@ -46,6 +48,7 @@ public function __construct( * @return list */ public function check( + Scope $scope, Function_|ClassMethod $node, ExtendedMethodReflection|FunctionReflection $reflection, ParametersAcceptor $acceptor, @@ -149,9 +152,9 @@ public function check( $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_ASSERT), $this->checkClassCaseSensitivity), ); } diff --git a/src/Rules/PhpDoc/FunctionAssertRule.php b/src/Rules/PhpDoc/FunctionAssertRule.php index 893192e2500..cf91c5ece63 100644 --- a/src/Rules/PhpDoc/FunctionAssertRule.php +++ b/src/Rules/PhpDoc/FunctionAssertRule.php @@ -31,7 +31,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->helper->check($node->getOriginalNode(), $function, $variants[0]); + return $this->helper->check($scope, $node->getOriginalNode(), $function, $variants[0]); } } diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 375e246f351..7dc718889ed 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; @@ -153,7 +154,9 @@ public function processNode(Node $node, Scope $scope): array $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $node), $referencedClasses), + ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_VAR), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/PhpDoc/MethodAssertRule.php b/src/Rules/PhpDoc/MethodAssertRule.php index 6694ad3e421..47279e6e4eb 100644 --- a/src/Rules/PhpDoc/MethodAssertRule.php +++ b/src/Rules/PhpDoc/MethodAssertRule.php @@ -31,7 +31,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - return $this->helper->check($node->getOriginalNode(), $method, $variants[0]); + return $this->helper->check($scope, $node->getOriginalNode(), $method, $variants[0]); } } diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index f6a602b2a49..233082b3a98 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -3,9 +3,11 @@ namespace PHPStan\Rules\PhpDoc; use PhpParser\Node; +use PHPStan\Analyser\Scope; use PHPStan\PhpDoc\Tag\RequireExtendsTag; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; @@ -30,7 +32,7 @@ public function __construct( * @param array $extendsTags * @return list */ - public function checkExtendsTags(Node $node, array $extendsTags): array + public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): array { $errors = []; @@ -75,9 +77,9 @@ public function checkExtendsTags(Node $node, array $extendsTags): array } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_EXTENDS), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php b/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php index 9540555e4ff..7b5779fa9ff 100644 --- a/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php +++ b/src/Rules/PhpDoc/RequireExtendsDefinitionClassRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array ]; } - return $this->requireExtendsCheck->checkExtendsTags($node, $extendsTags); + return $this->requireExtendsCheck->checkExtendsTags($scope, $node, $extendsTags); } } diff --git a/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php index de4be753d96..468e0a709f0 100644 --- a/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireExtendsDefinitionTraitRule.php @@ -37,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array $traitReflection = $this->reflectionProvider->getClass($node->namespacedName->toString()); $extendsTags = $traitReflection->getRequireExtendsTags(); - return $this->requireExtendsCheck->checkExtendsTags($node, $extendsTags); + return $this->requireExtendsCheck->checkExtendsTags($scope, $node, $extendsTags); } } diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 764c868e532..616ce798275 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; @@ -78,9 +79,9 @@ public function processNode(Node $node, Scope $scope): array } else { $errors = array_merge( $errors, - $this->classCheck->checkClassNames([ + $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_IMPLEMENTS), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 80f10341207..7bd8156cad8 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -131,7 +132,11 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]; } - $messages = $this->classCheck->checkClassNames([new ClassNameNodePair($class, $node->class)]); + $messages = $this->classCheck->checkClassNames( + $scope, + [new ClassNameNodePair($class, $node->class)], + ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_PROPERTY_ACCESS), + ); $classType = $scope->resolveTypeByName($node->class); } diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index d2d1dc5095f..1fee991c46f 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -9,6 +9,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -83,7 +84,9 @@ public function processNode(Node $node, Scope $scope): array $errors = array_merge( $errors, $this->classCheck->checkClassNames( + $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $node), $referencedClasses), + ClassNameUsageLocation::from(ClassNameUsageLocation::PROPERTY_TYPE), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php b/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php index be668710f66..dff1491617a 100644 --- a/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertyHookTypehintsRule.php @@ -47,6 +47,7 @@ public function processNode(Node $node, Scope $scope): array } return $this->check->checkClassMethod( + $scope, $hookReflection, $originalHookNode, sprintf( From 9ae17c9940b5d2d0d51c967ebf18d1744044d853 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 22 Apr 2025 14:33:07 +0200 Subject: [PATCH 1293/3097] RestrictedClassNameUsageExtension --- src/Rules/ClassNameCheck.php | 30 +++++++++++++ .../RestrictedClassNameUsageExtension.php | 42 +++++++++++++++++++ .../Rules/Classes/ClassAttributesRuleTest.php | 2 + .../ClassConstantAttributesRuleTest.php | 2 + .../Rules/Classes/ClassConstantRuleTest.php | 2 + .../ExistingClassInClassExtendsRuleTest.php | 2 + .../ExistingClassInInstanceOfRuleTest.php | 2 + .../ExistingClassInTraitUseRuleTest.php | 2 + ...istingClassesInClassImplementsRuleTest.php | 2 + ...xistingClassesInEnumImplementsRuleTest.php | 2 + ...stingClassesInInterfaceExtendsRuleTest.php | 2 + .../ForbiddenNameCheckExtensionRuleTest.php | 2 + .../Rules/Classes/InstantiationRuleTest.php | 2 + .../Classes/LocalTypeAliasesRuleTest.php | 2 + .../Classes/LocalTypeTraitAliasesRuleTest.php | 2 + .../LocalTypeTraitUseAliasesRuleTest.php | 2 + .../Rules/Classes/MethodTagRuleTest.php | 2 + .../Rules/Classes/MethodTagTraitRuleTest.php | 2 + .../Classes/MethodTagTraitUseRuleTest.php | 2 + tests/PHPStan/Rules/Classes/MixinRuleTest.php | 2 + .../Rules/Classes/MixinTraitRuleTest.php | 2 + .../Rules/Classes/MixinTraitUseRuleTest.php | 2 + .../Rules/Classes/PropertyTagRuleTest.php | 2 + .../Classes/PropertyTagTraitRuleTest.php | 2 + .../Classes/PropertyTagTraitUseRuleTest.php | 2 + .../EnumCases/EnumCaseAttributesRuleTest.php | 2 + .../CaughtExceptionExistenceRuleTest.php | 2 + .../ArrowFunctionAttributesRuleTest.php | 2 + .../Functions/ClosureAttributesRuleTest.php | 2 + ...lassesInArrowFunctionTypehintsRuleTest.php | 2 + ...stingClassesInClosureTypehintsRuleTest.php | 2 + .../ExistingClassesInTypehintsRuleTest.php | 2 + .../Functions/FunctionAttributesRuleTest.php | 2 + .../Functions/ParamAttributesRuleTest.php | 2 + .../Generics/ClassTemplateTypeRuleTest.php | 2 + .../Generics/FunctionTemplateTypeRuleTest.php | 2 + .../InterfaceTemplateTypeRuleTest.php | 2 + .../MethodTagTemplateTypeRuleTest.php | 2 + .../MethodTagTemplateTypeTraitRuleTest.php | 2 + .../Generics/MethodTemplateTypeRuleTest.php | 2 + .../Generics/TraitTemplateTypeRuleTest.php | 2 + .../Methods/CallStaticMethodsRuleTest.php | 2 + .../ExistingClassesInTypehintsRuleTest.php | 2 + .../Methods/MethodAttributesRuleTest.php | 2 + .../Methods/StaticMethodCallableRuleTest.php | 2 + .../ExistingNamesInGroupUseRuleTest.php | 2 + .../Namespaces/ExistingNamesInUseRuleTest.php | 2 + .../Rules/PhpDoc/FunctionAssertRuleTest.php | 7 +++- .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 2 + ...mpatiblePropertyHookPhpDocTypeRuleTest.php | 2 + ...IncompatiblePropertyPhpDocTypeRuleTest.php | 2 + .../InvalidPhpDocVarTagTypeRuleTest.php | 2 + .../Rules/PhpDoc/MethodAssertRuleTest.php | 7 +++- .../RequireExtendsDefinitionClassRuleTest.php | 2 + .../RequireExtendsDefinitionTraitRuleTest.php | 2 + ...quireImplementsDefinitionTraitRuleTest.php | 2 + ...AccessStaticPropertiesInAssignRuleTest.php | 2 + .../AccessStaticPropertiesRuleTest.php | 2 + .../ExistingClassesInPropertiesRuleTest.php | 2 + ...ClassesInPropertyHookTypehintsRuleTest.php | 2 + .../Properties/PropertyAttributesRuleTest.php | 2 + .../PropertyHookAttributesRuleTest.php | 2 + .../Rules/Traits/TraitAttributesRuleTest.php | 2 + 63 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/Rules/RestrictedUsage/RestrictedClassNameUsageExtension.php diff --git a/src/Rules/ClassNameCheck.php b/src/Rules/ClassNameCheck.php index c2f8d5f00a4..c8d786976b4 100644 --- a/src/Rules/ClassNameCheck.php +++ b/src/Rules/ClassNameCheck.php @@ -3,6 +3,9 @@ namespace PHPStan\Rules; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\Container; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; final class ClassNameCheck { @@ -10,6 +13,8 @@ final class ClassNameCheck public function __construct( private ClassCaseSensitivityCheck $classCaseSensitivityCheck, private ClassForbiddenNameCheck $classForbiddenNameCheck, + private ReflectionProvider $reflectionProvider, + private Container $container, ) { } @@ -36,6 +41,31 @@ public function checkClassNames( $errors[] = $error; } + /** @var RestrictedClassNameUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG); + if ($extensions === []) { + return $errors; + } + + foreach ($pairs as $pair) { + if (!$this->reflectionProvider->hasClass($pair->getClassName())) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($pair->getClassName()); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedClassNameUsage($classReflection, $scope, $location); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->line($pair->getNode()->getStartLine()) + ->build(); + } + } + return $errors; } diff --git a/src/Rules/RestrictedUsage/RestrictedClassNameUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedClassNameUsageExtension.php new file mode 100644 index 00000000000..1d1f619c998 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedClassNameUsageExtension.php @@ -0,0 +1,42 @@ +phpVersion), ); diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index ca4132139fe..83d23411dea 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), $reflectionProvider, true, diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 4e026df3a6c..26cf1773002 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -25,6 +25,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php index d6369b56a26..f6e5ac6b5eb 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), $reflectionProvider, true, diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php index a2533f17d9c..d51a34a6216 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), $reflectionProvider, true, diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php index e706e005d97..7ed57978958 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php @@ -23,6 +23,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), $reflectionProvider, true, diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php index 4caaf6f8e51..c6a3db9ad4d 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), $reflectionProvider, true, diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index b4fb7e23999..93e202ef4e7 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ); diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index bf5e7ab8af1..eb547315cf5 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ); diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 9f6ac7b3ec0..12631f5c746 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -32,6 +32,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new GenericObjectTypeCheck(), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 9654660857e..4662acc732a 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -31,6 +31,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new GenericObjectTypeCheck(), diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php index e99a521056f..0748cdb4b6a 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitUseAliasesRuleTest.php @@ -31,6 +31,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new GenericObjectTypeCheck(), diff --git a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php index 0c221078d22..93e05c36750 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php index 5cff88d7072..1ec2063551b 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php index bdacfb3c6ad..1696a2515de 100644 --- a/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MethodTagTraitUseRuleTest.php @@ -28,6 +28,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index c389d025a07..d7c804c2373 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -28,6 +28,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php index 80d1a3e9e13..7ae81a0f18d 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php index d7920d043ed..d11675e2088 100644 --- a/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinTraitUseRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php index 826047dbb9a..04f0ecd7f39 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php index 4f822c2a646..def6012d0d8 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php index f42aaca316f..9231ebd4e47 100644 --- a/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/PropertyTagTraitUseRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): TRule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php index 83a3a195786..5de7d45b5d0 100644 --- a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -40,6 +40,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php index e31c3d2faa3..1ec6854a485 100644 --- a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index b88e13b002a..a46163b89cf 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index 758c1e00eba..360ed89a3c2 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index 5b603724ec3..158d763831e 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index db804026330..988b42b6a9d 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 46e872ec74f..f03764a657e 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 0a7f95b2704..2fc38ca188f 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index c5d89a302ae..7f8e3cef19f 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index 0538311ee53..0ef409ddf8a 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -26,6 +26,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index f9d15da674b..af46e7e0f5e 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index b997498703f..3823a214f7f 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -25,6 +25,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php index 1db002fd944..758c11548d9 100644 --- a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeRuleTest.php @@ -28,6 +28,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php index 773f6c30c3a..470d48adc51 100644 --- a/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTagTemplateTypeTraitRuleTest.php @@ -28,6 +28,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php index 9276fec7c16..8450ba5f3e1 100644 --- a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php index 84351d9ea9a..335e1f707c6 100644 --- a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 8b1a40dd212..98d4eaf364f 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index e42210014db..170920bbf6c 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -29,6 +29,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 0af7093585e..b2a85a50c3b 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php index 8cec36f2369..8f3161de633 100644 --- a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php +++ b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php @@ -31,6 +31,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php index c13cde0b4ea..c49f6209f19 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php index f3e4ad26916..13fa5a254b9 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInUseRuleTest.php @@ -22,6 +22,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php index 8a691952879..2bc6ddba355 100644 --- a/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/FunctionAssertRuleTest.php @@ -25,7 +25,12 @@ protected function getRule(): Rule $initializerExprTypeResolver, $reflectionProvider, new UnresolvableTypeHelper(), - new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), + ), new MissingTypehintCheck(true, []), new GenericObjectTypeCheck(), true, diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index e2c76ab516f..b5ed921568d 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -34,6 +34,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php index b0d4d718ad5..e10eff3ff1a 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyHookPhpDocTypeRuleTest.php @@ -34,6 +34,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php index d8ec3604b3d..b0ed51d449e 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php @@ -30,6 +30,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), $typeAliasResolver, diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index d2477d1ca66..ec26814c8f3 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new GenericObjectTypeCheck(), new MissingTypehintCheck(true, []), diff --git a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php index c038a01891d..99005c23bf1 100644 --- a/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php @@ -25,7 +25,12 @@ protected function getRule(): Rule $initializerExprTypeResolver, $reflectionProvider, new UnresolvableTypeHelper(), - new ClassNameCheck(new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer())), + new ClassNameCheck( + new ClassCaseSensitivityCheck($reflectionProvider, true), + new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), + ), new MissingTypehintCheck(true, []), new GenericObjectTypeCheck(), true, diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 74e50e10f91..78934232279 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -24,6 +24,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index d14f249dcc9..93e516021ae 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -25,6 +25,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php index 4a795dd771f..4104983d3ff 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php @@ -24,6 +24,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, true, diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index ab157033034..355cf0dfa24 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -25,6 +25,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 88834051bb5..bae249fd958 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -24,6 +24,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ); diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php index cfe69723414..873654f585f 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersion), diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php index cab45fe36a6..ddb533c25f8 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertyHookTypehintsRuleTest.php @@ -27,6 +27,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), new UnresolvableTypeHelper(), new PhpVersion(PHP_VERSION_ID), diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index e9dcd4a4838..00874ab1511 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -38,6 +38,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php index fe5d59a5b5e..7a0e80e653d 100644 --- a/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyHookAttributesRuleTest.php @@ -39,6 +39,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), diff --git a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php index 67e500fc634..b4e2455cb8f 100644 --- a/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Traits/TraitAttributesRuleTest.php @@ -44,6 +44,8 @@ protected function getRule(): Rule new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, false), new ClassForbiddenNameCheck(self::getContainer()), + $reflectionProvider, + self::getContainer(), ), true, ), From 7d448c73469f7468d15d9d78f0af252bc0af2220 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 22 Apr 2025 15:13:26 +0200 Subject: [PATCH 1294/3097] RestrictedInternalUsageHelper extraction --- conf/config.neon | 3 +++ ...RestrictedInternalMethodUsageExtension.php | 25 +++++++----------- .../RestrictedInternalUsageHelper.php | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 src/Rules/InternalTag/RestrictedInternalUsageHelper.php diff --git a/conf/config.neon b/conf/config.neon index e4b43fe9ecf..5baa7ed1e3f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1002,6 +1002,9 @@ services: - class: PHPStan\Rules\Generics\VarianceCheck + - + class: PHPStan\Rules\InternalTag\RestrictedInternalUsageHelper + - class: PHPStan\Rules\IssetCheck arguments: diff --git a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php index 441d9f8f517..fe611165d02 100644 --- a/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalMethodUsageExtension.php @@ -9,40 +9,33 @@ use function array_slice; use function explode; use function sprintf; -use function str_starts_with; use function strtolower; final class RestrictedInternalMethodUsageExtension implements RestrictedMethodUsageExtension { + public function __construct(private RestrictedInternalUsageHelper $helper) + { + } + public function isRestrictedMethodUsage( ExtendedMethodReflection $methodReflection, Scope $scope, ): ?RestrictedUsage { $isMethodInternal = $methodReflection->isInternal()->yes(); - $isDeclaringClassInternal = $methodReflection->getDeclaringClass()->isInternal(); + $declaringClass = $methodReflection->getDeclaringClass(); + $isDeclaringClassInternal = $declaringClass->isInternal(); if (!$isMethodInternal && !$isDeclaringClassInternal) { return null; } - $currentNamespace = $scope->getNamespace(); - $declaringClassName = $methodReflection->getDeclaringClass()->getName(); - $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; - if ($currentNamespace === null) { - return $this->buildRestrictedUsage($methodReflection, $namespace, $isMethodInternal); - } - - $currentNamespace = explode('\\', $currentNamespace)[0]; - if (str_starts_with($namespace . '\\', $currentNamespace . '\\')) { + $declaringClassName = $declaringClass->getName(); + if (!$this->helper->shouldBeReported($scope, $declaringClassName)) { return null; } - return $this->buildRestrictedUsage($methodReflection, $namespace, $isMethodInternal); - } - - private function buildRestrictedUsage(ExtendedMethodReflection $methodReflection, ?string $namespace, bool $isMethodInternal): RestrictedUsage - { + $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; if ($namespace === null) { if (!$isMethodInternal) { return RestrictedUsage::create( diff --git a/src/Rules/InternalTag/RestrictedInternalUsageHelper.php b/src/Rules/InternalTag/RestrictedInternalUsageHelper.php new file mode 100644 index 00000000000..1767c02fbb5 --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalUsageHelper.php @@ -0,0 +1,26 @@ +getNamespace(); + $namespace = array_slice(explode('\\', $name), 0, -1)[0] ?? null; + if ($currentNamespace === null) { + return true; + } + + $currentNamespace = explode('\\', $currentNamespace)[0]; + + return !str_starts_with($namespace . '\\', $currentNamespace . '\\'); + } + +} From 1f54e5ac42c99241bdc2a6a14cec9053009f3600 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 22 Apr 2025 15:21:57 +0200 Subject: [PATCH 1295/3097] Bleeding edge - report restricted `@internal` class name usage --- conf/config.level0.neon | 7 + conf/config.neon | 4 + phpstan-baseline.neon | 72 +++++++++++ src/Rules/ClassNameCheck.php | 6 +- src/Rules/ClassNameUsageLocation.php | 121 +++++++++++++++--- ...trictedInternalClassNameUsageExtension.php | 46 +++++++ .../ExistingNamesInGroupUseRule.php | 3 +- .../Namespaces/ExistingNamesInUseRule.php | 3 +- ...teIdentifierDynamicReturnTypeExtension.php | 61 +++++++++ .../nsrt/class-name-usage-location.php | 10 ++ 10 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php create mode 100644 src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/class-name-usage-location.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 084738089e6..ea85702a318 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -103,6 +103,10 @@ rules: - PHPStan\Rules\Variables\UnsetRule - PHPStan\Rules\Whitespace\FileWhitespaceRule +conditionalTags: + PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension: + phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag% + services: - class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule @@ -290,3 +294,6 @@ services: currentWorkingDirectory: %currentWorkingDirectory% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension diff --git a/conf/config.neon b/conf/config.neon index 5baa7ed1e3f..7e5de29f450 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1988,6 +1988,10 @@ services: tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Type\PHPStan\ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension - class: PHPStan\Type\ClosureTypeFactory arguments: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 19bd8da64d7..1bb91878613 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -750,12 +750,30 @@ parameters: count: 1 path: src/Testing/LevelsTestCase.php + - + message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + identifier: catch.internalClass + count: 2 + path: src/Testing/LevelsTestCase.php + + - + message: '#^Return type references internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + identifier: return.internalClass + count: 1 + path: src/Testing/LevelsTestCase.php + - message: '#^Anonymous function has an unused use \$container\.$#' identifier: closure.unusedUse count: 1 path: src/Testing/PHPStanTestCase.php + - + message: '#^Catching internal class PHPUnit\\Framework\\ExpectationFailedException\.$#' + identifier: catch.internalClass + count: 1 + path: src/Testing/PHPStanTestCase.php + - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1470,6 +1488,12 @@ parameters: count: 4 path: src/Type/ObjectWithoutClassType.php + - + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + identifier: phpstanApi.runtimeReflection + count: 1 + path: src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php + - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -1932,6 +1956,24 @@ parameters: count: 1 path: tests/PHPStan/Node/FileNodeTest.php + - + message: '#^Access to constant on internal class InternalAnnotations\\InternalFoo\.$#' + identifier: classConstant.internalClass + count: 1 + path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php + + - + message: '#^Access to constant on internal interface InternalAnnotations\\InternalFooInterface\.$#' + identifier: classConstant.internalInterface + count: 1 + path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php + + - + message: '#^Access to constant on internal trait InternalAnnotations\\InternalFooTrait\.$#' + identifier: classConstant.internalTrait + count: 1 + path: tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php + - message: '#^PHPDoc tag @var with type string is not subtype of type class\-string\.$#' identifier: varTag.type @@ -1944,18 +1986,48 @@ parameters: count: 1 path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php + - + message: '#^Instanceof references internal interface PHPUnit\\Exception\.$#' + identifier: instanceof.internalInterface + count: 1 + path: tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php + - message: '#^Creating new PHPStan\\Php8StubsMap is not covered by backward compatibility promise\. The class might change in a minor PHPStan version\.$#' identifier: phpstanApi.constructor count: 1 path: tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php + - + message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + identifier: catch.internalClass + count: 1 + path: tests/PHPStan/Rules/WarningEmittingRuleTest.php + - message: '#^Call to method getComparisonFailure\(\) of internal class PHPUnit\\Framework\\ExpectationFailedException from outside its root namespace PHPUnit\.$#' identifier: method.internalClass count: 2 path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php + - + message: '#^Catching internal class PHPUnit\\Framework\\ExpectationFailedException\.$#' + identifier: catch.internalClass + count: 1 + path: tests/PHPStan/Testing/NonexistentAnalysedClassRuleTest.php + + - + message: '#^Access to constant on internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + identifier: classConstant.internalClass + count: 1 + path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php + + - + message: '#^Catching internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + identifier: catch.internalClass + count: 1 + path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php + - message: '#^PHPDoc tag @var assumes the expression with type PHPStan\\Type\\Generic\\TemplateType is always PHPStan\\Type\\Generic\\TemplateMixedType but it''s error\-prone and dangerous\.$#' identifier: phpstanApi.varTagAssumption diff --git a/src/Rules/ClassNameCheck.php b/src/Rules/ClassNameCheck.php index c8d786976b4..7ce8b7a27ec 100644 --- a/src/Rules/ClassNameCheck.php +++ b/src/Rules/ClassNameCheck.php @@ -26,7 +26,7 @@ public function __construct( public function checkClassNames( Scope $scope, array $pairs, - ClassNameUsageLocation $location, + ?ClassNameUsageLocation $location, bool $checkClassCaseSensitivity = true, ): array { @@ -41,6 +41,10 @@ public function checkClassNames( $errors[] = $error; } + if ($location === null) { + return $errors; + } + /** @var RestrictedClassNameUsageExtension[] $extensions */ $extensions = $this->container->getServicesByTag(RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG); if ($extensions === []) { diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index 6e3bf8cd17c..d3bf060ce9e 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -3,37 +3,38 @@ namespace PHPStan\Rules; use function array_key_exists; +use function sprintf; +use function ucfirst; final class ClassNameUsageLocation { public const TRAIT_USE = 'traitUse'; - public const STATIC_PROPERTY_ACCESS = 'staticPropertyAccess'; - public const PHPDOC_TAG_ASSERT = 'phpDocTagAssert'; + public const STATIC_PROPERTY_ACCESS = 'staticProperty'; + public const PHPDOC_TAG_ASSERT = 'assert'; public const ATTRIBUTE = 'attribute'; - public const EXCEPTION_CATCH = 'exceptionCatch'; - public const CLASS_CONSTANT_ACCESS = 'classConstantAccess'; + public const EXCEPTION_CATCH = 'catch'; + public const CLASS_CONSTANT_ACCESS = 'classConstant'; public const CLASS_IMPLEMENTS = 'classImplements'; public const ENUM_IMPLEMENTS = 'enumImplements'; public const INTERFACE_EXTENDS = 'interfaceExtends'; public const CLASS_EXTENDS = 'classExtends'; public const INSTANCEOF = 'instanceof'; - public const PROPERTY_TYPE = 'propertyType'; - public const USE_STATEMENT = 'use'; - public const PARAMETER_TYPE = 'parameterType'; - public const RETURN_TYPE = 'returnType'; - public const PHPDOC_TAG_SELF_OUT = 'phpDocTagSelfOut'; - public const PHPDOC_TAG_VAR = 'phpDocTagVar'; + public const PROPERTY_TYPE = 'property'; + public const PARAMETER_TYPE = 'parameter'; + public const RETURN_TYPE = 'return'; + public const PHPDOC_TAG_SELF_OUT = 'selfOut'; + public const PHPDOC_TAG_VAR = 'varTag'; public const INSTANTIATION = 'new'; public const TYPE_ALIAS = 'typeAlias'; - public const PHPDOC_TAG_METHOD = 'phpDocTagMethod'; - public const PHPDOC_TAG_MIXIN = 'phpDocTagMixin'; - public const PHPDOC_TAG_PROPERTY = 'phpDocTagProperty'; - public const PHPDOC_TAG_REQUIRE_EXTENDS = 'phpDocTagRequireExtends'; - public const PHPDOC_TAG_REQUIRE_IMPLEMENTS = 'phpDocTagRequireImplements'; - public const STATIC_METHOD_CALL = 'staticMethodCall'; - public const PHPDOC_TAG_TEMPLATE_BOUND = 'phpDocTemplateBound'; - public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'phpDocTemplateDefault'; + public const PHPDOC_TAG_METHOD = 'methodTag'; + public const PHPDOC_TAG_MIXIN = 'mixin'; + public const PHPDOC_TAG_PROPERTY = 'propertyTag'; + public const PHPDOC_TAG_REQUIRE_EXTENDS = 'requireExtends'; + public const PHPDOC_TAG_REQUIRE_IMPLEMENTS = 'requireImplements'; + public const STATIC_METHOD_CALL = 'staticMethod'; + public const PHPDOC_TAG_TEMPLATE_BOUND = 'templateBound'; + public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'templateDefault'; /** @var array */ public static array $registry = []; @@ -41,7 +42,7 @@ final class ClassNameUsageLocation /** * @param self::* $value */ - private function __construct(string $value) // @phpstan-ignore constructor.unusedParameter + private function __construct(public readonly string $value) { } @@ -57,4 +58,86 @@ public static function from(string $value): self return self::$registry[$value] = new self($value); } + public function createMessage(string $part): string + { + switch ($this->value) { + case self::TRAIT_USE: + return sprintf('Usage of %s.', $part); + case self::STATIC_PROPERTY_ACCESS: + return sprintf('Access to static property on %s.', $part); + case self::PHPDOC_TAG_ASSERT: + return sprintf('Assert tag references %s.', $part); + case self::ATTRIBUTE: + return sprintf('Attribute references %s.', $part); + case self::EXCEPTION_CATCH: + return sprintf('Catching %s.', $part); + case self::CLASS_CONSTANT_ACCESS: + return sprintf('Access to constant on %s.', $part); + case self::CLASS_IMPLEMENTS: + return sprintf('Class implements %s.', $part); + case self::ENUM_IMPLEMENTS: + return sprintf('Enum implements %s.', $part); + case self::INTERFACE_EXTENDS: + return sprintf('Interface extends %s.', $part); + case self::CLASS_EXTENDS: + return sprintf('Class extends %s.', $part); + case self::INSTANCEOF: + return sprintf('Instanceof references %s.', $part); + case self::PROPERTY_TYPE: + return sprintf('Property references %s in its type.', $part); + case self::PARAMETER_TYPE: + return sprintf('Parameter references %s in its type.', $part); + case self::RETURN_TYPE: + return sprintf('Return type references %s.', $part); + case self::PHPDOC_TAG_SELF_OUT: + return sprintf('PHPDoc tag @phpstan-self-out references %s.', $part); + case self::PHPDOC_TAG_VAR: + return sprintf('PHPDoc tag @var references %s.', $part); + case self::INSTANTIATION: + return sprintf('Instantiating %s.', $part); + case self::TYPE_ALIAS: + return sprintf('Type alias references %s.', $part); + case self::PHPDOC_TAG_METHOD: + return sprintf('PHPDoc tag @method references %s.', $part); + case self::PHPDOC_TAG_MIXIN: + return sprintf('PHPDoc tag @mixin references %s.', $part); + case self::PHPDOC_TAG_PROPERTY: + return sprintf('PHPDoc tag @property references %s.', $part); + case self::PHPDOC_TAG_REQUIRE_EXTENDS: + return sprintf('PHPDoc tag @phpstan-require-extends references %s.', $part); + case self::PHPDOC_TAG_REQUIRE_IMPLEMENTS: + return sprintf('PHPDoc tag @phpstan-require-implements references %s.', $part); + case self::STATIC_METHOD_CALL: + return sprintf('Call to static method on %s.', $part); + case self::PHPDOC_TAG_TEMPLATE_BOUND: + return sprintf('PHPDoc tag @template bound references %s.', $part); + case self::PHPDOC_TAG_TEMPLATE_DEFAULT: + return sprintf('PHPDoc tag @template default references %s.', $part); + } + } + + public function createIdentifier(string $secondPart): string + { + if ($this->value === self::CLASS_IMPLEMENTS) { + return sprintf('class.implements%s', ucfirst($secondPart)); + } + if ($this->value === self::ENUM_IMPLEMENTS) { + return sprintf('enum.implements%s', ucfirst($secondPart)); + } + if ($this->value === self::INTERFACE_EXTENDS) { + return sprintf('interface.extends%s', ucfirst($secondPart)); + } + if ($this->value === self::CLASS_EXTENDS) { + return sprintf('class.extends%s', ucfirst($secondPart)); + } + if ($this->value === self::PHPDOC_TAG_TEMPLATE_BOUND) { + return sprintf('generics.%sBound', $secondPart); + } + if ($this->value === self::PHPDOC_TAG_TEMPLATE_DEFAULT) { + return sprintf('generics.%sDefault', $secondPart); + } + + return sprintf('%s.%s', $this->value, $secondPart); + } + } diff --git a/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php new file mode 100644 index 00000000000..04f8a10339c --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php @@ -0,0 +1,46 @@ +isInternal()) { + return null; + } + + if (!$this->helper->shouldBeReported($scope, $classReflection->getName())) { + return null; + } + + if ($location->value === ClassNameUsageLocation::STATIC_METHOD_CALL) { + return null; + } + + return RestrictedUsage::create( + $location->createMessage(sprintf('internal %s %s', strtolower($classReflection->getClassTypeDescription()), $classReflection->getDisplayName())), + $location->createIdentifier(sprintf('internal%s', $classReflection->getClassTypeDescription())), + ); + } + +} diff --git a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php index 6e246de285c..b167becd52e 100644 --- a/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInGroupUseRule.php @@ -8,7 +8,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; -use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -128,7 +127,7 @@ private function checkClass(Scope $scope, Node\Name $name): ?IdentifierRuleError { $errors = $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair((string) $name, $name), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::USE_STATEMENT)); + ], null); if (count($errors) === 0) { return null; } elseif (count($errors) === 1) { diff --git a/src/Rules/Namespaces/ExistingNamesInUseRule.php b/src/Rules/Namespaces/ExistingNamesInUseRule.php index 41407def845..daf1ee2ce13 100644 --- a/src/Rules/Namespaces/ExistingNamesInUseRule.php +++ b/src/Rules/Namespaces/ExistingNamesInUseRule.php @@ -7,7 +7,6 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; -use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -135,7 +134,7 @@ private function checkClasses(Scope $scope, array $uses): array return $this->classCheck->checkClassNames( $scope, array_map(static fn (Node\UseItem $use): ClassNameNodePair => new ClassNameNodePair((string) $use->name, $use->name), $uses), - ClassNameUsageLocation::from(ClassNameUsageLocation::USE_STATEMENT), + null, ); } diff --git a/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php new file mode 100644 index 00000000000..590bafd4953 --- /dev/null +++ b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php @@ -0,0 +1,61 @@ +getName() === 'createIdentifier'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $args = $methodCall->getArgs(); + if (!isset($args[0])) { + return null; + } + + $secondPartType = $scope->getType($args[0]->value); + $secondPartValues = $secondPartType->getConstantStrings(); + if (count($secondPartValues) === 0) { + return null; + } + + $reflection = new ReflectionClass(ClassNameUsageLocation::class); + $identifiers = []; + foreach ($reflection->getConstants() as $constant) { + $location = ClassNameUsageLocation::from($constant); + foreach ($secondPartValues as $secondPart) { + $identifiers[] = $location->createIdentifier($secondPart->getValue()); + } + } + + sort($identifiers); + + $types = []; + foreach ($identifiers as $identifier) { + $types[] = $scope->getTypeFromValue($identifier); + } + + return TypeCombinator::union(...$types); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php b/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php new file mode 100644 index 00000000000..bdb30155c85 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php @@ -0,0 +1,10 @@ +createIdentifier('test')); +}; From 975afcd73bcde642c576c44bdc21c7e5eea7d14d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 23 Apr 2025 13:16:01 +0200 Subject: [PATCH 1296/3097] ClassNameUsageLocation is part of BC promise --- src/Rules/ClassNameUsageLocation.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index d3bf060ce9e..b2b2388791e 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -6,6 +6,9 @@ use function sprintf; use function ucfirst; +/** + * @api + */ final class ClassNameUsageLocation { From a3c6cad338d4a688624e54e9d341e2a9fdfb4713 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 14:07:05 +0200 Subject: [PATCH 1297/3097] RestrictedInternalClassNameUsageExtension - report static method call on internal subclass --- src/Rules/ClassNameUsageLocation.php | 25 +++++++------ ...trictedInternalClassNameUsageExtension.php | 7 +++- src/Rules/Methods/StaticMethodCallCheck.php | 8 ++++- ...InternalStaticMethodUsageExtensionTest.php | 10 ++++++ ...tatic-method-call-on-internal-subclass.php | 36 +++++++++++++++++++ .../Methods/CallStaticMethodsRuleTest.php | 14 ++++++++ 6 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/InternalTag/data/static-method-call-on-internal-subclass.php diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index b2b2388791e..dd593c6d2d9 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -2,7 +2,7 @@ namespace PHPStan\Rules; -use function array_key_exists; +use PHPStan\Reflection\ExtendedMethodReflection; use function sprintf; use function ucfirst; @@ -39,26 +39,26 @@ final class ClassNameUsageLocation public const PHPDOC_TAG_TEMPLATE_BOUND = 'templateBound'; public const PHPDOC_TAG_TEMPLATE_DEFAULT = 'templateDefault'; - /** @var array */ - public static array $registry = []; - /** * @param self::* $value + * @param mixed[] $data */ - private function __construct(public readonly string $value) + private function __construct(public readonly string $value, public readonly array $data) { } /** * @param self::* $value + * @param mixed[] $data */ - public static function from(string $value): self + public static function from(string $value, array $data = []): self { - if (array_key_exists($value, self::$registry)) { - return self::$registry[$value]; - } + return new self($value, $data); + } - return self::$registry[$value] = new self($value); + public function getMethod(): ?ExtendedMethodReflection + { + return $this->data['method'] ?? null; } public function createMessage(string $part): string @@ -111,6 +111,11 @@ public function createMessage(string $part): string case self::PHPDOC_TAG_REQUIRE_IMPLEMENTS: return sprintf('PHPDoc tag @phpstan-require-implements references %s.', $part); case self::STATIC_METHOD_CALL: + $method = $this->getMethod(); + if ($method !== null) { + return sprintf('Call to static method %s() on %s.', $method->getName(), $part); + } + return sprintf('Call to static method on %s.', $part); case self::PHPDOC_TAG_TEMPLATE_BOUND: return sprintf('PHPDoc tag @template bound references %s.', $part); diff --git a/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php index 04f8a10339c..e72cf807566 100644 --- a/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php @@ -34,7 +34,12 @@ public function isRestrictedClassNameUsage( } if ($location->value === ClassNameUsageLocation::STATIC_METHOD_CALL) { - return null; + $method = $location->getMethod(); + if ($method !== null) { + if ($method->isInternal()->yes() || $method->getDeclaringClass()->isInternal()) { + return null; + } + } } return RestrictedUsage::create( diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index df72cfb6bf7..373e41ddd99 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -140,10 +140,16 @@ public function check( ]; } + $locationData = []; + $locationClassReflection = $this->reflectionProvider->getClass($className); + if ($locationClassReflection->hasMethod($methodName)) { + $locationData['method'] = $locationClassReflection->getMethod($methodName, $scope); + } + $errors = $this->classCheck->checkClassNames( $scope, [new ClassNameNodePair($className, $class)], - ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_METHOD_CALL), + ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_METHOD_CALL, $locationData), ); $classType = $scope->resolveTypeByName($class); diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php index f03478cfb53..0f73bf967a2 100644 --- a/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticMethodUsageExtensionTest.php @@ -56,4 +56,14 @@ public function testRule(): void ]); } + public function testStaticMethodCallOnInternalSubclass(): void + { + $this->analyse([__DIR__ . '/data/static-method-call-on-internal-subclass.php'], [ + [ + 'Call to static method doBar() of internal class StaticMethodCallOnInternalSubclassOne\Bar from outside its root namespace StaticMethodCallOnInternalSubclassOne.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/InternalTag/data/static-method-call-on-internal-subclass.php b/tests/PHPStan/Rules/InternalTag/data/static-method-call-on-internal-subclass.php new file mode 100644 index 00000000000..fce54639f75 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/static-method-call-on-internal-subclass.php @@ -0,0 +1,36 @@ +checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/../InternalTag/data/static-method-call-on-internal-subclass.php'], [ + [ + 'Call to static method doFoo() on internal class StaticMethodCallOnInternalSubclassOne\Bar.', + 33, + ], + ]); + } + } From ff198c9d121d658c150ce74f11bde000d665117d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 15:00:09 +0200 Subject: [PATCH 1298/3097] `ExtendedPropertyReflection::getName()` --- .../Annotations/AnnotationPropertyReflection.php | 6 ++++++ .../AnnotationsPropertiesClassReflectionExtension.php | 1 + src/Reflection/ClassReflection.php | 6 +++--- src/Reflection/Dummy/ChangedTypePropertyReflection.php | 5 +++++ src/Reflection/Dummy/DummyPropertyReflection.php | 9 +++++++++ src/Reflection/ExtendedPropertyReflection.php | 2 ++ src/Reflection/Php/EnumPropertyReflection.php | 7 ++++++- src/Reflection/Php/PhpPropertyReflection.php | 5 +++++ src/Reflection/Php/SimpleXMLElementProperty.php | 6 ++++++ src/Reflection/Php/UniversalObjectCrateProperty.php | 6 ++++++ .../UniversalObjectCratesClassReflectionExtension.php | 2 +- src/Reflection/ResolvedPropertyReflection.php | 5 +++++ .../Type/IntersectionTypePropertyReflection.php | 5 +++++ src/Reflection/Type/UnionTypePropertyReflection.php | 5 +++++ src/Reflection/WrappedExtendedPropertyReflection.php | 7 ++++++- src/Type/Enum/EnumCaseObjectType.php | 4 ++-- src/Type/MixedType.php | 2 +- src/Type/ObjectShapePropertyReflection.php | 7 ++++++- src/Type/ObjectShapeType.php | 2 +- .../SimpleXMLElementClassPropertyReflectionExtension.php | 2 +- src/Type/Traits/MaybeObjectTypeTrait.php | 2 +- src/Type/Traits/ObjectTypeTrait.php | 2 +- 22 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index ea0d7e24189..8f4f322d081 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -14,6 +14,7 @@ final class AnnotationPropertyReflection implements ExtendedPropertyReflection { public function __construct( + private string $name, private ClassReflection $declaringClass, private Type $readableType, private Type $writableType, @@ -23,6 +24,11 @@ public function __construct( { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php index 5d25367e705..5c19c29ae73 100644 --- a/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php +++ b/src/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtension.php @@ -52,6 +52,7 @@ private function findClassReflectionWithProperty( } return new AnnotationPropertyReflection( + $propertyName, $declaringClass, TemplateTypeHelper::resolveTemplateTypes( $propertyTag->getReadableType() ?? new NeverType(), diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index f6e3da43e9e..a4adc7b9b1b 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -551,13 +551,13 @@ private function wrapExtendedMethod(MethodReflection $method): ExtendedMethodRef return new WrappedExtendedMethodReflection($method); } - private function wrapExtendedProperty(PropertyReflection $method): ExtendedPropertyReflection + private function wrapExtendedProperty(string $propertyName, PropertyReflection $method): ExtendedPropertyReflection { if ($method instanceof ExtendedPropertyReflection) { return $method; } - return new WrappedExtendedPropertyReflection($method); + return new WrappedExtendedPropertyReflection($propertyName, $method); } public function hasNativeMethod(string $methodName): bool @@ -663,7 +663,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco continue; } - $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName)); + $property = $this->wrapExtendedProperty($propertyName, $extension->getProperty($this, $propertyName)); if ($scope->canReadProperty($property)) { return $this->properties[$key] = $property; } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 0421bb3ff9d..3bf6a6eb843 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -16,6 +16,11 @@ public function __construct(private ClassReflection $declaringClass, private Ext { } + public function getName(): string + { + return $this->reflection->getName(); + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 133629a45c5..ca828a53fe2 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -15,6 +15,15 @@ final class DummyPropertyReflection implements ExtendedPropertyReflection { + public function __construct(private string $name) + { + } + + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 25f79396a16..1027c193f19 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -26,6 +26,8 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; + public function getName(): string; + public function hasPhpDocType(): bool; public function getPhpDocType(): Type; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index ca92d9258c6..912b779e679 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -13,10 +13,15 @@ final class EnumPropertyReflection implements ExtendedPropertyReflection { - public function __construct(private ClassReflection $declaringClass, private Type $type) + public function __construct(private string $name, private ClassReflection $declaringClass, private Type $type) { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 05228b7016b..8307f36d7b0 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -48,6 +48,11 @@ public function __construct( { } + public function getName(): string + { + return $this->reflection->getName(); + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index 45438eb3c2e..29975a5e3f8 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -19,12 +19,18 @@ final class SimpleXMLElementProperty implements ExtendedPropertyReflection { public function __construct( + private string $name, private ClassReflection $declaringClass, private Type $type, ) { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 0b478d51c5e..564613e2194 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -14,6 +14,7 @@ final class UniversalObjectCrateProperty implements ExtendedPropertyReflection { public function __construct( + private string $name, private ClassReflection $declaringClass, private Type $readableType, private Type $writableType, @@ -21,6 +22,11 @@ public function __construct( { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { return $this->declaringClass; diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index c26ef1dcfed..0cd79eb2328 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -85,7 +85,7 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa $writableType = new MixedType(); } - return new UniversalObjectCrateProperty($classReflection, $readableType, $writableType); + return new UniversalObjectCrateProperty($propertyName, $classReflection, $readableType, $writableType); } } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 797b1ede604..df7a33f84da 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -25,6 +25,11 @@ public function __construct( { } + public function getName(): string + { + return $this->reflection->getName(); + } + public function getOriginalReflection(): ExtendedPropertyReflection { return $this->reflection; diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index d0729a22604..71de28e1e60 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -23,6 +23,11 @@ public function __construct(private array $properties) { } + public function getName(): string + { + return $this->properties[0]->getName(); + } + public function getDeclaringClass(): ClassReflection { return $this->properties[0]->getDeclaringClass(); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index eb2d00aed55..77e1ed0397b 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -23,6 +23,11 @@ public function __construct(private array $properties) { } + public function getName(): string + { + return $this->properties[0]->getName(); + } + public function getDeclaringClass(): ClassReflection { return $this->properties[0]->getDeclaringClass(); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 9f4fb2d9045..04eceb48486 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -10,10 +10,15 @@ final class WrappedExtendedPropertyReflection implements ExtendedPropertyReflection { - public function __construct(private PropertyReflection $property) + public function __construct(private string $name, private PropertyReflection $property) { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { return $this->property->getDeclaringClass(); diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index e3ae5e23f5d..1093e24cb4c 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -135,7 +135,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember } if ($propertyName === 'name') { return new EnumUnresolvedPropertyPrototypeReflection( - new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName)), + new EnumPropertyReflection($propertyName, $classReflection, new ConstantStringType($this->enumCaseName)), ); } @@ -148,7 +148,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember } return new EnumUnresolvedPropertyPrototypeReflection( - new EnumPropertyReflection($classReflection, $valueType), + new EnumPropertyReflection($propertyName, $classReflection, $valueType), ); } } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 487a474827a..10c2b7cccd5 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -396,7 +396,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { - $property = new DummyPropertyReflection(); + $property = new DummyPropertyReflection($propertyName); return new CallbackUnresolvedPropertyPrototypeReflection( $property, $property->getDeclaringClass(), diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 971a96b2f6c..594c3278ca9 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -13,10 +13,15 @@ final class ObjectShapePropertyReflection implements ExtendedPropertyReflection { - public function __construct(private Type $type) + public function __construct(private string $name, private Type $type) { } + public function getName(): string + { + return $this->name; + } + public function getDeclaringClass(): ClassReflection { $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 1a1beed6c03..1e35513f0b5 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -113,7 +113,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ShouldNotHappenException(); } - $property = new ObjectShapePropertyReflection($this->properties[$propertyName]); + $property = new ObjectShapePropertyReflection($propertyName, $this->properties[$propertyName]); return new CallbackUnresolvedPropertyPrototypeReflection( $property, $property->getDeclaringClass(), diff --git a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php index 35ba562a16b..c6709148353 100644 --- a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php +++ b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php @@ -20,7 +20,7 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection { - return new SimpleXMLElementProperty($classReflection, new BenevolentUnionType([new ObjectType($classReflection->getName()), new NullType()])); + return new SimpleXMLElementProperty($propertyName, $classReflection, new BenevolentUnionType([new ObjectType($classReflection->getName()), new NullType()])); } } diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php index ff50c721d31..4625da358b1 100644 --- a/src/Type/Traits/MaybeObjectTypeTrait.php +++ b/src/Type/Traits/MaybeObjectTypeTrait.php @@ -52,7 +52,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { - $property = new DummyPropertyReflection(); + $property = new DummyPropertyReflection($propertyName); return new CallbackUnresolvedPropertyPrototypeReflection( $property, $property->getDeclaringClass(), diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 45fc20121f5..fe2a3f6ee65 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -63,7 +63,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { - $property = new DummyPropertyReflection(); + $property = new DummyPropertyReflection($propertyName); return new CallbackUnresolvedPropertyPrototypeReflection( $property, $property->getDeclaringClass(), From 68de666b190188a71da970dddba7c2d10464fdd9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 14:54:01 +0200 Subject: [PATCH 1299/3097] More data in ClassNameUsageLocation --- src/Rules/ClassNameUsageLocation.php | 109 ++++++++++++++++++ src/Rules/Classes/ClassConstantRule.php | 8 +- .../ExistingClassInClassExtendsRule.php | 13 ++- .../ExistingClassesInClassImplementsRule.php | 14 ++- .../ExistingClassesInEnumImplementsRule.php | 7 +- .../ExistingClassesInInterfaceExtendsRule.php | 6 +- src/Rules/Classes/LocalTypeAliasesCheck.php | 4 +- src/Rules/Classes/MethodTagCheck.php | 4 +- src/Rules/Classes/PropertyTagCheck.php | 4 +- src/Rules/FunctionDefinitionCheck.php | 8 +- src/Rules/Generics/TemplateTypeCheck.php | 8 +- ...trictedInternalClassNameUsageExtension.php | 18 +++ src/Rules/PhpDoc/AssertRuleHelper.php | 5 +- .../Properties/AccessStaticPropertiesRule.php | 8 +- .../ExistingClassesInPropertiesRule.php | 4 +- 15 files changed, 193 insertions(+), 27 deletions(-) diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index dd593c6d2d9..e9a14b1861c 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -2,7 +2,9 @@ namespace PHPStan\Rules; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use function sprintf; use function ucfirst; @@ -61,34 +63,123 @@ public function getMethod(): ?ExtendedMethodReflection return $this->data['method'] ?? null; } + public function getProperty(): ?ExtendedPropertyReflection + { + return $this->data['property'] ?? null; + } + + public function getPhpDocTagName(): ?string + { + return $this->data['phpDocTagName'] ?? null; + } + + public function getAssertedExprString(): ?string + { + return $this->data['assertedExprString'] ?? null; + } + + public function getClassConstant(): ?ClassConstantReflection + { + return $this->data['classConstant'] ?? null; + } + + public function getCurrentClassName(): ?string + { + return $this->data['currentClassName'] ?? null; + } + + public function getParameterName(): ?string + { + return $this->data['parameterName'] ?? null; + } + + public function getTypeAliasName(): ?string + { + return $this->data['typeAliasName'] ?? null; + } + + public function getMethodTagName(): ?string + { + return $this->data['methodTagName'] ?? null; + } + + public function getPropertyTagName(): ?string + { + return $this->data['propertyTagName'] ?? null; + } + + public function getTemplateTagName(): ?string + { + return $this->data['templateTagName'] ?? null; + } + public function createMessage(string $part): string { switch ($this->value) { case self::TRAIT_USE: return sprintf('Usage of %s.', $part); case self::STATIC_PROPERTY_ACCESS: + $property = $this->getProperty(); + if ($property !== null) { + return sprintf('Access to static property $%s on %s.', $property->getName(), $part); + } + return sprintf('Access to static property on %s.', $part); case self::PHPDOC_TAG_ASSERT: + $phpDocTagName = $this->getPhpDocTagName(); + $assertExprString = $this->getAssertedExprString(); + if ($phpDocTagName !== null && $assertExprString !== null) { + return sprintf('PHPDoc tag %s for %s references %s.', $phpDocTagName, $assertExprString, $part); + } + return sprintf('Assert tag references %s.', $part); case self::ATTRIBUTE: return sprintf('Attribute references %s.', $part); case self::EXCEPTION_CATCH: return sprintf('Catching %s.', $part); case self::CLASS_CONSTANT_ACCESS: + if ($this->getClassConstant() !== null) { + return sprintf('Access to constant %s on %s.', $this->getClassConstant()->getName(), $part); + } return sprintf('Access to constant on %s.', $part); case self::CLASS_IMPLEMENTS: + if ($this->getCurrentClassName() !== null) { + return sprintf('Class %s implements %s.', $this->getCurrentClassName(), $part); + } + return sprintf('Class implements %s.', $part); case self::ENUM_IMPLEMENTS: + if ($this->getCurrentClassName() !== null) { + return sprintf('Enum %s implements %s.', $this->getCurrentClassName(), $part); + } + return sprintf('Enum implements %s.', $part); case self::INTERFACE_EXTENDS: + if ($this->getCurrentClassName() !== null) { + return sprintf('Interface %s extends %s.', $this->getCurrentClassName(), $part); + } + return sprintf('Interface extends %s.', $part); case self::CLASS_EXTENDS: + if ($this->getCurrentClassName() !== null) { + return sprintf('Class %s extends %s.', $this->getCurrentClassName(), $part); + } + return sprintf('Class extends %s.', $part); case self::INSTANCEOF: return sprintf('Instanceof references %s.', $part); case self::PROPERTY_TYPE: + $property = $this->getProperty(); + if ($property !== null) { + return sprintf('Property $%s references %s in its type.', $property->getName(), $part); + } return sprintf('Property references %s in its type.', $part); case self::PARAMETER_TYPE: + $parameterName = $this->getParameterName(); + if ($parameterName !== null) { + return sprintf('Parameter $%s references %s in its type.', $parameterName, $part); + } + return sprintf('Parameter references %s in its type.', $part); case self::RETURN_TYPE: return sprintf('Return type references %s.', $part); @@ -99,12 +190,22 @@ public function createMessage(string $part): string case self::INSTANTIATION: return sprintf('Instantiating %s.', $part); case self::TYPE_ALIAS: + if ($this->getTypeAliasName() !== null) { + return sprintf('Type alias %s references %s.', $this->getTypeAliasName(), $part); + } + return sprintf('Type alias references %s.', $part); case self::PHPDOC_TAG_METHOD: + if ($this->getMethodTagName() !== null) { + return sprintf('PHPDoc tag @method for %s() references %s.', $this->getMethodTagName(), $part); + } return sprintf('PHPDoc tag @method references %s.', $part); case self::PHPDOC_TAG_MIXIN: return sprintf('PHPDoc tag @mixin references %s.', $part); case self::PHPDOC_TAG_PROPERTY: + if ($this->getPropertyTagName() !== null) { + return sprintf('PHPDoc tag @property for $%s references %s.', $this->getPropertyTagName(), $part); + } return sprintf('PHPDoc tag @property references %s.', $part); case self::PHPDOC_TAG_REQUIRE_EXTENDS: return sprintf('PHPDoc tag @phpstan-require-extends references %s.', $part); @@ -118,8 +219,16 @@ public function createMessage(string $part): string return sprintf('Call to static method on %s.', $part); case self::PHPDOC_TAG_TEMPLATE_BOUND: + if ($this->getTemplateTagName() !== null) { + return sprintf('PHPDoc tag @template %s bound references %s.', $this->getTemplateTagName(), $part); + } + return sprintf('PHPDoc tag @template bound references %s.', $part); case self::PHPDOC_TAG_TEMPLATE_DEFAULT: + if ($this->getTemplateTagName() !== null) { + return sprintf('PHPDoc tag @template %s default references %s.', $this->getTemplateTagName(), $part); + } + return sprintf('PHPDoc tag @template default references %s.', $part); } } diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 5f2b8640368..2d086029858 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -151,10 +151,16 @@ private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $nod } } + $locationData = []; + $locationClassReflection = $this->reflectionProvider->getClass($className); + if ($locationClassReflection->hasConstant($constantName)) { + $locationData['classConstant'] = $locationClassReflection->getConstant($constantName); + } + $messages = $this->classCheck->checkClassNames( $scope, [new ClassNameNodePair($className, $class)], - ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_CONSTANT_ACCESS), + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_CONSTANT_ACCESS, $locationData), ); } diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index 60a542ff17c..9bd5e0ef5ec 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -37,15 +37,18 @@ public function processNode(Node $node, Scope $scope): array return []; } $extendedClassName = (string) $node->extends; - $messages = $this->classCheck->checkClassNames( - $scope, - [new ClassNameNodePair($extendedClassName, $node->extends)], - ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_EXTENDS), - ); $currentClassName = null; if (isset($node->namespacedName)) { $currentClassName = (string) $node->namespacedName; } + $messages = $this->classCheck->checkClassNames( + $scope, + [new ClassNameNodePair($extendedClassName, $node->extends)], + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_EXTENDS, [ + 'currentClassName' => $currentClassName, + ]), + ); + if (!$this->reflectionProvider->hasClass($extendedClassName)) { if (!$scope->isInClassExists($extendedClassName)) { $errorBuilder = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php index 6101523b07f..b1e639af29a 100644 --- a/src/Rules/Classes/ExistingClassesInClassImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInClassImplementsRule.php @@ -34,17 +34,19 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $messages = $this->classCheck->checkClassNames( - $scope, - array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->implements), - ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_IMPLEMENTS), - ); - $currentClassName = null; if (isset($node->namespacedName)) { $currentClassName = (string) $node->namespacedName; } + $messages = $this->classCheck->checkClassNames( + $scope, + array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->implements), + ClassNameUsageLocation::from(ClassNameUsageLocation::CLASS_IMPLEMENTS, [ + 'currentClassName' => $currentClassName, + ]), + ); + foreach ($node->implements as $implements) { $implementedClassName = (string) $implements; if (!$this->reflectionProvider->hasClass($implementedClassName)) { diff --git a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php index 35d3f088fe2..1a7f8b28835 100644 --- a/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php +++ b/src/Rules/Classes/ExistingClassesInEnumImplementsRule.php @@ -34,14 +34,15 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + $currentEnumName = (string) $node->namespacedName; $messages = $this->classCheck->checkClassNames( $scope, array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->implements), - ClassNameUsageLocation::from(ClassNameUsageLocation::ENUM_IMPLEMENTS), + ClassNameUsageLocation::from(ClassNameUsageLocation::ENUM_IMPLEMENTS, [ + 'currentClassName' => $currentEnumName, + ]), ); - $currentEnumName = (string) $node->namespacedName; - foreach ($node->implements as $implements) { $implementedClassName = (string) $implements; if (!$this->reflectionProvider->hasClass($implementedClassName)) { diff --git a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php index 97f541b5a9c..8d0d0714716 100644 --- a/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php +++ b/src/Rules/Classes/ExistingClassesInInterfaceExtendsRule.php @@ -34,13 +34,15 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + $currentInterfaceName = (string) $node->namespacedName; $messages = $this->classCheck->checkClassNames( $scope, array_map(static fn (Node\Name $interfaceName): ClassNameNodePair => new ClassNameNodePair((string) $interfaceName, $interfaceName), $node->extends), - ClassNameUsageLocation::from(ClassNameUsageLocation::INTERFACE_EXTENDS), + ClassNameUsageLocation::from(ClassNameUsageLocation::INTERFACE_EXTENDS, [ + 'currentClassName' => $currentInterfaceName, + ]), ); - $currentInterfaceName = (string) $node->namespacedName; foreach ($node->extends as $extends) { $extendedInterfaceName = (string) $extends; if (!$this->reflectionProvider->hasClass($extendedInterfaceName)) { diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index 13f384974ad..c62d8abed5c 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -275,7 +275,9 @@ public function checkInTraitUseContext( $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::TYPE_ALIAS), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::TYPE_ALIAS, [ + 'typeAliasName' => $aliasName, + ]), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/MethodTagCheck.php b/src/Rules/Classes/MethodTagCheck.php index d0fcaace7ad..a5bc6a5b357 100644 --- a/src/Rules/Classes/MethodTagCheck.php +++ b/src/Rules/Classes/MethodTagCheck.php @@ -238,7 +238,9 @@ private function checkMethodTypeInTraitUseContext(Scope $scope, ClassReflection $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_METHOD), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_METHOD, [ + 'methodTagName' => $methodName, + ]), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/Classes/PropertyTagCheck.php b/src/Rules/Classes/PropertyTagCheck.php index e499f38ed8f..52ad3e72e25 100644 --- a/src/Rules/Classes/PropertyTagCheck.php +++ b/src/Rules/Classes/PropertyTagCheck.php @@ -219,7 +219,9 @@ private function checkPropertyTypeInTraitUseContext(Scope $scope, ClassReflectio $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_PROPERTY), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_PROPERTY, [ + 'propertyTagName' => $propertyName, + ]), $this->checkClassCaseSensitivity), ); } } diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index e4cb31e36d2..dfb47260190 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -177,7 +177,9 @@ public function checkAnonymousFunction( $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $param->type), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE, [ + 'parameterName' => $param->var->name, + ]), $this->checkClassCaseSensitivity), ); } } @@ -429,7 +431,9 @@ private function checkParametersAcceptor( $this->classCheck->checkClassNames( $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $parameterNodeCallback()), $referencedClasses), - ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE), + ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE, [ + 'parameterName' => $parameter->getName(), + ]), $this->checkClassCaseSensitivity, ), ); diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 2dbd279da26..27be48dd505 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -106,7 +106,9 @@ public function check( } $classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $boundType->getReferencedClasses()); - $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_BOUND), $this->checkClassCaseSensitivity)); + $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_BOUND, [ + 'templateTagName' => $templateTagName, + ]), $this->checkClassCaseSensitivity)); $boundTypeClass = get_class($boundType); if ( @@ -183,7 +185,9 @@ public function check( } $classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $defaultType->getReferencedClasses()); - $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_DEFAULT), $this->checkClassCaseSensitivity)); + $messages = array_merge($messages, $this->classCheck->checkClassNames($scope, $classNameNodePairs, ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_TEMPLATE_DEFAULT, [ + 'templateTagName' => $templateTagName, + ]), $this->checkClassCaseSensitivity)); $genericDefaultErrors = $this->genericObjectTypeCheck->check( $defaultType, diff --git a/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php index e72cf807566..d8b3a32e9ab 100644 --- a/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalClassNameUsageExtension.php @@ -42,6 +42,24 @@ public function isRestrictedClassNameUsage( } } + if ($location->value === ClassNameUsageLocation::STATIC_PROPERTY_ACCESS) { + $property = $location->getProperty(); + if ($property !== null) { + if ($property->isInternal()->yes() || $property->getDeclaringClass()->isInternal()) { + return null; + } + } + } + + if ($location->value === ClassNameUsageLocation::CLASS_CONSTANT_ACCESS) { + $constant = $location->getClassConstant(); + if ($constant !== null) { + if ($constant->isInternal()->yes() || $constant->getDeclaringClass()->isInternal()) { + return null; + } + } + } + return RestrictedUsage::create( $location->createMessage(sprintf('internal %s %s', strtolower($classReflection->getClassTypeDescription()), $classReflection->getDisplayName())), $location->createIdentifier(sprintf('internal%s', $classReflection->getClassTypeDescription())), diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 40824275575..473036edb10 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -154,7 +154,10 @@ public function check( $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_ASSERT), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_ASSERT, [ + 'phpDocTagName' => $tagName, + 'assertedExprString' => $assertedExprString, + ]), $this->checkClassCaseSensitivity), ); } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 7bd8156cad8..b15808ad5aa 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -132,10 +132,16 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]; } + $locationData = []; + $locationClassReflection = $this->reflectionProvider->getClass($class); + if ($locationClassReflection->hasProperty($name)) { + $locationData['property'] = $locationClassReflection->getProperty($name, $scope); + } + $messages = $this->classCheck->checkClassNames( $scope, [new ClassNameNodePair($class, $node->class)], - ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_PROPERTY_ACCESS), + ClassNameUsageLocation::from(ClassNameUsageLocation::STATIC_PROPERTY_ACCESS, $locationData), ); $classType = $scope->resolveTypeByName($node->class); diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index 1fee991c46f..869074e3586 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -86,7 +86,9 @@ public function processNode(Node $node, Scope $scope): array $this->classCheck->checkClassNames( $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $node), $referencedClasses), - ClassNameUsageLocation::from(ClassNameUsageLocation::PROPERTY_TYPE), + ClassNameUsageLocation::from(ClassNameUsageLocation::PROPERTY_TYPE, [ + 'property' => $propertyReflection, + ]), $this->checkClassCaseSensitivity, ), ); From 8ff89edc5f0075c8344cee76407c2ba6d8cf82cb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 16:53:54 +0200 Subject: [PATCH 1300/3097] Adjust message --- src/Rules/ClassNameUsageLocation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index e9a14b1861c..9981155bb24 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -188,7 +188,7 @@ public function createMessage(string $part): string case self::PHPDOC_TAG_VAR: return sprintf('PHPDoc tag @var references %s.', $part); case self::INSTANTIATION: - return sprintf('Instantiating %s.', $part); + return sprintf('Instantiation of %s.', $part); case self::TYPE_ALIAS: if ($this->getTypeAliasName() !== null) { return sprintf('Type alias %s references %s.', $this->getTypeAliasName(), $part); From 3c3757d4c867cb50db971582facd3e5bc522e9d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 17:03:14 +0200 Subject: [PATCH 1301/3097] Another message adjustment --- src/Rules/ClassNameUsageLocation.php | 3 +++ src/Rules/Classes/ExistingClassInTraitUseRule.php | 15 +++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index 9981155bb24..1c98edbf674 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -117,6 +117,9 @@ public function createMessage(string $part): string { switch ($this->value) { case self::TRAIT_USE: + if ($this->getCurrentClassName() !== null) { + return sprintf('Usage of %s in class %s.', $part, $this->getCurrentClassName()); + } return sprintf('Usage of %s.', $part); case self::STATIC_PROPERTY_ACCESS: $property = $this->getProperty(); diff --git a/src/Rules/Classes/ExistingClassInTraitUseRule.php b/src/Rules/Classes/ExistingClassInTraitUseRule.php index 328e2387620..08524a3fd41 100644 --- a/src/Rules/Classes/ExistingClassInTraitUseRule.php +++ b/src/Rules/Classes/ExistingClassInTraitUseRule.php @@ -35,17 +35,20 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $messages = $this->classCheck->checkClassNames( - $scope, - array_map(static fn (Node\Name $traitName): ClassNameNodePair => new ClassNameNodePair((string) $traitName, $traitName), $node->traits), - ClassNameUsageLocation::from(ClassNameUsageLocation::TRAIT_USE), - ); - if (!$scope->isInClass()) { throw new ShouldNotHappenException(); } $classReflection = $scope->getClassReflection(); + + $messages = $this->classCheck->checkClassNames( + $scope, + array_map(static fn (Node\Name $traitName): ClassNameNodePair => new ClassNameNodePair((string) $traitName, $traitName), $node->traits), + ClassNameUsageLocation::from(ClassNameUsageLocation::TRAIT_USE, [ + 'currentClassName' => $classReflection->isAnonymous() ? null : $classReflection->getName(), + ]), + ); + if ($classReflection->isInterface()) { if (!$scope->isInTrait()) { foreach ($node->traits as $trait) { From 452911305979a7949ea1f58404f3326d75d8573b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 25 Apr 2025 22:19:23 +0200 Subject: [PATCH 1302/3097] ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension - limit possible identifiers based on `$location->value` type --- ...ageLocationCreateIdentifierDynamicReturnTypeExtension.php | 5 +++++ tests/PHPStan/Analyser/nsrt/class-name-usage-location.php | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php index 590bafd4953..0b847b8ca62 100644 --- a/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php +++ b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\PHPStan; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\PropertyFetch; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Rules\ClassNameUsageLocation; @@ -41,7 +42,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $reflection = new ReflectionClass(ClassNameUsageLocation::class); $identifiers = []; + $locationValueType = $scope->getType(new PropertyFetch($methodCall->var, 'value')); foreach ($reflection->getConstants() as $constant) { + if (!$locationValueType->isSuperTypeOf($scope->getTypeFromValue($constant))->yes()) { + continue; + } $location = ClassNameUsageLocation::from($constant); foreach ($secondPartValues as $secondPart) { $identifiers[] = $location->createIdentifier($secondPart->getValue()); diff --git a/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php b/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php index bdb30155c85..b892739f19d 100644 --- a/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php +++ b/tests/PHPStan/Analyser/nsrt/class-name-usage-location.php @@ -7,4 +7,8 @@ function (ClassNameUsageLocation $location): void { assertType("'assert.test'|'attribute.test'|'catch.test'|'class.extendsTest'|'class.implementsTest'|'classConstant.test'|'enum.implementsTest'|'generics.testBound'|'generics.testDefault'|'instanceof.test'|'interface.extendsTest'|'methodTag.test'|'mixin.test'|'new.test'|'parameter.test'|'property.test'|'propertyTag.test'|'requireExtends.test'|'requireImplements.test'|'return.test'|'selfOut.test'|'staticMethod.test'|'staticProperty.test'|'traitUse.test'|'typeAlias.test'|'varTag.test'", $location->createIdentifier('test')); + + if ($location->value === ClassNameUsageLocation::INSTANTIATION || $location->value === ClassNameUsageLocation::PROPERTY_TYPE) { + assertType("'new.test'|'property.test'", $location->createIdentifier('test')); + } }; From 91a494cd6d9651079812b9c4a14a6933a5398037 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 25 Apr 2025 22:33:55 +0200 Subject: [PATCH 1303/3097] More precise string functions return type with replacement array --- ...aceFunctionsDynamicReturnTypeExtension.php | 3 + .../Rules/Methods/ReturnTypeRuleTest.php | 10 +++ .../PHPStan/Rules/Methods/data/bug-12928.php | 68 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12928.php diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index db046bbe100..01bb1dd29e4 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -89,6 +89,9 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( if (count($functionCall->getArgs()) > $replaceArgumentPosition) { $replaceArgumentType = $scope->getType($functionCall->getArgs()[$replaceArgumentPosition]->value); + if ($replaceArgumentType->isArray()->yes()) { + $replaceArgumentType = $replaceArgumentType->getIterableValueType(); + } $accessories = []; if ($subjectArgumentType->isNonFalsyString()->yes() && $replaceArgumentType->isNonFalsyString()->yes()) { diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index bc3a1b1fae7..548589722ef 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1246,4 +1246,14 @@ public function testBug4443(): void ]); } + public function testBug12928(): void + { + $this->analyse([__DIR__ . '/data/bug-12928.php'], [ + [ + 'Method Bug12928\FooBarBaz::render() should return non-empty-string but returns string.', + 59, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12928.php b/tests/PHPStan/Rules/Methods/data/bug-12928.php new file mode 100644 index 00000000000..2b6e038fe9d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12928.php @@ -0,0 +1,68 @@ + $replace + * + * @return non-falsy-string + */ + public function render(string $code, array $replace): string + { + return str_replace( + [ + '__DIR__', + '__FILE__', + ], + $replace, + $code, + ); + } +} + + +class FooBarBaz { + /** + * @param non-empty-string $phptFile + * @param non-empty-string $code + * + * @return non-empty-string + */ + public function render(string $code, array $replace): string + { + return str_replace( + [ + '__DIR__', + '__FILE__', + ], + $replace, + $code, + ); + } +} From b7bef9708de5161209eb0f45845559ff1e1aa41e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 09:26:50 +0200 Subject: [PATCH 1304/3097] ClassNameUsageLocation - improve messages for PARAMETER_TYPE and RETURN_TYPE --- phpstan-baseline.neon | 2 +- src/Rules/ClassNameUsageLocation.php | 47 +++++++++++++++++++-- src/Rules/FunctionDefinitionCheck.php | 61 ++++++++++++++++----------- 3 files changed, 81 insertions(+), 29 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1bb91878613..3e8f1278445 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -757,7 +757,7 @@ parameters: path: src/Testing/LevelsTestCase.php - - message: '#^Return type references internal class PHPUnit\\Framework\\AssertionFailedError\.$#' + message: '#^Return type of method PHPStan\\Testing\\LevelsTestCase\:\:compareFiles\(\) has typehint with internal class PHPUnit\\Framework\\AssertionFailedError\.$#' identifier: return.internalClass count: 1 path: src/Testing/LevelsTestCase.php diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index 1c98edbf674..8fe44f58db7 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\Reflection\FunctionReflection; use function sprintf; use function ucfirst; @@ -68,6 +69,11 @@ public function getProperty(): ?ExtendedPropertyReflection return $this->data['property'] ?? null; } + public function getFunction(): ?FunctionReflection + { + return $this->data['function'] ?? null; + } + public function getPhpDocTagName(): ?string { return $this->data['phpDocTagName'] ?? null; @@ -113,6 +119,11 @@ public function getTemplateTagName(): ?string return $this->data['templateTagName'] ?? null; } + public function isInAnomyousFunction(): bool + { + return $this->data['isInAnonymousFunction'] ?? false; + } + public function createMessage(string $part): string { switch ($this->value) { @@ -180,12 +191,42 @@ public function createMessage(string $part): string case self::PARAMETER_TYPE: $parameterName = $this->getParameterName(); if ($parameterName !== null) { - return sprintf('Parameter $%s references %s in its type.', $parameterName, $part); + if ($this->isInAnomyousFunction()) { + return sprintf('Parameter $%s of anonymous function has typehint with %s.', $parameterName, $part); + } + if ($this->getMethod() !== null) { + if ($this->getCurrentClassName() !== null) { + return sprintf('Parameter $%s of method %s::%s() has typehint with %s.', $parameterName, $this->getCurrentClassName(), $this->getMethod()->getName(), $part); + } + + return sprintf('Parameter $%s of method %s() in anonymous class has typehint with %s.', $parameterName, $this->getMethod()->getName(), $part); + } + + if ($this->getFunction() !== null) { + return sprintf('Parameter $%s of function %s() has typehint with %s.', $parameterName, $this->getFunction()->getName(), $part); + } + + return sprintf('Parameter $%s has typehint with %s.', $parameterName, $part); } - return sprintf('Parameter references %s in its type.', $part); + return sprintf('Parameter has typehint with %s.', $part); case self::RETURN_TYPE: - return sprintf('Return type references %s.', $part); + if ($this->isInAnomyousFunction()) { + return sprintf('Return type of anonymous function has typehint with %s.', $part); + } + if ($this->getMethod() !== null) { + if ($this->getCurrentClassName() !== null) { + return sprintf('Return type of method %s::%s() has typehint with %s.', $this->getCurrentClassName(), $this->getMethod()->getName(), $part); + } + + return sprintf('Return type of method %s() in anonymous class has typehint with %s.', $this->getMethod()->getName(), $part); + } + + if ($this->getFunction() !== null) { + return sprintf('Return type of function %s() has typehint with %s.', $this->getFunction()->getName(), $part); + } + + return sprintf('Return type has typehint with %s.', $part); case self::PHPDOC_TAG_SELF_OUT: return sprintf('PHPDoc tag @phpstan-self-out references %s.', $part); case self::PHPDOC_TAG_VAR: diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index dfb47260190..1cf7482738b 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -179,6 +179,7 @@ public function checkAnonymousFunction( new ClassNameNodePair($class, $param->type), ], ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE, [ 'parameterName' => $param->var->name, + 'isInAnonymousFunction' => true, ]), $this->checkClassCaseSensitivity), ); } @@ -237,7 +238,9 @@ public function checkAnonymousFunction( $errors, $this->classCheck->checkClassNames($scope, [ new ClassNameNodePair($returnTypeClass, $returnTypeNode), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE), $this->checkClassCaseSensitivity), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE, [ + 'isInAnonymousFunction' => true, + ]), $this->checkClassCaseSensitivity), ); } @@ -313,7 +316,7 @@ public function checkClassMethod( */ private function checkParametersAcceptor( Scope $scope, - ParametersAcceptor $parametersAcceptor, + PhpMethodFromParserNodeReflection|PhpFunctionFromParserNodeReflection $parametersAcceptor, FunctionLike $functionNode, string $parameterMessage, string $returnMessage, @@ -377,28 +380,26 @@ private function checkParametersAcceptor( return $parameterNode; }; - if ($parameter instanceof ExtendedParameterReflection) { - $parameterVar = $parameterNodeCallback()->var; - if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { - throw new ShouldNotHappenException(); - } - if ($parameter->getNativeType()->isVoid()->yes()) { - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void')) - ->line($parameterNodeCallback()->getStartLine()) - ->identifier('parameter.void') - ->nonIgnorable() - ->build(); - } - if ( - $this->phpVersion->supportsPureIntersectionTypes() - && $this->unresolvableTypeHelper->containsUnresolvableType($parameter->getNativeType()) - ) { - $errors[] = RuleErrorBuilder::message(sprintf($unresolvableParameterTypeMessage, $parameterVar->name)) - ->line($parameterNodeCallback()->getStartLine()) - ->identifier('parameter.unresolvableNativeType') - ->nonIgnorable() - ->build(); - } + $parameterVar = $parameterNodeCallback()->var; + if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { + throw new ShouldNotHappenException(); + } + if ($parameter->getNativeType()->isVoid()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void')) + ->line($parameterNodeCallback()->getStartLine()) + ->identifier('parameter.void') + ->nonIgnorable() + ->build(); + } + if ( + $this->phpVersion->supportsPureIntersectionTypes() + && $this->unresolvableTypeHelper->containsUnresolvableType($parameter->getNativeType()) + ) { + $errors[] = RuleErrorBuilder::message(sprintf($unresolvableParameterTypeMessage, $parameterVar->name)) + ->line($parameterNodeCallback()->getStartLine()) + ->identifier('parameter.unresolvableNativeType') + ->nonIgnorable() + ->build(); } foreach ($referencedClasses as $class) { if (!$this->reflectionProvider->hasClass($class)) { @@ -478,12 +479,22 @@ private function checkParametersAcceptor( ->build(); } + $locationData = []; + if ($parametersAcceptor instanceof PhpMethodFromParserNodeReflection) { + $locationData['method'] = $parametersAcceptor; + if (!$parametersAcceptor->getDeclaringClass()->isAnonymous()) { + $locationData['currentClassName'] = $parametersAcceptor->getDeclaringClass()->getName(); + } + } else { + $locationData['function'] = $parametersAcceptor; + } + $errors = array_merge( $errors, $this->classCheck->checkClassNames( $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $returnTypeNode), $returnTypeReferencedClasses), - ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE), + ClassNameUsageLocation::from(ClassNameUsageLocation::RETURN_TYPE, $locationData), $this->checkClassCaseSensitivity, ), ); From 85635821a1d0f7a22494e79879096312660d79d8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 11:22:07 +0200 Subject: [PATCH 1305/3097] Fix passed location data to PARAMETER_TYPE --- src/Rules/FunctionDefinitionCheck.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 1cf7482738b..af468ab670f 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -427,14 +427,24 @@ private function checkParametersAcceptor( ->build(); } + $locationData = [ + 'parameterName' => $parameter->getName(), + ]; + if ($parametersAcceptor instanceof PhpMethodFromParserNodeReflection) { + $locationData['method'] = $parametersAcceptor; + if (!$parametersAcceptor->getDeclaringClass()->isAnonymous()) { + $locationData['currentClassName'] = $parametersAcceptor->getDeclaringClass()->getName(); + } + } else { + $locationData['function'] = $parametersAcceptor; + } + $errors = array_merge( $errors, $this->classCheck->checkClassNames( $scope, array_map(static fn (string $class): ClassNameNodePair => new ClassNameNodePair($class, $parameterNodeCallback()), $referencedClasses), - ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE, [ - 'parameterName' => $parameter->getName(), - ]), + ClassNameUsageLocation::from(ClassNameUsageLocation::PARAMETER_TYPE, $locationData), $this->checkClassCaseSensitivity, ), ); From ce486670ffd24f222f8d8f75f1a26bab54132acc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 12:59:29 +0200 Subject: [PATCH 1306/3097] Adjust messages --- src/Rules/ClassNameUsageLocation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/ClassNameUsageLocation.php b/src/Rules/ClassNameUsageLocation.php index 8fe44f58db7..d8727ac5f2a 100644 --- a/src/Rules/ClassNameUsageLocation.php +++ b/src/Rules/ClassNameUsageLocation.php @@ -161,7 +161,7 @@ public function createMessage(string $part): string return sprintf('Class %s implements %s.', $this->getCurrentClassName(), $part); } - return sprintf('Class implements %s.', $part); + return sprintf('Anonymous class implements %s.', $part); case self::ENUM_IMPLEMENTS: if ($this->getCurrentClassName() !== null) { return sprintf('Enum %s implements %s.', $this->getCurrentClassName(), $part); @@ -179,7 +179,7 @@ public function createMessage(string $part): string return sprintf('Class %s extends %s.', $this->getCurrentClassName(), $part); } - return sprintf('Class extends %s.', $part); + return sprintf('Anonymous class extends %s.', $part); case self::INSTANCEOF: return sprintf('Instanceof references %s.', $part); case self::PROPERTY_TYPE: From 043a14c9389c44447c5143ee6b6b809a53e1e5d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 13:13:57 +0200 Subject: [PATCH 1307/3097] RestrictedFunctionUsageExtension --- conf/config.level0.neon | 5 ++ conf/config.neon | 2 + .../ConditionalTagsExtension.php | 2 + ...strictedInternalFunctionUsageExtension.php | 51 +++++++++++++ .../RestrictedFunctionCallableUsageRule.php | 65 +++++++++++++++++ .../RestrictedFunctionUsageExtension.php | 38 ++++++++++ .../RestrictedFunctionUsageRule.php | 64 ++++++++++++++++ ...ctedInternalFunctionUsageExtensionTest.php | 42 +++++++++++ .../data/function-internal-tag.php | 73 +++++++++++++++++++ ...estrictedFunctionCallableUsageRuleTest.php | 45 ++++++++++++ .../RestrictedFunctionUsageRuleTest.php | 40 ++++++++++ .../data/FunctionExtension.php | 25 +++++++ .../data/restricted-function-callable.php | 9 +++ .../data/restricted-function.php | 19 +++++ .../RestrictedUsage/restricted-usage.neon | 4 + 15 files changed, 484 insertions(+) create mode 100644 src/Rules/InternalTag/RestrictedInternalFunctionUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php create mode 100644 src/Rules/RestrictedUsage/RestrictedFunctionUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedFunctionUsageRule.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalFunctionUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/function-internal-tag.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedFunctionUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/FunctionExtension.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-function-callable.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-function.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index ea85702a318..11706d7659c 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -106,6 +106,8 @@ rules: conditionalTags: PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension: phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag% + PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension: + phpstan.restrictedFunctionUsageExtension: %featureToggles.internalTag% services: - @@ -297,3 +299,6 @@ services: - class: PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension + + - + class: PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension diff --git a/conf/config.neon b/conf/config.neon index 7e5de29f450..80ca10a5dbc 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -218,6 +218,8 @@ rules: - PHPStan\Rules\Debug\DumpPhpDocTypeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule + - PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedFunctionCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 8a4fe377c0e..837b138a752 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -26,6 +26,7 @@ use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; use PHPStan\ShouldNotHappenException; use function array_reduce; @@ -77,6 +78,7 @@ public function getConfigSchema(): Nette\Schema\Schema PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool, RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG => $bool, RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG => $bool, + RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Rules/InternalTag/RestrictedInternalFunctionUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalFunctionUsageExtension.php new file mode 100644 index 00000000000..1ab605cdd96 --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalFunctionUsageExtension.php @@ -0,0 +1,51 @@ +isInternal()->yes()) { + return null; + } + + if (!$this->helper->shouldBeReported($scope, $functionReflection->getName())) { + return null; + } + + $namespace = array_slice(explode('\\', $functionReflection->getName()), 0, -1)[0] ?? null; + if ($namespace === null) { + return RestrictedUsage::create( + sprintf( + 'Call to internal function %s().', + $functionReflection->getName(), + ), + 'function.internal', + ); + } + + return RestrictedUsage::create( + sprintf( + 'Call to internal function %s() from outside its root namespace %s.', + $functionReflection->getName(), + $namespace, + ), + 'function.internal', + ); + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php new file mode 100644 index 00000000000..ba15cb4f9cd --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php @@ -0,0 +1,65 @@ + + */ +final class RestrictedFunctionCallableUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return FunctionCallableNode::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!($node->getName() instanceof Name)) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->getName(), $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->getName(), $scope); + + /** @var RestrictedFunctionUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG); + $errors = []; + + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedFunctionUsage($functionReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + + return $errors; + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedFunctionUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedFunctionUsageExtension.php new file mode 100644 index 00000000000..f43c357190b --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedFunctionUsageExtension.php @@ -0,0 +1,38 @@ + + */ +final class RestrictedFunctionUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof Name)) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + + /** @var RestrictedFunctionUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG); + $errors = []; + + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedFunctionUsage($functionReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalFunctionUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalFunctionUsageExtensionTest.php new file mode 100644 index 00000000000..aa8d49eae2f --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalFunctionUsageExtensionTest.php @@ -0,0 +1,42 @@ + + */ +class RestrictedInternalFunctionUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedFunctionUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/function-internal-tag.php'], [ + [ + 'Call to internal function FunctionInternalTagOne\doInternal() from outside its root namespace FunctionInternalTagOne.', + 35, + ], + [ + 'Call to internal function FunctionInternalTagOne\doInternal() from outside its root namespace FunctionInternalTagOne.', + 44, + ], + [ + 'Call to internal function doInternalWithoutNamespace().', + 60, + ], + [ + 'Call to internal function doInternalWithoutNamespace().', + 69, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/function-internal-tag.php b/tests/PHPStan/Rules/InternalTag/data/function-internal-tag.php new file mode 100644 index 00000000000..092b49e898d --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/function-internal-tag.php @@ -0,0 +1,73 @@ + + */ +class RestrictedFunctionCallableUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new RestrictedFunctionCallableUsageRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/restricted-function-callable.php'], [ + [ + 'Cannot call doFoo', + 7, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedFunctionUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedFunctionUsageRuleTest.php new file mode 100644 index 00000000000..7bab882e702 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedFunctionUsageRuleTest.php @@ -0,0 +1,40 @@ + + */ +class RestrictedFunctionUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new RestrictedFunctionUsageRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-function.php'], [ + [ + 'Cannot call doFoo', + 17, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/FunctionExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/FunctionExtension.php new file mode 100644 index 00000000000..555b5b0a520 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/FunctionExtension.php @@ -0,0 +1,25 @@ +getName() !== 'RestrictedUsage\\doFoo') { + return null; + } + + return RestrictedUsage::create('Cannot call doFoo', 'restrictedUsage.doFoo'); + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function-callable.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function-callable.php new file mode 100644 index 00000000000..d1961bd8e9b --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function-callable.php @@ -0,0 +1,9 @@ += 8.1 + +namespace RestrictedUsage; + +function (): void { + doNonexistent(...); + doFoo(...); + doBar(...); +}; diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function.php new file mode 100644 index 00000000000..5026200f057 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-function.php @@ -0,0 +1,19 @@ + Date: Sat, 26 Apr 2025 17:15:17 +0200 Subject: [PATCH 1308/3097] Fix `session_set_cookie_params` call with named arguments --- src/Analyser/ArgumentsNormalizer.php | 3 ++- .../SignatureMap/Php8SignatureMapProvider.php | 4 ++-- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 10 ++++++++++ tests/PHPStan/Analyser/data/bug-12934.php | 9 +++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12934.php diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index 0b68559b426..da37080cfa8 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -18,6 +18,7 @@ use function count; use function ksort; use function max; +use function sprintf; /** * @api @@ -276,7 +277,7 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array $defaultValue = $parameter->getDefaultValue(); if ($defaultValue === null) { if (!$parameter->isVariadic()) { - throw new ShouldNotHappenException('An optional parameter must have a default value'); + throw new ShouldNotHappenException(sprintf('An optional parameter $%s must have a default value', $parameter->getName())); } $defaultValue = new ConstantArrayType([], []); } diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 9c456aec188..b787a4201b3 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -272,8 +272,8 @@ private function getMergedSignatures(FunctionSignature $nativeSignature, array $ $functionParam->getNativeType(), $functionParam->passedByReference(), $functionParam->isVariadic(), - $functionParam->getDefaultValue(), - $functionParam->getOutType(), + $functionParam->getDefaultValue() ?? $nativeParam->getDefaultValue(), + $functionParam->getOutType() ?? $nativeParam->getOutType(), ); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e8bbb0d1fd1..156b26cffa2 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1138,6 +1138,16 @@ public function testBug8147(): void $this->assertNoErrors($errors); } + public function testBug12934(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12934.php'); + $this->assertNoErrors($errors); + } + public function testConditionalExpressionInfiniteLoop(): void { $errors = $this->runAnalyse(__DIR__ . '/data/conditional-expression-infinite-loop.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12934.php b/tests/PHPStan/Analyser/data/bug-12934.php new file mode 100644 index 00000000000..36109899a8f --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12934.php @@ -0,0 +1,9 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug12934; + +function(string $path): void { + session_set_cookie_params(0, path: $path, secure: true, httponly: true); +}; From d2b7e81b1791a9c2fa59f230599badcb763ad265 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 26 Apr 2025 17:33:29 +0200 Subject: [PATCH 1309/3097] Micro optimize LazyInternalScopeFactory --- src/Analyser/LazyInternalScopeFactory.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 1d4154261d1..9835fb15914 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -17,10 +17,14 @@ final class LazyInternalScopeFactory implements InternalScopeFactory { + /** @var int|array{min: int, max: int}|null */ + private int|array|null $phpVersion; + public function __construct( private Container $container, ) { + $this->phpVersion = $this->container->getParameter('phpVersion'); } public function create( @@ -58,7 +62,7 @@ public function create( $context, $this->container->getByType(PhpVersion::class), $this->container->getByType(AttributeReflectionFactory::class), - $this->container->getParameter('phpVersion'), + $this->phpVersion, $declareStrictTypes, $function, $namespace, From 95f54cd1f99e975cff5059eda1cb4394a593d52b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 19:59:57 +0200 Subject: [PATCH 1310/3097] RestrictedPropertyUsageExtension --- conf/config.level2.neon | 5 + conf/config.neon | 2 + phpstan-baseline.neon | 18 +++ .../ConditionalTagsExtension.php | 2 + ...strictedInternalPropertyUsageExtension.php | 98 ++++++++++++++++ .../RestrictedPropertyUsageExtension.php | 38 ++++++ .../RestrictedPropertyUsageRule.php | 78 +++++++++++++ .../RestrictedStaticPropertyUsageRule.php | 98 ++++++++++++++++ ...ctedInternalPropertyUsageExtensionTest.php | 59 ++++++++++ ...ternalStaticPropertyUsageExtensionTest.php | 69 +++++++++++ .../data/property-internal-tag.php | 110 ++++++++++++++++++ ...c-property-access-on-internal-subclass.php | 30 +++++ .../data/static-property-internal-tag.php | 110 ++++++++++++++++++ .../RestrictedPropertyUsageRuleTest.php | 40 +++++++ .../RestrictedStaticPropertyUsageRuleTest.php | 43 +++++++ .../data/PropertyExtension.php | 25 ++++ .../data/restricted-property.php | 37 ++++++ .../RestrictedUsage/restricted-usage.neon | 4 + 18 files changed, 866 insertions(+) create mode 100644 src/Rules/InternalTag/RestrictedInternalPropertyUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedPropertyUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php create mode 100644 src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalPropertyUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticPropertyUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/property-internal-tag.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/static-property-access-on-internal-subclass.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/static-property-internal-tag.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedPropertyUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/PropertyExtension.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-property.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index aaba4b93656..51214ea5544 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -68,6 +68,8 @@ rules: - PHPStan\Rules\Pure\PureMethodRule conditionalTags: + PHPStan\Rules\InternalTag\RestrictedInternalPropertyUsageExtension: + phpstan.restrictedPropertyUsageExtension: %featureToggles.internalTag% PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension: phpstan.restrictedMethodUsageExtension: %featureToggles.internalTag% @@ -115,5 +117,8 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\InternalTag\RestrictedInternalPropertyUsageExtension + - class: PHPStan\Rules\InternalTag\RestrictedInternalMethodUsageExtension diff --git a/conf/config.neon b/conf/config.neon index 80ca10a5dbc..18cba84c586 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -222,8 +222,10 @@ rules: - PHPStan\Rules\RestrictedUsage\RestrictedFunctionCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodCallableUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodCallableUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedStaticPropertyUsageRule conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3e8f1278445..dc8fcfbcc10 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -204,6 +204,24 @@ parameters: count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php + - + message: '#^Access to property \$id of internal class Symfony\\Polyfill\\Php80\\PhpToken from outside its root namespace Symfony\.$#' + identifier: property.internalClass + count: 1 + path: src/Parser/RichParser.php + + - + message: '#^Access to property \$line of internal class Symfony\\Polyfill\\Php80\\PhpToken from outside its root namespace Symfony\.$#' + identifier: property.internalClass + count: 4 + path: src/Parser/RichParser.php + + - + message: '#^Access to property \$text of internal class Symfony\\Polyfill\\Php80\\PhpToken from outside its root namespace Symfony\.$#' + identifier: property.internalClass + count: 3 + path: src/Parser/RichParser.php + - message: '#^Call to function method_exists\(\) with PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocNode and ''getParamOutTypeTagV…'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 837b138a752..645d895293a 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -28,6 +28,7 @@ use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageExtension; use PHPStan\ShouldNotHappenException; use function array_reduce; use function count; @@ -79,6 +80,7 @@ public function getConfigSchema(): Nette\Schema\Schema RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG => $bool, RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG => $bool, RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG => $bool, + RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Rules/InternalTag/RestrictedInternalPropertyUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalPropertyUsageExtension.php new file mode 100644 index 00000000000..e6bbf4f3b9e --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalPropertyUsageExtension.php @@ -0,0 +1,98 @@ +isInternal()->yes(); + $declaringClass = $propertyReflection->getDeclaringClass(); + $isDeclaringClassInternal = $declaringClass->isInternal(); + if (!$isPropertyInternal && !$isDeclaringClassInternal) { + return null; + } + + $declaringClassName = $declaringClass->getName(); + if (!$this->helper->shouldBeReported($scope, $declaringClassName)) { + return null; + } + + $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; + if ($namespace === null) { + if (!$isPropertyInternal) { + return RestrictedUsage::create( + sprintf( + 'Access to %sproperty $%s of internal %s %s.', + $propertyReflection->isStatic() ? 'static ' : '', + $propertyReflection->getName(), + strtolower($propertyReflection->getDeclaringClass()->getClassTypeDescription()), + $propertyReflection->getDeclaringClass()->getDisplayName(), + ), + sprintf( + '%s.internal%s', + $propertyReflection->isStatic() ? 'staticProperty' : 'property', + $propertyReflection->getDeclaringClass()->getClassTypeDescription(), + ), + ); + } + + return RestrictedUsage::create( + sprintf( + 'Access to internal %sproperty %s::$%s.', + $propertyReflection->isStatic() ? 'static ' : '', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyReflection->getName(), + ), + sprintf('%s.internal', $propertyReflection->isStatic() ? 'staticProperty' : 'property'), + ); + } + + if (!$isPropertyInternal) { + return RestrictedUsage::create( + sprintf( + 'Access to %sproperty $%s of internal %s %s from outside its root namespace %s.', + $propertyReflection->isStatic() ? 'static ' : '', + $propertyReflection->getName(), + strtolower($propertyReflection->getDeclaringClass()->getClassTypeDescription()), + $propertyReflection->getDeclaringClass()->getDisplayName(), + $namespace, + ), + sprintf( + '%s.internal%s', + $propertyReflection->isStatic() ? 'staticProperty' : 'property', + $propertyReflection->getDeclaringClass()->getClassTypeDescription(), + ), + ); + } + + return RestrictedUsage::create( + sprintf( + 'Access to internal %sproperty %s::$%s from outside its root namespace %s.', + $propertyReflection->isStatic() ? 'static ' : '', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $propertyReflection->getName(), + $namespace, + ), + sprintf('%s.internal', $propertyReflection->isStatic() ? 'staticProperty' : 'property'), + ); + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedPropertyUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedPropertyUsageExtension.php new file mode 100644 index 00000000000..2216c7435b9 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedPropertyUsageExtension.php @@ -0,0 +1,38 @@ + + */ +final class RestrictedPropertyUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\PropertyFetch::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + /** @var RestrictedPropertyUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $propertyName = $node->name->name; + $propertyCalledOnType = $scope->getType($node->var); + $referencedClasses = $propertyCalledOnType->getObjectClassNames(); + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasProperty($propertyName)) { + continue; + } + + $propertyReflection = $classReflection->getProperty($propertyName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedPropertyUsage($propertyReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php new file mode 100644 index 00000000000..260672d0d56 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php @@ -0,0 +1,98 @@ + + */ +final class RestrictedStaticPropertyUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\StaticPropertyFetch::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + /** @var RestrictedPropertyUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $propertyName = $node->name->name; + $referencedClasses = []; + + if ($node->class instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->class); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', // We don't care about the error message + static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($propertyName)->yes(), + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasProperty($propertyName)) { + continue; + } + + $propertyReflection = $classReflection->getProperty($propertyName, $scope); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedPropertyUsage($propertyReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalPropertyUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalPropertyUsageExtensionTest.php new file mode 100644 index 00000000000..d3ee69910eb --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalPropertyUsageExtensionTest.php @@ -0,0 +1,59 @@ + + */ +class RestrictedInternalPropertyUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedPropertyUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/property-internal-tag.php'], [ + [ + 'Access to internal property PropertyInternalTagOne\Foo::$internal from outside its root namespace PropertyInternalTagOne.', + 49, + ], + [ + 'Access to property $foo of internal class PropertyInternalTagOne\FooInternal from outside its root namespace PropertyInternalTagOne.', + 54, + ], + [ + 'Access to internal property PropertyInternalTagOne\Foo::$internal from outside its root namespace PropertyInternalTagOne.', + 62, + ], + + [ + 'Access to property $foo of internal class PropertyInternalTagOne\FooInternal from outside its root namespace PropertyInternalTagOne.', + 67, + ], + [ + 'Access to internal property FooWithInternalPropertyWithoutNamespace::$internal.', + 89, + ], + [ + 'Access to property $foo of internal class FooInternalWithPropertyWithoutNamespace.', + 94, + ], + [ + 'Access to internal property FooWithInternalPropertyWithoutNamespace::$internal.', + 102, + ], + [ + 'Access to property $foo of internal class FooInternalWithPropertyWithoutNamespace.', + 107, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticPropertyUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticPropertyUsageExtensionTest.php new file mode 100644 index 00000000000..6b56240a7fd --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalStaticPropertyUsageExtensionTest.php @@ -0,0 +1,69 @@ + + */ +class RestrictedInternalStaticPropertyUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedStaticPropertyUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/static-property-internal-tag.php'], [ + [ + 'Access to internal static property StaticPropertyInternalTagOne\Foo::$internal from outside its root namespace StaticPropertyInternalTagOne.', + 49, + ], + [ + 'Access to static property $foo of internal class StaticPropertyInternalTagOne\FooInternal from outside its root namespace StaticPropertyInternalTagOne.', + 54, + ], + [ + 'Access to internal static property StaticPropertyInternalTagOne\Foo::$internal from outside its root namespace StaticPropertyInternalTagOne.', + 62, + ], + + [ + 'Access to static property $foo of internal class StaticPropertyInternalTagOne\FooInternal from outside its root namespace StaticPropertyInternalTagOne.', + 67, + ], + [ + 'Access to internal static property FooWithInternalStaticPropertyWithoutNamespace::$internal.', + 89, + ], + [ + 'Access to static property $foo of internal class FooInternalWithStaticPropertyWithoutNamespace.', + 94, + ], + [ + 'Access to internal static property FooWithInternalStaticPropertyWithoutNamespace::$internal.', + 102, + ], + [ + 'Access to static property $foo of internal class FooInternalWithStaticPropertyWithoutNamespace.', + 107, + ], + ]); + } + + public function testStaticPropertyAccessOnInternalSubclass(): void + { + $this->analyse([__DIR__ . '/data/static-property-access-on-internal-subclass.php'], [ + [ + 'Access to static property $bar of internal class StaticPropertyAccessOnInternalSubclassOne\Bar from outside its root namespace StaticPropertyAccessOnInternalSubclassOne.', + 28, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/property-internal-tag.php b/tests/PHPStan/Rules/InternalTag/data/property-internal-tag.php new file mode 100644 index 00000000000..bf3b0921a16 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/property-internal-tag.php @@ -0,0 +1,110 @@ +internal; + $foo->notInternal; + }; + + function (FooInternal $foo): void { + $foo->foo; + }; + +} + +namespace PropertyInternalTagOne\Test { + + function (\PropertyInternalTagOne\Foo $foo): void { + $foo->internal; + $foo->notInternal; + }; + + function (\PropertyInternalTagOne\FooInternal $foo): void { + $foo->foo; + }; +} + +namespace PropertyInternalTagTwo { + + function (\PropertyInternalTagOne\Foo $foo): void { + $foo->internal; + $foo->notInternal; + }; + + function (\PropertyInternalTagOne\FooInternal $foo): void { + $foo->foo; + }; + +} + +namespace { + + function (\PropertyInternalTagOne\Foo $foo): void { + $foo->internal; + $foo->notInternal; + }; + + function (\PropertyInternalTagOne\FooInternal $foo): void { + $foo->foo; + }; + + class FooWithInternalPropertyWithoutNamespace + { + /** @internal */ + public $internal; + + public $notInternal; + } + + /** + * @internal + */ + class FooInternalWithPropertyWithoutNamespace + { + + public $foo; + + } + + function (FooWithInternalPropertyWithoutNamespace $foo): void { + $foo->internal; + $foo->notInternal; + }; + + function (FooInternalWithPropertyWithoutNamespace $foo): void { + $foo->foo; + }; + +} + +namespace SomeNamespace { + + function (\FooWithInternalPropertyWithoutNamespace $foo): void { + $foo->internal; + $foo->notInternal; + }; + + function (\FooInternalWithPropertyWithoutNamespace $foo): void { + $foo->foo; + }; + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/static-property-access-on-internal-subclass.php b/tests/PHPStan/Rules/InternalTag/data/static-property-access-on-internal-subclass.php new file mode 100644 index 00000000000..182ef04e93a --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/static-property-access-on-internal-subclass.php @@ -0,0 +1,30 @@ + + */ +class RestrictedPropertyUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return new RestrictedPropertyUsageRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-property.php'], [ + [ + 'Cannot access $foo', + 17, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php new file mode 100644 index 00000000000..e1b057bf6b4 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php @@ -0,0 +1,43 @@ + + */ +class RestrictedStaticPropertyUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + $reflectionProvider = $this->createReflectionProvider(); + return new RestrictedStaticPropertyUsageRule( + self::getContainer(), + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, true, true, false, true), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-property.php'], [ + [ + 'Cannot access $foo', + 34, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/PropertyExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/PropertyExtension.php new file mode 100644 index 00000000000..52cfb126b61 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/PropertyExtension.php @@ -0,0 +1,25 @@ +getName() !== 'foo') { + return null; + } + + return RestrictedUsage::create('Cannot access $foo', 'restrictedUsage.foo'); + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-property.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-property.php new file mode 100644 index 00000000000..8f8e41e1ad9 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-property.php @@ -0,0 +1,37 @@ +test; + $this->doNonexistent; + $this->bar; + $this->foo; + } + +} + +class FooStatic +{ + + public static $bar; + + public static $foo; + + public static function doTest(): void + { + Nonexistent::$test; + self::$nonexistent; + self::$bar; + self::$foo; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon b/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon index 16f153456c6..d03dfccb594 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon +++ b/tests/PHPStan/Rules/RestrictedUsage/restricted-usage.neon @@ -7,3 +7,7 @@ services: class: RestrictedUsage\FunctionExtension tags: - phpstan.restrictedFunctionUsageExtension + - + class: RestrictedUsage\PropertyExtension + tags: + - phpstan.restrictedPropertyUsageExtension From e14e527fbfa34a1095174e9fe3736c50fc969166 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 26 Apr 2025 21:07:28 +0200 Subject: [PATCH 1311/3097] RestrictedClassConstantUsageExtension --- conf/config.level0.neon | 5 + conf/config.neon | 1 + .../ConditionalTagsExtension.php | 2 + ...tedInternalClassConstantUsageExtension.php | 93 +++++++++++++++ .../RestrictedClassConstantUsageExtension.php | 38 ++++++ .../RestrictedClassConstantUsageRule.php | 98 ++++++++++++++++ ...nternalClassConstantUsageExtensionTest.php | 69 +++++++++++ ...s-constant-access-on-internal-subclass.php | 30 +++++ .../data/class-constant-internal-tag.php | 110 ++++++++++++++++++ .../RestrictedClassConstantUsageRuleTest.php | 43 +++++++ .../data/ClassConstantExtension.php | 25 ++++ .../data/restricted-class-constant.php | 20 ++++ .../RestrictedUsage/restricted-usage.neon | 4 + 13 files changed, 538 insertions(+) create mode 100644 src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedClassConstantUsageExtension.php create mode 100644 src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php create mode 100644 tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/class-constant-access-on-internal-subclass.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/class-constant-internal-tag.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedClassConstantUsageRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/ClassConstantExtension.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-class-constant.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 11706d7659c..24b19d99bfa 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -104,6 +104,8 @@ rules: - PHPStan\Rules\Whitespace\FileWhitespaceRule conditionalTags: + PHPStan\Rules\InternalTag\RestrictedInternalClassConstantUsageExtension: + phpstan.restrictedClassConstantUsageExtension: %featureToggles.internalTag% PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension: phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag% PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension: @@ -297,6 +299,9 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\InternalTag\RestrictedInternalClassConstantUsageExtension + - class: PHPStan\Rules\InternalTag\RestrictedInternalClassNameUsageExtension diff --git a/conf/config.neon b/conf/config.neon index 18cba84c586..7a4a43a4dec 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -218,6 +218,7 @@ rules: - PHPStan\Rules\Debug\DumpPhpDocTypeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule + - PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedFunctionCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index 645d895293a..ccffd142900 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -25,6 +25,7 @@ use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; @@ -81,6 +82,7 @@ public function getConfigSchema(): Nette\Schema\Schema RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG => $bool, RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG => $bool, RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG => $bool, + RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG => $bool, ])->min(1)); } diff --git a/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php new file mode 100644 index 00000000000..89f58e22eb5 --- /dev/null +++ b/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php @@ -0,0 +1,93 @@ +isInternal()->yes(); + $declaringClass = $constantReflection->getDeclaringClass(); + $isDeclaringClassInternal = $declaringClass->isInternal(); + if (!$isConstantInternal && !$isDeclaringClassInternal) { + return null; + } + + $declaringClassName = $declaringClass->getName(); + if (!$this->helper->shouldBeReported($scope, $declaringClassName)) { + return null; + } + + $namespace = array_slice(explode('\\', $declaringClassName), 0, -1)[0] ?? null; + if ($namespace === null) { + if (!$isConstantInternal) { + return RestrictedUsage::create( + sprintf( + 'Access to constant %s of internal %s %s.', + $constantReflection->getName(), + strtolower($constantReflection->getDeclaringClass()->getClassTypeDescription()), + $constantReflection->getDeclaringClass()->getDisplayName(), + ), + sprintf( + 'classConstant.internal%s', + $constantReflection->getDeclaringClass()->getClassTypeDescription(), + ), + ); + } + + return RestrictedUsage::create( + sprintf( + 'Access to internal constant %s::%s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantReflection->getName(), + ), + 'classConstant.internal', + ); + } + + if (!$isConstantInternal) { + return RestrictedUsage::create( + sprintf( + 'Access to constant %s of internal %s %s from outside its root namespace %s.', + $constantReflection->getName(), + strtolower($constantReflection->getDeclaringClass()->getClassTypeDescription()), + $constantReflection->getDeclaringClass()->getDisplayName(), + $namespace, + ), + sprintf( + 'classConstant.internal%s', + $constantReflection->getDeclaringClass()->getClassTypeDescription(), + ), + ); + } + + return RestrictedUsage::create( + sprintf( + 'Access to constant %s of internal %s %s from outside its root namespace %s.', + $constantReflection->getName(), + strtolower($constantReflection->getDeclaringClass()->getClassTypeDescription()), + $constantReflection->getDeclaringClass()->getDisplayName(), + $namespace, + ), + 'classConstant.internal', + ); + } + +} diff --git a/src/Rules/RestrictedUsage/RestrictedClassConstantUsageExtension.php b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageExtension.php new file mode 100644 index 00000000000..ebb5d989d1c --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageExtension.php @@ -0,0 +1,38 @@ + + */ +final class RestrictedClassConstantUsageRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + private RuleLevelHelper $ruleLevelHelper, + ) + { + } + + public function getNodeType(): string + { + return Node\Expr\ClassConstFetch::class; + } + + /** + * @api + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Identifier) { + return []; + } + + /** @var RestrictedClassConstantUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $constantName = $node->name->name; + $referencedClasses = []; + + if ($node->class instanceof Name) { + $referencedClasses[] = $scope->resolveName($node->class); + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->class, + '', // We don't care about the error message + static fn (Type $type): bool => $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(), + ); + + if ($classTypeResult->getType() instanceof ErrorType) { + return []; + } + + $referencedClasses = $classTypeResult->getReferencedClasses(); + } + + $errors = []; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasConstant($constantName)) { + continue; + } + + $constantReflection = $classReflection->getConstant($constantName); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedClassConstantUsage($constantReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php new file mode 100644 index 00000000000..8580714385b --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php @@ -0,0 +1,69 @@ + + */ +class RestrictedInternalClassConstantUsageExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(RestrictedClassConstantUsageRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/class-constant-internal-tag.php'], [ + [ + 'Access to constant INTERNAL of internal class ClassConstantInternalTagOne\Foo from outside its root namespace ClassConstantInternalTagOne.', + 49, + ], + [ + 'Access to constant FOO of internal class ClassConstantInternalTagOne\FooInternal from outside its root namespace ClassConstantInternalTagOne.', + 54, + ], + [ + 'Access to constant INTERNAL of internal class ClassConstantInternalTagOne\Foo from outside its root namespace ClassConstantInternalTagOne.', + 62, + ], + + [ + 'Access to constant FOO of internal class ClassConstantInternalTagOne\FooInternal from outside its root namespace ClassConstantInternalTagOne.', + 67, + ], + [ + 'Access to internal constant FooWithInternalClassConstantWithoutNamespace::INTERNAL.', + 89, + ], + [ + 'Access to constant FOO of internal class FooInternalWithClassConstantWithoutNamespace.', + 94, + ], + [ + 'Access to internal constant FooWithInternalClassConstantWithoutNamespace::INTERNAL.', + 102, + ], + [ + 'Access to constant FOO of internal class FooInternalWithClassConstantWithoutNamespace.', + 107, + ], + ]); + } + + public function testStaticPropertyAccessOnInternalSubclass(): void + { + $this->analyse([__DIR__ . '/data/class-constant-access-on-internal-subclass.php'], [ + [ + 'Access to constant BAR of internal class ClassConstantAccessOnInternalSubclassOne\Bar from outside its root namespace ClassConstantAccessOnInternalSubclassOne.', + 28, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/data/class-constant-access-on-internal-subclass.php b/tests/PHPStan/Rules/InternalTag/data/class-constant-access-on-internal-subclass.php new file mode 100644 index 00000000000..d20ac7eb180 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/class-constant-access-on-internal-subclass.php @@ -0,0 +1,30 @@ + + */ +class RestrictedClassConstantUsageRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new RestrictedClassConstantUsageRule( + self::getContainer(), + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, true, true, false, true), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-class-constant.php'], [ + [ + 'Cannot access FOO', + 17, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/ClassConstantExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/ClassConstantExtension.php new file mode 100644 index 00000000000..033b140dab5 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/ClassConstantExtension.php @@ -0,0 +1,25 @@ +getName() !== 'FOO') { + return null; + } + + return RestrictedUsage::create('Cannot access FOO', 'restrictedUsage.foo'); + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-class-constant.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-class-constant.php new file mode 100644 index 00000000000..dd6f1c84023 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-class-constant.php @@ -0,0 +1,20 @@ + Date: Sat, 26 Apr 2025 22:03:50 +0200 Subject: [PATCH 1312/3097] Fix --- .../RestrictedInternalClassConstantUsageExtension.php | 5 ++--- .../RestrictedInternalClassConstantUsageExtensionTest.php | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php b/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php index 89f58e22eb5..6971c7b82e5 100644 --- a/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php +++ b/src/Rules/InternalTag/RestrictedInternalClassConstantUsageExtension.php @@ -80,10 +80,9 @@ public function isRestrictedClassConstantUsage( return RestrictedUsage::create( sprintf( - 'Access to constant %s of internal %s %s from outside its root namespace %s.', - $constantReflection->getName(), - strtolower($constantReflection->getDeclaringClass()->getClassTypeDescription()), + 'Access to internal constant %s::%s from outside its root namespace %s.', $constantReflection->getDeclaringClass()->getDisplayName(), + $constantReflection->getName(), $namespace, ), 'classConstant.internal', diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php index 8580714385b..e1ecf67c3de 100644 --- a/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php @@ -21,7 +21,7 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/class-constant-internal-tag.php'], [ [ - 'Access to constant INTERNAL of internal class ClassConstantInternalTagOne\Foo from outside its root namespace ClassConstantInternalTagOne.', + 'Access to internal constant ClassConstantInternalTagOne\Foo::INTERNAL from outside its root namespace ClassConstantInternalTagOne.', 49, ], [ @@ -29,7 +29,7 @@ public function testRule(): void 54, ], [ - 'Access to constant INTERNAL of internal class ClassConstantInternalTagOne\Foo from outside its root namespace ClassConstantInternalTagOne.', + 'Access to internal constant ClassConstantInternalTagOne\Foo::INTERNAL from outside its root namespace ClassConstantInternalTagOne.', 62, ], From 95d365e526e230f5a8c5027084bc76f55912dc46 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 27 Apr 2025 13:40:07 +0200 Subject: [PATCH 1313/3097] RestrictedUsage constructor is private --- src/Rules/RestrictedUsage/RestrictedUsage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/RestrictedUsage/RestrictedUsage.php b/src/Rules/RestrictedUsage/RestrictedUsage.php index fff3904d5e2..d6325829815 100644 --- a/src/Rules/RestrictedUsage/RestrictedUsage.php +++ b/src/Rules/RestrictedUsage/RestrictedUsage.php @@ -8,7 +8,7 @@ final class RestrictedUsage { - public function __construct( + private function __construct( public readonly string $errorMessage, public readonly string $identifier, ) From adf032ef28746cca946286aaee2858651470f496 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 28 Apr 2025 19:31:59 +0700 Subject: [PATCH 1314/3097] Remove useless assign `$parentNode = $parentNode` --- src/Analyser/NodeScopeResolver.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b43f0298cc5..a769db8499e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -395,9 +395,6 @@ public function processStmtNodes( $hasYield = $hasYield || $statementResult->hasYield(); if ($shouldCheckLastStatement && $isLast) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|PropertyHookStatementNode|Expr\Closure $parentNode */ - $parentNode = $parentNode; - $endStatements = $statementResult->getEndStatements(); if (count($endStatements) > 0) { foreach ($endStatements as $endStatement) { @@ -446,8 +443,6 @@ public function processStmtNodes( $statementResult = new StatementResult($scope, $hasYield, $alreadyTerminated, $exitPoints, $throwPoints, $impurePoints); if ($stmtCount === 0 && $shouldCheckLastStatement) { - /** @var Node\Stmt\Function_|Node\Stmt\ClassMethod|PropertyHookStatementNode|Expr\Closure $parentNode */ - $parentNode = $parentNode; $returnTypeNode = $parentNode->getReturnType(); if ($parentNode instanceof Expr\Closure) { $parentNode = new Node\Stmt\Expression($parentNode, $parentNode->getAttributes()); From 6b6c9c4df0178fb247371d27a0a6683186d7d0ae Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 21 Apr 2025 20:37:54 +0200 Subject: [PATCH 1315/3097] Fix `array_slice()` edge cases --- src/Type/Accessory/NonEmptyArrayType.php | 5 +---- src/Type/ArrayType.php | 4 ++++ src/Type/Constant/ConstantArrayType.php | 20 ++++++++++++----- src/Type/IntersectionType.php | 13 ++++++++++- tests/PHPStan/Analyser/nsrt/array-slice.php | 24 +++++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-5017.php | 4 ++-- 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index d4726fd5c22..2fbea580ca2 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -216,10 +216,7 @@ public function shuffleArray(): Type public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type { - if ( - (new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() - && ($lengthType->isNull()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) - ) { + if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() && $lengthType->isNull()->yes()) { return $this; } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index e68a6a61d3f..e6c0097db70 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -442,6 +442,10 @@ public function shuffleArray(): Type public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type { + if ((new ConstantIntegerType(0))->isSuperTypeOf($lengthType)->yes()) { + return new ConstantArrayType([], []); + } + if ($preserveKeys->no() && $this->keyType->isInteger()->yes()) { return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType()); } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 8e76f0d08f0..a24df69710a 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -944,17 +944,27 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre ->sliceArray($offsetType, $lengthType, $preserveKeys); } + if ($keyTypesCount + $offset <= 0) { + // A negative offset cannot reach left outside the array twice + $offset = 0; + } + + if ($keyTypesCount + $length <= 0) { + // A negative length cannot reach left outside the array twice + $length = 0; + } + + if ($length === 0 || ($offset < 0 && $length < 0 && $offset - $length >= 0)) { + // 0 / 0, 3 / 0 or e.g. -3 / -3 or -3 / -4 and so on never extract anything + return new self([], []); + } + if ($length < 0) { // Negative lengths prevent access to the most right n elements return $this->removeLastElements($length * -1) ->sliceArray($offsetType, new NullType(), $preserveKeys); } - if ($keyTypesCount + $offset <= 0) { - // A negative offset cannot reach left outside the array - $offset = 0; - } - if ($offset < 0) { /* * Transforms the problem with the negative offset in one with a positive offset using array reversion. diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 149536a573f..89c00f26d24 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -896,7 +896,18 @@ public function shuffleArray(): Type public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys)); + $result = $this->intersectTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys)); + + if ( + $this->isList()->yes() + && $this->isIterableAtLeastOnce()->yes() + && (new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() + && IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes() + ) { + $result = TypeCombinator::intersect($result, new NonEmptyArrayType()); + } + + return $result; } public function getEnumCases(): array diff --git a/tests/PHPStan/Analyser/nsrt/array-slice.php b/tests/PHPStan/Analyser/nsrt/array-slice.php index 12caf4fdbf9..caf08c8d65a 100644 --- a/tests/PHPStan/Analyser/nsrt/array-slice.php +++ b/tests/PHPStan/Analyser/nsrt/array-slice.php @@ -36,6 +36,22 @@ public function normalArrays(array $arr): void /** @var array $arr */ assertType('array', array_slice($arr, 1, 2)); assertType('array', array_slice($arr, 1, 2, true)); + + /** @var non-empty-array $arr */ + assertType('array{}', array_slice($arr, 0, 0)); + assertType('array{}', array_slice($arr, 0, 0, true)); + + /** @var non-empty-array $arr */ + assertType('array', array_slice($arr, 0, 1)); + assertType('array', array_slice($arr, 0, 1, true)); + + /** @var list $arr */ + assertType('list', array_slice($arr, 0, 1)); + assertType('list', array_slice($arr, 0, 1, true)); + + /** @var non-empty-list $arr */ + assertType('non-empty-list', array_slice($arr, 0, 1)); + assertType('non-empty-list', array_slice($arr, 0, 1, true)); } public function constantArrays(array $arr): void @@ -48,6 +64,14 @@ public function constantArrays(array $arr): void /** @var array{17: 'foo', 19: 'bar', 21: 'baz'}|array{foo: 17, bar: 19, baz: 21} $arr */ assertType('array{\'bar\', \'baz\'}|array{bar: 19, baz: 21}', array_slice($arr, 1, 2)); assertType('array{19: \'bar\', 21: \'baz\'}|array{bar: 19, baz: 21}', array_slice($arr, 1, 2, true)); + + /** @var array{17: 'foo', b: 'bar', 19: 'baz'} $arr */ + assertType('array{}', array_slice($arr, -1, -1)); + assertType('array{}', array_slice($arr, -1, -1, true)); + + /** @var array{17: 'foo', b: 'bar', 19: 'baz'} $arr */ + assertType('array{}', array_slice($arr, -1, -2)); + assertType('array{}', array_slice($arr, -1, -2, true)); } public function constantArraysWithOptionalKeys(array $arr): void diff --git a/tests/PHPStan/Analyser/nsrt/bug-5017.php b/tests/PHPStan/Analyser/nsrt/bug-5017.php index 918b56e6249..f90994ee894 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5017.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5017.php @@ -15,7 +15,7 @@ public function doFoo() assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); - assertType('non-empty-list<0|1|2|3|4>', $batch); + assertType('list<0|1|2|3|4>', $batch); } } @@ -28,7 +28,7 @@ public function doBar($items) assertType('non-empty-array', $items); $batch = array_splice($items, 0, 2); assertType('array', $items); - assertType('non-empty-array', $batch); + assertType('array', $batch); } } From 506e9ed5b4690d2d3af24f82af09fe4692385ed0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 28 Apr 2025 18:26:09 +0200 Subject: [PATCH 1316/3097] TypehintHelper: remove unneeded default param value --- src/Type/TypehintHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 333d9de4a79..5ae08aac5d0 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -77,7 +77,7 @@ public static function decideTypeFromReflection( public static function decideType( Type $type, - ?Type $phpDocType = null, + ?Type $phpDocType, ): Type { if ($type instanceof BenevolentUnionType) { From 13d47f50db4066652af8278d74cd8a61e830622e Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:04:07 +0000 Subject: [PATCH 1317/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index a184310b1ba..3a585da23c6 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", + "jetbrains/phpstorm-stubs": "dev-master#b22fb017543bb7147e3bcc53f08fb13a48aff994", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 07344c7cd81..91c8b4031c8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f2523c1a5da0b0b5802408bf7969ec24", + "content-hash": "a5aee6235dc8ddeac7b42ed53ce87902", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb" + "reference": "b22fb017543bb7147e3bcc53f08fb13a48aff994" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", - "reference": "4adc304bd0b6401f48f6e5524687c8ef2a1ff8fb", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/b22fb017543bb7147e3bcc53f08fb13a48aff994", + "reference": "b22fb017543bb7147e3bcc53f08fb13a48aff994", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-04-16T09:26:41+00:00" + "time": "2025-04-22T16:22:26+00:00" }, { "name": "nette/bootstrap", From 3854cbc5748a7cb51ee0b86ceffe29bd0564bc98 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 29 Apr 2025 10:58:20 +0200 Subject: [PATCH 1318/3097] Useful PhpMethodReflection native type refactoring from #3966 --- src/Reflection/Php/PhpMethodReflection.php | 47 +++++++++++----------- src/Type/TypehintHelper.php | 5 ++- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 432fd693504..b141bcec7e4 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -302,30 +302,9 @@ public function isPublic(): bool private function getReturnType(): Type { if ($this->returnType === null) { - $name = strtolower($this->getName()); - $returnType = $this->reflection->getReturnType(); - if ($returnType === null) { - if (in_array($name, ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { - return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType); - } - if ($name === '__tostring') { - return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType); - } - if ($name === '__isset') { - return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType); - } - if ($name === '__sleep') { - return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType); - } - if ($name === '__set_state') { - return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType); - } - } - - $this->returnType = TypehintHelper::decideTypeFromReflection( - $returnType, + $this->returnType = TypehintHelper::decideType( + $this->getNativeReturnType(), $this->phpDocReturnType, - $this->declaringClass, ); } @@ -344,8 +323,28 @@ private function getPhpDocReturnType(): Type private function getNativeReturnType(): Type { if ($this->nativeReturnType === null) { + $returnType = $this->reflection->getReturnType(); + if ($returnType === null) { + $name = strtolower($this->getName()); + if (in_array($this->getName(), ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { + return $this->nativeReturnType = new VoidType(); + } + if ($name === '__tostring') { + return $this->nativeReturnType = new StringType(); + } + if ($name === '__isset') { + return $this->nativeReturnType = new BooleanType(); + } + if ($name === '__sleep') { + return $this->nativeReturnType = new ArrayType(new IntegerType(), new StringType()); + } + if ($name === '__set_state') { + return $this->nativeReturnType = new ObjectWithoutClassType(); + } + } + $this->nativeReturnType = TypehintHelper::decideTypeFromReflection( - $this->reflection->getReturnType(), + $returnType, null, $this->declaringClass, ); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 5ae08aac5d0..df89b763b17 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -68,8 +68,6 @@ public static function decideTypeFromReflection( $type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass); if ($reflectionType->allowsNull()) { $type = TypeCombinator::addNull($type); - } elseif ($phpDocType !== null) { - $phpDocType = TypeCombinator::removeNull($phpDocType); } return self::decideType($type, $phpDocType); @@ -80,6 +78,9 @@ public static function decideType( ?Type $phpDocType, ): Type { + if ($phpDocType !== null && $type->isNull()->no()) { + $phpDocType = TypeCombinator::removeNull($phpDocType); + } if ($type instanceof BenevolentUnionType) { return $type; } From f615b1a39a70d9ec9e49963474c7f9f9577cf88b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 29 Apr 2025 16:01:53 +0200 Subject: [PATCH 1319/3097] non-falsy-string cannot be converted to 0 --- src/Type/Accessory/AccessoryNonFalsyStringType.php | 3 ++- tests/PHPStan/Analyser/nsrt/bug-10893.php | 12 ++++++------ tests/PHPStan/Analyser/nsrt/non-falsy-string.php | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 3befd2d478d..90e7c1f64d5 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -31,6 +31,7 @@ use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -183,7 +184,7 @@ public function toAbsoluteNumber(): Type public function toInteger(): Type { - return new IntegerType(); + return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType(0)); } public function toFloat(): Type diff --git a/tests/PHPStan/Analyser/nsrt/bug-10893.php b/tests/PHPStan/Analyser/nsrt/bug-10893.php index 0878d2f3028..a2b8396dd57 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10893.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10893.php @@ -10,16 +10,16 @@ function hasMicroseconds(\DateTimeInterface $value, string $str): bool { assertType('non-falsy-string&numeric-string', $str); - assertType('int', (int)$str); - assertType('bool', (int)$str !== 0); + assertType('int|int<1, max>', (int)$str); + assertType('true', (int)$str !== 0); assertType('non-falsy-string&numeric-string', $value->format('u')); - assertType('int', (int)$value->format('u')); - assertType('bool', (int)$value->format('u') !== 0); + assertType('int|int<1, max>', (int)$value->format('u')); + assertType('true', (int)$value->format('u') !== 0); assertType('non-falsy-string&numeric-string', $value->format('v')); - assertType('int', (int)$value->format('v')); - assertType('bool', (int)$value->format('v') !== 0); + assertType('int|int<1, max>', (int)$value->format('v')); + assertType('true', (int)$value->format('v') !== 0); assertType('float', $value->format('u') * 1e-6); assertType('float', $value->format('v') * 1e-3); diff --git a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php index cef87c8ac44..5e7e05f229d 100644 --- a/tests/PHPStan/Analyser/nsrt/non-falsy-string.php +++ b/tests/PHPStan/Analyser/nsrt/non-falsy-string.php @@ -11,7 +11,7 @@ class Foo { * @param truthy-string $truthyString */ public function bar($nonFalseyString, $truthyString) { - assertType('int', (int) $nonFalseyString); + assertType('int|int<1, max>', (int) $nonFalseyString); // truthy-string is an alias for non-falsy-string assertType('non-falsy-string', $truthyString); } From 2ac87fcb3d7bd0f148019bff21111e05233761a1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 30 Apr 2025 14:09:53 +0200 Subject: [PATCH 1320/3097] Fix crash on dynamic numeric-string symbols --- src/Rules/Classes/ClassConstantRule.php | 6 +++++- src/Rules/Methods/CallMethodsRule.php | 6 +++++- src/Rules/Methods/CallStaticMethodsRule.php | 6 +++++- src/Rules/Variables/DefinedVariableRule.php | 6 +++++- .../Analyser/AnalyserIntegrationTest.php | 14 ++++++++++++++ tests/PHPStan/Analyser/data/bug-12949.php | 19 +++++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12949.php diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 2d086029858..a14428d8908 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -64,7 +64,11 @@ public function processNode(Node $node, Scope $scope): array } foreach ($constantNameScopes as $constantName => $constantScope) { - $errors = array_merge($errors, $this->processSingleClassConstFetch($constantScope, $node, $constantName)); + $errors = array_merge($errors, $this->processSingleClassConstFetch( + $constantScope, + $node, + (string) $constantName, // @phpstan-ignore cast.useless + )); } return $errors; diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 8c01d0118a0..984e5c8efec 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -47,7 +47,11 @@ public function processNode(Node $node, Scope $scope): array } foreach ($methodNameScopes as $methodName => $methodScope) { - $errors = array_merge($errors, $this->processSingleMethodCall($methodScope, $node, $methodName)); + $errors = array_merge($errors, $this->processSingleMethodCall( + $methodScope, + $node, + (string) $methodName, // @phpstan-ignore cast.useless + )); } return $errors; diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 954c3a8669e..04ffc64325f 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -48,7 +48,11 @@ public function processNode(Node $node, Scope $scope): array } foreach ($methodNameScopes as $methodName => $methodScope) { - $errors = array_merge($errors, $this->processSingleMethodCall($methodScope, $node, $methodName)); + $errors = array_merge($errors, $this->processSingleMethodCall( + $methodScope, + $node, + (string) $methodName, // @phpstan-ignore cast.useless + )); } return $errors; diff --git a/src/Rules/Variables/DefinedVariableRule.php b/src/Rules/Variables/DefinedVariableRule.php index 2c7163434c6..2056a89dbee 100644 --- a/src/Rules/Variables/DefinedVariableRule.php +++ b/src/Rules/Variables/DefinedVariableRule.php @@ -48,7 +48,11 @@ public function processNode(Node $node, Scope $scope): array } foreach ($variableNameScopes as $name => $variableScope) { - $errors = array_merge($errors, $this->processSingleVariable($variableScope, $node, $name)); + $errors = array_merge($errors, $this->processSingleVariable( + $variableScope, + $node, + (string) $name, // @phpstan-ignore cast.useless + )); } return $errors; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 156b26cffa2..c94a5cff188 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1570,6 +1570,20 @@ public function testBug12800(): void $this->assertNoErrors($errors); } + public function testBug12949(): void + { + // Fetching class constants with a dynamic name is supported only on PHP 8.3 and later + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12949.php'); + $this->assertCount(3, $errors); + $this->assertSame('Call to an undefined method object::0().', $errors[0]->getMessage()); + $this->assertSame('Call to an undefined static method object::0().', $errors[1]->getMessage()); + $this->assertSame('Access to undefined constant object::0.', $errors[2]->getMessage()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12949.php b/tests/PHPStan/Analyser/data/bug-12949.php new file mode 100644 index 00000000000..eeafccb0de2 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12949.php @@ -0,0 +1,19 @@ +{$b}(); + $o::{$b}(); + echo $o::{$b}; + + echo ""; +} From ea7072c0a055935e4457077a370cf174f3b75b5e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 1 May 2025 22:42:27 +0200 Subject: [PATCH 1321/3097] More precise property types after assignment when `strict_types=0` --- src/Analyser/NodeScopeResolver.php | 42 +- .../Accessory/AccessoryLiteralStringType.php | 5 + .../AccessoryLowercaseStringType.php | 5 + .../Accessory/AccessoryNonEmptyStringType.php | 4 + .../Accessory/AccessoryNonFalsyStringType.php | 4 + .../Accessory/AccessoryNumericStringType.php | 4 + .../AccessoryUppercaseStringType.php | 5 + src/Type/BooleanType.php | 4 + src/Type/CallableType.php | 8 +- src/Type/Constant/ConstantBooleanType.php | 5 + src/Type/Constant/ConstantIntegerType.php | 10 + src/Type/FloatType.php | 4 + src/Type/IntegerType.php | 4 + src/Type/ObjectType.php | 12 + src/Type/StringType.php | 7 + src/Type/Traits/ObjectTypeTrait.php | 5 + .../PHPStan/Analyser/nsrt/bug-12393-php84.php | 23 + tests/PHPStan/Analyser/nsrt/bug-12393.php | 39 + .../Analyser/nsrt/bug-12393b-php84.php | 22 + tests/PHPStan/Analyser/nsrt/bug-12393b.php | 709 ++++++++++++++++++ .../remember-nullable-property-non-strict.php | 45 ++ ...rictComparisonOfDifferentTypesRuleTest.php | 5 + .../Rules/Comparison/data/bug-12946.php | 37 + .../Rules/Methods/CallMethodsRuleTest.php | 10 + .../PHPStan/Rules/Methods/data/bug-12940.php | 43 ++ 25 files changed, 1040 insertions(+), 21 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393-php84.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12393b.php create mode 100644 tests/PHPStan/Analyser/nsrt/remember-nullable-property-non-strict.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-12946.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-12940.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a769db8499e..d979a0f24a7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5628,24 +5628,25 @@ static function (): void { $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { if ($propertyReflection->hasNativeType()) { - $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); - $assignedTypeIsCompatible = false; - foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { - if ($type->isSuperTypeOf($assignedNativeType)->yes()) { - $assignedTypeIsCompatible = true; - break; + $assignedTypeIsCompatible = $propertyNativeType->isSuperTypeOf($assignedExprType)->yes(); + if (!$assignedTypeIsCompatible) { + foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { + if ($type->isSuperTypeOf($assignedExprType)->yes()) { + $assignedTypeIsCompatible = true; + break; + } } } if ($assignedTypeIsCompatible) { - $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); - } elseif ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } else { $scope = $scope->assignExpression( $var, - TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), - TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType), + TypeCombinator::intersect($assignedExprType->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), + TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), ); } } else { @@ -5716,24 +5717,25 @@ static function (): void { $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { if ($propertyReflection->hasNativeType()) { - $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); + $assignedTypeIsCompatible = $propertyNativeType->isSuperTypeOf($assignedExprType)->yes(); - $assignedTypeIsCompatible = false; - foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { - if ($type->isSuperTypeOf($assignedNativeType)->yes()) { - $assignedTypeIsCompatible = true; - break; + if (!$assignedTypeIsCompatible) { + foreach (TypeUtils::flattenTypes($propertyNativeType) as $type) { + if ($type->isSuperTypeOf($assignedExprType)->yes()) { + $assignedTypeIsCompatible = true; + break; + } } } if ($assignedTypeIsCompatible) { - $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); - } elseif ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + } else { $scope = $scope->assignExpression( $var, - TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), - TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType), + TypeCombinator::intersect($assignedExprType->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), + TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType($scope->isDeclareStrictTypes()), $propertyNativeType), ); } } else { diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 9af225a3c10..8bcc663327a 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -29,6 +29,7 @@ use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -215,6 +216,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 5ea351a924f..1e3b55b0ee9 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -29,6 +29,7 @@ use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -212,6 +213,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 961e2cbd953..f9fce63d94e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -213,6 +213,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 90e7c1f64d5..6600512da1d 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -215,6 +215,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 447bf76ecca..72f81cabdb2 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -215,6 +215,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index 18ee7399bf2..3fee19deb38 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -29,6 +29,7 @@ use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -212,6 +213,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 679c0b9824c..a703decac4d 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -113,6 +113,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this->toString(), $this); + } + return $this; } diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 72784cf114c..568c8477116 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -24,6 +24,7 @@ use PHPStan\Reflection\Php\DummyParameter; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -331,7 +332,12 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { - return TypeCombinator::union($this, new StringType(), new ArrayType(new MixedType(true), new MixedType(true)), new ObjectType(Closure::class)); + return TypeCombinator::union( + $this, + TypeCombinator::intersect(new StringType(), new AccessoryNonEmptyStringType()), + new ArrayType(new MixedType(true), new MixedType(true)), + new ObjectType(Closure::class), + ); } public function isOffsetAccessLegal(): TrinaryLogic diff --git a/src/Type/Constant/ConstantBooleanType.php b/src/Type/Constant/ConstantBooleanType.php index ea1c4b09efb..282b005c158 100644 --- a/src/Type/Constant/ConstantBooleanType.php +++ b/src/Type/Constant/ConstantBooleanType.php @@ -14,6 +14,7 @@ use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; /** @api */ @@ -109,6 +110,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this->toString(), $this); + } + return $this; } diff --git a/src/Type/Constant/ConstantIntegerType.php b/src/Type/Constant/ConstantIntegerType.php index 52f29d37a29..6b482c62e6e 100644 --- a/src/Type/Constant/ConstantIntegerType.php +++ b/src/Type/Constant/ConstantIntegerType.php @@ -14,6 +14,7 @@ use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait; use PHPStan\Type\Traits\ConstantScalarTypeTrait; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; use function abs; use function sprintf; @@ -92,6 +93,15 @@ public function toArrayKey(): Type return $this; } + public function toCoercedArgumentType(bool $strictTypes): Type + { + if (!$strictTypes) { + return TypeCombinator::union($this, $this->toFloat(), $this->toString(), $this->toBoolean()); + } + + return TypeCombinator::union($this, $this->toFloat()); + } + public function generalize(GeneralizePrecision $precision): Type { return new IntegerType(); diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 253af75e4e5..e38e5be35a4 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -145,6 +145,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this->toInteger(), $this, $this->toString(), $this->toBoolean()); + } + return $this; } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index e974888bc71..fcb6fcd8936 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -100,6 +100,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this, $this->toFloat(), $this->toString(), $this->toBoolean()); + } + return TypeCombinator::union($this, $this->toFloat()); } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index e5b2540d7b3..cc834e29500 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -706,6 +706,18 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + $classReflection = $this->getClassReflection(); + if ( + $classReflection === null + || !$classReflection->hasNativeMethod('__toString') + ) { + return $this; + } + + return TypeCombinator::union($this, $this->toString()); + } + return $this; } diff --git a/src/Type/StringType.php b/src/Type/StringType.php index c0114d84627..0f1778aa213 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -183,6 +183,13 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + if ($this->isNumericString()->no()) { + return TypeCombinator::union($this, $this->toBoolean()); + } + return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean()); + } + return $this; } diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index fe2a3f6ee65..c600f2d74a8 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -21,6 +21,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; trait ObjectTypeTrait { @@ -275,6 +276,10 @@ public function toArrayKey(): Type public function toCoercedArgumentType(bool $strictTypes): Type { + if (!$strictTypes) { + return TypeCombinator::union($this, $this->toString()); + } + return $this; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393-php84.php b/tests/PHPStan/Analyser/nsrt/bug-12393-php84.php new file mode 100644 index 00000000000..b73906fdfda --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393-php84.php @@ -0,0 +1,23 @@ += 8.4 + +declare(strict_types = 1); + +namespace Bug12393Php84; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + + +class StringableFoo { + private string $foo; + + // https://3v4l.org/2SPPj#v8.4.6 + public function doFoo3(\BcMath\Number $foo): void { + $this->foo = $foo; + assertType('*NEVER*', $this->foo); + } + + public function __toString(): string { + return 'Foo'; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393.php b/tests/PHPStan/Analyser/nsrt/bug-12393.php index 9445d8632b4..4edd2300c13 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12393.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12393.php @@ -143,3 +143,42 @@ public function getMixed() } } + +// https://3v4l.org/LK6Rh +class CallableString { + private string $foo; + + public function doFoo(callable $foo): void { + $this->foo = $foo; // PHPStorm wrongly reports an error on this line + assertType('callable-string|non-empty-string', $this->foo); + } +} + +// https://3v4l.org/WJ8NW +class CallableArray { + private array $foo; + + public function doFoo(callable $foo): void { + $this->foo = $foo; + assertType('array', $this->foo); // could be non-empty-array + } +} + +class StringableFoo { + private string $foo; + + // https://3v4l.org/DQSgA#v8.4.6 + public function doFoo(StringableFoo $foo): void { + $this->foo = $foo; + assertType('*NEVER*', $this->foo); + } + + public function doFoo2(NotStringable $foo): void { + $this->foo = $foo; + assertType('*NEVER*', $this->foo); + } + + public function __toString(): string { + return 'Foo'; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php b/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php new file mode 100644 index 00000000000..ae1946cdb2c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393b-php84.php @@ -0,0 +1,22 @@ += 8.4 + +declare(strict_types = 0); + +namespace Bug12393bPhp84; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class StringableFoo { + private string $foo; + + // https://3v4l.org/nelJF#v8.4.6 + public function doFoo3(\BcMath\Number $foo): void { + $this->foo = $foo; + assertType('non-empty-string&numeric-string', $this->foo); + } + + public function __toString(): string { + return 'Foo'; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-12393b.php b/tests/PHPStan/Analyser/nsrt/bug-12393b.php new file mode 100644 index 00000000000..7ec8f3012be --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12393b.php @@ -0,0 +1,709 @@ += 8.0 + +declare(strict_types = 0); + +namespace Bug12393b; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + private string $name; + + /** @var string */ + private $untypedName; + + private float $float; + + /** @var float */ + private $untypedFloat; + + private array $a; + + /** + * @param mixed[] $plugin + */ + public function __construct(array $plugin){ + $this->name = $plugin["name"]; + assertType('string', $this->name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + $this->untypedName = $plugin["name"]; + assertType('mixed', $this->untypedName); + } + + public function doBar(int $i){ + $this->float = $i; + assertType('float', $this->float); + } + + public function doBaz(int $i){ + $this->untypedFloat = $i; + assertType('int', $this->untypedFloat); + } + + public function doLorem(): void + { + $this->a = ['a' => 1]; + assertType('array{a: 1}', $this->a); + } + + public function doFloatTricky(){ + $this->float = 1; + assertType('1.0', $this->float); + } +} + +class HelloWorldStatic +{ + private static string $name; + + /** @var string */ + private static $untypedName; + + private static float $float; + + /** @var float */ + private static $untypedFloat; + + private static array $a; + + /** + * @param mixed[] $plugin + */ + public function __construct(array $plugin){ + self::$name = $plugin["name"]; + assertType('string', self::$name); + } + + /** + * @param mixed[] $plugin + */ + public function doFoo(array $plugin){ + self::$untypedName = $plugin["name"]; + assertType('mixed', self::$untypedName); + } + + public function doBar(int $i){ + self::$float = $i; + assertType('float', self::$float); + } + + public function doBaz(int $i){ + self::$untypedFloat = $i; + assertType('int', self::$untypedFloat); + } + + public function doLorem(): void + { + self::$a = ['a' => 1]; + assertType('array{a: 1}', self::$a); + } +} + +class EntryPointLookup +{ + + /** @var array|null */ + private ?array $entriesData = null; + + /** + * @return array + */ + public function doFoo(): void + { + if ($this->entriesData !== null) { + return; + } + + assertType('null', $this->entriesData); + assertNativeType('null', $this->entriesData); + + $data = $this->getMixed(); + if ($data !== null) { + $this->entriesData = $data; + assertType('array', $this->entriesData); + assertNativeType('array', $this->entriesData); + return; + } + + assertType('null', $this->entriesData); + assertNativeType('null', $this->entriesData); + } + + /** + * @return mixed + */ + public function getMixed() + { + + } + +} + +class FooStringInt +{ + + public int $foo; + + public function doFoo(string $s): void + { + $this->foo = $s; + assertType('int', $this->foo); + } + + public function doBar(): void + { + $this->foo = 'foo'; + assertType('*NEVER*', $this->foo); + $this->foo = '123'; + assertType('123', $this->foo); + } + + /** + * @param non-empty-string $nonEmpty + * @param non-falsy-string $nonFalsy + * @param numeric-string $numeric + * @param literal-string $literal + * @param lowercase-string $lower + * @param uppercase-string $upper + */ + function doStrings($nonEmpty, $nonFalsy, $numeric, $literal, $lower, $upper) { + $this->foo = $nonEmpty; + assertType('int', $this->foo); + $this->foo = $nonFalsy; + assertType('int|int<1, max>', $this->foo); + $this->foo = $numeric; + assertType('int', $this->foo); + $this->foo = $literal; + assertType('int', $this->foo); + $this->foo = $lower; + assertType('int', $this->foo); + $this->foo = $upper; + assertType('int', $this->foo); + } +} + +class FooStringFloat +{ + + public float $foo; + + public function doFoo(string $s): void + { + $this->foo = $s; + assertType('float', $this->foo); + } + + public function doBar(): void + { + $this->foo = 'foo'; + assertType('*NEVER*', $this->foo); + $this->foo = '123'; + assertType('123.0', $this->foo); + } + + /** + * @param non-empty-string $nonEmpty + * @param non-falsy-string $nonFalsy + * @param numeric-string $numeric + * @param literal-string $literal + * @param lowercase-string $lower + * @param uppercase-string $upper + */ + function doStrings($nonEmpty, $nonFalsy, $numeric, $literal, $lower, $upper) { + $this->foo = $nonEmpty; + assertType('float', $this->foo); + $this->foo = $nonFalsy; + assertType('float', $this->foo); + $this->foo = $numeric; + assertType('float', $this->foo); + $this->foo = $literal; + assertType('float', $this->foo); + $this->foo = $lower; + assertType('float', $this->foo); + $this->foo = $upper; + assertType('float', $this->foo); + } +} + +class FooStringBool +{ + + public bool $foo; + + public function doFoo(string $s): void + { + $this->foo = $s; + assertType('bool', $this->foo); + } + + public function doBar(): void + { + $this->foo = '0'; + assertType('false', $this->foo); + $this->foo = 'foo'; + assertType('true', $this->foo); + $this->foo = '123'; + assertType('true', $this->foo); + } + + /** + * @param non-empty-string $nonEmpty + * @param non-falsy-string $nonFalsy + * @param numeric-string $numeric + * @param literal-string $literal + * @param lowercase-string $lower + * @param uppercase-string $upper + */ + function doStrings($nonEmpty, $nonFalsy, $numeric, $literal, $lower, $upper) { + $this->foo = $nonEmpty; + assertType('bool', $this->foo); + $this->foo = $nonFalsy; + assertType('true', $this->foo); + $this->foo = $numeric; + assertType('bool', $this->foo); + $this->foo = $literal; + assertType('bool', $this->foo); + $this->foo = $lower; + assertType('bool', $this->foo); + $this->foo = $upper; + assertType('bool', $this->foo); + } +} + +class FooBoolInt +{ + + public int $foo; + + public function doFoo(bool $b): void + { + $this->foo = $b; + assertType('0|1', $this->foo); + } + + public function doBar(): void + { + $this->foo = true; + assertType('1', $this->foo); + $this->foo = false; + assertType('0', $this->foo); + } +} + +class FooVoidInt { + private ?int $foo; + private int $fooNonNull; + + public function doFoo(): void { + $this->foo = $this->returnVoid(); + assertType('null', $this->foo); + + $this->fooNonNull = $this->returnVoid(); + assertType('int|null', $this->foo); // should be *NEVER* + } + + public function returnVoid(): void { + return; + } +} + + +class FooBoolString +{ + + public string $foo; + + public function doFoo(bool $b): void + { + $this->foo = $b; + assertType("''|'1'", $this->foo); + } + + public function doBar(): void + { + $this->foo = true; + assertType("'1'", $this->foo); + $this->foo = false; + assertType("''", $this->foo); + } +} + +class FooIntString +{ + + public string $foo; + + public function doFoo(int $b): void + { + $this->foo = $b; + assertType('lowercase-string&numeric-string&uppercase-string', $this->foo); + } + + public function doBar(): void + { + $this->foo = -1; + assertType("'-1'", $this->foo); + $this->foo = 1; + assertType("'1'", $this->foo); + $this->foo = 0; + assertType("'0'", $this->foo); + } +} + +class FooIntBool +{ + + public bool $foo; + + public function doFoo(int $b): void + { + $this->foo = $b; + assertType('bool', $this->foo); + + if ($b !== 0) { + $this->foo = $b; + assertType('true', $this->foo); + } + if ($b !== 1) { + $this->foo = $b; + assertType('bool', $this->foo); + } + } + + public function doBar(): void + { + $this->foo = -1; + assertType("true", $this->foo); + $this->foo = 1; + assertType("true", $this->foo); + $this->foo = 0; + assertType("false", $this->foo); + } +} + +class FooIntRangeString +{ + + public string $foo; + + /** + * @param int<5, 10> $b + */ + public function doFoo(int $b): void + { + $this->foo = $b; + assertType("'10'|'5'|'6'|'7'|'8'|'9'", $this->foo); + } + + public function doBar(): void + { + $i = rand(5, 10); + $this->foo = $i; + assertType("'10'|'5'|'6'|'7'|'8'|'9'", $this->foo); + } +} + +class FooNullableIntString +{ + + public string $foo; + + public function doFoo(?int $b): void + { + $this->foo = $b; + assertType('lowercase-string&numeric-string&uppercase-string', $this->foo); + } + + public function doBar(): void + { + $this->foo = null; + assertType('*NEVER*', $this->foo); // null cannot be coerced to string, see https://3v4l.org/5k1Dl + } +} + +class FooFloatString +{ + + public string $foo; + + public function doFoo(float $b): void + { + $this->foo = $b; + assertType('numeric-string&uppercase-string', $this->foo); + } + + public function doBar(): void + { + $this->foo = 1.0; + assertType("'1'", $this->foo); + } +} + +class FooStringToUnion +{ + + public int|float $foo; + + public function doFoo(string $b): void + { + $this->foo = $b; + assertType('float|int', $this->foo); + } + + public function doBar(): void + { + $this->foo = "1.0"; + assertType('1|1.0', $this->foo); + } +} + +class FooNumericToString +{ + + public string $foo; + + public function doFoo(float|int $b): void + { + $this->foo = $b; + assertType('numeric-string&uppercase-string', $this->foo); + } + +} + +class FooMixedToInt +{ + + public int $foo; + + public function doFoo(mixed $b): void + { + $this->foo = $b; + assertType('int', $this->foo); + } + +} + + +class FooArrayToInt +{ + public int $foo; + + public function doFoo(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-array $arr + */ + public function doBar(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-list $list + */ + public function doBaz(array $list): void + { + $this->foo = $list; + assertType('*NEVER*', $this->foo); + } +} + +class FooArrayToFloat +{ + public float $foo; + + public function doFoo(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-array $arr + */ + public function doBar(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-list $list + */ + public function doBaz(array $list): void + { + $this->foo = $list; + assertType('*NEVER*', $this->foo); + } +} + +class FooArrayToString +{ + public string $foo; + + public function doFoo(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-array $arr + */ + public function doBar(array $arr): void + { + $this->foo = $arr; + assertType('*NEVER*', $this->foo); + } + + /** + * @param non-empty-list $list + */ + public function doBaz(array $list): void + { + $this->foo = $list; + assertType('*NEVER*', $this->foo); + } +} + +class FooArray +{ + public array $foo; + + /** + * @param non-empty-array $arr + */ + public function doFoo(array $arr): void + { + $this->foo = $arr; + assertType('non-empty-array', $this->foo); + + if (array_key_exists('foo', $arr)) { + $this->foo = $arr; + assertType("non-empty-array&hasOffset('foo')", $this->foo); + } + + if (array_key_exists('foo', $arr) && $arr['foo'] === 'bar') { + $this->foo = $arr; + assertType("non-empty-array&hasOffsetValue('foo', 'bar')", $this->foo); + } + } +} + +class FooTypedArray +{ + /** + * @var array + */ + public array $foo; + + /** + * @param array $arr + */ + public function doFoo(array $arr): void + { + $this->foo = $arr; + assertType('array', $this->foo); + } + + /** + * @param array $arr + */ + public function doBar(array $arr): void + { + $this->foo = $arr; + assertType('array', $this->foo); + } +} + +class FooList +{ + public array $foo; + + /** + * @param non-empty-list $list + */ + public function doFoo(array $list): void + { + $this->foo = $list; + assertType('non-empty-list', $this->foo); + + if (array_key_exists(3, $list)) { + $this->foo = $list; + assertType("non-empty-list&hasOffset(3)", $this->foo); + } + + if (array_key_exists(3, $list) && is_string($list[3])) { + $this->foo = $list; + assertType("non-empty-list&hasOffsetValue(3, string)", $this->foo); + } + } + +} + +// https://3v4l.org/LJiRB +class CallableString { + private string $foo; + + public function doFoo(callable $foo): void { + $this->foo = $foo; + assertType('callable-string|non-empty-string', $this->foo); + } +} + +// https://3v4l.org/VvUsp +class CallableArray { + private array $foo; + + public function doFoo(callable $foo): void { + $this->foo = $foo; + assertType('array', $this->foo); // could be non-empty-array + } +} + +class StringableFoo { + private string $foo; + + public function doFoo(StringableFoo $foo): void { + $this->foo = $foo; + assertType('string', $this->foo); + } + + public function doFoo2(NotStringable $foo): void { + $this->foo = $foo; + assertType('*NEVER*', $this->foo); + } + + public function __toString(): string { + return 'Foo'; + } +} + +final class NotStringable {} + +class ObjectWithToStringMethod { + private string $foo; + + public function doFoo(object $foo): void { + if (method_exists($foo, '__toString')) { + $this->foo = $foo; + assertType('string', $this->foo); + } + } + public function __toString(): string { + return 'Foo'; + } +} + diff --git a/tests/PHPStan/Analyser/nsrt/remember-nullable-property-non-strict.php b/tests/PHPStan/Analyser/nsrt/remember-nullable-property-non-strict.php new file mode 100644 index 00000000000..9618bc818f6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/remember-nullable-property-non-strict.php @@ -0,0 +1,45 @@ += 8.1 + +declare(strict_types = 0); + +namespace RememberNullablePropertyWhenStrictTypesDisabled; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +interface ObjectDataMapper +{ + /** + * @template OutType of object + * + * @param literal-string&class-string $class + * @param mixed $data + * + * @return OutType + * + * @throws \Exception + */ + public function map(string $class, $data): object; +} + +final class ApiProductController +{ + + protected ?SearchProductsVM $searchProductsVM = null; + + protected static ?SearchProductsVM $searchProductsVMStatic = null; + + public function search(ObjectDataMapper $dataMapper): void + { + $this->searchProductsVM = $dataMapper->map(SearchProductsVM::class, $_REQUEST); + assertType('RememberNullablePropertyWhenStrictTypesDisabled\SearchProductsVM', $this->searchProductsVM); + } + + public function searchStatic(ObjectDataMapper $dataMapper): void + { + self::$searchProductsVMStatic = $dataMapper->map(SearchProductsVM::class, $_REQUEST); + assertType('RememberNullablePropertyWhenStrictTypesDisabled\SearchProductsVM', self::$searchProductsVMStatic); + } +} + +class SearchProductsVM {} diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 68cd2cc0590..4c27bfd80f2 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1016,4 +1016,9 @@ public function testBug11019(): void $this->analyse([__DIR__ . '/data/bug-11019.php'], []); } + public function testBug12946(): void + { + $this->analyse([__DIR__ . '/data/bug-12946.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12946.php b/tests/PHPStan/Rules/Comparison/data/bug-12946.php new file mode 100644 index 00000000000..895b0573bfa --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12946.php @@ -0,0 +1,37 @@ += 8.1 + +namespace Bug12946; + +interface UserInterface {} +class User implements UserInterface{} + +class UserMapper { + function getFromId(int $id) : ?UserInterface { + return $id === 10 ? new User : null; + } +} + +class GetUserCommand { + + private ?UserInterface $currentUser = null; + + public function __construct( + private readonly UserMapper $userMapper, + private readonly int $id, + ) { + } + + public function __invoke() : UserInterface { + if( $this->currentUser ) { + return $this->currentUser; + } + + $this->currentUser = $this->userMapper->getFromId($this->id); + if( $this->currentUser === null ) { + throw new \Exception; + } + + return $this->currentUser; + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index bc177e9d0b4..decb237d34a 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3616,4 +3616,14 @@ public function testBug12880(): void $this->analyse([__DIR__ . '/data/bug-12880.php'], []); } + public function testBug12940(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12940.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-12940.php b/tests/PHPStan/Rules/Methods/data/bug-12940.php new file mode 100644 index 00000000000..ad00e11c1b3 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12940.php @@ -0,0 +1,43 @@ + $className + * @return T + */ + public static function makeInstance(string $className, mixed ...$args): object + { + return new $className(...$args); + } +} + +class PageRenderer +{ + public function setTemplateFile(string $path): void + { + } + + public function setLanguage(string $lang): void + { + } +} + +class TypoScriptFrontendController +{ + + protected ?PageRenderer $pageRenderer = null; + + public function initializePageRenderer(): void + { + if ($this->pageRenderer !== null) { + return; + } + $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $this->pageRenderer->setTemplateFile('EXT:frontend/Resources/Private/Templates/MainPage.html'); + $this->pageRenderer->setLanguage('DE'); + } +} From 0b9252ba4352ffa7bc00ed6c4d6af2edd9c882b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 03:15:12 +0000 Subject: [PATCH 1322/3097] Update symfony packages to v5.4.47 --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 91c8b4031c8..f07987c2b6a 100644 --- a/composer.lock +++ b/composer.lock @@ -3178,16 +3178,16 @@ }, { "name": "symfony/console", - "version": "v5.4.43", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e86f8554de667c16dde8aeb89a3990cfde924df9", - "reference": "e86f8554de667c16dde8aeb89a3990cfde924df9", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { @@ -3257,7 +3257,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.43" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -3273,7 +3273,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T16:31:56+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3344,16 +3344,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.43", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ae25a9145a900764158d439653d5630191155ca0" + "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", - "reference": "ae25a9145a900764158d439653d5630191155ca0", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { @@ -3387,7 +3387,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.43" + "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { @@ -3403,7 +3403,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:03:51+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4026,16 +4026,16 @@ }, { "name": "symfony/string", - "version": "v5.4.43", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450" + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8be1d484951ff5ca995eaf8edcbcb8b9a5888450", - "reference": "8be1d484951ff5ca995eaf8edcbcb8b9a5888450", + "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", + "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", "shasum": "" }, "require": { @@ -4092,7 +4092,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.43" + "source": "https://github.com/symfony/string/tree/v5.4.47" }, "funding": [ { @@ -4108,7 +4108,7 @@ "type": "tidelift" } ], - "time": "2024-08-01T10:24:28+00:00" + "time": "2024-11-10T20:33:58+00:00" } ], "packages-dev": [ From 1d1672fb832942c547a6813e77283a3837e5af34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 03:15:03 +0000 Subject: [PATCH 1323/3097] Update dependency phpunit/phpunit to v9.6.23 --- composer.lock | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/composer.lock b/composer.lock index f07987c2b6a..b304bec2cba 100644 --- a/composer.lock +++ b/composer.lock @@ -4384,16 +4384,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -4432,7 +4432,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -4440,7 +4440,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "ondrejmirtes/simple-downgrader", @@ -5199,16 +5199,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { @@ -5219,7 +5219,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -5282,7 +5282,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -5293,12 +5293,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", From f2acf146d170b3042f82abe528b63174c8206eb5 Mon Sep 17 00:00:00 2001 From: Niklan Date: Wed, 29 Jan 2025 19:49:53 +0500 Subject: [PATCH 1324/3097] Add stub for \DOMNode::hasAttributes --- stubs/dom.stub | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/stubs/dom.stub b/stubs/dom.stub index d2a5c575fcc..df039269152 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -30,6 +30,17 @@ class DOMDocument class DOMNode { + /** + * @var DOMNamedNodeMap|null + */ + public $attributes; + + /** + * @phpstan-assert-if-true !null $this->attributes + * @return bool + */ + public function hasAttributes() {} + } class DOMElement extends DOMNode From 1044f1112a0be3e4e6384ee7d35a66cd92e9cba6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 May 2025 10:09:40 +0200 Subject: [PATCH 1325/3097] Fix GetDebugTypeFunctionReturnTypeExtension - should use TypeCombinator instead of `new UnionType` --- ...etDebugTypeFunctionReturnTypeExtension.php | 6 ++--- .../Analyser/AnalyserIntegrationTest.php | 10 +++++++++ tests/PHPStan/Analyser/data/bug-12512.php | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12512.php diff --git a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php index fa22bc9c061..6860694293b 100644 --- a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php @@ -2,7 +2,6 @@ namespace PHPStan\Type\Php; -use Closure; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; @@ -10,6 +9,7 @@ use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use function array_key_first; use function array_map; @@ -31,7 +31,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($argType instanceof UnionType) { - return new UnionType(array_map(Closure::fromCallable([self::class, 'resolveOneType']), $argType->getTypes())); + return TypeCombinator::union(...array_map(static fn (Type $type) => self::resolveOneType($type), $argType->getTypes())); } return self::resolveOneType($argType); } @@ -92,7 +92,7 @@ private static function resolveOneType(Type $type): Type case 1: return $types[0]; default: - return new UnionType($types); + return TypeCombinator::union(...$types); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index c94a5cff188..325f09f20b2 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -448,6 +448,16 @@ public function testBug5231Two(): void $this->assertNotEmpty($errors); } + public function testBug12512(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12512.php'); + $this->assertNoErrors($errors); + } + public function testBug5529(): void { $errors = $this->runAnalyse(__DIR__ . '/nsrt/bug-5529.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12512.php b/tests/PHPStan/Analyser/data/bug-12512.php new file mode 100644 index 00000000000..612927a31ae --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12512.php @@ -0,0 +1,22 @@ += 8.1 + +namespace Bug12512; + +enum FooBarEnum: string +{ + case CASE_ONE = 'case_one'; + case CASE_TWO = 'case_two'; + case CASE_THREE = 'case_three'; + case CASE_FOUR = 'case_four'; + + public function matchFunction(): string + { + return match ($this) { + self::CASE_ONE => 'one', + self::CASE_TWO => 'two', + default => throw new \Exception( + sprintf('"%s" is not implemented yet', get_debug_type($this)) + ) + }; + } +} From 975c37cda9afe1a797bef47da110663b73d6d5b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 May 2025 10:19:06 +0200 Subject: [PATCH 1326/3097] Fix test --- tests/PHPStan/Analyser/nsrt/get-debug-type.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/get-debug-type.php b/tests/PHPStan/Analyser/nsrt/get-debug-type.php index 2408a6acde6..85c4d02b471 100644 --- a/tests/PHPStan/Analyser/nsrt/get-debug-type.php +++ b/tests/PHPStan/Analyser/nsrt/get-debug-type.php @@ -37,7 +37,7 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr assertType("string", get_debug_type($std)); assertType("'GetDebugType\\\\A'", get_debug_type($A)); assertType("string", get_debug_type($r)); - assertType("'bool'|string", get_debug_type($resource)); + assertType("string", get_debug_type($resource)); assertType("'null'", get_debug_type($null)); assertType("'int'|'string'", get_debug_type($intOrString)); assertType("'array'|'GetDebugType\\\\A'", get_debug_type($arrayOrObject)); From 031f34eb42db5682e0092074551607344b898e8a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 5 May 2025 10:23:29 +0200 Subject: [PATCH 1327/3097] Fix ImpossibleCheckTypeFunctionCallRule for `is_subclass_of` and `is_a` --- .../ImpossibleCheckTypeFunctionCallRule.php | 4 - .../IsAFunctionTypeSpecifyingExtension.php | 9 +- ...classOfFunctionTypeSpecifyingExtension.php | 9 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 4 +- ...mpossibleCheckTypeFunctionCallRuleTest.php | 37 +++++ .../Rules/Comparison/data/bug-3979.php | 130 ++++++++++++++++++ .../Rules/Comparison/data/bug-8464.php | 18 +++ .../Rules/Comparison/data/bug-8954.php | 28 ++++ .../Rules/Comparison/data/bug-pr-3404.php | 24 ++++ 9 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3979.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8464.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-8954.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-pr-3404.php diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 9033aa38658..29b0801eced 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use function sprintf; -use function strtolower; /** * @implements Rule @@ -38,9 +37,6 @@ public function processNode(Node $node, Scope $scope): array } $functionName = (string) $node->name; - if (strtolower($functionName) === 'is_a') { - return []; - } $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index c4000b9aff0..55789676aa9 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -47,9 +47,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(false); $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true), + $resultType, $context, false, $scope, diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index 2d52ee99e1b..71800c53668 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -48,9 +48,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes([], []); } + $resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false); + + // prevent false-positives in IsAFunctionTypeSpecifyingHelper + if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) { + return new SpecifiedTypes([], []); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, - $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false), + $resultType, $context, false, $scope, diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 999c7169a39..4ad6a7cec40 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -1133,9 +1133,7 @@ public function dataCondition(): iterable new Arg(new Variable('stringOrNull')), new Arg(new Expr\ConstFetch(new Name('false'))), ]), - [ - '$object' => 'object', - ], + [], [], ], [ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 16529f3a744..3236b7feeeb 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1102,4 +1102,41 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } + public function testBug3979(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-3979.php'], []); + } + + public function testBug8464(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8464.php'], []); + } + + public function testBug8954(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-8954.php'], []); + } + + public function testBugPR3404(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-pr-3404.php'], [ + [ + 'Call to function is_a() with arguments BugPR3404\Location, \'BugPR3404\\\\Location\' and true will always evaluate to true.', + 21, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3979.php b/tests/PHPStan/Rules/Comparison/data/bug-3979.php new file mode 100644 index 00000000000..f0f21220d11 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3979.php @@ -0,0 +1,130 @@ += 8.0 + +namespace Bug8464; + +final class ObjectUtil +{ + /** + * @param class-string $type + */ + public static function instanceOf(mixed $object, string $type): bool + { + return \is_object($object) + && ( + $object::class === $type || + is_subclass_of($object, $type) + ); + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-8954.php b/tests/PHPStan/Rules/Comparison/data/bug-8954.php new file mode 100644 index 00000000000..b89b47ba6d5 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-8954.php @@ -0,0 +1,28 @@ + $class + * @param class-string $expected + * + * @return ?class-string + */ +function ensureSubclassOf(?string $class, string $expected): ?string { + if ($class === null) { + return $class; + } + + if (!class_exists($class)) { + throw new \Exception("Class “{$class}” does not exist."); + } + + if (!is_subclass_of($class, $expected)) { + throw new \Exception("Class “{$class}” is not a subclass of “{$expected}”."); + } + + return $class; +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-pr-3404.php b/tests/PHPStan/Rules/Comparison/data/bug-pr-3404.php new file mode 100644 index 00000000000..7dd533ff988 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-pr-3404.php @@ -0,0 +1,24 @@ += 8.0 + +namespace BugPR3404; + +interface Location +{ + +} + +/** @return class-string */ +function aaa(): string +{ + +} + +function (Location $l): void { + if (is_a($l, aaa(), true)) { + // might not always be true. $l might be one subtype of Location, aaa() might return a name of a different subtype of Location + } + + if (is_a($l, Location::class, true)) { + // always true + } +}; From 916b8693f13d75ada60469299082f3ffe26b28c3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 May 2025 10:31:35 +0200 Subject: [PATCH 1328/3097] Fix build --- .../Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 13dc4a4f562..219e0450c1b 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -973,7 +973,6 @@ public function testAlwaysTruePregMatch(): void public function testBug3979(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-3979.php'], []); } @@ -984,21 +983,18 @@ public function testBug8464(): void $this->markTestSkipped('Test requires PHP 8.0.'); } - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8464.php'], []); } public function testBug8954(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-8954.php'], []); } public function testBugPR3404(): void { - $this->checkAlwaysTrueCheckTypeFunctionCall = true; $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-pr-3404.php'], [ [ From 2799c24a82ab27ce6b138ae40391ceabb022d550 Mon Sep 17 00:00:00 2001 From: schlndh Date: Mon, 5 May 2025 10:59:58 +0200 Subject: [PATCH 1329/3097] Add callback types for array_uintersect etc. --- src/Type/TypehintHelper.php | 7 +- stubs/arrayFunctions.stub | 153 ++++++++++++++++- .../nsrt/array_diff_intersect_callbacks.php | 127 ++++++++++++++ .../CallToFunctionParametersRuleTest.php | 162 +++++++++++++++++- .../Functions/data/array_diff_uassoc.php | 33 ++++ .../Rules/Functions/data/array_diff_ukey.php | 33 ++++ .../Functions/data/array_intersect_uassoc.php | 33 ++++ .../Functions/data/array_intersect_ukey.php | 33 ++++ .../Functions/data/array_udiff_assoc.php | 33 ++++ .../Functions/data/array_udiff_uassoc.php | 45 +++++ .../Rules/Functions/data/array_uintersect.php | 33 ++++ .../Functions/data/array_uintersect_assoc.php | 33 ++++ .../data/array_uintersect_uassoc.php | 45 +++++ .../PHPStan/Rules/Functions/data/bug-7707.php | 98 +++++++++++ 14 files changed, 859 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/array_diff_intersect_callbacks.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_diff_ukey.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-7707.php diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 8f9f5616f33..55e4843f368 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -201,8 +201,11 @@ public static function decideType( } if ( - (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) - && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + ($type->isCallable()->yes() && $phpDocType->isCallable()->yes()) + || ( + (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed())) + && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes() + ) ) { $resultType = $phpDocType; } else { diff --git a/stubs/arrayFunctions.stub b/stubs/arrayFunctions.stub index 8124ffe56a6..90822edddd8 100644 --- a/stubs/arrayFunctions.stub +++ b/stubs/arrayFunctions.stub @@ -58,20 +58,163 @@ function uksort(array &$array, callable $callback): bool } /** - * @template T of mixed + * @template TV of mixed + * @template TK of mixed * - * @param array $one - * @param array $two - * @param callable(T, T): int $three + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * @return array */ function array_udiff( array $one, array $two, callable $three -): int {} +): array {} /** * @param array $value * @return ($value is __always-list ? true : false) */ function array_is_list(array $value): bool {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int $three + * @return array + */ +function array_diff_uassoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int $three + * @return array + */ +function array_diff_ukey( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int $three + * @return array + */ +function array_intersect_uassoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TK, TK): int $three + * + * @return array + */ +function array_intersect_ukey( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * + * @return array + */ +function array_udiff_assoc( + array $one, + array $two, + callable $three +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * @param callable(TK, TK): int $four + * @return array + */ +function array_udiff_uassoc( + array $one, + array $two, + callable $three, + callable $four +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * @return array + */ +function array_uintersect_assoc( + array $one, + array $two, + callable $three, +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * @param callable(TK, TK): int $four + * @return array + */ +function array_uintersect_uassoc( + array $one, + array $two, + callable $three, + callable $four +): array {} + +/** + * @template TK of array-key + * @template TV of mixed + * + * @param array $one + * @param array $two + * @param callable(TV, TV): int $three + * @return array + */ +function array_uintersect( + array $one, + array $two, + callable $three, +): array {} diff --git a/tests/PHPStan/Analyser/nsrt/array_diff_intersect_callbacks.php b/tests/PHPStan/Analyser/nsrt/array_diff_intersect_callbacks.php new file mode 100644 index 00000000000..5dc721664e5 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array_diff_intersect_callbacks.php @@ -0,0 +1,127 @@ +, null given.', + 'Parameter #1 $arr1 of function array_udiff expects array<(int&TK)|(string&TK), string>, null given.', 20, ], [ - 'Parameter #2 $arr2 of function array_udiff expects array, null given.', + 'Parameter #2 $arr2 of function array_udiff expects array<(int&TK)|(string&TK), string>, null given.', 21, ], [ @@ -1839,6 +1839,164 @@ public function testCountArrayShift(): void $this->analyse([__DIR__ . '/data/count-array-shift.php'], $errors); } + public function testArrayDiffUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_diff_uassoc.php'], [ + [ + 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_comp_func of function array_diff_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayDiffUkey(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_diff_ukey.php'], [ + [ + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_comp_func of function array_diff_ukey expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayIntersectUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_intersect_uassoc.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayIntersectUkey(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_intersect_ukey.php'], [ + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_compare_func of function array_intersect_ukey expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUdiffAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_assoc.php'], [ + [ + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2, 1|2): int, Closure(string, string): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $key_comp_func of function array_udiff_assoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUdiffUasssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_udiff_uassoc.php'], [ + [ + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $data_comp_func of function array_udiff_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_comp_func of function array_udiff_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 42, + ], + ]); + } + + public function testArrayUintersectAssoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_assoc.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect_assoc expects callable(1|2|3|4, 1|2|3|4): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testArrayUintersectUassoc(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect_uassoc.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 28, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 31, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect_uassoc expects callable(1|2|3|4|5, 1|2|3|4|5): int, Closure(string, string): int<-1, 1> given.', + 39, + ], + [ + 'Parameter #4 $key_compare_func of function array_uintersect_uassoc expects callable(0|1|2|3, 0|1|2|3): int, Closure(string, string): int<-1, 1> given.', + 42, + ], + ]); + } + + public function testArrayUintersect(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/array_uintersect.php'], [ + [ + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(\'a\'|\'b\'|\'c\'|\'d\', \'a\'|\'b\'|\'c\'|\'d\'): int, Closure(int, int): int<-1, 1> given.', + 22, + ], + [ + 'Parameter #3 $data_compare_func of function array_uintersect expects callable(1|2|3|4, 1|2|3|4): int, Closure(string, string): int<-1, 1> given.', + 30, + ], + ]); + } + + public function testBug7707(): void + { + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-7707.php'], []); + } + public function testNoNamedArguments(): void { if (PHP_VERSION_ID < 80000) { diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php new file mode 100644 index 00000000000..257fc17c854 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_uassoc.php @@ -0,0 +1,33 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php new file mode 100644 index 00000000000..8a98630a887 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_diff_ukey.php @@ -0,0 +1,33 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_diff_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php new file mode 100644 index 00000000000..9f8ba1bfa3e --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_uassoc.php @@ -0,0 +1,33 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php new file mode 100644 index 00000000000..4d81086d244 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_intersect_ukey.php @@ -0,0 +1,33 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_intersect_ukey( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php new file mode 100644 index 00000000000..7827734e59f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_assoc.php @@ -0,0 +1,33 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_assoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php new file mode 100644 index 00000000000..f4767621c20 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_udiff_uassoc.php @@ -0,0 +1,45 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + }, +); + +array_udiff_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_udiff_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect.php b/tests/PHPStan/Rules/Functions/data/array_uintersect.php new file mode 100644 index 00000000000..dbe5307a821 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect.php @@ -0,0 +1,33 @@ + $b; + } +); + +array_uintersect( + [1, 2, 3], + [1, 2, 3, 4], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect( + ['a', 'b'], + ['c', 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect( + [1, 2, 3], + [1, 2, 3, 4], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php new file mode 100644 index 00000000000..e410f3827ec --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_assoc.php @@ -0,0 +1,33 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + [1, 2, 3], + [1, 2, 3, 4], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_assoc( + [1, 2, 3], + [1, 2, 3, 4], + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php new file mode 100644 index 00000000000..f079aec189d --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_uintersect_uassoc.php @@ -0,0 +1,45 @@ + 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + }, +); + +array_uintersect_uassoc( + ['a' => 'a', 'b' => 'b'], + ['c' => 'c', 'd' => 'd'], + static function (int $a, int $b): int { + return $a <=> $b; + }, + static function (int $a, int $b): int { + return $a <=> $b; + } +); + +array_uintersect_uassoc( + [1, 2, 3], + [1, 2, 4, 5], + static function (string $a, string $b): int { + return $a <=> $b; + }, + static function (string $a, string $b): int { + return $a <=> $b; + } +); diff --git a/tests/PHPStan/Rules/Functions/data/bug-7707.php b/tests/PHPStan/Rules/Functions/data/bug-7707.php new file mode 100644 index 00000000000..1fc7bbab6c8 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-7707.php @@ -0,0 +1,98 @@ + 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_diff_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_intersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_intersect_ukey( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_udiff_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + + var_dump(array_udiff_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + }, + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect_assoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect_uassoc( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + }, + // keys comparison + static function (string $a, string $b): int { + return $a <=> $b; + } + )); + + var_dump(array_uintersect( + ['a' => 1, 'b' => 2], + ['c' => 1, 'd' => 2], + // values comparison + static function (int $a, int $b): int { + return $a <=> $b; + } + )); + } +} From 76b094d985df05652805504359b534ee1becc657 Mon Sep 17 00:00:00 2001 From: Felix Bernhard Date: Mon, 5 May 2025 11:13:07 +0200 Subject: [PATCH 1330/3097] TableErrorFormatter: improve formatting of error tips --- .../ErrorFormatter/TableErrorFormatter.php | 6 ++++-- .../ErrorFormatter/TableErrorFormatterTest.php | 16 ++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index dc0ce7e244e..f69eca834e9 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -16,6 +16,7 @@ use function in_array; use function is_string; use function ltrim; +use function rtrim; use function sprintf; use function str_contains; use function str_replace; @@ -83,7 +84,7 @@ public function formatErrors( $filePath = $error->getTraitFilePath() ?? $error->getFilePath(); if ($error->getIdentifier() !== null && $error->canBeIgnored()) { $message .= "\n"; - $message .= '🪪 ' . $error->getIdentifier(); + $message .= '🪪 ' . $error->getIdentifier(); } if ($error->getTip() !== null) { $tip = $error->getTip(); @@ -95,6 +96,7 @@ public function formatErrors( foreach ($lines as $line) { $message .= '💡 ' . ltrim($line, ' •') . "\n"; } + $message = rtrim($message, "\n"); } else { $message .= '💡 ' . $tip; } @@ -116,7 +118,7 @@ public function formatErrors( $title = $this->relativePathHelper->getRelativePath($filePath); } - $message .= "\n✏️ ' . $title . ''; + $message .= "\n✏️ ' . $title . ''; } if ( diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 40db6547a86..1ada3c46241 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -190,13 +190,13 @@ public function dataFormatterOutputProvider(): iterable 'numGenericErrors' => 0, 'verbose' => false, 'extraEnvVars' => [], - 'expected' => ' ------ ---------------- + 'expected' => ' ------ --------------- Line foo.php - ------ ---------------- + ------ --------------- 5 Foobar\Buz - 🪪 foobar.buz + 🪪 foobar.buz 💡 a tip - ------ ---------------- + ------ --------------- [ERROR] Found 1 error @@ -211,13 +211,13 @@ public function dataFormatterOutputProvider(): iterable 'numGenericErrors' => 0, 'verbose' => true, 'extraEnvVars' => [], - 'expected' => ' ------ ---------------- + 'expected' => ' ------ --------------- Line foo.php - ------ ---------------- + ------ --------------- 5 Foobar\Buz - 🪪 foobar.buz + 🪪 foobar.buz 💡 a tip - ------ ---------------- + ------ --------------- [ERROR] Found 1 error From cc579572b7e595d568fca040fb77911539d33f53 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 9 Apr 2025 22:04:41 +0100 Subject: [PATCH 1331/3097] Remember value of arguments passed to `{min,max}()` --- .../Php/MinMaxFunctionReturnTypeExtension.php | 10 ++++++-- tests/PHPStan/Analyser/nsrt/bug-12731.php | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12731.php diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index 5beabcbcf39..9faa3563c70 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Ternary; use PHPStan\Analyser\Scope; +use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantArrayType; @@ -60,15 +61,20 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $argType1 = $scope->getType($args[1]->value); if ($argType0->isArray()->no() && $argType1->isArray()->no()) { + $comparisonExpr = new Smaller( + new AlwaysRememberedExpr($args[0]->value, $argType0, $scope->getNativeType($args[0]->value)), + new AlwaysRememberedExpr($args[1]->value, $argType1, $scope->getNativeType($args[1]->value)), + ); + if ($functionName === 'min') { return $scope->getType(new Ternary( - new Smaller($args[0]->value, $args[1]->value), + $comparisonExpr, $args[0]->value, $args[1]->value, )); } elseif ($functionName === 'max') { return $scope->getType(new Ternary( - new Smaller($args[0]->value, $args[1]->value), + $comparisonExpr, $args[1]->value, $args[0]->value, )); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12731.php b/tests/PHPStan/Analyser/nsrt/bug-12731.php new file mode 100644 index 00000000000..79c8160461b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12731.php @@ -0,0 +1,24 @@ +', max(4, pure_int())); + +$_ = impure_int(); +assertType('int<4, max>', max(4, $_)); + +assertType('int<4, max>', max(4, impure_int())); +assertType('int<4, max>', max(impure_int(), 4)); +assertType('int', impure_int()); From 551d3fdd581e1ebeff2616851d0680dcbea3cf47 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 00:44:00 +0900 Subject: [PATCH 1332/3097] Replace error-prone instanceof in Rules classes --- phpstan-baseline.neon | 68 +------------------ .../NodeConnectingVisitorAttributesRule.php | 48 ++++++------- .../DuplicateKeysInLiteralArraysRule.php | 42 ++++++------ src/Rules/Classes/RequireExtendsRule.php | 65 +++++++++--------- .../ConstantLooseComparisonRule.php | 7 +- .../Comparison/ImpossibleCheckTypeHelper.php | 5 +- src/Rules/PhpDoc/RequireExtendsCheck.php | 67 ++++++++++-------- .../RequireImplementsDefinitionTraitRule.php | 52 +++++++------- src/Rules/RuleLevelHelper.php | 8 +-- .../TooWideMethodReturnTypehintRule.php | 7 +- src/Rules/UnusedFunctionParametersCheck.php | 7 +- src/Rules/Variables/CompactVariablesRule.php | 19 +++--- ...odeConnectingVisitorAttributesRuleTest.php | 7 +- .../Api/data/node-connecting-visitor.php | 5 ++ .../DuplicateKeysInLiteralArraysRuleTest.php | 12 ++++ .../Rules/Arrays/data/duplicate-keys.php | 15 ++++ .../Rules/Classes/RequireExtendsRuleTest.php | 16 +++++ .../RequireExtendsDefinitionClassRuleTest.php | 16 ++++- .../RequireExtendsDefinitionTraitRuleTest.php | 10 +++ .../data/incompatible-require-extends.php | 22 ++++++ 20 files changed, 270 insertions(+), 228 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dc8fcfbcc10..035be525fec 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -459,30 +459,12 @@ parameters: count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - - - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/Classes/RequireExtendsRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -507,12 +489,6 @@ parameters: count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/Comparison/ConstantLooseComparisonRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -540,7 +516,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 2 + count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - @@ -693,18 +669,6 @@ parameters: count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/PhpDoc/RequireExtendsCheck.php - - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType @@ -723,36 +687,6 @@ parameters: count: 1 path: src/Rules/RuleLevelHelper.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/RuleLevelHelper.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/UnusedFunctionParametersCheck.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Variables/CompactVariablesRule.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Variables/CompactVariablesRule.php - - message: ''' #^Call to deprecated method assertFileNotExists\(\) of class PHPUnit\\Framework\\Assert\: diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index 7ad74631b92..282c2189d2d 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -7,7 +7,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ObjectType; use function array_keys; use function in_array; @@ -41,37 +40,40 @@ public function processNode(Node $node, Scope $scope): array if (!isset($args[0])) { return []; } + + $messages = []; $argType = $scope->getType($args[0]->value); - if (!$argType instanceof ConstantStringType) { - return []; - } - if (!in_array($argType->getValue(), ['parent', 'previous', 'next'], true)) { - return []; - } - if (!$scope->isInClass()) { - return []; - } + foreach ($argType->getConstantStrings() as $constantString) { + $argValue = $constantString->getValue(); + if (!in_array($argValue, ['parent', 'previous', 'next'], true)) { + continue; + } - $classReflection = $scope->getClassReflection(); - $hasPhpStanInterface = false; - foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) { - if (!str_starts_with($interfaceName, 'PHPStan\\')) { + if (!$scope->isInClass()) { continue; } - $hasPhpStanInterface = true; - } + $classReflection = $scope->getClassReflection(); + $hasPhpStanInterface = false; + foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) { + if (!str_starts_with($interfaceName, 'PHPStan\\')) { + continue; + } - if (!$hasPhpStanInterface) { - return []; - } + $hasPhpStanInterface = true; + } - return [ - RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argType->getValue())) + if (!$hasPhpStanInterface) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argValue)) ->identifier('phpParser.nodeConnectingAttribute') ->tip('See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules') - ->build(), - ]; + ->build(); + } + + return $messages; } } diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 46b5e712e10..7e54a2cb159 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -9,10 +9,10 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\ConstantScalarType; use function array_keys; use function count; use function implode; +use function is_int; use function max; use function sprintf; use function var_export; @@ -70,38 +70,38 @@ public function processNode(Node $node, Scope $scope): array } } else { $keyType = $itemNode->getScope()->getType($key); - - $arrayKeyValue = $keyType->toArrayKey(); - if ($arrayKeyValue instanceof ConstantIntegerType) { + $arrayKeyValues = $keyType->toArrayKey()->getConstantScalarValues(); + if (count($arrayKeyValues) === 1 && is_int($arrayKeyValues[0])) { $autoGeneratedIndex = $autoGeneratedIndex === null - ? $arrayKeyValue->getValue() - : max($autoGeneratedIndex, $arrayKeyValue->getValue()); + ? $arrayKeyValues[0] + : max($autoGeneratedIndex, $arrayKeyValues[0]); } } - if (!$keyType instanceof ConstantScalarType) { + $keyValues = $keyType->getConstantScalarValues(); + if (count($keyValues) === 0) { $autoGeneratedIndex = false; continue; } - $value = $keyType->getValue(); - $printedValue = $key !== null - ? $this->exprPrinter->printExpr($key) - : $value; + foreach ($keyValues as $value) { + $printedValue = $key !== null + ? $this->exprPrinter->printExpr($key) + : $value; + $printedValues[$value][] = $printedValue; - $printedValues[$value][] = $printedValue; + if (!isset($valueLines[$value])) { + $valueLines[$value] = $item->getStartLine(); + } - if (!isset($valueLines[$value])) { - $valueLines[$value] = $item->getStartLine(); - } + $previousCount = count($values); + $values[$value] = $printedValue; + if ($previousCount !== count($values)) { + continue; + } - $previousCount = count($values); - $values[$value] = $printedValue; - if ($previousCount !== count($values)) { - continue; + $duplicateKeys[$value] = true; } - - $duplicateKeys[$value] = true; } $messages = []; diff --git a/src/Rules/Classes/RequireExtendsRule.php b/src/Rules/Classes/RequireExtendsRule.php index 036cf3aa85a..88c0b88ccda 100644 --- a/src/Rules/Classes/RequireExtendsRule.php +++ b/src/Rules/Classes/RequireExtendsRule.php @@ -7,7 +7,6 @@ use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -35,24 +34,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $interface->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } - if ($classReflection->is($type->getClassName())) { - continue; - } + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Interface %s requires implementing class to extend %s, but %s does not.', + $interface->getDisplayName(), + $type->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + ), + ) + ->identifier('class.missingExtends') + ->build(); - $errors[] = RuleErrorBuilder::message( - sprintf( - 'Interface %s requires implementing class to extend %s, but %s does not.', - $interface->getDisplayName(), - $type->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(), - ), - ) - ->identifier('class.missingExtends') - ->build(); + break; + } } } @@ -60,24 +59,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $trait->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } - if ($classReflection->is($type->getClassName())) { - continue; - } + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Trait %s requires using class to extend %s, but %s does not.', + $trait->getDisplayName(), + $type->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + ), + ) + ->identifier('class.missingExtends') + ->build(); - $errors[] = RuleErrorBuilder::message( - sprintf( - 'Trait %s requires using class to extend %s, but %s does not.', - $trait->getDisplayName(), - $type->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(), - ), - ) - ->identifier('class.missingExtends') - ->build(); + break; + } } } diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 09961335e7b..b7d3fe977d5 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -7,7 +7,6 @@ use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -37,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array } $nodeType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node); - if (!$nodeType instanceof ConstantBooleanType) { + if (!$nodeType->isTrue()->yes() && !$nodeType->isFalse()->yes()) { return []; } @@ -47,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array } $instanceofTypeWithoutPhpDocs = $scope->getNativeType($node); - if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { + if ($instanceofTypeWithoutPhpDocs->isTrue()->yes() || $instanceofTypeWithoutPhpDocs->isFalse()->yes()) { return $ruleErrorBuilder; } if (!$this->treatPhpDocTypesAsCertainTip) { @@ -57,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; - if (!$nodeType->getValue()) { + if ($nodeType->isFalse()->yes()) { return [ $addTip(RuleErrorBuilder::message(sprintf( 'Loose comparison using %s between %s and %s will always evaluate to false.', diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 48eea97d6f7..5cba66c18e3 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -69,11 +69,12 @@ public function findSpecifiedType( if ($functionName === 'assert' && $argsCount >= 1) { $arg = $node->getArgs()[0]->value; $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); - if (!$assertValue instanceof ConstantBooleanType) { + $assertValueIsTrue = $assertValue->isTrue()->yes(); + if (! $assertValueIsTrue && ! $assertValue->isFalse()->yes()) { return null; } - return $assertValue->getValue(); + return $assertValueIsTrue; } if (in_array($functionName, [ 'class_exists', diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 233082b3a98..e34e2c36cc0 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -10,10 +10,12 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; use function count; +use function sort; use function sprintf; use function strtolower; @@ -44,43 +46,52 @@ public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireExtends.nonObject') ->build(); continue; } - $class = $type->getClassName(); - $referencedClassReflection = $type->getClassReflection(); + sort($classNames); + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); + foreach ($classNames as $class) { + $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; + if ($referencedClassReflection === null) { + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) + ->identifier('class.notFound'); - if ($referencedClassReflection === null) { - $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) - ->identifier('class.notFound'); + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } - if ($this->discoveringSymbolsTip) { - $errorBuilder->discoveringSymbolsTip(); + $errors[] = $errorBuilder->build(); + continue; } - $errors[] = $errorBuilder->build(); - continue; - } - - if (!$referencedClassReflection->isClass()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class)) - ->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) - ->build(); - } elseif ($referencedClassReflection->isFinal()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class)) - ->identifier('requireExtends.finalClass') - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames($scope, [ - new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_EXTENDS), $this->checkClassCaseSensitivity), - ); + if ($referencedClassReflection->isInterface()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain an interface %s, expected a class.', $class)) + ->tip('If you meant an interface, use @phpstan-require-implements instead.') + ->identifier('requireExtends.interface') + ->build(); + } elseif (!$referencedClassReflection->isClass()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class)) + ->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->build(); + } elseif ($referencedClassReflection->isFinal()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class)) + ->identifier('requireExtends.finalClass') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames($scope, [ + new ClassNameNodePair($class, $node), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_EXTENDS), $this->checkClassCaseSensitivity), + ); + } } } diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 616ce798275..f8eed38f4f3 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -10,9 +10,11 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; +use function count; use function sprintf; use function strtolower; @@ -51,38 +53,42 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($implementsTags as $implementsTag) { $type = $implementsTag->getType(); - if (!$type instanceof ObjectType) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireImplements.nonObject') ->build(); continue; } - $class = $type->getClassName(); - $referencedClassReflection = $type->getClassReflection(); - if ($referencedClassReflection === null) { - $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); + foreach ($classNames as $class) { + $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; + if ($referencedClassReflection === null) { + $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) ->identifier('class.notFound'); - if ($this->discoveringSymbolsTip) { - $errorBuilder->discoveringSymbolsTip(); - } + if ($this->discoveringSymbolsTip) { + $errorBuilder->discoveringSymbolsTip(); + } - $errors[] = $errorBuilder->build(); - continue; - } + $errors[] = $errorBuilder->build(); + continue; + } - if (!$referencedClassReflection->isInterface()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements cannot contain non-interface type %s.', $class)) - ->identifier(sprintf('requireImplements.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames($scope, [ - new ClassNameNodePair($class, $node), - ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_IMPLEMENTS), $this->checkClassCaseSensitivity), - ); + if (!$referencedClassReflection->isInterface()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements cannot contain non-interface type %s.', $class)) + ->identifier(sprintf('requireImplements.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames($scope, [ + new ClassNameNodePair($class, $node), + ], ClassNameUsageLocation::from(ClassNameUsageLocation::PHPDOC_TAG_REQUIRE_IMPLEMENTS), $this->checkClassCaseSensitivity), + ); + } } } diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 2f9dd979030..20b9597722a 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -14,7 +14,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; -use PHPStan\Type\ObjectType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -309,10 +308,9 @@ private function findTypeToCheckImplementation( if ( $type instanceof UnionType && count($type->getTypes()) === 2 - && $type->getTypes()[0] instanceof ObjectType - && $type->getTypes()[1] instanceof ObjectType - && $type->getTypes()[0]->getClassName() === 'PhpParser\\Node\\Arg' - && $type->getTypes()[1]->getClassName() === 'PhpParser\\Node\\VariadicPlaceholder' + && $type->isObject()->yes() + && $type->getTypes()[0]->getObjectClassNames() === ['PhpParser\\Node\\Arg'] + && $type->getTypes()[1]->getObjectClassNames() === ['PhpParser\\Node\\VariadicPlaceholder'] && !$unionTypeCriteriaCallback($type) ) { $tip = 'Use ->getArgs() instead of ->args.'; diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 1c40c4a9ce7..87378551753 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -7,7 +7,6 @@ use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; @@ -82,9 +81,9 @@ public function processNode(Node $node, Scope $scope): array $returnType = TypeCombinator::union(...$returnTypes); if ( - !$method->isPrivate() - && ($returnType->isNull()->yes() || $returnType instanceof ConstantBooleanType) - && !$isFirstDeclaration + !$isFirstDeclaration + && !$method->isPrivate() + && ($returnType->isNull()->yes() || $returnType->isTrue()->yes() || $returnType->isFalse()->yes()) ) { return []; } diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 628041a0320..68c15490915 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -7,7 +7,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\Constant\ConstantStringType; use function array_combine; use function array_map; use function array_merge; @@ -92,11 +91,9 @@ private function getUsedVariables(Scope $scope, $node): array ) { foreach ($node->getArgs() as $arg) { $argType = $scope->getType($arg->value); - if (!($argType instanceof ConstantStringType)) { - continue; + foreach ($argType->getConstantStrings() as $constantStringType) { + $variableNames[] = $constantStringType->getValue(); } - - $variableNames[] = $argType->getValue(); } } foreach ($node->getSubNodeNames() as $subNodeName) { diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index c6324f76787..8555675bd3e 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -6,10 +6,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; use function array_merge; +use function count; use function sprintf; use function strtolower; @@ -66,25 +66,24 @@ public function processNode(Node $node, Scope $scope): array } /** - * @return array + * @return list */ private function findConstantStrings(Type $type): array { - if ($type instanceof ConstantStringType) { - return [$type]; + $constantStrings = $type->getConstantStrings(); + if (count($constantStrings) > 0) { + return $constantStrings; } - if ($type instanceof ConstantArrayType) { - $result = []; - foreach ($type->getValueTypes() as $valueType) { + $result = []; + foreach ($type->getConstantArrays() as $constantArrayType) { + foreach ($constantArrayType->getValueTypes() as $valueType) { $constantStrings = $this->findConstantStrings($valueType); $result = array_merge($result, $constantStrings); } - - return $result; } - return []; + return $result; } } diff --git a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php index c7fe99bc18f..b811039ec70 100644 --- a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php @@ -21,7 +21,12 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/node-connecting-visitor.php'], [ [ 'Node attribute \'parent\' is no longer available.', - 18, + 22, + 'See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules', + ], + [ + 'Node attribute \'parent\' is no longer available.', + 24, 'See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules', ], ]); diff --git a/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php b/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php index e61c7c3a4dc..93f18f28a9c 100644 --- a/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php +++ b/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php @@ -8,6 +8,10 @@ class MyRule implements Rule { + + /** @var 'parent'|'myCustomAttribute' */ + public string $attrName; + public function getNodeType(): string { return Node::class; @@ -17,6 +21,7 @@ public function processNode(Node $node, Scope $scope): array { $parent = $node->getAttribute("parent"); $custom = $node->getAttribute("myCustomAttribute"); + $parent = $node->getAttribute($this->attrName); return []; } diff --git a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php index 87b5a12e5fb..6d775a2f7c9 100644 --- a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php @@ -61,6 +61,18 @@ public function testDuplicateKeys(): void 'Array has 2 duplicate keys with value -41 (-41, -41).', 76, ], + [ + 'Array has 2 duplicate keys with value \'foo\' (\'foo\', $key).', + 102, + ], + [ + 'Array has 2 duplicate keys with value \'bar\' (\'bar\', $key).', + 103, + ], + [ + 'Array has 2 duplicate keys with value \'key\' (\'key\', $key2).', + 105, + ], ]); } diff --git a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php index 02176773ace..06dbbeb0f5a 100644 --- a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php +++ b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php @@ -92,4 +92,19 @@ public function doWithoutKeys(int $int) ]; } + /** + * @param 'foo'|'bar' $key + */ + public function doUnionKeys(string $key): void + { + $key2 = 'key'; + $a = [ + 'foo' => 'foo', + 'bar' => 'bar', + $key => 'foo|bar', + 'key' => 'bar', + $key2 => 'foo', + ]; + } + } diff --git a/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php index 0ca40d9fa4e..03f3361befd 100644 --- a/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php @@ -52,6 +52,22 @@ public function testRule(): void 'Trait IncompatibleRequireExtends\ValidPsalmTrait requires using class to extend IncompatibleRequireExtends\SomeClass, but class@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:163 does not.', 163, ], + [ + 'Interface IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface requires implementing class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:185 does not.', + 185, + ], + [ + 'Interface IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface requires implementing class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\SomeClass@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:187 does not.', + 187, + ], + [ + 'Trait IncompatibleRequireExtends\RequireNonExisstentUnionClassTrait requires using class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but class@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:194 does not.', + 194, + ], + [ + 'Trait IncompatibleRequireExtends\RequireNonExisstentUnionClassTrait requires using class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\SomeClass@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:198 does not.', + 198, + ], ]; $this->analyse([__DIR__ . '/../PhpDoc/data/incompatible-require-extends.php'], $expectedErrors); diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 78934232279..68e2bfd718e 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -45,8 +45,9 @@ public function testRule(): void 8, ], [ - 'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeInterface.', + 'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\SomeInterface, expected a class.', 13, + 'If you meant an interface, use @phpstan-require-implements instead.', ], [ 'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeEnum.', @@ -74,13 +75,24 @@ public function testRule(): void 121, ], [ - 'PHPDoc tag @phpstan-require-extends contains non-object type IncompatibleRequireExtends\UnresolvableExtendsInterface&stdClass.', + 'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\UnresolvableExtendsInterface, expected a class.', 135, + 'If you meant an interface, use @phpstan-require-implements instead.', ], [ 'PHPDoc tag @phpstan-require-extends can only be used once.', 178, ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 183, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 183, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index 93e516021ae..96f423723cc 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -53,6 +53,16 @@ public function testRule(): void 'PHPDoc tag @phpstan-require-extends can only be used once.', 171, ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php index eb362e5b611..6b5af9e3403 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php @@ -176,3 +176,25 @@ trait TooMuchExtends {} * @phpstan-require-extends SomeOtherClass */ interface TooMuchExtendsIface {} + +/** + * @phpstan-require-extends SomeClass|NonExistentClass + */ +interface RequireNonExisstentUnionClassinterface {} + +new class implements RequireNonExisstentUnionClassinterface {}; + +new class extends SomeClass implements RequireNonExisstentUnionClassinterface {}; + +/** + * @phpstan-require-extends SomeClass|NonExistentClass + */ +trait RequireNonExisstentUnionClassTrait {} + +new class { + use RequireNonExisstentUnionClassTrait; +}; + +new class extends SomeClass { + use RequireNonExisstentUnionClassTrait; +}; From 6bdf04b5e1f665c48ff54a21045a64effcc48507 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 5 May 2025 21:39:09 +0700 Subject: [PATCH 1333/3097] Revert "Useful PhpMethodReflection native type refactoring from #3966" --- src/Reflection/Php/PhpMethodReflection.php | 47 ++++++++++--------- .../Analyser/nsrt/bug-to-string-type.php | 36 ++++++++++++++ 2 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-to-string-type.php diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index b141bcec7e4..432fd693504 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -302,9 +302,30 @@ public function isPublic(): bool private function getReturnType(): Type { if ($this->returnType === null) { - $this->returnType = TypehintHelper::decideType( - $this->getNativeReturnType(), + $name = strtolower($this->getName()); + $returnType = $this->reflection->getReturnType(); + if ($returnType === null) { + if (in_array($name, ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { + return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType); + } + if ($name === '__tostring') { + return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType); + } + if ($name === '__isset') { + return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType); + } + if ($name === '__sleep') { + return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType); + } + if ($name === '__set_state') { + return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType); + } + } + + $this->returnType = TypehintHelper::decideTypeFromReflection( + $returnType, $this->phpDocReturnType, + $this->declaringClass, ); } @@ -323,28 +344,8 @@ private function getPhpDocReturnType(): Type private function getNativeReturnType(): Type { if ($this->nativeReturnType === null) { - $returnType = $this->reflection->getReturnType(); - if ($returnType === null) { - $name = strtolower($this->getName()); - if (in_array($this->getName(), ['__construct', '__destruct', '__unset', '__wakeup', '__clone'], true)) { - return $this->nativeReturnType = new VoidType(); - } - if ($name === '__tostring') { - return $this->nativeReturnType = new StringType(); - } - if ($name === '__isset') { - return $this->nativeReturnType = new BooleanType(); - } - if ($name === '__sleep') { - return $this->nativeReturnType = new ArrayType(new IntegerType(), new StringType()); - } - if ($name === '__set_state') { - return $this->nativeReturnType = new ObjectWithoutClassType(); - } - } - $this->nativeReturnType = TypehintHelper::decideTypeFromReflection( - $returnType, + $this->reflection->getReturnType(), null, $this->declaringClass, ); diff --git a/tests/PHPStan/Analyser/nsrt/bug-to-string-type.php b/tests/PHPStan/Analyser/nsrt/bug-to-string-type.php new file mode 100644 index 00000000000..31e1b97281b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-to-string-type.php @@ -0,0 +1,36 @@ +__toString()); + assertNativeType('mixed', $test->__toString()); +} From 8f05c281bcf610750b553ade386060281dba2e50 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 18 Mar 2025 17:19:53 +0100 Subject: [PATCH 1334/3097] ResultCacheManager: fix meta key difference for projectConfig --- .../ResultCache/ResultCacheManager.php | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index e2ad34e8178..912ccdc8580 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -313,18 +313,27 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? } /** - * @param mixed[] $cachedMeta - * @param mixed[] $currentMeta + * @param mixed[]|null $projectConfig */ - private function isMetaDifferent(array $cachedMeta, array $currentMeta): bool + private function normalizeMetaProjectConfig(?array $projectConfig): ?string { - $projectConfig = $currentMeta['projectConfig']; if ($projectConfig !== null) { - ksort($currentMeta['projectConfig']); + ksort($projectConfig); - $currentMeta['projectConfig'] = Neon::encode($currentMeta['projectConfig']); + return Neon::encode($projectConfig); } + return null; + } + + /** + * @param mixed[] $cachedMeta + * @param mixed[] $currentMeta + */ + private function isMetaDifferent(array $cachedMeta, array $currentMeta): bool + { + $currentMeta['projectConfig'] = $this->normalizeMetaProjectConfig($currentMeta['projectConfig']); + return $cachedMeta !== $currentMeta; } @@ -338,6 +347,10 @@ private function getMetaKeyDifferences(array $cachedMeta, array $currentMeta): a { $diffs = []; foreach ($cachedMeta as $key => $value) { + if ($key === 'projectConfig') { + $value = $this->normalizeMetaProjectConfig($value); + } + if (!array_key_exists($key, $currentMeta)) { $diffs[] = $key; continue; From b350eb94d50c50bf5dc0e49fc5191331acf402b5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 5 May 2025 17:50:32 +0200 Subject: [PATCH 1335/3097] Extract ArrayColumnHelper from ArrayColumnFunctionReturnTypeExtension --- conf/config.neon | 3 + ...ArrayColumnFunctionReturnTypeExtension.php | 174 +--------------- src/Type/Php/ArrayColumnHelper.php | 196 ++++++++++++++++++ 3 files changed, 204 insertions(+), 169 deletions(-) create mode 100644 src/Type/Php/ArrayColumnHelper.php diff --git a/conf/config.neon b/conf/config.neon index 7a4a43a4dec..a0083d77536 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1177,6 +1177,9 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayColumnHelper + - class: PHPStan\Type\Php\ArrayColumnFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 51d8999c2fc..70f5892c2b6 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -4,27 +4,17 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\ShouldNotHappenException; -use PHPStan\TrinaryLogic; -use PHPStan\Type\Accessory\AccessoryArrayListType; -use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\ArrayType; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; -use PHPStan\Type\NeverType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - public function __construct(private PhpVersion $phpVersion) + public function __construct( + private ArrayColumnHelper $arrayColumnHelper, + ) { } @@ -46,167 +36,13 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $constantArrayTypes = $arrayType->getConstantArrays(); if (count($constantArrayTypes) === 1) { - $type = $this->handleConstantArray($constantArrayTypes[0], $columnType, $indexType, $scope); + $type = $this->arrayColumnHelper->handleConstantArray($constantArrayTypes[0], $columnType, $indexType, $scope); if ($type !== null) { return $type; } } - return $this->handleAnyArray($arrayType, $columnType, $indexType, $scope); - } - - private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type - { - $iterableAtLeastOnce = $arrayType->isIterableAtLeastOnce(); - if ($iterableAtLeastOnce->no()) { - return new ConstantArrayType([], []); - } - - $iterableValueType = $arrayType->getIterableValueType(); - $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); - - if ($returnValueType === null) { - $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, true); - $iterableAtLeastOnce = TrinaryLogic::createMaybe(); - if ($returnValueType === null) { - throw new ShouldNotHappenException(); - } - } - - if ($returnValueType instanceof NeverType) { - return new ConstantArrayType([], []); - } - - if ($indexType !== null) { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); - if ($type !== null) { - $returnKeyType = $type; - } else { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); - if ($type !== null) { - $returnKeyType = TypeCombinator::union($type, new IntegerType()); - } else { - $returnKeyType = new IntegerType(); - } - } - } else { - $returnKeyType = new IntegerType(); - } - - $returnType = new ArrayType($this->castToArrayKeyType($returnKeyType), $returnValueType); - - if ($iterableAtLeastOnce->yes()) { - $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); - } - if ($indexType === null) { - $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); - } - - return $returnType; - } - - private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type - { - $builder = ConstantArrayTypeBuilder::createEmpty(); - - foreach ($arrayType->getValueTypes() as $i => $iterableValueType) { - $valueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); - if ($valueType === null) { - return null; - } - if ($valueType instanceof NeverType) { - continue; - } - - if ($indexType !== null) { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); - if ($type !== null) { - $keyType = $type; - } else { - $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); - if ($type !== null) { - $keyType = TypeCombinator::union($type, new IntegerType()); - } else { - $keyType = null; - } - } - } else { - $keyType = null; - } - - if ($keyType !== null) { - $keyType = $this->castToArrayKeyType($keyType); - } - $builder->setOffsetValueType($keyType, $valueType, $arrayType->isOptionalKey($i)); - } - - return $builder->getArray(); - } - - private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type - { - $offsetIsNull = $offsetOrProperty->isNull(); - if ($offsetIsNull->yes()) { - return $type; - } - - $returnTypes = []; - - if ($offsetIsNull->maybe()) { - $returnTypes[] = $type; - } - - if (!$type->canAccessProperties()->no()) { - $propertyTypes = $offsetOrProperty->getConstantStrings(); - if ($propertyTypes === []) { - return new MixedType(); - } - foreach ($propertyTypes as $propertyType) { - $propertyName = $propertyType->getValue(); - $hasProperty = $type->hasProperty($propertyName); - if ($hasProperty->maybe()) { - return $allowMaybe ? new MixedType() : null; - } - if (!$hasProperty->yes()) { - continue; - } - - $returnTypes[] = $type->getProperty($propertyName, $scope)->getReadableType(); - } - } - - if ($type->isOffsetAccessible()->yes()) { - $hasOffset = $type->hasOffsetValueType($offsetOrProperty); - if (!$allowMaybe && $hasOffset->maybe()) { - return null; - } - if (!$hasOffset->no()) { - $returnTypes[] = $type->getOffsetValueType($offsetOrProperty); - } - } - - if ($returnTypes === []) { - return new NeverType(); - } - - return TypeCombinator::union(...$returnTypes); - } - - private function castToArrayKeyType(Type $type): Type - { - $isArray = $type->isArray(); - if ($isArray->yes()) { - return $this->phpVersion->throwsTypeErrorForInternalFunctions() ? new NeverType() : new IntegerType(); - } - if ($isArray->no()) { - return $type->toArrayKey(); - } - $withoutArrayType = TypeCombinator::remove($type, new ArrayType(new MixedType(), new MixedType())); - $keyType = $withoutArrayType->toArrayKey(); - if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { - return $keyType; - } - return TypeCombinator::union($keyType, new IntegerType()); + return $this->arrayColumnHelper->handleAnyArray($arrayType, $columnType, $indexType, $scope); } } diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php new file mode 100644 index 00000000000..a0796febf40 --- /dev/null +++ b/src/Type/Php/ArrayColumnHelper.php @@ -0,0 +1,196 @@ +isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return [new NeverType(), $iterableAtLeastOnce]; + } + + $iterableValueType = $arrayType->getIterableValueType(); + $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); + + if ($returnValueType === null) { + $returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, true); + $iterableAtLeastOnce = TrinaryLogic::createMaybe(); + if ($returnValueType === null) { + throw new ShouldNotHappenException(); + } + } + + return [$returnValueType, $iterableAtLeastOnce]; + } + + public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $scope): Type + { + if ($indexType !== null) { + $iterableValueType = $arrayType->getIterableValueType(); + + $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); + if ($type !== null) { + return $type; + } + + $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); + if ($type !== null) { + return TypeCombinator::union($type, new IntegerType()); + } + } + + return new IntegerType(); + } + + public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type + { + [$returnValueType, $iterableAtLeastOnce] = $this->getReturnValueType($arrayType, $columnType, $scope); + if ($returnValueType instanceof NeverType) { + return new ConstantArrayType([], []); + } + + $returnKeyType = $this->getReturnIndexType($arrayType, $indexType, $scope); + $returnType = new ArrayType($this->castToArrayKeyType($returnKeyType), $returnValueType); + + if ($iterableAtLeastOnce->yes()) { + $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); + } + if ($indexType === null) { + $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); + } + + return $returnType; + } + + public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + + foreach ($arrayType->getValueTypes() as $i => $iterableValueType) { + $valueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false); + if ($valueType === null) { + return null; + } + if ($valueType instanceof NeverType) { + continue; + } + + if ($indexType !== null) { + $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); + if ($type !== null) { + $keyType = $type; + } else { + $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true); + if ($type !== null) { + $keyType = TypeCombinator::union($type, new IntegerType()); + } else { + $keyType = null; + } + } + } else { + $keyType = null; + } + + if ($keyType !== null) { + $keyType = $this->castToArrayKeyType($keyType); + } + $builder->setOffsetValueType($keyType, $valueType, $arrayType->isOptionalKey($i)); + } + + return $builder->getArray(); + } + + private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type + { + $offsetIsNull = $offsetOrProperty->isNull(); + if ($offsetIsNull->yes()) { + return $type; + } + + $returnTypes = []; + + if ($offsetIsNull->maybe()) { + $returnTypes[] = $type; + } + + if (!$type->canAccessProperties()->no()) { + $propertyTypes = $offsetOrProperty->getConstantStrings(); + if ($propertyTypes === []) { + return new MixedType(); + } + foreach ($propertyTypes as $propertyType) { + $propertyName = $propertyType->getValue(); + $hasProperty = $type->hasProperty($propertyName); + if ($hasProperty->maybe()) { + return $allowMaybe ? new MixedType() : null; + } + if (!$hasProperty->yes()) { + continue; + } + + $returnTypes[] = $type->getProperty($propertyName, $scope)->getReadableType(); + } + } + + if ($type->isOffsetAccessible()->yes()) { + $hasOffset = $type->hasOffsetValueType($offsetOrProperty); + if (!$allowMaybe && $hasOffset->maybe()) { + return null; + } + if (!$hasOffset->no()) { + $returnTypes[] = $type->getOffsetValueType($offsetOrProperty); + } + } + + if ($returnTypes === []) { + return new NeverType(); + } + + return TypeCombinator::union(...$returnTypes); + } + + private function castToArrayKeyType(Type $type): Type + { + $isArray = $type->isArray(); + if ($isArray->yes()) { + return $this->phpVersion->throwsTypeErrorForInternalFunctions() ? new NeverType() : new IntegerType(); + } + if ($isArray->no()) { + return $type->toArrayKey(); + } + $withoutArrayType = TypeCombinator::remove($type, new ArrayType(new MixedType(), new MixedType())); + $keyType = $withoutArrayType->toArrayKey(); + if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { + return $keyType; + } + return TypeCombinator::union($keyType, new IntegerType()); + } + +} From 228c589106d6d9e07cf728ac32badffa08dd2251 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 May 2025 17:52:58 +0200 Subject: [PATCH 1336/3097] Revert "ResultCacheManager: fix meta key difference for projectConfig" This reverts commit 8f05c281bcf610750b553ade386060281dba2e50. --- .../ResultCache/ResultCacheManager.php | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 912ccdc8580..e2ad34e8178 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -312,27 +312,18 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? return new ResultCache($filesToAnalyse, false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredLinesToIgnore, $filteredUnmatchedLineIgnores, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles']); } - /** - * @param mixed[]|null $projectConfig - */ - private function normalizeMetaProjectConfig(?array $projectConfig): ?string - { - if ($projectConfig !== null) { - ksort($projectConfig); - - return Neon::encode($projectConfig); - } - - return null; - } - /** * @param mixed[] $cachedMeta * @param mixed[] $currentMeta */ private function isMetaDifferent(array $cachedMeta, array $currentMeta): bool { - $currentMeta['projectConfig'] = $this->normalizeMetaProjectConfig($currentMeta['projectConfig']); + $projectConfig = $currentMeta['projectConfig']; + if ($projectConfig !== null) { + ksort($currentMeta['projectConfig']); + + $currentMeta['projectConfig'] = Neon::encode($currentMeta['projectConfig']); + } return $cachedMeta !== $currentMeta; } @@ -347,10 +338,6 @@ private function getMetaKeyDifferences(array $cachedMeta, array $currentMeta): a { $diffs = []; foreach ($cachedMeta as $key => $value) { - if ($key === 'projectConfig') { - $value = $this->normalizeMetaProjectConfig($value); - } - if (!array_key_exists($key, $currentMeta)) { $diffs[] = $key; continue; From f2cf5cad45037071e4032573d4eaf929080ea166 Mon Sep 17 00:00:00 2001 From: Dmytro Dymarchuk Date: Sun, 19 Jan 2025 21:57:22 +0200 Subject: [PATCH 1337/3097] Narrow variable type in switch cases --- src/Analyser/NodeScopeResolver.php | 31 +++++++++ src/Analyser/TypeSpecifier.php | 65 ++++++++++++++++--- .../Analyser/NodeScopeResolverTest.php | 3 + .../Analyser/data/bug-12432-nullable-enum.php | 35 ++++++++++ .../Analyser/data/bug-12432-nullable-int.php | 26 ++++++++ .../PHPStan/Analyser/nsrt/in_array_loose.php | 2 +- 6 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php create mode 100644 tests/PHPStan/Analyser/data/bug-12432-nullable-int.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d979a0f24a7..58aa455c4a6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -206,6 +206,7 @@ use function array_merge; use function array_pop; use function array_reverse; +use function array_shift; use function array_slice; use function array_values; use function base64_decode; @@ -1566,10 +1567,12 @@ private function processStmtNode( $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $fullCondExpr = null; + $defaultCondExprs = []; foreach ($stmt->cases as $caseNode) { if ($caseNode->cond !== null) { $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); $fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr); + $defaultCondExprs[] = new BinaryOp\NotEqual($stmt->cond, $caseNode->cond); $caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); $scopeForBranches = $caseResult->getScope(); $hasYield = $hasYield || $caseResult->hasYield(); @@ -1580,6 +1583,11 @@ private function processStmtNode( $hasDefaultCase = true; $fullCondExpr = null; $branchScope = $scopeForBranches; + $defaultConditions = $this->createBooleanAndFromExpressions($defaultCondExprs); + if ($defaultConditions !== null) { + $branchScope = $this->processExprNode($stmt, $defaultConditions, $scope, static function (): void { + }, ExpressionContext::createDeep())->getTruthyScope()->filterByTruthyValue($defaultConditions); + } } $branchScope = $branchScope->mergeWith($prevScope); @@ -6701,6 +6709,29 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ return null; } + /** + * @param list $expressions + */ + private function createBooleanAndFromExpressions(array $expressions): ?Expr + { + if (count($expressions) === 0) { + return null; + } + + if (count($expressions) === 1) { + return $expressions[0]; + } + + $left = array_shift($expressions); + $right = $this->createBooleanAndFromExpressions($expressions); + + if ($right === null) { + throw new ShouldNotHappenException(); + } + + return new BooleanAnd($left, $right); + } + /** * @param array $nodes * @return list diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 761aa267ae8..405b4a9adc1 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1643,15 +1643,8 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ $leftType = $scope->getType($binaryOperation->left); $rightType = $scope->getType($binaryOperation->right); - $rightExpr = $binaryOperation->right; - if ($rightExpr instanceof AlwaysRememberedExpr) { - $rightExpr = $rightExpr->getExpr(); - } - - $leftExpr = $binaryOperation->left; - if ($leftExpr instanceof AlwaysRememberedExpr) { - $leftExpr = $leftExpr->getExpr(); - } + $rightExpr = $this->extractExpression($binaryOperation->right); + $leftExpr = $this->extractExpression($binaryOperation->left); if ( $leftType instanceof ConstantScalarType @@ -1670,6 +1663,39 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ return null; } + /** + * @return array{Expr, Type, Type}|null + */ + private function findEnumTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array + { + $leftType = $scope->getType($binaryOperation->left); + $rightType = $scope->getType($binaryOperation->right); + + $rightExpr = $this->extractExpression($binaryOperation->right); + $leftExpr = $this->extractExpression($binaryOperation->left); + + if ( + $leftType->getEnumCases() === [$leftType] + && !$rightExpr instanceof ConstFetch + && !$rightExpr instanceof ClassConstFetch + ) { + return [$binaryOperation->right, $leftType, $rightType]; + } elseif ( + $rightType->getEnumCases() === [$rightType] + && !$leftExpr instanceof ConstFetch + && !$leftExpr instanceof ClassConstFetch + ) { + return [$binaryOperation->left, $rightType, $leftType]; + } + + return null; + } + + private function extractExpression(Expr $expr): Expr + { + return $expr instanceof AlwaysRememberedExpr ? $expr->getExpr() : $expr; + } + /** @api */ public function create( Expr $expr, @@ -2061,6 +2087,27 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif ) { return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context)->setRootExpr($expr); } + + if (!$context->null() && TypeCombinator::containsNull($otherType)) { + if ($constantType->toBoolean()->isTrue()->yes()) { + $otherType = TypeCombinator::remove($otherType, new NullType()); + } + + if (!$otherType->isSuperTypeOf($constantType)->no()) { + return $this->create($exprNode, TypeCombinator::intersect($constantType, $otherType), $context, $scope)->setRootExpr($expr); + } + } + } + + $expressions = $this->findEnumTypeExpressionsFromBinaryOperation($scope, $expr); + if ($expressions !== null) { + $exprNode = $expressions[0]; + $enumCaseObjectType = $expressions[1]; + $otherType = $expressions[2]; + + if (!$context->null()) { + return $this->create($exprNode, TypeCombinator::intersect($enumCaseObjectType, $otherType), $context, $scope)->setRootExpr($expr); + } } $leftType = $scope->getType($expr->left); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a2e9ef06192..2bfafc8404b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -101,10 +101,13 @@ private static function findTestFiles(): iterable define('TEST_FALSE_CONSTANT', false); define('TEST_ARRAY_CONSTANT', [true, false, null]); define('TEST_ENUM_CONSTANT', Foo::ONE); + yield __DIR__ . '/data/bug-12432-nullable-enum.php'; yield __DIR__ . '/data/new-in-initializers-runtime.php'; yield __DIR__ . '/data/scope-in-enum-match-arm-body.php'; } + yield __DIR__ . '/data/bug-12432-nullable-int.php'; + yield __DIR__ . '/../Rules/Comparison/data/bug-6473.php'; yield __DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php'; diff --git a/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php b/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php new file mode 100644 index 00000000000..ee24d3580aa --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php @@ -0,0 +1,35 @@ +|int<3, max>', $nullable); + break; + } + + return $nullable; +} diff --git a/tests/PHPStan/Analyser/nsrt/in_array_loose.php b/tests/PHPStan/Analyser/nsrt/in_array_loose.php index 78d2899b8cb..8018fd7f659 100644 --- a/tests/PHPStan/Analyser/nsrt/in_array_loose.php +++ b/tests/PHPStan/Analyser/nsrt/in_array_loose.php @@ -42,7 +42,7 @@ public function looseComparison( assertType('int|string', $stringOrInt); // could be '1'|'2'|1|2 } if (in_array($stringOrNull, ['1', 'a'])) { - assertType('string|null', $stringOrNull); // could be '1'|'a' + assertType("'1'|'a'", $stringOrNull); } } } From 36b3925f5bd1883184cd215b05f925229173e6db Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 5 May 2025 18:00:26 +0200 Subject: [PATCH 1338/3097] Fix `numeric-string` to `array-key` --- .../Accessory/AccessoryNumericStringType.php | 8 ++++- src/Type/Constant/ConstantArrayType.php | 12 ++++++-- src/Type/IntersectionType.php | 5 +++- tests/PHPStan/Analyser/nsrt/bug-3133.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8592.php | 15 ++++++++++ .../nsrt/isset-coalesce-empty-type.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 ++++ tests/PHPStan/Rules/Arrays/data/bug-11390.php | 29 ++++++++++++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 10 +++++++ tests/PHPStan/Rules/Methods/data/bug-4163.php | 30 +++++++++++++++++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 5 +--- 11 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8592.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-11390.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4163.php diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 79c83c7b0f3..70803e43790 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -229,7 +229,13 @@ public function toArray(): Type public function toArrayKey(): Type { - return new IntegerType(); + return new UnionType([ + new IntegerType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + ]); } public function isNull(): TrinaryLogic diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 1b770d68897..16e999bcb31 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -609,11 +609,17 @@ public function findTypeAndMethodNames(): array public function hasOffsetValueType(Type $offsetType): TrinaryLogic { - $offsetType = $offsetType->toArrayKey(); + $offsetArrayKeyType = $offsetType->toArrayKey(); + + return $this->recursiveHasOffsetValueType($offsetArrayKeyType); + } + + private function recursiveHasOffsetValueType(Type $offsetType): TrinaryLogic + { if ($offsetType instanceof UnionType) { $results = []; foreach ($offsetType->getTypes() as $innerType) { - $results[] = $this->hasOffsetValueType($innerType); + $results[] = $this->recursiveHasOffsetValueType($innerType); } return TrinaryLogic::extremeIdentity(...$results); @@ -623,7 +629,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic if ($finiteTypes !== []) { $results = []; foreach ($finiteTypes as $innerType) { - $results[] = $this->hasOffsetValueType($innerType); + $results[] = $this->recursiveHasOffsetValueType($innerType); } return TrinaryLogic::extremeIdentity(...$results); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 519649a384e..f683ad9cbd9 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1029,7 +1029,10 @@ public function toArray(): Type public function toArrayKey(): Type { if ($this->isNumericString()->yes()) { - return new IntegerType(); + return TypeCombinator::union( + new IntegerType(), + $this, + ); } if ($this->isString()->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-3133.php b/tests/PHPStan/Analyser/nsrt/bug-3133.php index 98eb5841f03..c6516dfe709 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3133.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3133.php @@ -52,7 +52,7 @@ public function doLorem( { $a = []; $a[$numericString] = 'foo'; - assertType('non-empty-array', $a); + assertType('non-empty-array', $a); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-8592.php b/tests/PHPStan/Analyser/nsrt/bug-8592.php new file mode 100644 index 00000000000..e8765978533 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8592.php @@ -0,0 +1,15 @@ + $foo + */ +function foo(array $foo): void +{ + foreach ($foo as $key => $value) { + assertType('int|numeric-string', $key); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php index 8ffb1ddb895..a926f11293a 100644 --- a/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php +++ b/tests/PHPStan/Analyser/nsrt/isset-coalesce-empty-type.php @@ -296,7 +296,7 @@ class Bug4671 */ public function doFoo(int $intput, array $strings): void { - assertType('false', isset($strings[(string) $intput])); + assertType('bool', isset($strings[(string) $intput])); } } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index aa4d8cd83b9..f079710e492 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -898,6 +898,11 @@ public function testBug2634(): void $this->analyse([__DIR__ . '/data/bug-2634.php'], []); } + public function testBug11390(): void + { + $this->analyse([__DIR__ . '/data/bug-11390.php'], []); + } + public function testInternalClassesWithOverloadedOffsetAccess(): void { $this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access.php'], []); diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11390.php b/tests/PHPStan/Rules/Arrays/data/bug-11390.php new file mode 100644 index 00000000000..de601158ba5 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11390.php @@ -0,0 +1,29 @@ + $tags + * @param numeric-string $tagId + */ +function printTagName(array $tags, string $tagId): void +{ + // Adding the second `*` to either of the following lines makes the error disappear + + $tagsById = array_combine(array_column($tags, 'id'), $tags); + if (false !== $tagsById) { + echo $tagsById[$tagId]['tagName'] . PHP_EOL; + } +} + +printTagName( + [ + ['id' => '123', 'tagName' => 'abc'], + ['id' => '4.5', 'tagName' => 'def'], + ['id' => '6e78', 'tagName' => 'ghi'] + ], + '4.5' +); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index a7981429416..548b240e975 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1069,6 +1069,16 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug4163(): void + { + $this->analyse([__DIR__ . '/data/bug-4163.php'], [ + [ + 'Method Bug4163\HelloWorld::lall() should return array but returns array.', + 28, + ], + ]); + } + public function testBug11663(): void { $this->analyse([__DIR__ . '/data/bug-11663.php'], []); diff --git a/tests/PHPStan/Rules/Methods/data/bug-4163.php b/tests/PHPStan/Rules/Methods/data/bug-4163.php new file mode 100644 index 00000000000..7b8de0491cb --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4163.php @@ -0,0 +1,30 @@ + + */ + function lall() { + $helloCollection = [new HelloWorld(), new HelloWorld()]; + $result = []; + + foreach ($helloCollection as $hello) { + $key = (string)$hello->lall; + + if (!isset($result[$key])) { + $lall = 'do_something_here'; + $result[$key] = $lall; + } + } + + return $result; + } +} diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index f96185de697..60795c05814 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -233,10 +233,7 @@ public function testBug4671(): void { $this->treatPhpDocTypesAsCertain = true; $this->strictUnnecessaryNullsafePropertyFetch = false; - $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ - 'Offset numeric-string on array in isset() does not exist.', - 13, - ]]); + $this->analyse([__DIR__ . '/data/bug-4671.php'], []); } public function testVariableCertaintyInIsset(): void From adf21bc4482d2c37de158360d5bddbc0d1cb8022 Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Mon, 9 Dec 2024 22:56:34 -0500 Subject: [PATCH 1339/3097] Respect asserts and throws on pure functions that return void --- src/Rules/Pure/FunctionPurityCheck.php | 8 +- .../Rules/Pure/PureFunctionRuleTest.php | 10 ++ .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 8 ++ tests/PHPStan/Rules/Pure/data/bug-12224.php | 91 +++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Pure/data/bug-12224.php diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index 817b6a32739..ccc85a1c24b 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -59,7 +59,13 @@ public function check( ))->identifier(sprintf('pure%s.parameterByRef', $identifier))->build(); } - if ($returnType->isVoid()->yes() && !$isConstructor) { + $throwType = $functionReflection->getThrowType(); + if ( + $returnType->isVoid()->yes() + && !$isConstructor + && ($throwType === null || $throwType->isVoid()->yes()) + && $functionReflection->getAsserts()->getAll() === [] + ) { $errors[] = RuleErrorBuilder::message(sprintf( '%s is marked as pure but returns void.', $functionDescription, diff --git a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php index c310f6177c4..9be4df61535 100644 --- a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php @@ -167,4 +167,14 @@ public function testBug11361(): void ]); } + public function testBug12224(): void + { + $this->analyse([__DIR__ . '/data/bug-12224.php'], [ + [ + 'Function PHPStan\Rules\Pure\data\pureWithThrowsVoid() is marked as pure but returns void.', + 18, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 19d1eed2633..a483c6d5807 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -212,4 +212,12 @@ public function testBug12048(): void $this->analyse([__DIR__ . '/data/bug-12048.php'], []); } + public function testBug12224(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12224.php'], [ + ['Method PHPStan\Rules\Pure\data\A::pureWithThrowsVoid() is marked as pure but returns void.', 47], + ]); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-12224.php b/tests/PHPStan/Rules/Pure/data/bug-12224.php new file mode 100644 index 00000000000..b93de831788 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-12224.php @@ -0,0 +1,91 @@ + Date: Mon, 31 Mar 2025 23:05:17 +0200 Subject: [PATCH 1340/3097] Fix mb_convert_encoding signature --- resources/functionMap.php | 2 +- ...ertEncodingFunctionReturnTypeExtension.php | 61 ++++++++++++++++--- tests/PHPStan/Analyser/nsrt/bug-3336.php | 8 +-- .../Analyser/nsrt/mb_convert_encoding.php | 32 ++++++++++ 4 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php diff --git a/resources/functionMap.php b/resources/functionMap.php index c772596bcc2..f5d9a827157 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6312,7 +6312,7 @@ 'mb_check_encoding' => ['bool', 'var='=>'string|array', 'encoding='=>'string'], 'mb_chr' => ['string|false', 'cp'=>'int', 'encoding='=>'string'], 'mb_convert_case' => ['string', 'sourcestring'=>'string', 'mode'=>'int', 'encoding='=>'string'], -'mb_convert_encoding' => ['string|array|false', 'val'=>'string|array', 'to_encoding'=>'string', 'from_encoding='=>'mixed'], +'mb_convert_encoding' => ['string|array|false', 'val'=>'string|array', 'to_encoding'=>'string', 'from_encoding='=>'mixed'], 'mb_convert_kana' => ['string', 'str'=>'string', 'option='=>'string', 'encoding='=>'string'], 'mb_convert_variables' => ['string|false', 'to_encoding'=>'string', 'from_encoding'=>'array|string', '&rw_vars'=>'string|array|object', '&...rw_vars='=>'string|array|object'], 'mb_decode_mimeheader' => ['string', 'string'=>'string'], diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index dd46ed4c2ae..a09f0a823ab 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -5,11 +5,18 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\IntegerType; +use PHPStan\Type\NeverType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; +use function count; final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -30,16 +37,54 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($functionCall->getArgs()[0]->value); - $isString = $argType->isString(); - $isArray = $argType->isArray(); - $compare = $isString->compareTo($isArray); - if ($compare === $isString) { + + $initialReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants(), + )->getReturnType(); + + $result = TypeCombinator::intersect($initialReturnType, $this->generalizeStringType($argType)); + if ($result instanceof NeverType) { + return null; + } + + return TypeCombinator::union($result, new ConstantBooleanType(false)); + } + + public function generalizeStringType(Type $type): Type + { + if ($type instanceof UnionType) { + return $type->traverse([$this, 'generalizeStringType']); + } + + if ($type->isString()->yes()) { return new StringType(); - } elseif ($compare === $isArray) { - return new ArrayType(new IntegerType(), new StringType()); } - return null; + $constantArrays = $type->getConstantArrays(); + if (count($constantArrays) > 0) { + $types = []; + foreach ($constantArrays as $constantArray) { + $types[] = $constantArray->traverse([$this, 'generalizeStringType']); + } + + return TypeCombinator::union(...$types); + } + + if ($type->isArray()->yes()) { + $newArrayType = new ArrayType($type->getIterableKeyType(), $this->generalizeStringType($type->getIterableValueType())); + if ($type->isIterableAtLeastOnce()->yes()) { + $newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); + } + if ($type->isList()->yes()) { + $newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType()); + } + + return $newArrayType; + } + + return $type; } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-3336.php b/tests/PHPStan/Analyser/nsrt/bug-3336.php index a6712e6f69f..b0707c61aa8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-3336.php +++ b/tests/PHPStan/Analyser/nsrt/bug-3336.php @@ -3,8 +3,8 @@ namespace Bug3336; function (array $arr, string $str, $mixed): void { - \PHPStan\Testing\assertType('array', mb_convert_encoding($arr)); - \PHPStan\Testing\assertType('string', mb_convert_encoding($str)); - \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($mixed)); - \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding()); + \PHPStan\Testing\assertType('array|false', mb_convert_encoding($arr)); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($str)); + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($mixed)); + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding()); }; diff --git a/tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php b/tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php new file mode 100644 index 00000000000..f5f524d44fa --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/mb_convert_encoding.php @@ -0,0 +1,32 @@ + $stringList + * @param list $intList + * @param 'foo'|'bar'|array{foo: string, bar: int, baz: 'foo'}|bool $union + */ +function test_mb_convert_encoding( + mixed $mixed, + string $constantString, + string $string, + array $mixedArray, + array $structuredArray, + array $stringList, + array $intList, + string|array|bool $union, + int $int, +): void { + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($mixed, 'UTF-8')); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($constantString, 'UTF-8')); + \PHPStan\Testing\assertType('string|false', mb_convert_encoding($string, 'UTF-8')); + \PHPStan\Testing\assertType('array|false', mb_convert_encoding($mixedArray, 'UTF-8')); + \PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|false', mb_convert_encoding($structuredArray, 'UTF-8')); + \PHPStan\Testing\assertType('list|false', mb_convert_encoding($stringList, 'UTF-8')); + \PHPStan\Testing\assertType('list|false', mb_convert_encoding($intList, 'UTF-8')); + \PHPStan\Testing\assertType('array{foo: string, bar: int, baz: string}|string|false', mb_convert_encoding($union, 'UTF-8')); + \PHPStan\Testing\assertType('array|string|false', mb_convert_encoding($int, 'UTF-8')); +}; From 4c36cd93d41015240da37ba02f5ef62bcaf5ac25 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:57:44 +0000 Subject: [PATCH 1341/3097] Update PHPStan packages to v2.0.3 --- composer.json | 2 +- composer.lock | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 3a585da23c6..1b5ef4e5152 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#b22fb017543bb7147e3bcc53f08fb13a48aff994", + "jetbrains/phpstorm-stubs": "dev-master#44f320d4e03204709450e15105536751add593cd", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index b304bec2cba..280e1ddce5b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a5aee6235dc8ddeac7b42ed53ce87902", + "content-hash": "a1dba49658a71b1032e5a3ad804f2936", "packages": [ { "name": "clue/ndjson-react", @@ -4720,21 +4720,21 @@ }, { "name": "phpstan/phpstan-nette", - "version": "2.0.0", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "cacb6983bbdf44d5c3a7222e5ca74f61f8531806" + "reference": "4b291c9f4c41fed86f5a7e308f0ac6a6664839bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/cacb6983bbdf44d5c3a7222e5ca74f61f8531806", - "reference": "cacb6983bbdf44d5c3a7222e5ca74f61f8531806", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/4b291c9f4c41fed86f5a7e308f0ac6a6664839bf", + "reference": "4b291c9f4c41fed86f5a7e308f0ac6a6664839bf", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.3" }, "conflict": { "nette/application": "<2.3.0", @@ -4775,33 +4775,35 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.0" + "source": "https://github.com/phpstan/phpstan-nette/tree/2.0.3" }, - "time": "2024-10-26T16:03:48+00:00" + "time": "2025-02-12T09:01:49+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.0", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "3cc855474263ad6220dfa49167cbea34ca1dd300" + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/3cc855474263ad6220dfa49167cbea34ca1dd300", - "reference": "3cc855474263ad6220dfa49167cbea34ca1dd300", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.0.4" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { + "nikic/php-parser": "^5", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6" }, @@ -4826,9 +4828,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.0" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" }, - "time": "2024-10-14T03:16:27+00:00" + "time": "2025-03-26T12:47:06+00:00" }, { "name": "phpstan/phpstan-strict-rules", From e8e58dbca393813797527513d67f3942750b22c9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 6 May 2025 14:40:35 +0200 Subject: [PATCH 1342/3097] Disable purity check for non-final methods --- src/Rules/Pure/FunctionPurityCheck.php | 5 ++ .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 23 ++++++++ tests/PHPStan/Rules/Pure/data/bug-12382.php | 56 +++++++++++++++++++ .../Rules/Pure/data/pure-constructor.php | 6 +- tests/PHPStan/Rules/Pure/data/pure-method.php | 40 ++++++------- 5 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 tests/PHPStan/Rules/Pure/data/bug-12382.php diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index ccc85a1c24b..a799cd4351c 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -92,6 +92,11 @@ public function check( count($throwPoints) === 0 && count($impurePoints) === 0 && count($functionReflection->getAsserts()->getAll()) === 0 + && ( + !$functionReflection instanceof ExtendedMethodReflection + || $functionReflection->isFinal()->yes() + || $functionReflection->getDeclaringClass()->isFinal() + ) ) { $errors[] = RuleErrorBuilder::message(sprintf( '%s is marked as impure but does not have any side effects.', diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index a483c6d5807..c31874629d2 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -220,4 +220,27 @@ public function testBug12224(): void ]); } + public function testBug12382(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12382.php'], [ + [ + 'Method Bug12382\FinalHelloWorld1::dummy() is marked as impure but does not have any side effects.', + 25, + ], + [ + 'Method Bug12382\FinalHelloWorld2::dummy() is marked as impure but does not have any side effects.', + 33, + ], + [ + 'Method Bug12382\FinalHelloWorld3::dummy() is marked as impure but does not have any side effects.', + 42, + ], + [ + 'Method Bug12382\FinalHelloWorld4::dummy() is marked as impure but does not have any side effects.', + 53, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-12382.php b/tests/PHPStan/Rules/Pure/data/bug-12382.php new file mode 100644 index 00000000000..7a66941da8a --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-12382.php @@ -0,0 +1,56 @@ +prop++; + return $this; + } +} + +final class FinalHelloWorld1 +{ + /** @phpstan-impure */ + public function dummy() : self{ + return $this; + } +} + +class FinalHelloWorld2 +{ + /** @phpstan-impure */ + final public function dummy() : self{ + return $this; + } +} + +/** @final */ +class FinalHelloWorld3 +{ + /** @phpstan-impure */ + public function dummy() : self{ + return $this; + } +} + +class FinalHelloWorld4 +{ + /** + * @final + * @phpstan-impure + */ + public function dummy() : self{ + return $this; + } +} diff --git a/tests/PHPStan/Rules/Pure/data/pure-constructor.php b/tests/PHPStan/Rules/Pure/data/pure-constructor.php index 71045fd3ed9..baa1f755cf4 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-constructor.php +++ b/tests/PHPStan/Rules/Pure/data/pure-constructor.php @@ -2,7 +2,7 @@ namespace PureConstructor; -class Foo +final class Foo { private string $prop; @@ -21,7 +21,7 @@ public function __construct( } -class Bar +final class Bar { private string $prop; @@ -37,7 +37,7 @@ public function __construct( } -class AssignOtherThanThis +final class AssignOtherThanThis { private int $i = 0; diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index efa83c9191e..ba2ce7e3be4 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -2,7 +2,7 @@ namespace PureMethod; -class Foo +final class Foo { /** @@ -92,7 +92,7 @@ public function doFoo5() } -class PureConstructor +final class PureConstructor { /** @@ -105,7 +105,7 @@ public function __construct() } -class ImpureConstructor +final class ImpureConstructor { /** @@ -118,7 +118,7 @@ public function __construct() } -class PossiblyImpureConstructor +final class PossiblyImpureConstructor { public function __construct() @@ -128,7 +128,7 @@ public function __construct() } -class TestConstructors +final class TestConstructors { /** @@ -144,7 +144,7 @@ public function doFoo(string $s) } -class ActuallyPure +final class ActuallyPure { /** @@ -175,7 +175,7 @@ public function impure(): int } -class ExtendingClass extends ToBeExtended +final class ExtendingClass extends ToBeExtended { public function pure(): int @@ -191,7 +191,7 @@ public function impure(): int } -class ClassWithVoidMethods +final class ClassWithVoidMethods { public function voidFunctionThatThrows(): void @@ -235,12 +235,12 @@ public function purePostGetAssign(array $post = [], array $get = []): int } -class NoMagicMethods +final class NoMagicMethods { } -class PureMagicMethods +final class PureMagicMethods { /** @@ -253,7 +253,7 @@ public function __toString(): string } -class MaybePureMagicMethods +final class MaybePureMagicMethods { public function __toString(): string @@ -263,7 +263,7 @@ public function __toString(): string } -class ImpureMagicMethods +final class ImpureMagicMethods { /** @@ -277,7 +277,7 @@ public function __toString(): string } -class TestMagicMethods +final class TestMagicMethods { /** @@ -298,12 +298,12 @@ public function doFoo( } -class NoConstructor +final class NoConstructor { } -class TestNoConstructor +final class TestNoConstructor { /** @@ -318,7 +318,7 @@ public function doFoo(): int } -class MaybeCallableFromUnion +final class MaybeCallableFromUnion { /** @@ -334,7 +334,7 @@ public function doFoo($p): int } -class VoidMethods +final class VoidMethods { private function doFoo(): void @@ -361,7 +361,7 @@ private function doBaz(): void } -class AssertingImpureVoidMethod +final class AssertingImpureVoidMethod { /** @@ -376,7 +376,7 @@ public function assertSth($value): void } -class StaticMethodAccessingStaticProperty +final class StaticMethodAccessingStaticProperty { /** @var int */ public static $a = 0; @@ -397,7 +397,7 @@ public static function getB(): int } } -class StaticMethodAssigningStaticProperty +final class StaticMethodAssigningStaticProperty { /** @var int */ public static $a = 0; From 8db230f6e6322c2c5625789b10f0bac6b75bda23 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 13 May 2025 00:04:17 +0000 Subject: [PATCH 1343/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 1b5ef4e5152..d10f4174915 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#44f320d4e03204709450e15105536751add593cd", + "jetbrains/phpstorm-stubs": "dev-master#1af43913cbb6d4dd4c8b776caae02dae1a2f35de", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 280e1ddce5b..38509d2839c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a1dba49658a71b1032e5a3ad804f2936", + "content-hash": "baacfd9f6f313439fbc41174b0ac33c8", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "b22fb017543bb7147e3bcc53f08fb13a48aff994" + "reference": "1af43913cbb6d4dd4c8b776caae02dae1a2f35de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/b22fb017543bb7147e3bcc53f08fb13a48aff994", - "reference": "b22fb017543bb7147e3bcc53f08fb13a48aff994", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/1af43913cbb6d4dd4c8b776caae02dae1a2f35de", + "reference": "1af43913cbb6d4dd4c8b776caae02dae1a2f35de", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-04-22T16:22:26+00:00" + "time": "2025-05-12T09:33:59+00:00" }, { "name": "nette/bootstrap", From 8368b0c27e2ae919e1cb874d18d19b2e14a43723 Mon Sep 17 00:00:00 2001 From: Robert Meijers Date: Sun, 11 May 2025 19:28:11 +0200 Subject: [PATCH 1344/3097] Support iterable as template type Fixes phpstan/phpstan#12214 --- phpstan-baseline.neon | 10 +++++ src/Rules/Generics/TemplateTypeCheck.php | 2 + src/Type/Generic/TemplateIterableType.php | 38 +++++++++++++++++++ src/Type/Generic/TemplateTypeFactory.php | 5 +++ .../Analyser/AnalyserIntegrationTest.php | 6 +++ tests/PHPStan/Analyser/data/bug-12214.php | 31 +++++++++++++++ 6 files changed, 92 insertions(+) create mode 100644 src/Type/Generic/TemplateIterableType.php create mode 100644 tests/PHPStan/Analyser/data/bug-12214.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d9980551b24..47ac0acf518 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1040,6 +1040,11 @@ parameters: count: 3 path: src/Type/Generic/TemplateIntersectionType.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" + count: 3 + path: src/Type/Generic/TemplateIterableType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 @@ -1115,6 +1120,11 @@ parameters: count: 1 path: src/Type/Generic/TemplateTypeFactory.php + - + message: "#^Doing instanceof PHPStan\\\\Type\\\\IterableType is error\\-prone and deprecated\\. Use Type\\:\\:isIterable\\(\\) instead\\.$#" + count: 1 + path: src/Type/Generic/TemplateTypeFactory.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\ObjectShapeType is error\\-prone and deprecated\\. Use Type\\:\\:isObject\\(\\) and Type\\:\\:hasProperty\\(\\) instead\\.$#" count: 1 diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index dda84c8629e..16ac12aa9d3 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -22,6 +22,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IterableType; use PHPStan\Type\KeyOfType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectShapeType; @@ -123,6 +124,7 @@ public function check( && $boundTypeClass !== ObjectShapeType::class && $boundTypeClass !== GenericObjectType::class && $boundTypeClass !== KeyOfType::class + && $boundTypeClass !== IterableType::class && !$boundType instanceof UnionType && !$boundType instanceof IntersectionType && !$boundType instanceof TemplateType diff --git a/src/Type/Generic/TemplateIterableType.php b/src/Type/Generic/TemplateIterableType.php new file mode 100644 index 00000000000..5308ad3b580 --- /dev/null +++ b/src/Type/Generic/TemplateIterableType.php @@ -0,0 +1,38 @@ + */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + /** + * @param non-empty-string $name + */ + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + IterableType $bound, + ?Type $default, + ) + { + parent::__construct($bound->getKeyType(), $bound->getItemType()); + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + $this->default = $default; + } + +} diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index 8f56cc6cbbf..dd4764ee666 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -12,6 +12,7 @@ use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\IterableType; use PHPStan\Type\KeyOfType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectShapeType; @@ -107,6 +108,10 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou return new TemplateKeyOfType($scope, $strategy, $variance, $name, $bound, $default); } + if ($bound instanceof IterableType && ($boundClass === IterableType::class || $bound instanceof TemplateType)) { + return new TemplateIterableType($scope, $strategy, $variance, $name, $bound, $default); + } + return new TemplateMixedType($scope, $strategy, $variance, $name, new MixedType(true), $default); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3c54d1da2c0..087f33b3954 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1469,6 +1469,12 @@ public function testBug11511(): void $this->assertSame('Access to an undefined property object::$bar.', $errors[0]->getMessage()); } + public function testBug12214(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12214.php'); + $this->assertNoErrors($errors); + } + public function testBug11640(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-11640.php'); diff --git a/tests/PHPStan/Analyser/data/bug-12214.php b/tests/PHPStan/Analyser/data/bug-12214.php new file mode 100644 index 00000000000..990e8741bc5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12214.php @@ -0,0 +1,31 @@ + $test + */ +function test_iterable(iterable $test): void +{ + assertType('iterable<(int|string), mixed>', $test); +} + +/** + * @template T of array + * @param T $test + */ +function test_array(array $test): void +{ + assertType('T of array (function Bug12214\test_array(), argument)', $test); +} + +/** + * @template T of iterable + * @param T $test + */ +function test_generic_iterable(iterable $test): void +{ + assertType('T of iterable<(int|string), mixed> (function Bug12214\test_generic_iterable(), argument)', $test); +} From fbba482ba7d423069f94fcc0efbf95e2e098e939 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 02:44:37 +0000 Subject: [PATCH 1345/3097] Update dependency brianium/paratest to v6.11.1 --- composer.lock | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 38509d2839c..b893805abac 100644 --- a/composer.lock +++ b/composer.lock @@ -4114,16 +4114,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v6.6.3", + "version": "v6.11.1", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "f2d781bb9136cda2f5e73ee778049e80ba681cf6" + "reference": "78e297a969049ca7cc370e80ff5e102921ef39a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/f2d781bb9136cda2f5e73ee778049e80ba681cf6", - "reference": "f2d781bb9136cda2f5e73ee778049e80ba681cf6", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/78e297a969049ca7cc370e80ff5e102921ef39a3", + "reference": "78e297a969049ca7cc370e80ff5e102921ef39a3", "shasum": "" }, "require": { @@ -4131,26 +4131,25 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "jean85/pretty-package-versions": "^2.0.5", "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.16", + "phpunit/php-code-coverage": "^9.2.25", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.5.23", - "sebastian/environment": "^5.1.4", - "symfony/console": "^5.4.9 || ^6.1.2", - "symfony/polyfill-php80": "^v1.26.0", - "symfony/process": "^5.4.8 || ^6.1.0" + "phpunit/phpunit": "^9.6.4", + "sebastian/environment": "^5.1.5", + "symfony/console": "^5.4.28 || ^6.3.4 || ^7.0.0", + "symfony/process": "^5.4.28 || ^6.3.4 || ^7.0.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0.0", + "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.26.13", - "malukenho/mcbumpface": "^1.1.5", - "squizlabs/php_codesniffer": "^3.7.1", - "symfony/filesystem": "^5.4.9 || ^6.1.0", - "vimeo/psalm": "^4.26.0" + "infection/infection": "^0.27.6", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/filesystem": "^5.4.25 || ^6.3.1 || ^7.0.0", + "vimeo/psalm": "^5.7.7" }, "bin": [ "bin/paratest", @@ -4191,7 +4190,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.6.3" + "source": "https://github.com/paratestphp/paratest/tree/v6.11.1" }, "funding": [ { @@ -4203,7 +4202,7 @@ "type": "paypal" } ], - "time": "2022-08-25T05:44:14+00:00" + "time": "2024-03-13T06:54:29+00:00" }, { "name": "cweagans/composer-patches", From a12642a0bf0b0c0480771fa9c858ca6f1ecf487f Mon Sep 17 00:00:00 2001 From: Andrei Ivchenkov Date: Thu, 8 May 2025 13:49:18 +0300 Subject: [PATCH 1346/3097] Fix callable-string must be non-empty-string --- .../Accessory/AccessoryNonEmptyStringType.php | 3 +++ src/Type/IntersectionType.php | 3 +++ .../Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-12979.php | 21 +++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-12979.php diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 117779e376e..dc25ccf5444 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -76,6 +76,9 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult { + if ($type->isNonEmptyString()->yes()) { + return AcceptsResult::createYes(); + } if ($type instanceof CompoundType) { return $type->isAcceptedWithReasonBy($this, $strictTypes); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index f683ad9cbd9..52abfdff30f 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -645,6 +645,9 @@ public function isNumericString(): TrinaryLogic public function isNonEmptyString(): TrinaryLogic { + if ($this->isCallable()->yes() && $this->isString()->yes()) { + return TrinaryLogic::createYes(); + } return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isNonEmptyString()); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 087f33b3954..1e5491bbc3b 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1487,6 +1487,12 @@ public function testBug11913(): void $this->assertNoErrors($errors); } + public function testBug12979(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-12979.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-12979.php b/tests/PHPStan/Analyser/data/bug-12979.php new file mode 100644 index 00000000000..5cd103b2270 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-12979.php @@ -0,0 +1,21 @@ +acceptNonEmptyString($this->callableString()); + } +} From 89717513ab32ccdd6d7f94b2c8d78fe5a4b95b19 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 13 May 2025 15:45:26 +0200 Subject: [PATCH 1347/3097] Fix build --- tests/PHPStan/Analyser/data/bug-12214.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/bug-12214.php b/tests/PHPStan/Analyser/data/bug-12214.php index 990e8741bc5..a2efd62ee5b 100644 --- a/tests/PHPStan/Analyser/data/bug-12214.php +++ b/tests/PHPStan/Analyser/data/bug-12214.php @@ -18,7 +18,7 @@ function test_iterable(iterable $test): void */ function test_array(array $test): void { - assertType('T of array (function Bug12214\test_array(), argument)', $test); + assertType('T of array (function Bug12214\test_array(), argument)', $test); } /** From 7ce53be31de748c3b5ab63ed6b745c37086a8484 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Apr 2025 20:21:49 +0200 Subject: [PATCH 1348/3097] Use SoapClientMethodsClassReflectionExtension as last extension --- conf/config.neon | 2 -- ...yClassReflectionExtensionRegistryProvider.php | 4 +++- tests/PHPStan/Analyser/nsrt/bug-12834.php | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12834.php diff --git a/conf/config.neon b/conf/config.neon index 205827901ef..5e643bc081e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -838,8 +838,6 @@ services: - class: PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension - tags: - - phpstan.broker.methodsClassReflectionExtension - class: PHPStan\Reflection\Php\EnumAllowedSubTypesClassReflectionExtension diff --git a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php index cb8c2d6543b..eaf112129d6 100644 --- a/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php +++ b/src/DependencyInjection/Reflection/LazyClassReflectionExtensionRegistryProvider.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension; use PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension; use PHPStan\Reflection\Php\PhpClassReflectionExtension; +use PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension; use PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension; use PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension; use function array_merge; @@ -33,11 +34,12 @@ public function getRegistry(): ClassReflectionExtensionRegistry $mixinMethodsClassReflectionExtension = $this->container->getByType(MixinMethodsClassReflectionExtension::class); $mixinPropertiesClassReflectionExtension = $this->container->getByType(MixinPropertiesClassReflectionExtension::class); + $soapClientMethodsClassReflectionExtension = $this->container->getByType(SoapClientMethodsClassReflectionExtension::class); $this->registry = new ClassReflectionExtensionRegistry( $this->container->getByType(Broker::class), array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsPropertiesClassReflectionExtension, $mixinPropertiesClassReflectionExtension]), - array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension, $mixinMethodsClassReflectionExtension]), + array_merge([$phpClassReflectionExtension], $this->container->getServicesByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG), [$annotationsMethodsClassReflectionExtension, $mixinMethodsClassReflectionExtension, $soapClientMethodsClassReflectionExtension]), $this->container->getServicesByTag(BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG), $this->container->getByType(RequireExtendsPropertiesClassReflectionExtension::class), $this->container->getByType(RequireExtendsMethodsClassReflectionExtension::class), diff --git a/tests/PHPStan/Analyser/nsrt/bug-12834.php b/tests/PHPStan/Analyser/nsrt/bug-12834.php new file mode 100644 index 00000000000..de44fa47f80 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12834.php @@ -0,0 +1,16 @@ +test()); From d825a7b1dc69f6e84375938c71e8235bc4d84f0a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 13 May 2025 19:33:01 +0200 Subject: [PATCH 1349/3097] Fix `IterableType::equals()` with `TemplateIterableType` --- phpstan-baseline.neon | 2 +- src/Type/IterableType.php | 3 ++- tests/PHPStan/Generics/TemplateTypeFactoryTest.php | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a887358f522..a07ea295562 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1383,7 +1383,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\IterableType is error\-prone and deprecated\. Use Type\:\:isIterable\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 2 + count: 1 path: src/Type/IterableType.php - diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 2e6d26a3813..2242c477a5a 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -20,6 +20,7 @@ use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use Traversable; use function array_merge; +use function get_class; use function sprintf; /** @api */ @@ -167,7 +168,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes public function equals(Type $type): bool { - if (!$type instanceof self) { + if (get_class($type) !== static::class) { return false; } diff --git a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php index 16fe1d24df2..58af77cd3b2 100644 --- a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php +++ b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php @@ -8,6 +8,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; +use PHPStan\Type\IterableType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; @@ -66,6 +67,10 @@ public function dataCreate(): array new IntegerType(), ]), ], + [ + new IterableType(new IntegerType(), new StringType()), + new IterableType(new IntegerType(), new StringType()), + ], ]; } From 84e231661a9b542d3114e8d9499626b784864282 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 21 Mar 2025 10:19:15 +0100 Subject: [PATCH 1350/3097] Remove MongoDB extension from function map --- resources/functionMap.php | 355 -------------------------------------- 1 file changed, 355 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 61320119403..1938de18e31 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6712,361 +6712,6 @@ 'MongoDB::setReadPreference' => ['bool', 'read_preference'=>'string', 'tags='=>'array'], 'MongoDB::setSlaveOkay' => ['bool', 'ok='=>'bool'], 'MongoDB::setWriteConcern' => ['bool', 'w'=>'mixed', 'wtimeout='=>'int'], -'MongoDB\BSON\fromJSON' => ['string', 'json'=>'string'], -'MongoDB\BSON\fromPHP' => ['string', 'value'=>'object|array'], -'MongoDB\BSON\toCanonicalExtendedJSON' => ['string', 'bson'=>'string'], -'MongoDB\BSON\toJSON' => ['string', 'bson'=>'string'], -'MongoDB\BSON\toPHP' => ['object|array', 'bson'=>'string', 'typemap='=>'?array'], -'MongoDB\BSON\toRelaxedExtendedJSON' => ['string', 'bson'=>'string'], -'MongoDB\Driver\Monitoring\addSubscriber' => ['void', 'subscriber'=>'MongoDB\Driver\Monitoring\Subscriber'], -'MongoDB\Driver\Monitoring\removeSubscriber' => ['void', 'subscriber'=>'MongoDB\Driver\Monitoring\Subscriber'], -'MongoDB\BSON\Binary::__construct' => ['void', 'data'=>'string', 'type='=>'int'], -'MongoDB\BSON\Binary::getData' => ['string'], -'MongoDB\BSON\Binary::getType' => ['int'], -'MongoDB\BSON\Binary::__toString' => ['string'], -'MongoDB\BSON\Binary::serialize' => ['string'], -'MongoDB\BSON\Binary::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Binary::jsonSerialize' => ['mixed'], -'MongoDB\BSON\BinaryInterface::getData' => ['string'], -'MongoDB\BSON\BinaryInterface::getType' => ['int'], -'MongoDB\BSON\BinaryInterface::__toString' => ['string'], -'MongoDB\BSON\DBPointer::__toString' => ['string'], -'MongoDB\BSON\DBPointer::serialize' => ['string'], -'MongoDB\BSON\DBPointer::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\DBPointer::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Decimal128::__construct' => ['void', 'value'=>'string'], -'MongoDB\BSON\Decimal128::__toString' => ['string'], -'MongoDB\BSON\Decimal128::serialize' => ['string'], -'MongoDB\BSON\Decimal128::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Decimal128::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Decimal128Interface::__toString' => ['string'], -'MongoDB\BSON\Document::fromBSON' => ['MongoDB\BSON\Document', 'bson'=>'string'], -'MongoDB\BSON\Document::fromJSON' => ['MongoDB\BSON\Document', 'json'=>'string'], -'MongoDB\BSON\Document::fromPHP' => ['MongoDB\BSON\Document', 'value'=>'object|array'], -'MongoDB\BSON\Document::get' => ['mixed', 'key'=>'string'], -'MongoDB\BSON\Document::getIterator' => ['MongoDB\BSON\Iterator'], -'MongoDB\BSON\Document::has' => ['bool', 'key'=>'string'], -'MongoDB\BSON\Document::toPHP' => ['object|array', 'typeMap='=>'?array'], -'MongoDB\BSON\Document::toCanonicalExtendedJSON' => ['string'], -'MongoDB\BSON\Document::toRelaxedExtendedJSON' => ['string'], -'MongoDB\BSON\Document::offsetExists' => ['bool', 'offset'=>'mixed'], -'MongoDB\BSON\Document::offsetGet' => ['mixed', 'offset'=>'mixed'], -'MongoDB\BSON\Document::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], -'MongoDB\BSON\Document::offsetUnset' => ['void', 'offset'=>'mixed'], -'MongoDB\BSON\Document::__toString' => ['string'], -'MongoDB\BSON\Document::serialize' => ['string'], -'MongoDB\BSON\Document::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Int64::__construct' => ['void', 'value'=>'string|int'], -'MongoDB\BSON\Int64::__toString' => ['string'], -'MongoDB\BSON\Int64::serialize' => ['string'], -'MongoDB\BSON\Int64::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Int64::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Iterator::current' => ['mixed'], -'MongoDB\BSON\Iterator::key' => ['string|int'], -'MongoDB\BSON\Iterator::next' => ['void'], -'MongoDB\BSON\Iterator::rewind' => ['void'], -'MongoDB\BSON\Iterator::valid' => ['bool'], -'MongoDB\BSON\Javascript::__construct' => ['void', 'code'=>'string', 'scope='=>'object|array|null'], -'MongoDB\BSON\Javascript::getCode' => ['string'], -'MongoDB\BSON\Javascript::getScope' => ['?object'], -'MongoDB\BSON\Javascript::__toString' => ['string'], -'MongoDB\BSON\Javascript::serialize' => ['string'], -'MongoDB\BSON\Javascript::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Javascript::jsonSerialize' => ['mixed'], -'MongoDB\BSON\JavascriptInterface::getCode' => ['string'], -'MongoDB\BSON\JavascriptInterface::getScope' => ['?object'], -'MongoDB\BSON\JavascriptInterface::__toString' => ['string'], -'MongoDB\BSON\MaxKey::serialize' => ['string'], -'MongoDB\BSON\MaxKey::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\MaxKey::jsonSerialize' => ['mixed'], -'MongoDB\BSON\MinKey::serialize' => ['string'], -'MongoDB\BSON\MinKey::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\MinKey::jsonSerialize' => ['mixed'], -'MongoDB\BSON\ObjectId::__construct' => ['void', 'id='=>'?string'], -'MongoDB\BSON\ObjectId::getTimestamp' => ['int'], -'MongoDB\BSON\ObjectId::__toString' => ['string'], -'MongoDB\BSON\ObjectId::serialize' => ['string'], -'MongoDB\BSON\ObjectId::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\ObjectId::jsonSerialize' => ['mixed'], -'MongoDB\BSON\ObjectIdInterface::getTimestamp' => ['int'], -'MongoDB\BSON\ObjectIdInterface::__toString' => ['string'], -'MongoDB\BSON\PackedArray::fromPHP' => ['MongoDB\BSON\PackedArray', 'value'=>'array'], -'MongoDB\BSON\PackedArray::get' => ['mixed', 'index'=>'int'], -'MongoDB\BSON\PackedArray::getIterator' => ['MongoDB\BSON\Iterator'], -'MongoDB\BSON\PackedArray::has' => ['bool', 'index'=>'int'], -'MongoDB\BSON\PackedArray::toPHP' => ['object|array', 'typeMap='=>'?array'], -'MongoDB\BSON\PackedArray::offsetExists' => ['bool', 'offset'=>'mixed'], -'MongoDB\BSON\PackedArray::offsetGet' => ['mixed', 'offset'=>'mixed'], -'MongoDB\BSON\PackedArray::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], -'MongoDB\BSON\PackedArray::offsetUnset' => ['void', 'offset'=>'mixed'], -'MongoDB\BSON\PackedArray::__toString' => ['string'], -'MongoDB\BSON\PackedArray::serialize' => ['string'], -'MongoDB\BSON\PackedArray::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Persistable::bsonSerialize' => ['stdClass|MongoDB\BSON\Document|array'], -'MongoDB\BSON\Regex::__construct' => ['void', 'pattern'=>'string', 'flags='=>'string'], -'MongoDB\BSON\Regex::getPattern' => ['string'], -'MongoDB\BSON\Regex::getFlags' => ['string'], -'MongoDB\BSON\Regex::__toString' => ['string'], -'MongoDB\BSON\Regex::serialize' => ['string'], -'MongoDB\BSON\Regex::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Regex::jsonSerialize' => ['mixed'], -'MongoDB\BSON\RegexInterface::getPattern' => ['string'], -'MongoDB\BSON\RegexInterface::getFlags' => ['string'], -'MongoDB\BSON\RegexInterface::__toString' => ['string'], -'MongoDB\BSON\Serializable::bsonSerialize' => ['stdClass|MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array'], -'MongoDB\BSON\Symbol::__toString' => ['string'], -'MongoDB\BSON\Symbol::serialize' => ['string'], -'MongoDB\BSON\Symbol::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Symbol::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Timestamp::__construct' => ['void', 'increment'=>'string|int', 'timestamp'=>'string|int'], -'MongoDB\BSON\Timestamp::getTimestamp' => ['int'], -'MongoDB\BSON\Timestamp::getIncrement' => ['int'], -'MongoDB\BSON\Timestamp::__toString' => ['string'], -'MongoDB\BSON\Timestamp::serialize' => ['string'], -'MongoDB\BSON\Timestamp::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Timestamp::jsonSerialize' => ['mixed'], -'MongoDB\BSON\TimestampInterface::getTimestamp' => ['int'], -'MongoDB\BSON\TimestampInterface::getIncrement' => ['int'], -'MongoDB\BSON\TimestampInterface::__toString' => ['string'], -'MongoDB\BSON\UTCDateTime::__construct' => ['void', 'milliseconds='=>'DateTimeInterface|string|int|float|null'], -'MongoDB\BSON\UTCDateTime::toDateTime' => ['DateTime'], -'MongoDB\BSON\UTCDateTime::__toString' => ['string'], -'MongoDB\BSON\UTCDateTime::serialize' => ['string'], -'MongoDB\BSON\UTCDateTime::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\UTCDateTime::jsonSerialize' => ['mixed'], -'MongoDB\BSON\UTCDateTimeInterface::toDateTime' => ['DateTime'], -'MongoDB\BSON\UTCDateTimeInterface::__toString' => ['string'], -'MongoDB\BSON\Undefined::__toString' => ['string'], -'MongoDB\BSON\Undefined::serialize' => ['string'], -'MongoDB\BSON\Undefined::unserialize' => ['void', 'data'=>'string'], -'MongoDB\BSON\Undefined::jsonSerialize' => ['mixed'], -'MongoDB\BSON\Unserializable::bsonUnserialize' => ['void', 'data'=>'array'], -'MongoDB\Driver\BulkWrite::__construct' => ['void', 'options='=>'?array'], -'MongoDB\Driver\BulkWrite::count' => ['int'], -'MongoDB\Driver\BulkWrite::delete' => ['void', 'filter'=>'object|array', 'deleteOptions='=>'?array'], -'MongoDB\Driver\BulkWrite::insert' => ['mixed', 'document'=>'object|array'], -'MongoDB\Driver\BulkWrite::update' => ['void', 'filter'=>'object|array', 'newObj'=>'object|array', 'updateOptions='=>'?array'], -'MongoDB\Driver\ClientEncryption::__construct' => ['void', 'options'=>'array'], -'MongoDB\Driver\ClientEncryption::addKeyAltName' => ['?object', 'keyId'=>'MongoDB\BSON\Binary', 'keyAltName'=>'string'], -'MongoDB\Driver\ClientEncryption::createDataKey' => ['MongoDB\BSON\Binary', 'kmsProvider'=>'string', 'options='=>'?array'], -'MongoDB\Driver\ClientEncryption::decrypt' => ['mixed', 'value'=>'MongoDB\BSON\Binary'], -'MongoDB\Driver\ClientEncryption::deleteKey' => ['object', 'keyId'=>'MongoDB\BSON\Binary'], -'MongoDB\Driver\ClientEncryption::encrypt' => ['MongoDB\BSON\Binary', 'value'=>'mixed', 'options='=>'?array'], -'MongoDB\Driver\ClientEncryption::encryptExpression' => ['object', 'expr'=>'object|array', 'options='=>'?array'], -'MongoDB\Driver\ClientEncryption::getKey' => ['?object', 'keyId'=>'MongoDB\BSON\Binary'], -'MongoDB\Driver\ClientEncryption::getKeyByAltName' => ['?object', 'keyAltName'=>'string'], -'MongoDB\Driver\ClientEncryption::getKeys' => ['MongoDB\Driver\Cursor'], -'MongoDB\Driver\ClientEncryption::removeKeyAltName' => ['?object', 'keyId'=>'MongoDB\BSON\Binary', 'keyAltName'=>'string'], -'MongoDB\Driver\ClientEncryption::rewrapManyDataKey' => ['object', 'filter'=>'object|array', 'options='=>'?array'], -'MongoDB\Driver\Command::__construct' => ['void', 'document'=>'object|array', 'commandOptions='=>'?array'], -'MongoDB\Driver\Cursor::current' => ['object|array|null'], -'MongoDB\Driver\Cursor::getId' => ['MongoDB\Driver\CursorId'], -'MongoDB\Driver\Cursor::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\Cursor::isDead' => ['bool'], -'MongoDB\Driver\Cursor::key' => ['?int'], -'MongoDB\Driver\Cursor::next' => ['void'], -'MongoDB\Driver\Cursor::rewind' => ['void'], -'MongoDB\Driver\Cursor::setTypeMap' => ['void', 'typemap'=>'array'], -'MongoDB\Driver\Cursor::toArray' => ['array'], -'MongoDB\Driver\Cursor::valid' => ['bool'], -'MongoDB\Driver\CursorId::__toString' => ['string'], -'MongoDB\Driver\CursorId::serialize' => ['string'], -'MongoDB\Driver\CursorId::unserialize' => ['void', 'data'=>'string'], -'MongoDB\Driver\CursorInterface::getId' => ['MongoDB\Driver\CursorId'], -'MongoDB\Driver\CursorInterface::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\CursorInterface::isDead' => ['bool'], -'MongoDB\Driver\CursorInterface::setTypeMap' => ['void', 'typemap'=>'array'], -'MongoDB\Driver\CursorInterface::toArray' => ['array'], -'MongoDB\Driver\Exception\AuthenticationException::__toString' => ['string'], -'MongoDB\Driver\Exception\BulkWriteException::__toString' => ['string'], -'MongoDB\Driver\Exception\CommandException::getResultDocument' => ['object'], -'MongoDB\Driver\Exception\CommandException::__toString' => ['string'], -'MongoDB\Driver\Exception\ConnectionException::__toString' => ['string'], -'MongoDB\Driver\Exception\ConnectionTimeoutException::__toString' => ['string'], -'MongoDB\Driver\Exception\EncryptionException::__toString' => ['string'], -'MongoDB\Driver\Exception\Exception::__toString' => ['string'], -'MongoDB\Driver\Exception\ExecutionTimeoutException::__toString' => ['string'], -'MongoDB\Driver\Exception\InvalidArgumentException::__toString' => ['string'], -'MongoDB\Driver\Exception\LogicException::__toString' => ['string'], -'MongoDB\Driver\Exception\RuntimeException::hasErrorLabel' => ['bool', 'errorLabel'=>'string'], -'MongoDB\Driver\Exception\RuntimeException::__toString' => ['string'], -'MongoDB\Driver\Exception\SSLConnectionException::__toString' => ['string'], -'MongoDB\Driver\Exception\ServerException::__toString' => ['string'], -'MongoDB\Driver\Exception\UnexpectedValueException::__toString' => ['string'], -'MongoDB\Driver\Exception\WriteException::getWriteResult' => ['MongoDB\Driver\WriteResult'], -'MongoDB\Driver\Exception\WriteException::__toString' => ['string'], -'MongoDB\Driver\Manager::__construct' => ['void', 'uri='=>'?string', 'uriOptions='=>'?array', 'driverOptions='=>'?array'], -'MongoDB\Driver\Manager::addSubscriber' => ['void', 'subscriber'=>'MongoDB\Driver\Monitoring\Subscriber'], -'MongoDB\Driver\Manager::createClientEncryption' => ['MongoDB\Driver\ClientEncryption', 'options'=>'array'], -'MongoDB\Driver\Manager::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulk'=>'MongoDB\Driver\BulkWrite', 'options='=>'MongoDB\Driver\WriteConcern|array|null'], -'MongoDB\Driver\Manager::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'MongoDB\Driver\ReadPreference|array|null'], -'MongoDB\Driver\Manager::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'options='=>'MongoDB\Driver\ReadPreference|array|null'], -'MongoDB\Driver\Manager::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Manager::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Manager::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Manager::getEncryptedFieldsMap' => ['object|array|null'], -'MongoDB\Driver\Manager::getReadConcern' => ['MongoDB\Driver\ReadConcern'], -'MongoDB\Driver\Manager::getReadPreference' => ['MongoDB\Driver\ReadPreference'], -'MongoDB\Driver\Manager::getServers' => ['array'], -'MongoDB\Driver\Manager::getWriteConcern' => ['MongoDB\Driver\WriteConcern'], -'MongoDB\Driver\Manager::removeSubscriber' => ['void', 'subscriber'=>'MongoDB\Driver\Monitoring\Subscriber'], -'MongoDB\Driver\Manager::selectServer' => ['MongoDB\Driver\Server', 'readPreference='=>'?MongoDB\Driver\ReadPreference'], -'MongoDB\Driver\Manager::startSession' => ['MongoDB\Driver\Session', 'options='=>'?array'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getCommandName' => ['string'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getDurationMicros' => ['int'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getError' => ['Exception'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getOperationId' => ['string'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getReply' => ['object'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getRequestId' => ['string'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\CommandFailedEvent::getServerConnectionId' => ['?int'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommand' => ['object'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getCommandName' => ['string'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getDatabaseName' => ['string'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getOperationId' => ['string'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getRequestId' => ['string'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\CommandStartedEvent::getServerConnectionId' => ['?int'], -'MongoDB\Driver\Monitoring\CommandSubscriber::commandStarted' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandStartedEvent'], -'MongoDB\Driver\Monitoring\CommandSubscriber::commandSucceeded' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandSucceededEvent'], -'MongoDB\Driver\Monitoring\CommandSubscriber::commandFailed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\CommandFailedEvent'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getCommandName' => ['string'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getDurationMicros' => ['int'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getOperationId' => ['string'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getReply' => ['object'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getRequestId' => ['string'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServiceId' => ['?MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\CommandSucceededEvent::getServerConnectionId' => ['?int'], -'MongoDB\Driver\Monitoring\LogSubscriber::log' => ['void', 'level'=>'int', 'domain'=>'string', 'message'=>'string'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverChanged' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerChangedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverClosed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerClosedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverOpening' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerOpeningEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatFailed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatStarted' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::serverHeartbeatSucceeded' => ['void', 'event'=>'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyChanged' => ['void', 'event'=>'MongoDB\Driver\Monitoring\TopologyChangedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyClosed' => ['void', 'event'=>'MongoDB\Driver\Monitoring\TopologyClosedEvent'], -'MongoDB\Driver\Monitoring\SDAMSubscriber::topologyOpening' => ['void', 'event'=>'MongoDB\Driver\Monitoring\TopologyOpeningEvent'], -'MongoDB\Driver\Monitoring\ServerChangedEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerChangedEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerChangedEvent::getNewDescription' => ['MongoDB\Driver\ServerDescription'], -'MongoDB\Driver\Monitoring\ServerChangedEvent::getPreviousDescription' => ['MongoDB\Driver\ServerDescription'], -'MongoDB\Driver\Monitoring\ServerChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\ServerClosedEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerClosedEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getDurationMicros' => ['int'], -'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getError' => ['Exception'], -'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerHeartbeatFailedEvent::isAwaited' => ['bool'], -'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerHeartbeatStartedEvent::isAwaited' => ['bool'], -'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getDurationMicros' => ['int'], -'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getReply' => ['object'], -'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerHeartbeatSucceededEvent::isAwaited' => ['bool'], -'MongoDB\Driver\Monitoring\ServerOpeningEvent::getPort' => ['int'], -'MongoDB\Driver\Monitoring\ServerOpeningEvent::getHost' => ['string'], -'MongoDB\Driver\Monitoring\ServerOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\TopologyChangedEvent::getNewDescription' => ['MongoDB\Driver\TopologyDescription'], -'MongoDB\Driver\Monitoring\TopologyChangedEvent::getPreviousDescription' => ['MongoDB\Driver\TopologyDescription'], -'MongoDB\Driver\Monitoring\TopologyChangedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\TopologyClosedEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Monitoring\TopologyOpeningEvent::getTopologyId' => ['MongoDB\BSON\ObjectId'], -'MongoDB\Driver\Query::__construct' => ['void', 'filter'=>'object|array', 'queryOptions='=>'?array'], -'MongoDB\Driver\ReadConcern::__construct' => ['void', 'level='=>'?string'], -'MongoDB\Driver\ReadConcern::getLevel' => ['?string'], -'MongoDB\Driver\ReadConcern::isDefault' => ['bool'], -'MongoDB\Driver\ReadConcern::bsonSerialize' => ['stdClass'], -'MongoDB\Driver\ReadConcern::serialize' => ['string'], -'MongoDB\Driver\ReadConcern::unserialize' => ['void', 'data'=>'string'], -'MongoDB\Driver\ReadPreference::__construct' => ['void', 'mode'=>'string|int', 'tagSets='=>'?array', 'options='=>'?array'], -'MongoDB\Driver\ReadPreference::getHedge' => ['?object'], -'MongoDB\Driver\ReadPreference::getMaxStalenessSeconds' => ['int'], -'MongoDB\Driver\ReadPreference::getMode' => ['int'], -'MongoDB\Driver\ReadPreference::getModeString' => ['string'], -'MongoDB\Driver\ReadPreference::getTagSets' => ['array'], -'MongoDB\Driver\ReadPreference::bsonSerialize' => ['stdClass'], -'MongoDB\Driver\ReadPreference::serialize' => ['string'], -'MongoDB\Driver\ReadPreference::unserialize' => ['void', 'data'=>'string'], -'MongoDB\Driver\Server::executeBulkWrite' => ['MongoDB\Driver\WriteResult', 'namespace'=>'string', 'bulkWrite'=>'MongoDB\Driver\BulkWrite', 'options='=>'MongoDB\Driver\WriteConcern|array|null'], -'MongoDB\Driver\Server::executeCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'MongoDB\Driver\ReadPreference|array|null'], -'MongoDB\Driver\Server::executeQuery' => ['MongoDB\Driver\Cursor', 'namespace'=>'string', 'query'=>'MongoDB\Driver\Query', 'options='=>'MongoDB\Driver\ReadPreference|array|null'], -'MongoDB\Driver\Server::executeReadCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Server::executeReadWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Server::executeWriteCommand' => ['MongoDB\Driver\Cursor', 'db'=>'string', 'command'=>'MongoDB\Driver\Command', 'options='=>'?array'], -'MongoDB\Driver\Server::getHost' => ['string'], -'MongoDB\Driver\Server::getInfo' => ['array'], -'MongoDB\Driver\Server::getLatency' => ['?int'], -'MongoDB\Driver\Server::getPort' => ['int'], -'MongoDB\Driver\Server::getServerDescription' => ['MongoDB\Driver\ServerDescription'], -'MongoDB\Driver\Server::getTags' => ['array'], -'MongoDB\Driver\Server::getType' => ['int'], -'MongoDB\Driver\Server::isArbiter' => ['bool'], -'MongoDB\Driver\Server::isHidden' => ['bool'], -'MongoDB\Driver\Server::isPassive' => ['bool'], -'MongoDB\Driver\Server::isPrimary' => ['bool'], -'MongoDB\Driver\Server::isSecondary' => ['bool'], -'MongoDB\Driver\ServerApi::__construct' => ['void', 'version'=>'string', 'strict='=>'?bool', 'deprecationErrors='=>'?bool'], -'MongoDB\Driver\ServerApi::bsonSerialize' => ['stdClass'], -'MongoDB\Driver\ServerApi::serialize' => ['string'], -'MongoDB\Driver\ServerApi::unserialize' => ['void', 'data'=>'string'], -'MongoDB\Driver\ServerDescription::getHelloResponse' => ['array'], -'MongoDB\Driver\ServerDescription::getHost' => ['string'], -'MongoDB\Driver\ServerDescription::getLastUpdateTime' => ['int'], -'MongoDB\Driver\ServerDescription::getPort' => ['int'], -'MongoDB\Driver\ServerDescription::getRoundTripTime' => ['?int'], -'MongoDB\Driver\ServerDescription::getType' => ['string'], -'MongoDB\Driver\Session::abortTransaction' => ['void'], -'MongoDB\Driver\Session::advanceClusterTime' => ['void', 'clusterTime'=>'object|array'], -'MongoDB\Driver\Session::advanceOperationTime' => ['void', 'operationTime'=>'MongoDB\BSON\TimestampInterface'], -'MongoDB\Driver\Session::commitTransaction' => ['void'], -'MongoDB\Driver\Session::endSession' => ['void'], -'MongoDB\Driver\Session::getClusterTime' => ['?object'], -'MongoDB\Driver\Session::getLogicalSessionId' => ['object'], -'MongoDB\Driver\Session::getOperationTime' => ['?MongoDB\BSON\Timestamp'], -'MongoDB\Driver\Session::getServer' => ['?MongoDB\Driver\Server'], -'MongoDB\Driver\Session::getTransactionOptions' => ['?array'], -'MongoDB\Driver\Session::getTransactionState' => ['string'], -'MongoDB\Driver\Session::isDirty' => ['bool'], -'MongoDB\Driver\Session::isInTransaction' => ['bool'], -'MongoDB\Driver\Session::startTransaction' => ['void', 'options='=>'?array'], -'MongoDB\Driver\TopologyDescription::getServers' => ['array'], -'MongoDB\Driver\TopologyDescription::getType' => ['string'], -'MongoDB\Driver\TopologyDescription::hasReadableServer' => ['bool', 'readPreference='=>'?MongoDB\Driver\ReadPreference'], -'MongoDB\Driver\TopologyDescription::hasWritableServer' => ['bool'], -'MongoDB\Driver\WriteConcern::__construct' => ['void', 'w'=>'string|int', 'wtimeout='=>'?int', 'journal='=>'?bool'], -'MongoDB\Driver\WriteConcern::getJournal' => ['?bool'], -'MongoDB\Driver\WriteConcern::getW' => ['string|int|null'], -'MongoDB\Driver\WriteConcern::getWtimeout' => ['int'], -'MongoDB\Driver\WriteConcern::isDefault' => ['bool'], -'MongoDB\Driver\WriteConcern::bsonSerialize' => ['stdClass'], -'MongoDB\Driver\WriteConcern::serialize' => ['string'], -'MongoDB\Driver\WriteConcern::unserialize' => ['void', 'data'=>'string'], -'MongoDB\Driver\WriteConcernError::getCode' => ['int'], -'MongoDB\Driver\WriteConcernError::getInfo' => ['?object'], -'MongoDB\Driver\WriteConcernError::getMessage' => ['string'], -'MongoDB\Driver\WriteError::getCode' => ['int'], -'MongoDB\Driver\WriteError::getIndex' => ['int'], -'MongoDB\Driver\WriteError::getInfo' => ['?object'], -'MongoDB\Driver\WriteError::getMessage' => ['string'], -'MongoDB\Driver\WriteResult::getInsertedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getMatchedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getModifiedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getDeletedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getUpsertedCount' => ['?int'], -'MongoDB\Driver\WriteResult::getServer' => ['MongoDB\Driver\Server'], -'MongoDB\Driver\WriteResult::getUpsertedIds' => ['array'], -'MongoDB\Driver\WriteResult::getWriteConcernError' => ['?MongoDB\Driver\WriteConcernError'], -'MongoDB\Driver\WriteResult::getWriteErrors' => ['array'], -'MongoDB\Driver\WriteResult::getErrorReplies' => ['array'], -'MongoDB\Driver\WriteResult::isAcknowledged' => ['bool'], 'MongoDBRef::create' => ['array', 'collection'=>'string', 'id'=>'mixed', 'database='=>'string'], 'MongoDBRef::get' => ['array', 'db'=>'mongodb', 'ref'=>'array'], 'MongoDBRef::isRef' => ['bool', 'ref'=>'mixed'], From 94a6678b35c2b60c590f73d5e7687a3b33926e7c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 13 May 2025 21:34:30 +0200 Subject: [PATCH 1351/3097] Introduce StrrevFunctionReturnTypeExtension --- conf/config.neon | 5 ++ .../Php/StrrevFunctionReturnTypeExtension.php | 73 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/strrev.php | 37 ++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/Type/Php/StrrevFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/strrev.php diff --git a/conf/config.neon b/conf/config.neon index 5e643bc081e..aab1b08dfc1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1772,6 +1772,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\StrrevFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/StrrevFunctionReturnTypeExtension.php b/src/Type/Php/StrrevFunctionReturnTypeExtension.php new file mode 100644 index 00000000000..4d02ec8a8c4 --- /dev/null +++ b/src/Type/Php/StrrevFunctionReturnTypeExtension.php @@ -0,0 +1,73 @@ +getName() === 'strrev'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope, + ): ?Type + { + $args = $functionCall->getArgs(); + if (count($args) < 1) { + return null; + } + + $inputType = $scope->getType($args[0]->value); + $constantStrings = $inputType->getConstantStrings(); + if (count($constantStrings) > 0) { + $resultTypes = []; + foreach ($constantStrings as $constantString) { + $resultTypes[] = new ConstantStringType(strrev($constantString->getValue())); + } + + return TypeCombinator::union(...$resultTypes); + } + + $accessoryTypes = []; + if ($inputType->isNonFalsyString()->yes()) { + $accessoryTypes[] = new AccessoryNonFalsyStringType(); + } elseif ($inputType->isNonEmptyString()->yes()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + if ($inputType->isLowercaseString()->yes()) { + $accessoryTypes[] = new AccessoryLowercaseStringType(); + } + if ($inputType->isUppercaseString()->yes()) { + $accessoryTypes[] = new AccessoryUppercaseStringType(); + } + + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + + return new IntersectionType($accessoryTypes); + } + + return null; + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/strrev.php b/tests/PHPStan/Analyser/nsrt/strrev.php new file mode 100644 index 00000000000..2029c56df8b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/strrev.php @@ -0,0 +1,37 @@ + Date: Wed, 14 May 2025 09:12:19 +0200 Subject: [PATCH 1352/3097] Revert "Narrow variable type in switch cases" This reverts commit f2cf5cad45037071e4032573d4eaf929080ea166. --- src/Analyser/NodeScopeResolver.php | 31 --------- src/Analyser/TypeSpecifier.php | 65 +++---------------- .../Analyser/NodeScopeResolverTest.php | 3 - .../Analyser/data/bug-12432-nullable-enum.php | 35 ---------- .../Analyser/data/bug-12432-nullable-int.php | 26 -------- .../PHPStan/Analyser/nsrt/in_array_loose.php | 2 +- 6 files changed, 10 insertions(+), 152 deletions(-) delete mode 100644 tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php delete mode 100644 tests/PHPStan/Analyser/data/bug-12432-nullable-int.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 58aa455c4a6..d979a0f24a7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -206,7 +206,6 @@ use function array_merge; use function array_pop; use function array_reverse; -use function array_shift; use function array_slice; use function array_values; use function base64_decode; @@ -1567,12 +1566,10 @@ private function processStmtNode( $throwPoints = $condResult->getThrowPoints(); $impurePoints = $condResult->getImpurePoints(); $fullCondExpr = null; - $defaultCondExprs = []; foreach ($stmt->cases as $caseNode) { if ($caseNode->cond !== null) { $condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond); $fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr); - $defaultCondExprs[] = new BinaryOp\NotEqual($stmt->cond, $caseNode->cond); $caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep()); $scopeForBranches = $caseResult->getScope(); $hasYield = $hasYield || $caseResult->hasYield(); @@ -1583,11 +1580,6 @@ private function processStmtNode( $hasDefaultCase = true; $fullCondExpr = null; $branchScope = $scopeForBranches; - $defaultConditions = $this->createBooleanAndFromExpressions($defaultCondExprs); - if ($defaultConditions !== null) { - $branchScope = $this->processExprNode($stmt, $defaultConditions, $scope, static function (): void { - }, ExpressionContext::createDeep())->getTruthyScope()->filterByTruthyValue($defaultConditions); - } } $branchScope = $branchScope->mergeWith($prevScope); @@ -6709,29 +6701,6 @@ private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $ return null; } - /** - * @param list $expressions - */ - private function createBooleanAndFromExpressions(array $expressions): ?Expr - { - if (count($expressions) === 0) { - return null; - } - - if (count($expressions) === 1) { - return $expressions[0]; - } - - $left = array_shift($expressions); - $right = $this->createBooleanAndFromExpressions($expressions); - - if ($right === null) { - throw new ShouldNotHappenException(); - } - - return new BooleanAnd($left, $right); - } - /** * @param array $nodes * @return list diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 405b4a9adc1..761aa267ae8 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1643,45 +1643,24 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ $leftType = $scope->getType($binaryOperation->left); $rightType = $scope->getType($binaryOperation->right); - $rightExpr = $this->extractExpression($binaryOperation->right); - $leftExpr = $this->extractExpression($binaryOperation->left); - - if ( - $leftType instanceof ConstantScalarType - && !$rightExpr instanceof ConstFetch - && !$rightExpr instanceof ClassConstFetch - ) { - return [$binaryOperation->right, $leftType, $rightType]; - } elseif ( - $rightType instanceof ConstantScalarType - && !$leftExpr instanceof ConstFetch - && !$leftExpr instanceof ClassConstFetch - ) { - return [$binaryOperation->left, $rightType, $leftType]; + $rightExpr = $binaryOperation->right; + if ($rightExpr instanceof AlwaysRememberedExpr) { + $rightExpr = $rightExpr->getExpr(); } - return null; - } - - /** - * @return array{Expr, Type, Type}|null - */ - private function findEnumTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array - { - $leftType = $scope->getType($binaryOperation->left); - $rightType = $scope->getType($binaryOperation->right); - - $rightExpr = $this->extractExpression($binaryOperation->right); - $leftExpr = $this->extractExpression($binaryOperation->left); + $leftExpr = $binaryOperation->left; + if ($leftExpr instanceof AlwaysRememberedExpr) { + $leftExpr = $leftExpr->getExpr(); + } if ( - $leftType->getEnumCases() === [$leftType] + $leftType instanceof ConstantScalarType && !$rightExpr instanceof ConstFetch && !$rightExpr instanceof ClassConstFetch ) { return [$binaryOperation->right, $leftType, $rightType]; } elseif ( - $rightType->getEnumCases() === [$rightType] + $rightType instanceof ConstantScalarType && !$leftExpr instanceof ConstFetch && !$leftExpr instanceof ClassConstFetch ) { @@ -1691,11 +1670,6 @@ private function findEnumTypeExpressionsFromBinaryOperation(Scope $scope, Node\E return null; } - private function extractExpression(Expr $expr): Expr - { - return $expr instanceof AlwaysRememberedExpr ? $expr->getExpr() : $expr; - } - /** @api */ public function create( Expr $expr, @@ -2087,27 +2061,6 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif ) { return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context)->setRootExpr($expr); } - - if (!$context->null() && TypeCombinator::containsNull($otherType)) { - if ($constantType->toBoolean()->isTrue()->yes()) { - $otherType = TypeCombinator::remove($otherType, new NullType()); - } - - if (!$otherType->isSuperTypeOf($constantType)->no()) { - return $this->create($exprNode, TypeCombinator::intersect($constantType, $otherType), $context, $scope)->setRootExpr($expr); - } - } - } - - $expressions = $this->findEnumTypeExpressionsFromBinaryOperation($scope, $expr); - if ($expressions !== null) { - $exprNode = $expressions[0]; - $enumCaseObjectType = $expressions[1]; - $otherType = $expressions[2]; - - if (!$context->null()) { - return $this->create($exprNode, TypeCombinator::intersect($enumCaseObjectType, $otherType), $context, $scope)->setRootExpr($expr); - } } $leftType = $scope->getType($expr->left); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2bfafc8404b..a2e9ef06192 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -101,13 +101,10 @@ private static function findTestFiles(): iterable define('TEST_FALSE_CONSTANT', false); define('TEST_ARRAY_CONSTANT', [true, false, null]); define('TEST_ENUM_CONSTANT', Foo::ONE); - yield __DIR__ . '/data/bug-12432-nullable-enum.php'; yield __DIR__ . '/data/new-in-initializers-runtime.php'; yield __DIR__ . '/data/scope-in-enum-match-arm-body.php'; } - yield __DIR__ . '/data/bug-12432-nullable-int.php'; - yield __DIR__ . '/../Rules/Comparison/data/bug-6473.php'; yield __DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php'; diff --git a/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php b/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php deleted file mode 100644 index ee24d3580aa..00000000000 --- a/tests/PHPStan/Analyser/data/bug-12432-nullable-enum.php +++ /dev/null @@ -1,35 +0,0 @@ -|int<3, max>', $nullable); - break; - } - - return $nullable; -} diff --git a/tests/PHPStan/Analyser/nsrt/in_array_loose.php b/tests/PHPStan/Analyser/nsrt/in_array_loose.php index 8018fd7f659..78d2899b8cb 100644 --- a/tests/PHPStan/Analyser/nsrt/in_array_loose.php +++ b/tests/PHPStan/Analyser/nsrt/in_array_loose.php @@ -42,7 +42,7 @@ public function looseComparison( assertType('int|string', $stringOrInt); // could be '1'|'2'|1|2 } if (in_array($stringOrNull, ['1', 'a'])) { - assertType("'1'|'a'", $stringOrNull); + assertType('string|null', $stringOrNull); // could be '1'|'a' } } } From 4a907f16f034d42f5246a0fd72225f0fba0fcb0c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 May 2025 09:31:09 +0200 Subject: [PATCH 1353/3097] Restricted usage extensions - do not report false positives for Symfony polyfills --- .../RestrictedClassConstantUsageRule.php | 8 + ...estrictedStaticMethodCallableUsageRule.php | 8 + .../RestrictedStaticMethodUsageRule.php | 8 + .../RestrictedStaticPropertyUsageRule.php | 8 + ...nDeclaringClassClassConstantReflection.php | 111 +++++++++++++ ...ewrittenDeclaringClassMethodReflection.php | 148 +++++++++++++++++ ...rittenDeclaringClassPropertyReflection.php | 151 ++++++++++++++++++ ...nternalClassConstantUsageExtensionTest.php | 13 +- .../InternalTag/data/bug-12951-constant.php | 8 + .../InternalTag/data/bug-12951-define.php | 29 ++++ .../data/bug-12951-static-method.php | 11 ++ .../data/bug-12951-static-property.php | 8 + ...ictedStaticMethodCallableUsageRuleTest.php | 15 ++ .../RestrictedStaticMethodUsageRuleTest.php | 16 ++ .../RestrictedStaticPropertyUsageRuleTest.php | 11 ++ 15 files changed, 552 insertions(+), 1 deletion(-) create mode 100644 src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php create mode 100644 src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php create mode 100644 src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/bug-12951-constant.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/bug-12951-static-method.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/bug-12951-static-property.php diff --git a/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php index 3f887671ff7..fce21cbbdcd 100644 --- a/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php @@ -86,6 +86,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($classReflection->getName() !== $constantReflection->getDeclaringClass()->getName()) { + $rewrittenConstantReflection = new RewrittenDeclaringClassClassConstantReflection($classReflection, $constantReflection); + $rewrittenRestrictedUsage = $extension->isRestrictedClassConstantUsage($rewrittenConstantReflection, $scope); + if ($rewrittenRestrictedUsage === null) { + continue; + } + } + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) ->identifier($restrictedUsage->identifier) ->build(); diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php index a6172e69dd4..b4c86efb475 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php @@ -87,6 +87,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($classReflection->getName() !== $methodReflection->getDeclaringClass()->getName()) { + $rewrittenMethodReflection = new RewrittenDeclaringClassMethodReflection($classReflection, $methodReflection); + $rewrittenRestrictedUsage = $extension->isRestrictedMethodUsage($rewrittenMethodReflection, $scope); + if ($rewrittenRestrictedUsage === null) { + continue; + } + } + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) ->identifier($restrictedUsage->identifier) ->build(); diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php index b9f061bc3b4..024ab870161 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php @@ -86,6 +86,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($classReflection->getName() !== $methodReflection->getDeclaringClass()->getName()) { + $rewrittenMethodReflection = new RewrittenDeclaringClassMethodReflection($classReflection, $methodReflection); + $rewrittenRestrictedUsage = $extension->isRestrictedMethodUsage($rewrittenMethodReflection, $scope); + if ($rewrittenRestrictedUsage === null) { + continue; + } + } + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) ->identifier($restrictedUsage->identifier) ->build(); diff --git a/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php index 260672d0d56..9b0c011e834 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php @@ -86,6 +86,14 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($classReflection->getName() !== $propertyReflection->getDeclaringClass()->getName()) { + $rewrittenPropertyReflection = new RewrittenDeclaringClassPropertyReflection($classReflection, $propertyReflection); + $rewrittenRestrictedUsage = $extension->isRestrictedPropertyUsage($rewrittenPropertyReflection, $scope); + if ($rewrittenRestrictedUsage === null) { + continue; + } + } + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) ->identifier($restrictedUsage->identifier) ->build(); diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php new file mode 100644 index 00000000000..b7a102db9fc --- /dev/null +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php @@ -0,0 +1,111 @@ +constantReflection->getValueExpr(); + } + + public function isFinal(): bool + { + return $this->constantReflection->isFinal(); + } + + public function hasPhpDocType(): bool + { + return $this->constantReflection->hasPhpDocType(); + } + + public function getPhpDocType(): ?Type + { + return $this->constantReflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->constantReflection->hasNativeType(); + } + + public function getNativeType(): ?Type + { + return $this->constantReflection->getNativeType(); + } + + public function getAttributes(): array + { + return $this->constantReflection->getAttributes(); + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->constantReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->constantReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->constantReflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->constantReflection->getDocComment(); + } + + public function getName(): string + { + return $this->constantReflection->getName(); + } + + public function getValueType(): Type + { + return $this->constantReflection->getValueType(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->constantReflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->constantReflection->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->constantReflection->isInternal(); + } + + public function getFileName(): ?string + { + return $this->constantReflection->getFileName(); + } + +} diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php new file mode 100644 index 00000000000..a2575177a5d --- /dev/null +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php @@ -0,0 +1,148 @@ +declaringClass; + } + + public function isStatic(): bool + { + return $this->methodReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->methodReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->methodReflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->methodReflection->getDocComment(); + } + + public function getVariants(): array + { + return $this->methodReflection->getVariants(); + } + + public function getOnlyVariant(): ExtendedParametersAcceptor + { + return $this->methodReflection->getOnlyVariant(); + } + + public function getNamedArgumentsVariants(): ?array + { + return $this->methodReflection->getNamedArgumentsVariants(); + } + + public function acceptsNamedArguments(): TrinaryLogic + { + return $this->methodReflection->acceptsNamedArguments(); + } + + public function getAsserts(): Assertions + { + return $this->methodReflection->getAsserts(); + } + + public function getSelfOutType(): ?Type + { + return $this->methodReflection->getSelfOutType(); + } + + public function returnsByReference(): TrinaryLogic + { + return $this->methodReflection->returnsByReference(); + } + + public function isFinalByKeyword(): TrinaryLogic + { + return $this->methodReflection->isFinalByKeyword(); + } + + public function isAbstract(): TrinaryLogic|bool + { + return $this->methodReflection->isAbstract(); + } + + public function isBuiltin(): TrinaryLogic|bool + { + return $this->methodReflection->isBuiltin(); + } + + public function isPure(): TrinaryLogic + { + return $this->methodReflection->isPure(); + } + + public function getAttributes(): array + { + return $this->methodReflection->getAttributes(); + } + + public function getName(): string + { + return $this->methodReflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->methodReflection->getPrototype(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->methodReflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->methodReflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->methodReflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->methodReflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->methodReflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->methodReflection->hasSideEffects(); + } + +} diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php new file mode 100644 index 00000000000..69eb2dbf4fe --- /dev/null +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php @@ -0,0 +1,151 @@ +declaringClass; + } + + public function isStatic(): bool + { + return $this->propertyReflection->isStatic(); + } + + public function isPrivate(): bool + { + return $this->propertyReflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->propertyReflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->propertyReflection->getDocComment(); + } + + public function getName(): string + { + return $this->propertyReflection->getName(); + } + + public function hasPhpDocType(): bool + { + return $this->propertyReflection->hasPhpDocType(); + } + + public function getPhpDocType(): Type + { + return $this->propertyReflection->getPhpDocType(); + } + + public function hasNativeType(): bool + { + return $this->propertyReflection->hasNativeType(); + } + + public function getNativeType(): Type + { + return $this->propertyReflection->getNativeType(); + } + + public function isAbstract(): TrinaryLogic + { + return $this->propertyReflection->isAbstract(); + } + + public function isFinal(): TrinaryLogic + { + return $this->propertyReflection->isFinal(); + } + + public function isVirtual(): TrinaryLogic + { + return $this->propertyReflection->isVirtual(); + } + + public function hasHook(string $hookType): bool + { + return $this->propertyReflection->hasHook($hookType); + } + + public function getHook(string $hookType): ExtendedMethodReflection + { + return $this->propertyReflection->getHook($hookType); + } + + public function isProtectedSet(): bool + { + return $this->propertyReflection->isProtectedSet(); + } + + public function isPrivateSet(): bool + { + return $this->propertyReflection->isPrivateSet(); + } + + public function getAttributes(): array + { + return $this->propertyReflection->getAttributes(); + } + + public function getReadableType(): Type + { + return $this->propertyReflection->getReadableType(); + } + + public function getWritableType(): Type + { + return $this->propertyReflection->getWritableType(); + } + + public function canChangeTypeAfterAssignment(): bool + { + return $this->propertyReflection->canChangeTypeAfterAssignment(); + } + + public function isReadable(): bool + { + return $this->propertyReflection->isReadable(); + } + + public function isWritable(): bool + { + return $this->propertyReflection->isWritable(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->propertyReflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->propertyReflection->getDeprecatedDescription(); + } + + public function isInternal(): TrinaryLogic + { + return $this->propertyReflection->isInternal(); + } + +} diff --git a/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php index e1ecf67c3de..209c7304e19 100644 --- a/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php +++ b/tests/PHPStan/Rules/InternalTag/RestrictedInternalClassConstantUsageExtensionTest.php @@ -56,7 +56,7 @@ public function testRule(): void ]); } - public function testStaticPropertyAccessOnInternalSubclass(): void + public function testClassConstantAccessOnInternalSubclass(): void { $this->analyse([__DIR__ . '/data/class-constant-access-on-internal-subclass.php'], [ [ @@ -66,4 +66,15 @@ public function testStaticPropertyAccessOnInternalSubclass(): void ]); } + public function testBug12951(): void + { + require_once __DIR__ . '/data/bug-12951-define.php'; + $this->analyse([__DIR__ . '/data/bug-12951-constant.php'], [ + [ + 'Access to constant NUMERIC_COLLATION of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.', + 7, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/InternalTag/data/bug-12951-constant.php b/tests/PHPStan/Rules/InternalTag/data/bug-12951-constant.php new file mode 100644 index 00000000000..ab8ccf77084 --- /dev/null +++ b/tests/PHPStan/Rules/InternalTag/data/bug-12951-constant.php @@ -0,0 +1,8 @@ +analyse([__DIR__ . '/../InternalTag/data/bug-12951-static-method.php'], [ + [ + 'Call to static method doBar() of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.', + 10, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodUsageRuleTest.php index fdefc813983..98119797443 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodUsageRuleTest.php +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticMethodUsageRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule as TRule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -32,6 +33,21 @@ public function testRule(): void ]); } + public function testBug12951(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1'); + } + + require_once __DIR__ . '/../InternalTag/data/bug-12951-define.php'; + $this->analyse([__DIR__ . '/../InternalTag/data/bug-12951-static-method.php'], [ + [ + 'Call to static method doBar() of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.', + 7, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php index e1b057bf6b4..267f5dd531e 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRuleTest.php @@ -32,6 +32,17 @@ public function testRule(): void ]); } + public function testBug12951(): void + { + require_once __DIR__ . '/../InternalTag/data/bug-12951-define.php'; + $this->analyse([__DIR__ . '/../InternalTag/data/bug-12951-static-property.php'], [ + [ + 'Access to static property $prop of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.', + 7, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ From d6446f9d07bd77d7b9e9c8212067aadf659fa567 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 May 2025 10:55:26 +0200 Subject: [PATCH 1354/3097] Fix build --- .../PHPStan/Rules/InternalTag/data/bug-12951-static-method.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/InternalTag/data/bug-12951-static-method.php b/tests/PHPStan/Rules/InternalTag/data/bug-12951-static-method.php index 31ec1642c2b..83340aa2250 100644 --- a/tests/PHPStan/Rules/InternalTag/data/bug-12951-static-method.php +++ b/tests/PHPStan/Rules/InternalTag/data/bug-12951-static-method.php @@ -1,4 +1,4 @@ -= 8.1 namespace Bug12951; From 8edfa9fa880141f97239229bbdefb54168cc2b78 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 May 2025 10:58:35 +0200 Subject: [PATCH 1355/3097] InstantiationRule - call RestrictedMethodUsageExtension for constructor --- src/Rules/Classes/InstantiationRule.php | 26 ++++++++++++++++ .../ForbiddenNameCheckExtensionRuleTest.php | 1 + .../Rules/Classes/InstantiationRuleTest.php | 30 +++++++++++++++++++ .../Classes/data/internal-constructor.php | 22 ++++++++++++++ .../data/bug-12951-constructor.php | 8 +++++ .../InternalTag/data/bug-12951-define.php | 7 ++++- 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Classes/data/internal-constructor.php create mode 100644 tests/PHPStan/Rules/InternalTag/data/bug-12951-constructor.php diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 6c5ef87c205..3b9a850086c 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\New_; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\Container; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -15,6 +16,8 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\FunctionCallParametersCheck; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; +use PHPStan\Rules\RestrictedUsage\RewrittenDeclaringClassMethodReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -33,6 +36,7 @@ final class InstantiationRule implements Rule { public function __construct( + private Container $container, private ReflectionProvider $reflectionProvider, private FunctionCallParametersCheck $check, private ClassNameCheck $classCheck, @@ -197,6 +201,28 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ ->build(); } + /** @var RestrictedMethodUsageExtension[] $restrictedUsageExtensions */ + $restrictedUsageExtensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + + foreach ($restrictedUsageExtensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($constructorReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + if ($classReflection->getName() !== $constructorReflection->getDeclaringClass()->getName()) { + $rewrittenConstructorReflection = new RewrittenDeclaringClassMethodReflection($classReflection, $constructorReflection); + $rewrittenRestrictedUsage = $extension->isRestrictedMethodUsage($rewrittenConstructorReflection, $scope); + if ($rewrittenRestrictedUsage === null) { + continue; + } + } + + $messages[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + $classDisplayName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); return array_merge($messages, $this->check->check( diff --git a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php index 93e202ef4e7..94e1ff695da 100644 --- a/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( + self::getContainer(), $reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index eb547315cf5..1a84e5e1708 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -24,6 +24,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new InstantiationRule( + self::getContainer(), $reflectionProvider, new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true), new ClassNameCheck( @@ -556,4 +557,33 @@ public function testClassString(): void ]); } + public function testInternalConstructor(): void + { + $this->analyse([__DIR__ . '/data/internal-constructor.php'], [ + [ + 'Call to internal method InternalConstructorDefinition\Foo::__construct() from outside its root namespace InternalConstructorDefinition.', + 21, + ], + ]); + } + + public function testBug12951(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1'); + } + + require_once __DIR__ . '/../InternalTag/data/bug-12951-define.php'; + $this->analyse([__DIR__ . '/../InternalTag/data/bug-12951-constructor.php'], [ + [ + 'Instantiation of internal class Bug12951Polyfill\NumberFormatter.', + 7, + ], + [ + 'Call to method __construct() of internal class Bug12951Polyfill\NumberFormatter from outside its root namespace Bug12951Polyfill.', + 7, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/internal-constructor.php b/tests/PHPStan/Rules/Classes/data/internal-constructor.php new file mode 100644 index 00000000000..ba1dfd596eb --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/internal-constructor.php @@ -0,0 +1,22 @@ += 8.1 + +namespace Bug12951; + +function (): void { + new \Bug12951Core\NumberFormatter(); + new \Bug12951Polyfill\NumberFormatter(); +}; diff --git a/tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php b/tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php index 64fb5c97ca2..0289ff38af5 100644 --- a/tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php +++ b/tests/PHPStan/Rules/InternalTag/data/bug-12951-define.php @@ -12,13 +12,18 @@ class NumberFormatter extends \Bug12951Polyfill\NumberFormatter namespace Bug12951Polyfill { /** @internal */ - abstract class NumberFormatter + class NumberFormatter { public const NUMERIC_COLLATION = 1; public static $prop; + public function __construct() + { + + } + public static function doBar(): void { From ce257d9acf2e182b2ae1c6fea06f7738986e54ab Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 May 2025 11:23:51 +0200 Subject: [PATCH 1356/3097] Call RestrictedMethodUsageExtension for `__toString` methods in `(string)` cast --- conf/config.neon | 1 + ...trictedUsageOfDeprecatedStringCastRule.php | 70 +++++++++++++++++++ ...tedUsageOfDeprecatedStringCastRuleTest.php | 40 +++++++++++ .../RestrictedUsage/data/MethodExtension.php | 5 +- .../data/restricted-to-string.php | 29 ++++++++ 5 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRuleTest.php create mode 100644 tests/PHPStan/Rules/RestrictedUsage/data/restricted-to-string.php diff --git a/conf/config.neon b/conf/config.neon index 1dde61b8b94..26703d01efd 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -227,6 +227,7 @@ rules: - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodCallableUsageRule - PHPStan\Rules\RestrictedUsage\RestrictedStaticPropertyUsageRule + - PHPStan\Rules\RestrictedUsage\RestrictedUsageOfDeprecatedStringCastRule conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php b/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php new file mode 100644 index 00000000000..938982fe565 --- /dev/null +++ b/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php @@ -0,0 +1,70 @@ + + */ +final class RestrictedUsageOfDeprecatedStringCastRule implements Rule +{ + + public function __construct( + private Container $container, + private ReflectionProvider $reflectionProvider, + ) + { + } + + public function getNodeType(): string + { + return Cast\String_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + /** @var RestrictedMethodUsageExtension[] $extensions */ + $extensions = $this->container->getServicesByTag(RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG); + if ($extensions === []) { + return []; + } + + $exprType = $scope->getType($node->expr); + $referencedClasses = $exprType->getObjectClassNames(); + + $errors = []; + + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$classReflection->hasNativeMethod('__toString')) { + continue; + } + + $methodReflection = $classReflection->getNativeMethod('__toString'); + foreach ($extensions as $extension) { + $restrictedUsage = $extension->isRestrictedMethodUsage($methodReflection, $scope); + if ($restrictedUsage === null) { + continue; + } + + $errors[] = RuleErrorBuilder::message($restrictedUsage->errorMessage) + ->identifier($restrictedUsage->identifier) + ->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRuleTest.php b/tests/PHPStan/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRuleTest.php new file mode 100644 index 00000000000..6db182b4f58 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRuleTest.php @@ -0,0 +1,40 @@ + + */ +class RestrictedUsageOfDeprecatedStringCastRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new RestrictedUsageOfDeprecatedStringCastRule( + self::getContainer(), + $this->createReflectionProvider(), + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/restricted-to-string.php'], [ + [ + 'Cannot call __toString', + 11, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/restricted-usage.neon', + ...parent::getAdditionalConfigFiles(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php index 4fee1f8eef5..fbbd1c63fe4 100644 --- a/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php +++ b/tests/PHPStan/Rules/RestrictedUsage/data/MethodExtension.php @@ -6,6 +6,7 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; use PHPStan\Rules\RestrictedUsage\RestrictedUsage; +use function sprintf; class MethodExtension implements RestrictedMethodUsageExtension { @@ -15,11 +16,11 @@ public function isRestrictedMethodUsage( Scope $scope ): ?RestrictedUsage { - if ($methodReflection->getName() !== 'doFoo') { + if ($methodReflection->getName() !== 'doFoo' && $methodReflection->getName() !== '__toString') { return null; } - return RestrictedUsage::create('Cannot call doFoo', 'restrictedUsage.doFoo'); + return RestrictedUsage::create(sprintf('Cannot call %s', $methodReflection->getName()), 'restrictedUsage.doFoo'); } } diff --git a/tests/PHPStan/Rules/RestrictedUsage/data/restricted-to-string.php b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-to-string.php new file mode 100644 index 00000000000..6742e3116f6 --- /dev/null +++ b/tests/PHPStan/Rules/RestrictedUsage/data/restricted-to-string.php @@ -0,0 +1,29 @@ + Date: Wed, 14 May 2025 23:00:58 +1000 Subject: [PATCH 1357/3097] Support for `@final` PHPDoc tag above properties --- .../AnnotationPropertyReflection.php | 5 ++++ .../Dummy/ChangedTypePropertyReflection.php | 5 ++++ .../Dummy/DummyPropertyReflection.php | 5 ++++ src/Reflection/ExtendedPropertyReflection.php | 2 ++ src/Reflection/Php/EnumPropertyReflection.php | 5 ++++ .../Php/PhpClassReflectionExtension.php | 5 +++- src/Reflection/Php/PhpPropertyReflection.php | 8 +++++- .../Php/SimpleXMLElementProperty.php | 5 ++++ .../Php/UniversalObjectCrateProperty.php | 5 ++++ src/Reflection/ResolvedPropertyReflection.php | 5 ++++ .../IntersectionTypePropertyReflection.php | 5 ++++ .../Type/UnionTypePropertyReflection.php | 5 ++++ .../WrappedExtendedPropertyReflection.php | 5 ++++ .../Properties/FoundPropertyReflection.php | 5 ++++ .../Properties/OverridingPropertyRule.php | 11 +++++++- ...rittenDeclaringClassPropertyReflection.php | 5 ++++ src/Type/ObjectShapePropertyReflection.php | 5 ++++ .../Properties/OverridingPropertyRuleTest.php | 16 +++++++++--- .../data/overriding-final-property.php | 10 +++++++ .../PHPStan/Rules/Variables/UnsetRuleTest.php | 26 +++++++++---------- .../Variables/data/unset-hooked-property.php | 4 +++ 21 files changed, 127 insertions(+), 20 deletions(-) diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index 8f4f322d081..a79ffaaf3b0 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -119,6 +119,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index 3bf6a6eb843..b43cf534533 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -116,6 +116,11 @@ public function isAbstract(): TrinaryLogic return $this->reflection->isAbstract(); } + public function isFinalByKeyword(): TrinaryLogic + { + return $this->reflection->isFinalByKeyword(); + } + public function isFinal(): TrinaryLogic { return $this->reflection->isFinal(); diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index ca828a53fe2..c90f546344c 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -116,6 +116,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 1027c193f19..ad8b5a26d83 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -38,6 +38,8 @@ public function getNativeType(): Type; public function isAbstract(): TrinaryLogic; + public function isFinalByKeyword(): TrinaryLogic; + public function isFinal(): TrinaryLogic; public function isVirtual(): TrinaryLogic; diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index 912b779e679..891491a1c5b 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -112,6 +112,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index fc0dd70dbe8..19144986c48 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -218,7 +218,7 @@ private function createProperty( $types[] = $value; } - return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false, []); + return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false, [], false); } } @@ -227,6 +227,7 @@ private function createProperty( $isDeprecated = $deprecation !== null; $isInternal = false; $isReadOnlyByPhpDoc = $classReflection->isImmutable(); + $isFinal = $classReflection->isFinal() || $propertyReflection->isFinal(); $isAllowedPrivateMutation = false; if ( @@ -308,6 +309,7 @@ private function createProperty( } $isInternal = $resolvedPhpDoc->isInternal(); $isReadOnlyByPhpDoc = $isReadOnlyByPhpDoc || $resolvedPhpDoc->isReadOnly(); + $isFinal = $isFinal || $resolvedPhpDoc->isFinal(); $isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation(); } @@ -435,6 +437,7 @@ private function createProperty( $isReadOnlyByPhpDoc, $isAllowedPrivateMutation, $this->attributeReflectionFactory->fromNativeReflection($propertyReflection->getAttributes(), InitializerExprContext::fromClass($declaringClassReflection->getName(), $declaringClassReflection->getFileName())), + $isFinal, ); } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 8307f36d7b0..8c4ea07439a 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -44,6 +44,7 @@ public function __construct( private bool $isReadOnlyByPhpDoc, private bool $isAllowedPrivateMutation, private array $attributes, + private bool $isFinal, ) { } @@ -242,11 +243,16 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->reflection->isAbstract()); } - public function isFinal(): TrinaryLogic + public function isFinalByKeyword(): TrinaryLogic { return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); } + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->isFinal); + } + public function isVirtual(): TrinaryLogic { return TrinaryLogic::createFromBoolean($this->reflection->isVirtual()); diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index 29975a5e3f8..6fb9316fb5e 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -127,6 +127,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 564613e2194..924b249e6f5 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -117,6 +117,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index df7a33f84da..8b54c0785a1 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -174,6 +174,11 @@ public function isAbstract(): TrinaryLogic return $this->reflection->isAbstract(); } + public function isFinalByKeyword(): TrinaryLogic + { + return $this->reflection->isFinalByKeyword(); + } + public function isFinal(): TrinaryLogic { return $this->reflection->isFinal(); diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 71de28e1e60..1e3e4d31792 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -148,6 +148,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isAbstract()); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinalByKeyword()); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::lazyMaxMin($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinal()); diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 77e1ed0397b..1862d3059f8 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -148,6 +148,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isAbstract()); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinalByKeyword()); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::lazyExtremeIdentity($this->properties, static fn (ExtendedPropertyReflection $propertyReflection): TrinaryLogic => $propertyReflection->isFinal()); diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index 04eceb48486..ffb2a34363f 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -109,6 +109,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 1b14b785aa4..b1a890b6be3 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -143,6 +143,11 @@ public function isAbstract(): TrinaryLogic return $this->originalPropertyReflection->isAbstract(); } + public function isFinalByKeyword(): TrinaryLogic + { + return $this->originalPropertyReflection->isFinalByKeyword(); + } + public function isFinal(): TrinaryLogic { return $this->originalPropertyReflection->isFinal(); diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index c1806565e23..8f4e23a8618 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -135,7 +135,7 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.visibility')->nonIgnorable()->build(); } - if ($prototype->isFinal()->yes()) { + if ($prototype->isFinalByKeyword()->yes()) { $errors[] = RuleErrorBuilder::message(sprintf( 'Property %s::$%s overrides final property %s::$%s.', $classReflection->getDisplayName(), @@ -145,6 +145,15 @@ public function processNode(Node $node, Scope $scope): array ))->identifier('property.parentPropertyFinal') ->nonIgnorable() ->build(); + } elseif ($prototype->isFinal()->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overrides @final property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + ))->identifier('property.parentPropertyFinalByPhpDoc') + ->build(); } $typeErrors = []; diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php index 69eb2dbf4fe..d6b0424be94 100644 --- a/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassPropertyReflection.php @@ -73,6 +73,11 @@ public function isAbstract(): TrinaryLogic return $this->propertyReflection->isAbstract(); } + public function isFinalByKeyword(): TrinaryLogic + { + return $this->propertyReflection->isFinalByKeyword(); + } + public function isFinal(): TrinaryLogic { return $this->propertyReflection->isFinal(); diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index 594c3278ca9..1d022cb7268 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -114,6 +114,11 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isFinalByKeyword(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isFinal(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index c5f5cd929cf..dd9749e079b 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -187,19 +187,27 @@ public function testFinal(): void $this->analyse([__DIR__ . '/data/overriding-final-property.php'], [ [ 'Property OverridingFinalProperty\Bar::$a overrides final property OverridingFinalProperty\Foo::$a.', - 21, + 27, ], [ 'Property OverridingFinalProperty\Bar::$b overrides final property OverridingFinalProperty\Foo::$b.', - 23, + 29, ], [ 'Property OverridingFinalProperty\Bar::$c overrides final property OverridingFinalProperty\Foo::$c.', - 25, + 31, ], [ 'Property OverridingFinalProperty\Bar::$d overrides final property OverridingFinalProperty\Foo::$d.', - 27, + 33, + ], + [ + 'Property OverridingFinalProperty\Bar::$e overrides @final property OverridingFinalProperty\Foo::$e.', + 35, + ], + [ + 'Property OverridingFinalProperty\Bar::$f overrides @final property OverridingFinalProperty\Foo::$f.', + 37, ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php index 02d7467e57c..b41b531c984 100644 --- a/tests/PHPStan/Rules/Properties/data/overriding-final-property.php +++ b/tests/PHPStan/Rules/Properties/data/overriding-final-property.php @@ -13,6 +13,12 @@ class Foo protected private(set) int $d; + /** @final */ + public $e; + + /** @final */ + protected $f; + } class Bar extends Foo @@ -26,4 +32,8 @@ class Bar extends Foo public int $d; + public $e; + + protected $f; + } diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index df34c15d502..cfe63c0dd7d 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -167,55 +167,55 @@ public function testUnsetHookedProperty(): void ], [ 'Cannot unset property UnsetHookedProperty\NonFinalClass::$publicProperty because it might have hooks in a subclass.', - 13, + 14, ], [ 'Cannot unset property UnsetHookedProperty\ContainerClass::$finalClass because it might have hooks in a subclass.', - 83, + 86, ], [ 'Cannot unset property UnsetHookedProperty\ContainerClass::$nonFinalClass because it might have hooks in a subclass.', - 87, + 91, ], [ 'Cannot unset hooked UnsetHookedProperty\Foo::$iii property.', - 89, + 93, ], [ 'Cannot unset property UnsetHookedProperty\ContainerClass::$foo because it might have hooks in a subclass.', - 90, + 94, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$name property.', - 92, + 96, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', - 93, + 97, ], [ 'Cannot unset property UnsetHookedProperty\ContainerClass::$user because it might have hooks in a subclass.', - 94, + 98, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$name property.', - 96, + 100, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$name property.', - 97, + 101, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', - 98, + 102, ], [ 'Cannot unset hooked UnsetHookedProperty\User::$fullName property.', - 99, + 103, ], [ 'Cannot unset property UnsetHookedProperty\ContainerClass::$arrayOfUsers because it might have hooks in a subclass.', - 100, + 104, ], ]); } diff --git a/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php index d98eed672a8..97ba5781cdf 100644 --- a/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php +++ b/tests/PHPStan/Rules/Variables/data/unset-hooked-property.php @@ -9,6 +9,7 @@ function doUnset(Foo $foo, User $user, NonFinalClass $nonFinalClass, FinalClass unset($foo->ii); unset($foo->iii); + unset($nonFinalClass->publicAnnotatedFinalProperty); unset($nonFinalClass->publicFinalProperty); unset($nonFinalClass->publicProperty); @@ -49,6 +50,8 @@ class NonFinalClass { private string $privateProperty; public string $publicProperty; final public string $publicFinalProperty; + /** @final */ + public string $publicAnnotatedFinalProperty; function doFoo() { unset($this->privateProperty); @@ -82,6 +85,7 @@ function dooNestedUnset(ContainerClass $containerClass) { unset($containerClass->finalClass->publicProperty); unset($containerClass->finalClass); + unset($containerClass->nonFinalClass->publicAnnotatedFinalProperty); unset($containerClass->nonFinalClass->publicFinalProperty); unset($containerClass->nonFinalClass->publicProperty); unset($containerClass->nonFinalClass); From 6a0ca8c7127e9bd14643ad98ccd679bd59ef2fe4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 15 May 2025 09:12:27 +0200 Subject: [PATCH 1358/3097] Faster ClassNameHelper --- src/Reflection/ClassNameHelper.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Reflection/ClassNameHelper.php b/src/Reflection/ClassNameHelper.php index 8fe817e91d9..815534f3106 100644 --- a/src/Reflection/ClassNameHelper.php +++ b/src/Reflection/ClassNameHelper.php @@ -3,15 +3,23 @@ namespace PHPStan\Reflection; use Nette\Utils\Strings; +use function array_key_exists; use function ltrim; final class ClassNameHelper { + /** @var array */ + private static array $checked = []; + public static function isValidClassName(string $name): bool { - // from https://stackoverflow.com/questions/3195614/validate-class-method-names-with-regex#comment104531582_12011255 - return Strings::match(ltrim($name, '\\'), '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/') !== null; + if (!array_key_exists($name, self::$checked)) { + // from https://stackoverflow.com/questions/3195614/validate-class-method-names-with-regex#comment104531582_12011255 + self::$checked[$name] = Strings::match(ltrim($name, '\\'), '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/') !== null; + + } + return self::$checked[$name]; } } From c8716d6957a7d03c2ee272c8c3c3788bf496a81c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 May 2025 11:27:36 +0200 Subject: [PATCH 1359/3097] DuplicateKeysInLiteralArraysRule: Fixed union type handling --- .../DuplicateKeysInLiteralArraysRule.php | 47 ++++++++++- .../DuplicateKeysInLiteralArraysRuleTest.php | 30 ++++++++ tests/PHPStan/Rules/Arrays/data/bug-13013.php | 77 +++++++++++++++++++ tests/PHPStan/Rules/Arrays/data/bug-13022.php | 29 +++++++ .../Rules/Arrays/data/duplicate-keys.php | 65 ++++++++++++++++ 5 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-13013.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-13022.php diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 7e54a2cb159..b8c0c2497af 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -9,7 +9,9 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantIntegerType; +use function array_key_first; use function array_keys; +use function array_search; use function count; use function implode; use function is_int; @@ -36,7 +38,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $values = []; $duplicateKeys = []; $printedValues = []; $valueLines = []; @@ -49,6 +50,8 @@ public function processNode(Node $node, Scope $scope): array * - False means a non-scalar value was encountered and we cannot be sure of the next keys. */ $autoGeneratedIndex = null; + $seenKeys = []; + $seenUnions = []; foreach ($node->getItemNodes() as $itemNode) { $item = $itemNode->getArrayItem(); if ($item === null) { @@ -84,6 +87,44 @@ public function processNode(Node $node, Scope $scope): array continue; } + $duplicate = false; + $newValues = $keyValues; + foreach ($newValues as $k => $newValue) { + if (array_search($newValue, $seenKeys, true) !== false) { + unset($newValues[$k]); + } + + if ($newValues === []) { + $duplicate = true; + break; + } + } + + if ($newValues !== []) { + if (count($newValues) === 1) { + $newValue = $newValues[array_key_first($newValues)]; + foreach ($seenUnions as $k => $union) { + $offset = array_search($newValue, $union, true); + if ($offset === false) { + continue; + } + + unset($seenUnions[$k][$offset]); + + // turn a union into a seen key, when all its elements have been seen + if (count($seenUnions[$k]) !== 1) { + continue; + } + + $seenKeys[] = $seenUnions[$k][array_key_first($seenUnions[$k])]; + unset($seenUnions[$k]); + } + $seenKeys[] = $newValue; + } else { + $seenUnions[] = $newValues; + } + } + foreach ($keyValues as $value) { $printedValue = $key !== null ? $this->exprPrinter->printExpr($key) @@ -94,9 +135,7 @@ public function processNode(Node $node, Scope $scope): array $valueLines[$value] = $item->getStartLine(); } - $previousCount = count($values); - $values[$value] = $printedValue; - if ($previousCount !== count($values)) { + if (!$duplicate) { continue; } diff --git a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php index 6d775a2f7c9..a01e02424d9 100644 --- a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php @@ -73,7 +73,37 @@ public function testDuplicateKeys(): void 'Array has 2 duplicate keys with value \'key\' (\'key\', $key2).', 105, ], + [ + "Array has 2 duplicate keys with value 'bar' (\$key, 'bar').", + 128, + ], + [ + "Array has 2 duplicate keys with value 'bar' (\$key, 'bar').", + 139, + ], + [ + "Array has 2 duplicate keys with value 'foo' ('foo', \$key).", + 151, + ], + [ + "Array has 2 duplicate keys with value 'bar' ('bar', \$key).", + 152, + ], + [ + "Array has 2 duplicate keys with value 'baz' (\$key, 'baz').", + 171, + ], ]); } + public function testBug13013(): void + { + $this->analyse([__DIR__ . '/data/bug-13013.php'], []); + } + + public function testBug13022(): void + { + $this->analyse([__DIR__ . '/data/bug-13022.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-13013.php b/tests/PHPStan/Rules/Arrays/data/bug-13013.php new file mode 100644 index 00000000000..dd5180869c6 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-13013.php @@ -0,0 +1,77 @@ +> + */ + public array $mailsGroupedByTemplate; + + /** + * @var array> + */ + protected array $mailCounts; + + private function __construct() + { + $this->mailsGroupedByTemplate = []; + $this->mailCounts = []; + } + + public function countMailStates(): void + { + foreach ($this->mailsGroupedByTemplate as $templateId => $mails) { + $this->mailCounts[$templateId] = [ + MailStatus::notActive()->code => 0, + MailStatus::simulation()->code => 0, + MailStatus::active()->code => 0, + ]; + } + } +} + +final class MailStatus +{ + private const CODE_NOT_ACTIVE = 0; + + private const CODE_SIMULATION = 1; + + private const CODE_ACTIVE = 2; + + /** + * @var self::CODE_* + */ + public int $code; + + public string $name; + + public string $description; + + /** + * @param self::CODE_* $status + */ + public function __construct(int $status, string $name, string $description) + { + $this->code = $status; + $this->name = $name; + $this->description = $description; + } + + public static function notActive(): self + { + return new self(self::CODE_NOT_ACTIVE, _('Pausiert'), _('Es findet kein Mailversand an Kunden statt')); + } + + public static function simulation(): self + { + return new self(self::CODE_SIMULATION, _('Simulation'), _('Wenn Template zugewiesen, werden im Simulationsmodus E-Mails nur in der Datenbank gespeichert und nicht an den Kunden gesendet')); + } + + public static function active(): self + { + return new self(self::CODE_ACTIVE, _('Aktiv'), _('Wenn Template zugewiesen, findet Mailversand an Kunden statt')); + } + +} diff --git a/tests/PHPStan/Rules/Arrays/data/bug-13022.php b/tests/PHPStan/Rules/Arrays/data/bug-13022.php new file mode 100644 index 00000000000..22727c110a6 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-13022.php @@ -0,0 +1,29 @@ + $object->getId(), + $targetId => 'info', + ]; + + // sql()->insert('tablename', $array); - example how this will be used +} diff --git a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php index 06dbbeb0f5a..7a73af9da98 100644 --- a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php +++ b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php @@ -107,4 +107,69 @@ public function doUnionKeys(string $key): void ]; } + /** + * @param 'foo'|'bar' $key + */ + public function maybeDuplicate(string $key): void + { + $a = [ + 'foo' => 'foo', + $key => 'foo|bar', + ]; + } + + /** + * @param 'foo'|'bar' $key + */ + public function sureDuplicate(string $key): void + { + $a = [ + 'foo' => 'foo', + $key => 'foo|bar', + 'bar' => 'bar', + ]; + } + + /** + * @param 'foo'|'bar' $key + */ + public function sureDuplicate2(string $key): void + { + $a = [ + $key => 'foo|bar', + 'foo' => 'foo', + 'bar' => 'bar', + ]; + } + + /** + * @param 'foo'|'bar' $key + */ + public function sureDuplicate3(string $key): void + { + $a = [ + 'foo' => 'foo', + 'bar' => 'bar', + $key => 'foo|bar', + ]; + } + + /** + * @param 'foo'|'bar'|'baz' $key + */ + public function sureDuplicate4(string $key): void + { + $a = [ + 'foo' => 'foo', + 'bar' => 'bar', + $key => 'foo|bar|baz', + ]; + + $b = [ + 'foo' => 'foo', + 'bar' => 'bar', + $key => 'foo|bar|baz', + 'baz' => 'baz', + ]; + } } From 1f4d9c35fe91c0f41615e9b6bab8207f8c2bf953 Mon Sep 17 00:00:00 2001 From: malsuke <153903070+malsuke@users.noreply.github.com> Date: Fri, 16 May 2025 18:32:14 +0900 Subject: [PATCH 1360/3097] Improve `preg_split()` function return type --- resources/functionMap.php | 2 +- .../PregSplitDynamicReturnTypeExtension.php | 179 +++++++++++++++++- .../Analyser/AnalyserIntegrationTest.php | 2 +- tests/PHPStan/Analyser/nsrt/preg_split.php | 79 +++++++- 4 files changed, 243 insertions(+), 19 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 1938de18e31..f2a4c6d582e 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8726,7 +8726,7 @@ 'preg_replace' => ['string|array|null', 'regex'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable(array):string', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], -'preg_split' => ['list|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'], +'preg_split' => ['list|list}>|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'], 'prev' => ['mixed', '&rw_array_arg'=>'array|object'], 'print_r' => ['string|true', 'var'=>'mixed', 'return='=>'bool'], 'printf' => ['int', 'format'=>'string', '...values='=>'__stringAndStringable|int|float|null|bool'], diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index d51b5314b08..b54b6676d06 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -2,29 +2,41 @@ namespace PHPStan\Type\Php; +use Nette\Utils\RegexpException; +use Nette\Utils\Strings; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BitwiseFlagHelper; -use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; +use function count; +use function is_array; +use function is_int; +use function is_numeric; +use function preg_split; use function strtolower; final class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function __construct( - private BitwiseFlagHelper $bitwiseFlagAnalyser, + private readonly BitwiseFlagHelper $bitwiseFlagAnalyser, ) { } @@ -36,17 +48,164 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - $flagsArg = $functionCall->getArgs()[3] ?? null; + $args = $functionCall->getArgs(); + if (count($args) < 2) { + return null; + } + $patternArg = $args[0]; + $subjectArg = $args[1]; + $limitArg = $args[2] ?? null; + $capturesOffset = $args[3] ?? null; + $patternType = $scope->getType($patternArg->value); + $patternConstantTypes = $patternType->getConstantStrings(); + if (count($patternConstantTypes) > 0) { + foreach ($patternConstantTypes as $patternConstantType) { + if ($this->isValidPattern($patternConstantType->getValue()) === false) { + return new ErrorType(); + } + } + } + + $subjectType = $scope->getType($subjectArg->value); + $subjectConstantTypes = $subjectType->getConstantStrings(); + + $limits = []; + if ($limitArg === null) { + $limits = [-1]; + } else { + $limitType = $scope->getType($limitArg->value); + if (!$this->isIntOrStringValue($limitType)) { + return new ErrorType(); + } + foreach ($limitType->getConstantScalarValues() as $limit) { + if (!is_int($limit) && !is_numeric($limit)) { + return new ErrorType(); + } + $limits[] = $limit; + } + } + + $flags = []; + if ($capturesOffset === null) { + $flags = [0]; + } else { + $flagType = $scope->getType($capturesOffset->value); + if (!$this->isIntOrStringValue($flagType)) { + return new ErrorType(); + } + foreach ($flagType->getConstantScalarValues() as $flag) { + if (!is_int($flag) && !is_numeric($flag)) { + return new ErrorType(); + } + $flags[] = $flag; + } + } - if ($flagsArg !== null && $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($flagsArg->value, $scope, 'PREG_SPLIT_OFFSET_CAPTURE')->yes()) { - $type = new ArrayType( - new IntegerType(), - new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), IntegerRangeType::fromInterval(0, null)], [2], [], TrinaryLogic::createYes()), + if ($this->isPatternOrSubjectEmpty($patternConstantTypes, $subjectConstantTypes)) { + if ($capturesOffset !== null + && $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($capturesOffset->value, $scope, 'PREG_SPLIT_NO_EMPTY')->yes()) { + $returnStringType = TypeCombinator::intersect( + new StringType(), + new AccessoryNonEmptyStringType(), + ); + } else { + $returnStringType = new StringType(); + } + + $arrayTypeBuilder = ConstantArrayTypeBuilder::createEmpty(); + $arrayTypeBuilder->setOffsetValueType( + new ConstantIntegerType(0), + $returnStringType, + ); + $arrayTypeBuilder->setOffsetValueType( + new ConstantIntegerType(1), + IntegerRangeType::fromInterval(0, null), ); - return TypeCombinator::union(TypeCombinator::intersect($type, new AccessoryArrayListType()), new ConstantBooleanType(false)); + $capturedArrayType = $arrayTypeBuilder->getArray(); + + $returnInternalValueType = $returnStringType; + if ($capturesOffset !== null) { + $flagState = $this->bitwiseFlagAnalyser->bitwiseOrContainsConstant($capturesOffset->value, $scope, 'PREG_SPLIT_OFFSET_CAPTURE'); + if ($flagState->yes()) { + $capturedArrayListType = TypeCombinator::intersect( + new ArrayType(new IntegerType(), $capturedArrayType), + new AccessoryArrayListType(), + ); + + if ($subjectType->isNonEmptyString()->yes()) { + $capturedArrayListType = TypeCombinator::intersect($capturedArrayListType, new NonEmptyArrayType()); + } + return TypeCombinator::union($capturedArrayListType, new ConstantBooleanType(false)); + } + if ($flagState->maybe()) { + $returnInternalValueType = TypeCombinator::union(new StringType(), $capturedArrayType); + } + } + + $returnListType = TypeCombinator::intersect(new ArrayType(new MixedType(), $returnInternalValueType), new AccessoryArrayListType()); + if ($subjectType->isNonEmptyString()->yes()) { + $returnListType = TypeCombinator::intersect( + $returnListType, + new NonEmptyArrayType(), + ); + } + + return TypeCombinator::union($returnListType, new ConstantBooleanType(false)); } - return null; + $resultTypes = []; + foreach ($patternConstantTypes as $patternConstantType) { + foreach ($subjectConstantTypes as $subjectConstantType) { + foreach ($limits as $limit) { + foreach ($flags as $flag) { + $result = @preg_split($patternConstantType->getValue(), $subjectConstantType->getValue(), (int) $limit, (int) $flag); + if ($result === false) { + return new ErrorType(); + } + $constantArray = ConstantArrayTypeBuilder::createEmpty(); + foreach ($result as $key => $value) { + if (is_array($value)) { + $valueConstantArray = ConstantArrayTypeBuilder::createEmpty(); + $valueConstantArray->setOffsetValueType(new ConstantIntegerType(0), new ConstantStringType($value[0])); + $valueConstantArray->setOffsetValueType(new ConstantIntegerType(1), new ConstantIntegerType($value[1])); + $returnInternalValueType = $valueConstantArray->getArray(); + } else { + $returnInternalValueType = new ConstantStringType($value); + } + $constantArray->setOffsetValueType(new ConstantIntegerType($key), $returnInternalValueType); + } + + $resultTypes[] = $constantArray->getArray(); + } + } + } + } + $resultTypes[] = new ConstantBooleanType(false); + return TypeCombinator::union(...$resultTypes); + } + + /** + * @param ConstantStringType[] $patternConstantArray + * @param ConstantStringType[] $subjectConstantArray + */ + private function isPatternOrSubjectEmpty(array $patternConstantArray, array $subjectConstantArray): bool + { + return count($patternConstantArray) === 0 || count($subjectConstantArray) === 0; + } + + private function isValidPattern(string $pattern): bool + { + try { + Strings::match('', $pattern); + } catch (RegexpException) { + return false; + } + return true; + } + + private function isIntOrStringValue(Type $type): bool + { + return (new UnionType([new IntegerType(), new StringType()]))->isSuperTypeOf($type)->yes(); } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 7399d39c978..826016fc923 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -936,7 +936,7 @@ public function testBug7554(): void $this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, list|string>>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage()); $this->assertSame(26, $errors[0]->getLine()); - $this->assertSame('Cannot access offset int<1, max> on list}>|false.', $errors[1]->getMessage()); + $this->assertSame('Cannot access offset int<1, max> on list}>|false.', $errors[1]->getMessage()); $this->assertSame(27, $errors[1]->getLine()); } diff --git a/tests/PHPStan/Analyser/nsrt/preg_split.php b/tests/PHPStan/Analyser/nsrt/preg_split.php index 7122c16150c..210a467372a 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_split.php +++ b/tests/PHPStan/Analyser/nsrt/preg_split.php @@ -6,12 +6,77 @@ class HelloWorld { + private const NUMERIC_STRING_1 = "1"; + private const NUMERIC_STRING_NEGATIVE_1 = "-1"; + public function doFoo() { - assertType('list|false', preg_split('/-/', '1-2-3')); - assertType('list|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY)); - assertType('list}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE)); - assertType('list}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{''}|false", preg_split('/-/', '')); + assertType("array{}|false", preg_split('/-/', '', -1, PREG_SPLIT_NO_EMPTY)); + assertType("array{'1', '-', '2', '-', '3'}|false", preg_split('/ *(-) */', '1- 2-3', -1, PREG_SPLIT_DELIM_CAPTURE)); + assertType("array{array{'', 0}}|false", preg_split('/-/', '', -1, PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{}|false", preg_split('/-/', '', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{'1', '2', '3'}|false", preg_split('/-/', '1-2-3')); + assertType("array{'1', '2', '3'}|false", preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY)); + assertType("array{'1', '3'}|false", preg_split('/-/', '1--3', -1, PREG_SPLIT_NO_EMPTY)); + assertType("array{array{'1', 0}, array{'2', 2}, array{'3', 4}}|false", preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{array{'1', 0}, array{'2', 2}, array{'3', 4}}|false", preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{array{'1', 0}, array{'', 2}, array{'3', 3}}|false", preg_split('/-/', '1--3', -1, PREG_SPLIT_OFFSET_CAPTURE)); + assertType("array{array{'1', 0}, array{'3', 3}}|false", preg_split('/-/', '1--3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); + + assertType("array{'1', '2', '3'}|false", preg_split('/-/', '1-2-3', self::NUMERIC_STRING_NEGATIVE_1)); + assertType("array{'1', '2', '3'}|false", preg_split('/-/', '1-2-3', -1, self::NUMERIC_STRING_1)); + } + + public function doWithError() { + assertType('*ERROR*', preg_split('/[0-9a]', '1-2-3')); + assertType('*ERROR*', preg_split('/-/', '1-2-3', 'hogehoge')); + assertType('*ERROR*', preg_split('/-/', '1-2-3', -1, 'hogehoge')); + assertType('*ERROR*', preg_split('/-/', '1-2-3', [], self::NUMERIC_STRING_NEGATIVE_1)); + assertType('*ERROR*', preg_split('/-/', '1-2-3', null, self::NUMERIC_STRING_NEGATIVE_1)); + assertType('*ERROR*', preg_split('/-/', '1-2-3', -1, [])); + assertType('*ERROR*', preg_split('/-/', '1-2-3', -1, null)); + } + + public function doWithVariables(string $pattern, string $subject, int $offset, int $flags): void + { + assertType("array{'1', '2', '3'}|array{'1-2-3'}|false", preg_split('/-/', '1-2-3', $this->generate())); + assertType("array{'1', '2', '3'}|array{'1-2-3'}|false", preg_split('/-/', '1-2-3', $this->generate(), $this->generate())); + + assertType('list}|string>|false', preg_split($pattern, $subject, $offset, $flags)); + assertType('list}|string>|false', preg_split("//", $subject, $offset, $flags)); + + assertType('non-empty-list}|string>|false', preg_split($pattern, "1-2-3", $offset, $flags)); + assertType('list}|string>|false', preg_split($pattern, $subject, -1, $flags)); + assertType('list|false', preg_split($pattern, $subject, $offset, PREG_SPLIT_NO_EMPTY)); + assertType('list}>|false', preg_split($pattern, $subject, $offset, PREG_SPLIT_OFFSET_CAPTURE)); + assertType("list|false", preg_split($pattern, $subject, $offset, PREG_SPLIT_DELIM_CAPTURE)); + assertType('list}>|false', preg_split($pattern, $subject, $offset, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE)); + } + + /** + * @return 1|'17' + */ + private function generate(): int|string { + return (rand() % 2 === 0) ? 1 : "17"; + } + + /** + * @param non-empty-string $nonEmptySubject + */ + public function doWithNonEmptySubject(string $pattern, string $nonEmptySubject, int $offset, int $flags): void + { + assertType('non-empty-list|false', preg_split("//", $nonEmptySubject)); + + assertType('non-empty-list}|string>|false', preg_split($pattern, $nonEmptySubject, $offset, $flags)); + assertType('non-empty-list}|string>|false', preg_split("//", $nonEmptySubject, $offset, $flags)); + + assertType('non-empty-list}>|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_OFFSET_CAPTURE)); + assertType('non-empty-list|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_NO_EMPTY)); + assertType('non-empty-list|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_DELIM_CAPTURE)); + assertType('non-empty-list}>|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE)); + assertType('non-empty-list}>|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); + assertType('non-empty-list|false', preg_split("/-/", $nonEmptySubject, $offset, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)); } /** @@ -26,8 +91,7 @@ public static function splitWithOffset($pattern, $subject, $limit = -1, $flags = { assertType('list}>|false', preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE)); assertType('list}>|false', preg_split($pattern, $subject, $limit, PREG_SPLIT_OFFSET_CAPTURE | $flags)); - - assertType('list}>|false', preg_split($pattern, $subject, $limit, PREG_SPLIT_OFFSET_CAPTURE | $flags | PREG_SPLIT_NO_EMPTY)); + assertType('list}>|false', preg_split($pattern, $subject, $limit, PREG_SPLIT_OFFSET_CAPTURE | $flags | PREG_SPLIT_NO_EMPTY)); } /** @@ -35,7 +99,8 @@ public static function splitWithOffset($pattern, $subject, $limit = -1, $flags = * @param string $subject * @param int $limit */ - public static function dynamicFlags($pattern, $subject, $limit = -1) { + public static function dynamicFlags($pattern, $subject, $limit = -1) + { $flags = PREG_SPLIT_OFFSET_CAPTURE; if ($subject === '1-2-3') { From f61439f6a30215926e0b772ebe5021dc28c00845 Mon Sep 17 00:00:00 2001 From: Michael Newton Date: Fri, 16 May 2025 03:33:31 -0600 Subject: [PATCH 1361/3097] Update SNMP class signatures --- resources/functionMap.php | 63 ++++++++++++++++------------ tests/PHPStan/Analyser/nsrt/snmp.php | 18 ++++++++ 2 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/snmp.php diff --git a/resources/functionMap.php b/resources/functionMap.php index f2a4c6d582e..2f8761856cb 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10180,39 +10180,48 @@ 'sinh' => ['float', 'number'=>'float'], 'sizeof' => ['int', 'var'=>'Countable|array', 'mode='=>'int'], 'sleep' => ['int|false', 'seconds'=>'int'], -'snmp2_get' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp2_getnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp2_real_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp2_set' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp2_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp3_get' => ['string|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp3_getnext' => ['string|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp3_real_walk' => ['array|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp3_set' => ['bool', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmp3_walk' => ['array|false', 'host'=>'string', 'sec_name'=>'string', 'sec_level'=>'string', 'auth_protocol'=>'string', 'auth_passphrase'=>'string', 'priv_protocol'=>'string', 'priv_passphrase'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'SNMP::__construct' => ['void', 'version'=>'int', 'hostname'=>'string', 'community'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp2_get' => ['string|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_get\'1' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_getnext' => ['string|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_real_walk' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_set' => ['bool', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_set\'1' => ['bool', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-array', 'type'=>'string|non-empty-array', 'value'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp2_walk' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_get' => ['string|false', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_get\'1' => ['array|false', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_getnext' => ['string|false', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_real_walk' => ['array|false', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_set' => ['bool', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_set\'1' => ['bool', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'array', 'type'=>'string|non-empty-array', 'value'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmp3_walk' => ['array|false', 'hostname'=>'string', 'sec_name'=>'string', 'sec_level'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'auth_protocol'=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'auth_passphrase'=>'string', 'priv_protocol'=>'\'AES\'|\'AES128\'|\'DES\'', 'priv_passphrase'=>'string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'SNMP::__construct' => ['void', 'version'=>'int', 'hostname'=>'string', 'community'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], 'SNMP::close' => ['bool'], -'SNMP::get' => ['array|string|false', 'object_id'=>'string|array', 'preserve_keys='=>'bool'], +'SNMP::get' => ['array|false', 'objectId'=>'string[]', 'preserveKeys='=>'bool'], +'SNMP::get\'1' => ['string|false', 'objectId'=>'string', 'preserveKeys='=>'bool'], 'SNMP::getErrno' => ['int'], 'SNMP::getError' => ['string'], -'SNMP::getnext' => ['string|array|false', 'object_id'=>'string|array'], -'SNMP::set' => ['bool', 'object_id'=>'string|array', 'type'=>'string|array', 'value'=>'mixed'], -'SNMP::setSecurity' => ['bool', 'sec_level'=>'string', 'auth_protocol='=>'string', 'auth_passphrase='=>'string', 'priv_protocol='=>'string', 'priv_passphrase='=>'string', 'contextname='=>'string', 'contextengineid='=>'string'], -'SNMP::walk' => ['array|false', 'object_id'=>'string', 'suffix_as_key='=>'bool', 'non_repeaters='=>'int', 'max_repetitions='=>'int'], +'SNMP::getnext' => ['array|false', 'objectId'=>'string[]'], +'SNMP::getnext\'1' => ['string|false', 'objectId'=>'string'], +'SNMP::set' => ['bool', 'objectId'=>'string[]', 'type'=>'string[]|string', 'value'=>'mixed[]'], +'SNMP::set\'1' => ['bool', 'objectId'=>'string', 'type'=>'string', 'value'=>'string'], +'SNMP::setSecurity' => ['bool', 'securityLevel'=>'\'authPriv\'|\'authNoPriv\'|\'noAuthNoPriv\'', 'authProtocol='=>'\'SHA\'|\'SHA256\'|\'SHA512\'|\'MD5\'', 'authPassphrase='=>'string', 'privacyProtocol='=>'\'AES\'|\'AES128\'|\'DES\'', 'privacyPassphrase='=>'string', 'contextName='=>'string', 'contextEngineId='=>'string'], +'SNMP::walk' => ['array|false', 'objectId'=>'string', 'suffixAsKey='=>'bool', 'maxRepetitions='=>'int<-1,max>', 'nonRepeaters='=>'int<-1,max>'], 'snmp_get_quick_print' => ['bool'], 'snmp_get_valueretrieval' => ['int'], 'snmp_read_mib' => ['bool', 'filename'=>'string'], -'snmp_set_enum_print' => ['bool', 'enum_print'=>'int'], -'snmp_set_oid_numeric_print' => ['void', 'oid_format'=>'int'], -'snmp_set_oid_output_format' => ['bool', 'oid_format'=>'int'], -'snmp_set_quick_print' => ['bool', 'quick_print'=>'int'], -'snmp_set_valueretrieval' => ['bool', 'method='=>'int'], -'snmpget' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmpgetnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmprealwalk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmpset' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'mixed', 'timeout='=>'int', 'retries='=>'int'], -'snmpwalk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'snmpwalkoid' => ['array|false', 'hostname'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], +'snmp_set_enum_print' => ['true', 'enable'=>'bool'], +'snmp_set_oid_numeric_print' => ['true', 'format'=>'int'], +'snmp_set_oid_output_format' => ['true', 'format'=>'int'], +'snmp_set_quick_print' => ['true', 'enable'=>'bool'], +'snmp_set_valueretrieval' => ['true', 'method='=>'int'], +'snmpget' => ['string|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpget\'1' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpgetnext' => ['string|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmprealwalk' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpset' => ['bool', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'type'=>'string', 'value'=>'string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpset\'1' => ['bool', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-array', 'type'=>'string|non-empty-array', 'value'=>'non-empty-array', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpwalk' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], +'snmpwalkoid' => ['array|false', 'hostname'=>'string', 'community'=>'non-empty-string', 'object_id'=>'non-empty-string', 'timeout='=>'int<-1,max>', 'retries='=>'int<-1,max>'], 'SoapClient::__call' => ['mixed', 'function_name'=>'string', 'arguments'=>'array'], 'SoapClient::__construct' => ['void', 'wsdl'=>'mixed', 'options='=>'array|null'], 'SoapClient::__doRequest' => ['string|null', 'request'=>'string', 'location'=>'string', 'action'=>'string', 'version'=>'int', 'one_way='=>'int'], diff --git a/tests/PHPStan/Analyser/nsrt/snmp.php b/tests/PHPStan/Analyser/nsrt/snmp.php new file mode 100644 index 00000000000..fe3cfd7b596 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/snmp.php @@ -0,0 +1,18 @@ +get('SNMPv2-MIB::sysContact.0'); + assertType('string|false', $result); + + $result = $snmp->get(['SNMPv2-MIB::sysContact.0', 'SNMPv2-MIB::sysDescr.0']); + assertType('array|false', $result); + } +} From 4d58c28d07e260752e7adb38f43a9aedcfd3c182 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 29 Apr 2025 09:08:15 +0200 Subject: [PATCH 1362/3097] Fix `array_column()` with explicit null `$index_key` --- ...ArrayColumnFunctionReturnTypeExtension.php | 3 +- src/Type/Php/ArrayColumnHelper.php | 12 +++--- .../Analyser/nsrt/array-column-php82.php | 19 ++++++++++ tests/PHPStan/Analyser/nsrt/array-column.php | 20 ++++++++++ tests/PHPStan/Analyser/nsrt/bug-12954.php | 38 +++++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 6 +++ 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12954.php diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 70f5892c2b6..68272571798 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\NullType; use PHPStan\Type\Type; use function count; @@ -32,7 +33,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayType = $scope->getType($functionCall->getArgs()[0]->value); $columnType = $scope->getType($functionCall->getArgs()[1]->value); - $indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : null; + $indexType = $numArgs >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : new NullType(); $constantArrayTypes = $arrayType->getConstantArrays(); if (count($constantArrayTypes) === 1) { diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index a0796febf40..ef72623879b 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -50,9 +50,9 @@ public function getReturnValueType(Type $arrayType, Type $columnType, Scope $sco return [$returnValueType, $iterableAtLeastOnce]; } - public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $scope): Type + public function getReturnIndexType(Type $arrayType, Type $indexType, Scope $scope): Type { - if ($indexType !== null) { + if (!$indexType->isNull()->yes()) { $iterableValueType = $arrayType->getIterableValueType(); $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); @@ -69,7 +69,7 @@ public function getReturnIndexType(Type $arrayType, ?Type $indexType, Scope $sco return new IntegerType(); } - public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type + public function handleAnyArray(Type $arrayType, Type $columnType, Type $indexType, Scope $scope): Type { [$returnValueType, $iterableAtLeastOnce] = $this->getReturnValueType($arrayType, $columnType, $scope); if ($returnValueType instanceof NeverType) { @@ -82,14 +82,14 @@ public function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexTy if ($iterableAtLeastOnce->yes()) { $returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType()); } - if ($indexType === null) { + if ($indexType->isNull()->yes()) { $returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType()); } return $returnType; } - public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type + public function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, Type $indexType, Scope $scope): ?Type { $builder = ConstantArrayTypeBuilder::createEmpty(); @@ -102,7 +102,7 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy continue; } - if ($indexType !== null) { + if (!$indexType->isNull()->yes()) { $type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false); if ($type !== null) { $keyType = $type; diff --git a/tests/PHPStan/Analyser/nsrt/array-column-php82.php b/tests/PHPStan/Analyser/nsrt/array-column-php82.php index 7f0a545edc0..e55e7a38ba0 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column-php82.php +++ b/tests/PHPStan/Analyser/nsrt/array-column-php82.php @@ -13,6 +13,7 @@ class ArrayColumnTest public function testArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array>', array_column($array, null, 'key')); } @@ -22,12 +23,14 @@ public function testArray2(array $array): void { // Note: Array may still be empty! assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); } /** @param array{} $array */ public function testArray3(array $array): void { assertType('array{}', array_column($array, 'column')); + assertType('array{}', array_column($array, 'column', null)); assertType('array{}', array_column($array, 'column', 'key')); assertType('array{}', array_column($array, null, 'key')); } @@ -66,6 +69,7 @@ public function testArray8(array $array): void public function testConstantArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array', array_column($array, null, 'key')); } @@ -74,6 +78,7 @@ public function testConstantArray1(array $array): void public function testConstantArray2(array $array): void { assertType('array{}', array_column($array, 'foo')); + assertType('array{}', array_column($array, 'foo', null)); assertType('array{}', array_column($array, 'foo', 'key')); } @@ -81,6 +86,7 @@ public function testConstantArray2(array $array): void public function testConstantArray3(array $array): void { assertType("array{string}", array_column($array, 'column')); + assertType("array{string}", array_column($array, 'column', null)); assertType("array{bar: string}", array_column($array, 'column', 'key')); assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key')); } @@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void public function testConstantArray5(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); } @@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void public function testConstantArray6(array $array): void { assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2')); + assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null)); } /** @param non-empty-array $array */ public function testConstantArray7(array $array): void { assertType('non-empty-list', array_column($array, 'column')); + assertType('non-empty-list', array_column($array, 'column', null)); assertType('non-empty-array', array_column($array, 'column', 'key')); assertType('non-empty-array', array_column($array, null, 'key')); } @@ -142,6 +151,7 @@ public function testConstantArray11(array $array): void public function testConstantArray12(array $array): void { assertType("array{0?: 'foo'}", array_column($array, 'column')); + assertType("array{0?: 'foo'}", array_column($array, 'column', null)); assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key')); } @@ -151,6 +161,7 @@ public function testConstantArray12(array $array): void public function testImprecise1(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key')); assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key')); } @@ -166,6 +177,7 @@ public function testImprecise2(array $array): void public function testImprecise3(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); } @@ -173,9 +185,11 @@ public function testImprecise3(array $array): void public function testImprecise5(array $array): void { assertType('list', array_column($array, 'nodeName')); + assertType('list', array_column($array, 'nodeName', null)); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -185,9 +199,11 @@ public function testImprecise5(array $array): void public function testObjects1(array $array): void { assertType('non-empty-list', array_column($array, 'nodeName')); + assertType('non-empty-list', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -197,9 +213,11 @@ public function testObjects1(array $array): void public function testObjects2(array $array): void { assertType('array{string}', array_column($array, 'nodeName')); + assertType('array{string}', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -214,6 +232,7 @@ final class Foo public function doFoo(array $a): void { assertType('array{}', array_column($a, 'nodeName')); + assertType('array{}', array_column($a, 'nodeName', null)); assertType('array{}', array_column($a, 'nodeName', 'tagName')); } diff --git a/tests/PHPStan/Analyser/nsrt/array-column.php b/tests/PHPStan/Analyser/nsrt/array-column.php index ee4ad005271..4f830b96d91 100644 --- a/tests/PHPStan/Analyser/nsrt/array-column.php +++ b/tests/PHPStan/Analyser/nsrt/array-column.php @@ -13,6 +13,7 @@ class ArrayColumnTest public function testArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array>', array_column($array, null, 'key')); } @@ -22,12 +23,14 @@ public function testArray2(array $array): void { // Note: Array may still be empty! assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); } /** @param array{} $array */ public function testArray3(array $array): void { assertType('array{}', array_column($array, 'column')); + assertType('array{}', array_column($array, 'column', null)); assertType('array{}', array_column($array, 'column', 'key')); assertType('array{}', array_column($array, null, 'key')); } @@ -66,6 +69,7 @@ public function testArray8(array $array): void public function testConstantArray1(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); assertType('array', array_column($array, null, 'key')); } @@ -74,6 +78,7 @@ public function testConstantArray1(array $array): void public function testConstantArray2(array $array): void { assertType('array{}', array_column($array, 'foo')); + assertType('array{}', array_column($array, 'foo', null)); assertType('array{}', array_column($array, 'foo', 'key')); } @@ -81,6 +86,7 @@ public function testConstantArray2(array $array): void public function testConstantArray3(array $array): void { assertType("array{string}", array_column($array, 'column')); + assertType("array{string}", array_column($array, 'column', null)); assertType("array{bar: string}", array_column($array, 'column', 'key')); assertType("array{bar: array{column: string, key: 'bar'}}", array_column($array, null, 'key')); } @@ -96,6 +102,7 @@ public function testConstantArray4(array $array): void public function testConstantArray5(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar'|int, 'foo'>", array_column($array, 'column', 'key')); assertType("array<'bar'|int, array{column?: 'foo', key?: 'bar'}>", array_column($array, null, 'key')); } @@ -104,12 +111,14 @@ public function testConstantArray5(array $array): void public function testConstantArray6(array $array): void { assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2')); + assertType('list', array_column($array, mt_rand(0, 1) === 0 ? 'column1' : 'column2', null)); } /** @param non-empty-array $array */ public function testConstantArray7(array $array): void { assertType('non-empty-list', array_column($array, 'column')); + assertType('non-empty-list', array_column($array, 'column', null)); assertType('non-empty-array', array_column($array, 'column', 'key')); assertType('non-empty-array', array_column($array, null, 'key')); } @@ -142,6 +151,7 @@ public function testConstantArray11(array $array): void public function testConstantArray12(array $array): void { assertType("array{0?: 'foo'}", array_column($array, 'column')); + assertType("array{0?: 'foo'}", array_column($array, 'column', null)); assertType("array{bar?: 'foo'}", array_column($array, 'column', 'key')); } @@ -149,6 +159,7 @@ public function testConstantArray12(array $array): void public function testConstantArray13(array $array): void { assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column')); + assertType("array{0?: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null)); assertType("array{bar1?: 'foo1', bar2?: 'foo2'}", array_column($array, 'column', 'key')); } @@ -156,6 +167,7 @@ public function testConstantArray13(array $array): void public function testConstantArray14(array $array): void { assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column')); + assertType("array{0: 'foo1'|'foo2', 1?: 'foo2'}", array_column($array, 'column', null)); assertType("array{bar1?: 'foo1', bar2: 'foo2'}", array_column($array, 'column', 'key')); } @@ -165,6 +177,7 @@ public function testConstantArray14(array $array): void public function testImprecise1(array $array): void { assertType("list<'foo'>", array_column($array, 'column')); + assertType("list<'foo'>", array_column($array, 'column', null)); assertType("array<'bar', 'foo'>", array_column($array, 'column', 'key')); assertType("array{bar: array{column?: 'foo', key: 'bar'}}", array_column($array, null, 'key')); } @@ -180,6 +193,7 @@ public function testImprecise2(array $array): void public function testImprecise3(array $array): void { assertType('list', array_column($array, 'column')); + assertType('list', array_column($array, 'column', null)); assertType('array', array_column($array, 'column', 'key')); } @@ -187,9 +201,11 @@ public function testImprecise3(array $array): void public function testImprecise5(array $array): void { assertType('list', array_column($array, 'nodeName')); + assertType('list', array_column($array, 'nodeName', null)); assertType('array', array_column($array, 'nodeName', 'tagName')); assertType('array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('array', array_column($array, 'nodeName', 'foo')); assertType('array', array_column($array, null, 'foo')); @@ -199,9 +215,11 @@ public function testImprecise5(array $array): void public function testObjects1(array $array): void { assertType('non-empty-list', array_column($array, 'nodeName')); + assertType('non-empty-list', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); @@ -211,9 +229,11 @@ public function testObjects1(array $array): void public function testObjects2(array $array): void { assertType('array{string}', array_column($array, 'nodeName')); + assertType('array{string}', array_column($array, 'nodeName', null)); assertType('non-empty-array', array_column($array, 'nodeName', 'tagName')); assertType('non-empty-array', array_column($array, null, 'tagName')); assertType('list', array_column($array, 'foo')); + assertType('list', array_column($array, 'foo', null)); assertType('array', array_column($array, 'foo', 'tagName')); assertType('non-empty-array', array_column($array, 'nodeName', 'foo')); assertType('non-empty-array', array_column($array, null, 'foo')); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12954.php b/tests/PHPStan/Analyser/nsrt/bug-12954.php new file mode 100644 index 00000000000..5fbc5087990 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12954.php @@ -0,0 +1,38 @@ + [ + 'name' => 'ROLE_USER', + 'description' => 'User role' + ], + 28 => [ + 'name' => 'ROLE_ADMIN', + 'description' => 'Admin role' + ], + 43 => [ + 'name' => 'ROLE_SUPER_ADMIN', + 'description' => 'SUPER Admin role' + ], +]; + +$list = ['ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN']; + +$result = array_column($plop, 'name', null); + +/** + * @param list $array + */ +function doSomething(array $array): void +{ + assertType('list', $array); +} + +doSomething($result); +doSomething($list); + +assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $result); +assertType('array{\'ROLE_USER\', \'ROLE_ADMIN\', \'ROLE_SUPER_ADMIN\'}', $list); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 409535685f5..bf8aba48a12 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2242,4 +2242,10 @@ public function testBug12847(): void ]); } + public function testBug12954(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-12954.php'], []); + } + } From df4c1f39e9b63c2e58e7666c664f7c8ac2d74f26 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 13:58:29 +0200 Subject: [PATCH 1363/3097] Call ProcessPromise::cancel() from deferred canceller --- src/Process/ProcessPromise.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Process/ProcessPromise.php b/src/Process/ProcessPromise.php index 31f975460a1..5a526b771e8 100644 --- a/src/Process/ProcessPromise.php +++ b/src/Process/ProcessPromise.php @@ -24,7 +24,9 @@ final class ProcessPromise public function __construct(private LoopInterface $loop, private string $name, private string $command) { - $this->deferred = new Deferred(); + $this->deferred = new Deferred(function (): void { + $this->cancel(); + }); } public function getName(): string @@ -85,7 +87,7 @@ public function run(): PromiseInterface return $this->deferred->promise(); } - public function cancel(): void + private function cancel(): void { if ($this->process === null) { throw new ShouldNotHappenException('Cancelling process before running'); From e1e8302327e791ce191f25a0b37f21d30e1cf7d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:05:08 +0200 Subject: [PATCH 1364/3097] Solve some cases of dead code --- build/phpstan.neon | 1 - src/Analyser/UndefinedVariableException.php | 6 +++ src/Broker/ClassAutoloadingException.php | 47 --------------------- src/Broker/ClassNotFoundException.php | 7 +++ src/Broker/ConstantNotFoundException.php | 7 +++ src/Broker/FunctionNotFoundException.php | 7 +++ 6 files changed, 27 insertions(+), 48 deletions(-) delete mode 100644 src/Broker/ClassAutoloadingException.php diff --git a/build/phpstan.neon b/build/phpstan.neon index b285f56e127..bf0a4652acb 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -61,7 +61,6 @@ parameters: - 'PHPStan\Reflection\MissingPropertyFromReflectionException' - 'PHPStan\Reflection\MissingConstantFromReflectionException' - 'PHPStan\Type\CircularTypeAliasDefinitionException' - - 'PHPStan\Broker\ClassAutoloadingException' - 'LogicException' - 'Error' check: diff --git a/src/Analyser/UndefinedVariableException.php b/src/Analyser/UndefinedVariableException.php index 4755296c6bc..a6e805d69ab 100644 --- a/src/Analyser/UndefinedVariableException.php +++ b/src/Analyser/UndefinedVariableException.php @@ -5,6 +5,12 @@ use PHPStan\AnalysedCodeException; use function sprintf; +/** + * @api + * + * Unchecked exception thrown from `PHPStan\Analyser\Scope::getVariableType()` + * in case the user doesn't check `hasVariableType()` is not `no()`. + */ final class UndefinedVariableException extends AnalysedCodeException { diff --git a/src/Broker/ClassAutoloadingException.php b/src/Broker/ClassAutoloadingException.php deleted file mode 100644 index 5451987023d..00000000000 --- a/src/Broker/ClassAutoloadingException.php +++ /dev/null @@ -1,47 +0,0 @@ -getMessage(), - $functionName, - ), 0, $previous); - } else { - parent::__construct(sprintf( - 'Class %s not found.', - $functionName, - ), 0); - } - - $this->className = $functionName; - } - - public function getClassName(): string - { - return $this->className; - } - - public function getTip(): string - { - return 'Learn more at https://phpstan.org/user-guide/discovering-symbols'; - } - -} diff --git a/src/Broker/ClassNotFoundException.php b/src/Broker/ClassNotFoundException.php index 1276d663ff3..0cabab122c1 100644 --- a/src/Broker/ClassNotFoundException.php +++ b/src/Broker/ClassNotFoundException.php @@ -5,6 +5,13 @@ use PHPStan\AnalysedCodeException; use function sprintf; +/** + * @api + * + * Unchecked exception thrown from `ReflectionProvider` and other places + * in case the user does not check the existence of the class beforehand + * with `hasClass()` or similar. + */ final class ClassNotFoundException extends AnalysedCodeException { diff --git a/src/Broker/ConstantNotFoundException.php b/src/Broker/ConstantNotFoundException.php index 5d633de3808..41981f07d85 100644 --- a/src/Broker/ConstantNotFoundException.php +++ b/src/Broker/ConstantNotFoundException.php @@ -5,6 +5,13 @@ use PHPStan\AnalysedCodeException; use function sprintf; +/** + * @api + * + * Unchecked exception thrown from `ReflectionProvider` + * in case the user does not check the existence of the constant beforehand + * with `hasConstant()`. + */ final class ConstantNotFoundException extends AnalysedCodeException { diff --git a/src/Broker/FunctionNotFoundException.php b/src/Broker/FunctionNotFoundException.php index a313b60e2aa..9607608462a 100644 --- a/src/Broker/FunctionNotFoundException.php +++ b/src/Broker/FunctionNotFoundException.php @@ -5,6 +5,13 @@ use PHPStan\AnalysedCodeException; use function sprintf; +/** + * @api + * + * Unchecked exception thrown from `ReflectionProvider` + * in case the user does not check the existence of the function beforehand + * with `hasFunction()`. + */ final class FunctionNotFoundException extends AnalysedCodeException { From 222676e94aeb92e1b73e7545db6c0d68e003b77e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:09:25 +0200 Subject: [PATCH 1365/3097] AlwaysRememberedExpr - use getNativeExprType --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 16a9c15f053..856bb901705 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -843,7 +843,7 @@ private function resolveType(string $exprString, Expr $node): Type } if ($node instanceof AlwaysRememberedExpr) { - return $node->getExprType(); + return $this->nativeTypesPromoted ? $node->getNativeExprType() : $node->getExprType(); } if ($node instanceof Expr\BinaryOp\Smaller) { From d88e2fece595ca5190c1cf507f7b983bdd4695f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:10:57 +0200 Subject: [PATCH 1366/3097] Remove dead code --- src/Analyser/NodeScopeResolver.php | 8 ++++---- src/Node/PropertyHookStatementNode.php | 9 ++------- src/Node/VariableAssignNode.php | 6 ------ 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d979a0f24a7..8c070bb0400 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5404,7 +5404,7 @@ private function processAssignVar( $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); - $nodeCallback(new VariableAssignNode($var, $assignedExpr, $isAssignOp), $result->getScope()); + $nodeCallback(new VariableAssignNode($var, $assignedExpr), $result->getScope()); $scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr), TrinaryLogic::createYes()); foreach ($conditionalExpressions as $exprString => $holders) { $scope = $scope->addConditionalExpressions($exprString, $holders); @@ -5542,7 +5542,7 @@ private function processAssignVar( if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { - $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); + $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); } else { if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { @@ -5574,7 +5574,7 @@ private function processAssignVar( } } else { if ($var instanceof Variable) { - $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); + $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); } elseif ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) { @@ -5861,7 +5861,7 @@ static function (): void { } if ($var instanceof Variable && is_string($var->name)) { - $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); + $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); } else { if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { diff --git a/src/Node/PropertyHookStatementNode.php b/src/Node/PropertyHookStatementNode.php index 34bdbcfd319..1be9a3d3013 100644 --- a/src/Node/PropertyHookStatementNode.php +++ b/src/Node/PropertyHookStatementNode.php @@ -20,14 +20,9 @@ final class PropertyHookStatementNode extends Stmt implements VirtualNode { - public function __construct(private PropertyHook $propertyHook) + public function __construct(PropertyHook $propertyHook) { - parent::__construct($this->propertyHook->getAttributes()); - } - - public function getPropertyHook(): PropertyHook - { - return $this->propertyHook; + parent::__construct($propertyHook->getAttributes()); } /** diff --git a/src/Node/VariableAssignNode.php b/src/Node/VariableAssignNode.php index 695f59bf2ae..3ebcbce6202 100644 --- a/src/Node/VariableAssignNode.php +++ b/src/Node/VariableAssignNode.php @@ -11,7 +11,6 @@ final class VariableAssignNode extends NodeAbstract implements VirtualNode public function __construct( private Expr\Variable $variable, private Expr $assignedExpr, - private bool $assignOp, ) { parent::__construct($variable->getAttributes()); @@ -27,11 +26,6 @@ public function getAssignedExpr(): Expr return $this->assignedExpr; } - public function isAssignOp(): bool - { - return $this->assignOp; - } - public function getType(): string { return 'PHPStan_Node_VariableAssignNodeNode'; From 2ac0a171e66601ea3c1d80f7e4195a0f420d929c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:19:50 +0200 Subject: [PATCH 1367/3097] Remove unused method from interface --- src/Reflection/ResolvedFunctionVariant.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Reflection/ResolvedFunctionVariant.php b/src/Reflection/ResolvedFunctionVariant.php index 5b5cb6b4e65..92675f4f197 100644 --- a/src/Reflection/ResolvedFunctionVariant.php +++ b/src/Reflection/ResolvedFunctionVariant.php @@ -11,6 +11,4 @@ public function getOriginalParametersAcceptor(): ParametersAcceptor; public function getReturnTypeWithUnresolvableTemplateTypes(): Type; - public function getPhpDocReturnTypeWithUnresolvableTemplateTypes(): Type; - } From aaa1424c0f90fffc425c397219b26104a99e9231 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:20:54 +0200 Subject: [PATCH 1368/3097] Remove unused method from PathNotFoundException --- src/File/PathNotFoundException.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/File/PathNotFoundException.php b/src/File/PathNotFoundException.php index b58cda6e15b..9dc613ccb70 100644 --- a/src/File/PathNotFoundException.php +++ b/src/File/PathNotFoundException.php @@ -8,14 +8,9 @@ final class PathNotFoundException extends Exception { - public function __construct(private string $path) + public function __construct(string $path) { parent::__construct(sprintf('Path %s does not exist', $path)); } - public function getPath(): string - { - return $this->path; - } - } From e3c76ade59ac2753d9ff073a7260d228bc424c50 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:30:12 +0200 Subject: [PATCH 1369/3097] Remove getPhpDocReturnTypeWithUnresolvableTemplateTypes from ResolvedFunctionVariantWithCallable --- src/Reflection/ResolvedFunctionVariantWithCallable.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Reflection/ResolvedFunctionVariantWithCallable.php b/src/Reflection/ResolvedFunctionVariantWithCallable.php index 7dbd3824057..59c9cf1bec0 100644 --- a/src/Reflection/ResolvedFunctionVariantWithCallable.php +++ b/src/Reflection/ResolvedFunctionVariantWithCallable.php @@ -67,11 +67,6 @@ public function getReturnTypeWithUnresolvableTemplateTypes(): Type return $this->parametersAcceptor->getReturnTypeWithUnresolvableTemplateTypes(); } - public function getPhpDocReturnTypeWithUnresolvableTemplateTypes(): Type - { - return $this->parametersAcceptor->getPhpDocReturnTypeWithUnresolvableTemplateTypes(); - } - public function getReturnType(): Type { return $this->parametersAcceptor->getReturnType(); From 2f66ec60f9d2a9ff9d8a87a914f31dd121a9ed18 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 14:33:55 +0200 Subject: [PATCH 1370/3097] Remove more dead code --- src/Reflection/MethodPrototypeReflection.php | 6 ------ src/Reflection/Native/NativeMethodReflection.php | 1 - src/Reflection/Php/PhpMethodReflection.php | 1 - src/Type/GeneralizePrecision.php | 5 ----- src/Type/SubtractableType.php | 2 -- 5 files changed, 15 deletions(-) diff --git a/src/Reflection/MethodPrototypeReflection.php b/src/Reflection/MethodPrototypeReflection.php index c92c6c5b748..b47b4930cb2 100644 --- a/src/Reflection/MethodPrototypeReflection.php +++ b/src/Reflection/MethodPrototypeReflection.php @@ -17,7 +17,6 @@ public function __construct( private bool $isPrivate, private bool $isPublic, private bool $isAbstract, - private bool $isFinal, private bool $isInternal, private array $variants, private ?Type $tentativeReturnType, @@ -55,11 +54,6 @@ public function isAbstract(): bool return $this->isAbstract; } - public function isFinal(): bool - { - return $this->isFinal; - } - public function isInternal(): bool { return $this->isInternal; diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 34ec4e3e51f..76be89b4932 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -94,7 +94,6 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod->isPrivate(), $prototypeMethod->isPublic(), $prototypeMethod->isAbstract(), - $prototypeMethod->isFinal(), $prototypeMethod->isInternal(), $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants(), $tentativeReturnType, diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 432fd693504..f032585242c 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -135,7 +135,6 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod->isPrivate(), $prototypeMethod->isPublic(), $prototypeMethod->isAbstract(), - $prototypeMethod->isFinal(), $prototypeMethod->isInternal(), $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants(), $tentativeReturnType, diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php index d69e030e2de..3f4d3be6295 100644 --- a/src/Type/GeneralizePrecision.php +++ b/src/Type/GeneralizePrecision.php @@ -40,11 +40,6 @@ public static function templateArgument(): self return self::create(self::TEMPLATE_ARGUMENT); } - public function isLessSpecific(): bool - { - return $this->value === self::LESS_SPECIFIC; - } - public function isMoreSpecific(): bool { return $this->value === self::MORE_SPECIFIC; diff --git a/src/Type/SubtractableType.php b/src/Type/SubtractableType.php index c40188af2d8..931fe0bc4c6 100644 --- a/src/Type/SubtractableType.php +++ b/src/Type/SubtractableType.php @@ -5,8 +5,6 @@ interface SubtractableType extends Type { - public function subtract(Type $type): Type; - public function getTypeWithoutSubtractedType(): Type; public function changeSubtractedType(?Type $subtractedType): Type; From a5f7c060c8a091da391263decdef8025a3202d82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 16 May 2025 13:59:03 +0200 Subject: [PATCH 1371/3097] Bleeding edge - report `new static()` in static method of abstract class --- conf/bleedingEdge.neon | 1 + conf/config.level0.neon | 5 ++ conf/config.neon | 1 + conf/parametersSchema.neon | 1 + ...wStaticInAbstractClassStaticMethodRule.php | 64 +++++++++++++++++++ ...ticInAbstractClassStaticMethodRuleTest.php | 30 +++++++++ ...static-in-abstract-class-static-method.php | 33 ++++++++++ 7 files changed, 135 insertions(+) create mode 100644 src/Rules/Classes/NewStaticInAbstractClassStaticMethodRule.php create mode 100644 tests/PHPStan/Rules/Classes/NewStaticInAbstractClassStaticMethodRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/new-static-in-abstract-class-static-method.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 22487e357c1..81ea6c216ba 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -6,3 +6,4 @@ parameters: stricterFunctionMap: true reportPreciseLineForUnusedFunctionParameter: true internalTag: true + newStaticInAbstractClassStaticMethod: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 24b19d99bfa..6da8d4f26e6 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -110,6 +110,8 @@ conditionalTags: phpstan.restrictedClassNameUsageExtension: %featureToggles.internalTag% PHPStan\Rules\InternalTag\RestrictedInternalFunctionUsageExtension: phpstan.restrictedFunctionUsageExtension: %featureToggles.internalTag% + PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule: + phpstan.rules.rule: %featureToggles.newStaticInAbstractClassStaticMethod% services: - @@ -178,6 +180,9 @@ services: checkFunctionNameCase: %checkFunctionNameCase% discoveringSymbolsTip: %tips.discoveringSymbols% + - + class: PHPStan\Rules\Classes\NewStaticInAbstractClassStaticMethodRule + - class: PHPStan\Rules\Constants\OverridingConstantRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 26703d01efd..5c29c4836a6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -27,6 +27,7 @@ parameters: stricterFunctionMap: false reportPreciseLineForUnusedFunctionParameter: false internalTag: false + newStaticInAbstractClassStaticMethod: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d18df776e35..610d6cb2e26 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -33,6 +33,7 @@ parametersSchema: stricterFunctionMap: bool() reportPreciseLineForUnusedFunctionParameter: bool() internalTag: bool() + newStaticInAbstractClassStaticMethod: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/Classes/NewStaticInAbstractClassStaticMethodRule.php b/src/Rules/Classes/NewStaticInAbstractClassStaticMethodRule.php new file mode 100644 index 00000000000..0b2842648b2 --- /dev/null +++ b/src/Rules/Classes/NewStaticInAbstractClassStaticMethodRule.php @@ -0,0 +1,64 @@ + + */ +final class NewStaticInAbstractClassStaticMethodRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\New_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->class instanceof Node\Name) { + return []; + } + + if (!$scope->isInClass()) { + return []; + } + + if (strtolower($node->class->toString()) !== 'static') { + return []; + } + + $classReflection = $scope->getClassReflection(); + if (!$classReflection->isAbstract()) { + return []; + } + + $inMethod = $scope->getFunction(); + if (!$inMethod instanceof PhpMethodFromParserNodeReflection) { + return []; + } + + if (!$inMethod->isStatic()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Unsafe usage of new static() in abstract class %s in static method %s().', + $classReflection->getDisplayName(), + $inMethod->getName(), + )) + ->identifier('new.staticInAbstractClassStaticMethod') + ->tip(sprintf('Direct call to %s::%s() would crash because an abstract class cannot be instantiated.', $classReflection->getName(), $inMethod->getName())) + ->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Classes/NewStaticInAbstractClassStaticMethodRuleTest.php b/tests/PHPStan/Rules/Classes/NewStaticInAbstractClassStaticMethodRuleTest.php new file mode 100644 index 00000000000..2212bafe421 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/NewStaticInAbstractClassStaticMethodRuleTest.php @@ -0,0 +1,30 @@ + + */ +class NewStaticInAbstractClassStaticMethodRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(NewStaticInAbstractClassStaticMethodRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/new-static-in-abstract-class-static-method.php'], [ + [ + 'Unsafe usage of new static() in abstract class NewStaticInAbstractClassStaticMethod\Bar in static method staticDoFoo().', + 30, + 'Direct call to NewStaticInAbstractClassStaticMethod\Bar::staticDoFoo() would crash because an abstract class cannot be instantiated.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/new-static-in-abstract-class-static-method.php b/tests/PHPStan/Rules/Classes/data/new-static-in-abstract-class-static-method.php new file mode 100644 index 00000000000..bbcfea8ef80 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/new-static-in-abstract-class-static-method.php @@ -0,0 +1,33 @@ + Date: Fri, 16 May 2025 12:20:44 +0200 Subject: [PATCH 1372/3097] Install & configure shipmonk/dead-code-detector --- build/phpstan.neon | 11 +++++++ composer.json | 1 + composer.lock | 80 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index bf0a4652acb..d4a1986e42c 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -4,6 +4,7 @@ includes: - ../vendor/phpstan/phpstan-phpunit/extension.neon - ../vendor/phpstan/phpstan-phpunit/rules.neon - ../vendor/phpstan/phpstan-strict-rules/rules.neon + - ../vendor/shipmonk/dead-code-detector/rules.neon - ../conf/bleedingEdge.neon - ../phpstan-baseline.neon - ../phpstan-baseline.php @@ -20,6 +21,10 @@ parameters: - ../tests/phpstan-bootstrap.php cache: nodesByStringCountMax: 128 + shipmonkDeadCode: + usageExcluders: + tests: + enabled: true checkUninitializedProperties: true checkMissingCallableSignature: true excludePaths: @@ -71,6 +76,12 @@ parameters: - '#should be contravariant with parameter \$node \(PhpParser\\Node\) of method PHPStan\\Rules\\Rule::processNode\(\)$#' - '#Variable property access on PhpParser\\Node#' - '#Test::data[a-zA-Z0-9_]+\(\) return type has no value type specified in iterable type#' + - + identifier: shipmonk.deadMethod + message: '#^Unused .*?Factory::create#' # likely used in DIC + - + identifier: shipmonk.deadMethod + path: ../src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php - message: '#Fetching class constant class of deprecated class DeprecatedAnnotations\\DeprecatedFoo.#' path: ../tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php diff --git a/composer.json b/composer.json index d10f4174915..4e6a2ad0b27 100644 --- a/composer.json +++ b/composer.json @@ -62,6 +62,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "shipmonk/composer-dependency-analyser": "^1.5", + "shipmonk/dead-code-detector": "dev-api-phpdoc", "shipmonk/name-collision-detector": "^2.0" }, "config": { diff --git a/composer.lock b/composer.lock index b893805abac..f4ddb42e9ab 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "baacfd9f6f313439fbc41174b0ac33c8", + "content-hash": "03d7ba380a92b25500e9631a644b06d2", "packages": [ { "name": "clue/ndjson-react", @@ -6338,6 +6338,81 @@ }, "time": "2024-08-08T08:12:32+00:00" }, + { + "name": "shipmonk/dead-code-detector", + "version": "dev-api-phpdoc", + "source": { + "type": "git", + "url": "https://github.com/shipmonk-rnd/dead-code-detector.git", + "reference": "c4300075b3d75fda71c818f7e76933bcf1e267dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/c4300075b3d75fda71c818f7e76933bcf1e267dd", + "reference": "c4300075b3d75fda71c818f7e76933bcf1e267dd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.9" + }, + "require-dev": { + "composer-runtime-api": "^2.0", + "composer/semver": "^3.4", + "doctrine/orm": "^2.19 || ^3.0", + "editorconfig-checker/editorconfig-checker": "^10.6.0", + "ergebnis/composer-normalize": "^2.45.0", + "nette/application": "^3.1", + "nette/component-model": "^3.0", + "nette/utils": "^3.0 || ^4.0", + "nikic/php-parser": "^5.4.0", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpstan/phpstan-symfony": "^2.0.2", + "phpunit/phpunit": "^9.6.22", + "shipmonk/composer-dependency-analyser": "^1.8.2", + "shipmonk/name-collision-detector": "^2.1.1", + "shipmonk/phpstan-rules": "^4.1.0", + "slevomat/coding-standard": "^8.16.0", + "symfony/contracts": "^2.5 || ^3.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/routing": "^5.4 || ^6.0 || ^7.0", + "symfony/validator": "^5.4 || ^6.0 || ^7.0", + "twig/twig": "^3.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "ShipMonk\\PHPStan\\DeadCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.", + "keywords": [ + "PHPStan", + "dead code", + "static analysis", + "unused code" + ], + "support": { + "issues": "https://github.com/shipmonk-rnd/dead-code-detector/issues", + "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/api-phpdoc" + }, + "time": "2025-05-16T10:10:34+00:00" + }, { "name": "shipmonk/name-collision-detector", "version": "2.1.1", @@ -6450,7 +6525,8 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20 + "jetbrains/phpstorm-stubs": 20, + "shipmonk/dead-code-detector": 20 }, "prefer-stable": true, "prefer-lowest": false, From a9c36b72a62e693069092482619a9deb955fae05 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 12:27:46 +0200 Subject: [PATCH 1373/3097] Drop unused SymfonyStyle::getSymfonyStyle --- src/Command/Symfony/SymfonyStyle.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Command/Symfony/SymfonyStyle.php b/src/Command/Symfony/SymfonyStyle.php index e8782a5f59e..4008ec0b2ae 100644 --- a/src/Command/Symfony/SymfonyStyle.php +++ b/src/Command/Symfony/SymfonyStyle.php @@ -15,11 +15,6 @@ public function __construct(private StyleInterface $symfonyStyle) { } - public function getSymfonyStyle(): StyleInterface - { - return $this->symfonyStyle; - } - public function title(string $message): void { $this->symfonyStyle->title($message); From d32e25c837050b34b9f86b96a8e4cc1f44f83775 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 12:30:41 +0200 Subject: [PATCH 1374/3097] Drop unused NeonAdapter::dump --- src/DependencyInjection/NeonAdapter.php | 54 ------------------------- 1 file changed, 54 deletions(-) diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index 0cd90db6452..a6d189a7ba1 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -4,7 +4,6 @@ use Nette\DI\Config\Adapter; use Nette\DI\Config\Helpers; -use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Nette\DI\InvalidConfigurationException; use Nette\Neon\Entity; @@ -14,7 +13,6 @@ use PHPStan\File\FileHelper; use PHPStan\File\FileReader; use function array_values; -use function array_walk_recursive; use function count; use function dirname; use function implode; @@ -155,58 +153,6 @@ public function process(array $arr, string $fileKey, string $file): array return $res; } - /** - * @param mixed[] $data - */ - public function dump(array $data): string - { - array_walk_recursive( - $data, - static function (&$val): void { - if (!($val instanceof Statement)) { - return; - } - - $val = self::statementToEntity($val); - }, - ); - return "# generated by Nette\n\n" . Neon::encode($data, Neon::BLOCK); - } - - private static function statementToEntity(Statement $val): Entity - { - array_walk_recursive( - $val->arguments, - static function (&$val): void { - if ($val instanceof Statement) { - $val = self::statementToEntity($val); - } elseif ($val instanceof Reference) { - $val = '@' . $val->getValue(); - } - }, - ); - - $entity = $val->getEntity(); - if ($entity instanceof Reference) { - $entity = '@' . $entity->getValue(); - } elseif (is_array($entity)) { - if ($entity[0] instanceof Statement) { - return new Entity( - Neon::CHAIN, - [ - self::statementToEntity($entity[0]), - new Entity('::' . $entity[1], $val->arguments), - ], - ); - } elseif ($entity[0] instanceof Reference) { - $entity = '@' . $entity[0]->getValue() . '::' . $entity[1]; - } elseif (is_string($entity[0])) { - $entity = $entity[0] . '::' . $entity[1]; - } - } - return new Entity($entity, $val->arguments); - } - private function createFileHelperByFile(string $file): FileHelper { $dir = dirname($file); From 0d46d4f396700731eb90ea2ace86f25098a1684e Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 12:32:24 +0200 Subject: [PATCH 1375/3097] Drop unused FileMonitor::getTotalFilesCount --- src/File/FileMonitor.php | 2 -- src/File/FileMonitorResult.php | 6 ------ 2 files changed, 8 deletions(-) diff --git a/src/File/FileMonitor.php b/src/File/FileMonitor.php index 6fd0eaf8ef9..3f8b9f0d3ce 100644 --- a/src/File/FileMonitor.php +++ b/src/File/FileMonitor.php @@ -5,7 +5,6 @@ use PHPStan\ShouldNotHappenException; use function array_key_exists; use function array_keys; -use function count; use function sha1_file; final class FileMonitor @@ -75,7 +74,6 @@ public function getChanges(): FileMonitorResult $newFiles, $changedFiles, $deletedFiles, - count($fileHashes), ); } diff --git a/src/File/FileMonitorResult.php b/src/File/FileMonitorResult.php index 8c7e405dc09..f76ae9dde43 100644 --- a/src/File/FileMonitorResult.php +++ b/src/File/FileMonitorResult.php @@ -16,7 +16,6 @@ public function __construct( private array $newFiles, private array $changedFiles, private array $deletedFiles, - private int $totalFilesCount, ) { } @@ -36,9 +35,4 @@ public function hasAnyChanges(): bool || count($this->deletedFiles) > 0; } - public function getTotalFilesCount(): int - { - return $this->totalFilesCount; - } - } From d0dbb2c0492ec203b1eb1ecb90367ae9553af558 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:36:38 +0200 Subject: [PATCH 1376/3097] Drop unused SetterReflectionProviderProvider --- build/baseline-7.4.neon | 4 ---- .../SetterReflectionProviderProvider.php | 22 ------------------- 2 files changed, 26 deletions(-) delete mode 100644 src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php diff --git a/build/baseline-7.4.neon b/build/baseline-7.4.neon index 82e6b89e0c0..b28b9f9f5de 100644 --- a/build/baseline-7.4.neon +++ b/build/baseline-7.4.neon @@ -77,7 +77,3 @@ parameters: message: "#^Class PHPStan\\\\Reflection\\\\BetterReflection\\\\SourceLocator\\\\CachingVisitor has an uninitialized property \\$constantNodes\\. Give it default value or assign it in the constructor\\.$#" count: 1 path: ../src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php - - - message: "#^Class PHPStan\\\\Reflection\\\\ReflectionProvider\\\\SetterReflectionProviderProvider has an uninitialized property \\$reflectionProvider\\. Give it default value or assign it in the constructor\\.$#" - count: 1 - path: ../src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php diff --git a/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php b/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php deleted file mode 100644 index 2ae0d7c9ffd..00000000000 --- a/src/Reflection/ReflectionProvider/SetterReflectionProviderProvider.php +++ /dev/null @@ -1,22 +0,0 @@ -reflectionProvider = $reflectionProvider; - } - - public function getReflectionProvider(): ReflectionProvider - { - return $this->reflectionProvider; - } - -} From 5c352cfc6e47021cf55e48851952b8a2df3a1e71 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:37:06 +0200 Subject: [PATCH 1377/3097] Drop dead dataProvider in ExistingClassesInTypehintsRuleTest --- .../Functions/ExistingClassesInTypehintsRuleTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index f03764a657e..a1a6beb6d0c 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -416,16 +416,6 @@ public function testIntersectionTypes(int $phpVersion, array $errors): void $this->analyse([__DIR__ . '/data/intersection-types.php'], $errors); } - public function dataTrueTypes(): array - { - return [ - [ - 80200, - [], - ], - ]; - } - public function testTrueTypehint(): void { if (PHP_VERSION_ID >= 80200) { From 671ab0dcf1798299e30f24956b22d2a98ca254d1 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:37:33 +0200 Subject: [PATCH 1378/3097] Keep unused BleedingEdgeToggle --- src/DependencyInjection/BleedingEdgeToggle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/BleedingEdgeToggle.php b/src/DependencyInjection/BleedingEdgeToggle.php index 29170f7fe9a..98e9a52d41f 100644 --- a/src/DependencyInjection/BleedingEdgeToggle.php +++ b/src/DependencyInjection/BleedingEdgeToggle.php @@ -7,7 +7,7 @@ final class BleedingEdgeToggle private static bool $bleedingEdge = false; - public static function isBleedingEdge(): bool + public static function isBleedingEdge(): bool // @phpstan-ignore shipmonk.deadMethod (kept for future use) { return self::$bleedingEdge; } From 6e27754e00a862cf9d2687b0f4b3017ab141197d Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:38:07 +0200 Subject: [PATCH 1379/3097] Ignore fixture issues in tests --- build/phpstan.neon | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index d4a1986e42c..51842a2530a 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -21,10 +21,6 @@ parameters: - ../tests/phpstan-bootstrap.php cache: nodesByStringCountMax: 128 - shipmonkDeadCode: - usageExcluders: - tests: - enabled: true checkUninitializedProperties: true checkMissingCallableSignature: true excludePaths: @@ -79,6 +75,15 @@ parameters: - identifier: shipmonk.deadMethod message: '#^Unused .*?Factory::create#' # likely used in DIC + - + identifier: shipmonk.deadMethod + paths: + - ../tests/PHPStan/Tests + - ../tests/e2e + - + identifier: shipmonk.deadConstant + paths: + - ../tests/PHPStan/Fixture - identifier: shipmonk.deadMethod path: ../src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php From e80e894a7cfbf0ec5cbc1934befbe899b055650a Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:46:40 +0200 Subject: [PATCH 1380/3097] Drop unused ResolvedPropertyReflection::getDeclaringTrait --- src/Reflection/ResolvedPropertyReflection.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index 8b54c0785a1..7646de8651b 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection; -use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -40,15 +39,6 @@ public function getDeclaringClass(): ClassReflection return $this->reflection->getDeclaringClass(); } - public function getDeclaringTrait(): ?ClassReflection - { - if ($this->reflection instanceof PhpPropertyReflection) { - return $this->reflection->getDeclaringTrait(); - } - - return null; - } - public function isStatic(): bool { return $this->reflection->isStatic(); From 4e00b876e3ab7a844487419993595524d9a59a1b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 13:54:16 +0200 Subject: [PATCH 1381/3097] Drop unused ProcessPromise::getName --- src/Command/FixerApplication.php | 2 +- src/Process/ProcessPromise.php | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 1a9e64d7feb..f6c62244c0e 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -465,7 +465,7 @@ private function analyse( }); }); - $process = new ProcessPromise($loop, 'changedFileAnalysis', ProcessHelper::getWorkerCommand( + $process = new ProcessPromise($loop, ProcessHelper::getWorkerCommand( $mainScript, 'fixer:worker', $projectConfigFile, diff --git a/src/Process/ProcessPromise.php b/src/Process/ProcessPromise.php index 5a526b771e8..afc50f087de 100644 --- a/src/Process/ProcessPromise.php +++ b/src/Process/ProcessPromise.php @@ -22,18 +22,13 @@ final class ProcessPromise private bool $canceled = false; - public function __construct(private LoopInterface $loop, private string $name, private string $command) + public function __construct(private LoopInterface $loop, private string $command) { $this->deferred = new Deferred(function (): void { $this->cancel(); }); } - public function getName(): string - { - return $this->name; - } - /** * @return PromiseInterface */ From ee9ec927f8ac6eba64e5bb82027186c030a0b406 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 14:12:38 +0200 Subject: [PATCH 1382/3097] Drop unused PhpDocBlock::isExplicit --- src/PhpDoc/PhpDocBlock.php | 22 ---------------------- src/PhpDoc/PhpDocInheritanceResolver.php | 3 --- 2 files changed, 25 deletions(-) diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 8036cd78ee7..434770b42d8 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -27,7 +27,6 @@ private function __construct( private ?string $file, private ClassReflection $classReflection, private ?string $trait, - private bool $explicit, private array $parameterNameMapping, private array $parents, ) @@ -54,11 +53,6 @@ public function getTrait(): ?string return $this->trait; } - public function isExplicit(): bool - { - return $this->explicit; - } - /** * @return array */ @@ -115,7 +109,6 @@ public static function resolvePhpDocBlockForProperty( ?string $trait, string $propertyName, ?string $file, - ?bool $explicit, ): self { $docBlocksFromParents = []; @@ -123,7 +116,6 @@ public static function resolvePhpDocBlockForProperty( $oneResult = self::resolvePropertyPhpDocBlockFromClass( $parentReflection, $propertyName, - $explicit ?? $docComment !== null, ); if ($oneResult === null) { // Null if it is private or from a wrong trait. @@ -138,7 +130,6 @@ public static function resolvePhpDocBlockForProperty( $file, $classReflection, $trait, - $explicit ?? true, [], $docBlocksFromParents, ); @@ -149,7 +140,6 @@ public static function resolvePhpDocBlockForConstant( ClassReflection $classReflection, string $constantName, ?string $file, - ?bool $explicit, ): self { $docBlocksFromParents = []; @@ -157,7 +147,6 @@ public static function resolvePhpDocBlockForConstant( $oneResult = self::resolveConstantPhpDocBlockFromClass( $parentReflection, $constantName, - $explicit ?? $docComment !== null, ); if ($oneResult === null) { // Null if it is private or from a wrong trait. @@ -172,7 +161,6 @@ public static function resolvePhpDocBlockForConstant( $file, $classReflection, null, - $explicit ?? true, [], $docBlocksFromParents, ); @@ -188,7 +176,6 @@ public static function resolvePhpDocBlockForMethod( ?string $trait, string $methodName, ?string $file, - ?bool $explicit, array $originalPositionalParameterNames, array $newPositionalParameterNames, ): self @@ -198,7 +185,6 @@ public static function resolvePhpDocBlockForMethod( $oneResult = self::resolveMethodPhpDocBlockFromClass( $parentReflection, $methodName, - $explicit ?? $docComment !== null, $newPositionalParameterNames, ); @@ -234,7 +220,6 @@ public static function resolvePhpDocBlockForMethod( $classReflection->getFileName(), $classReflection, $traitReflection->getName(), - $explicit ?? $traitMethod->getDocComment() !== null, self::remapParameterNames($newPositionalParameterNames, $positionalMethodParameterNames), [], ); @@ -245,7 +230,6 @@ public static function resolvePhpDocBlockForMethod( $file, $classReflection, $trait, - $explicit ?? true, self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), $docBlocksFromParents, ); @@ -294,7 +278,6 @@ private static function getParentReflections(ClassReflection $classReflection): private static function resolveConstantPhpDocBlockFromClass( ClassReflection $classReflection, string $name, - bool $explicit, ): ?self { if ($classReflection->hasConstant($name)) { @@ -310,7 +293,6 @@ private static function resolveConstantPhpDocBlockFromClass( $classReflection, $name, $classReflection->getFileName(), - $explicit, ); } @@ -320,7 +302,6 @@ private static function resolveConstantPhpDocBlockFromClass( private static function resolvePropertyPhpDocBlockFromClass( ClassReflection $classReflection, string $name, - bool $explicit, ): ?self { if ($classReflection->hasNativeProperty($name)) { @@ -342,7 +323,6 @@ private static function resolvePropertyPhpDocBlockFromClass( $trait, $name, $classReflection->getFileName(), - $explicit, ); } @@ -355,7 +335,6 @@ private static function resolvePropertyPhpDocBlockFromClass( private static function resolveMethodPhpDocBlockFromClass( ClassReflection $classReflection, string $name, - bool $explicit, array $positionalParameterNames, ): ?self { @@ -396,7 +375,6 @@ private static function resolveMethodPhpDocBlockFromClass( $trait, $name, $classReflection->getFileName(), - $explicit, $positionalParameterNames, $positionalMethodParameterNames, ); diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 5b6aaacc3bd..b240a793222 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -32,7 +32,6 @@ public function resolvePhpDocForProperty( null, $propertyName, $classReflectionFileName, - null, ); return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null, $propertyName, null); @@ -50,7 +49,6 @@ public function resolvePhpDocForConstant( $classReflection, $constantName, $classReflectionFileName, - null, ); return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, null, null, null, $constantName); @@ -74,7 +72,6 @@ public function resolvePhpDocForMethod( $declaringTraitName, $methodName, $fileName, - null, $positionalParameterNames, $positionalParameterNames, ); From 3c3a22bcab9e25125c282f9652c486c749c32fa2 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 14:14:31 +0200 Subject: [PATCH 1383/3097] Drop dead TemplateTypeTrait::shouldGeneralizeInferredType (overwritten in all children) --- src/Type/Generic/TemplateTypeTrait.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 52c13a96805..a35451b64fe 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -297,11 +297,6 @@ public function getStrategy(): TemplateTypeStrategy return $this->strategy; } - protected function shouldGeneralizeInferredType(): bool - { - return true; - } - public function traverse(callable $cb): Type { $bound = $cb($this->getBound()); From 5c6d9434c15bc52555a76aa3cbe913a03cb932e2 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 14:25:37 +0200 Subject: [PATCH 1384/3097] SimultaneousTypeTraverser to be api --- src/Type/SimultaneousTypeTraverser.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Type/SimultaneousTypeTraverser.php b/src/Type/SimultaneousTypeTraverser.php index 046727de880..d9a3d697830 100644 --- a/src/Type/SimultaneousTypeTraverser.php +++ b/src/Type/SimultaneousTypeTraverser.php @@ -2,6 +2,9 @@ namespace PHPStan\Type; +/** + * @api + */ final class SimultaneousTypeTraverser { From dfcbdc47fe2ee119deff20cc3533718f0a4a3478 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 14:47:00 +0200 Subject: [PATCH 1385/3097] Dead constants in tests/PHPStan/Fixture are on enums, thus not working on PHP8- --- build/phpstan.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/build/phpstan.neon b/build/phpstan.neon index 51842a2530a..3d099857e2f 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -84,6 +84,7 @@ parameters: identifier: shipmonk.deadConstant paths: - ../tests/PHPStan/Fixture + reportUnmatched: false # constants on enums, not reported on PHP8- - identifier: shipmonk.deadMethod path: ../src/Reflection/BetterReflection/SourceLocator/FileReadTrapStreamWrapper.php From 45a377e01902035ca7fa6ac16f8a29a9900f2d11 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 16 May 2025 15:04:16 +0200 Subject: [PATCH 1386/3097] Use stable version of Dead Code Detector --- composer.json | 2 +- composer.lock | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 4e6a2ad0b27..11a1ee76b79 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", "shipmonk/composer-dependency-analyser": "^1.5", - "shipmonk/dead-code-detector": "dev-api-phpdoc", + "shipmonk/dead-code-detector": "^0.12.0", "shipmonk/name-collision-detector": "^2.0" }, "config": { diff --git a/composer.lock b/composer.lock index f4ddb42e9ab..59365ba683c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "03d7ba380a92b25500e9631a644b06d2", + "content-hash": "3048aa1c538e9ea0ccfcddbb6a95c705", "packages": [ { "name": "clue/ndjson-react", @@ -6340,16 +6340,16 @@ }, { "name": "shipmonk/dead-code-detector", - "version": "dev-api-phpdoc", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/shipmonk-rnd/dead-code-detector.git", - "reference": "c4300075b3d75fda71c818f7e76933bcf1e267dd" + "reference": "1f0c70ec4e9868c785f6505592dfb01ef53af2ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/c4300075b3d75fda71c818f7e76933bcf1e267dd", - "reference": "c4300075b3d75fda71c818f7e76933bcf1e267dd", + "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/1f0c70ec4e9868c785f6505592dfb01ef53af2ca", + "reference": "1f0c70ec4e9868c785f6505592dfb01ef53af2ca", "shasum": "" }, "require": { @@ -6409,9 +6409,9 @@ ], "support": { "issues": "https://github.com/shipmonk-rnd/dead-code-detector/issues", - "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/api-phpdoc" + "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/0.12.0" }, - "time": "2025-05-16T10:10:34+00:00" + "time": "2025-05-16T13:02:10+00:00" }, { "name": "shipmonk/name-collision-detector", @@ -6525,8 +6525,7 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20, - "shipmonk/dead-code-detector": 20 + "jetbrains/phpstorm-stubs": 20 }, "prefer-stable": true, "prefer-lowest": false, From f75fa8dfbf9631f54c67d0080678b73819d15845 Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Tue, 29 Apr 2025 17:09:45 -0400 Subject: [PATCH 1387/3097] Update-libxml_get_errors()-stub --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 2f8761856cb..b716fae1999 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5926,7 +5926,7 @@ 'levenshtein\'1' => ['int', 'str1'=>'string', 'str2'=>'string', 'cost_ins'=>'int', 'cost_rep'=>'int', 'cost_del'=>'int'], 'libxml_clear_errors' => ['void'], 'libxml_disable_entity_loader' => ['bool', 'disable='=>'bool'], -'libxml_get_errors' => ['array'], +'libxml_get_errors' => ['list'], 'libxml_get_last_error' => ['LibXMLError|false'], 'libxml_set_external_entity_loader' => ['bool', 'resolver_function'=>'callable'], 'libxml_set_streams_context' => ['void', 'streams_context'=>'resource'], From 0536d1148de2edf065dc10a95d9011813abd98bd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 14:10:58 +0200 Subject: [PATCH 1388/3097] Fix result cache getting stale when editing files mid-analysis --- src/Analyser/ResultCache/ResultCache.php | 10 ++++++ .../ResultCache/ResultCacheManager.php | 35 +++++++++++-------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index f409f9c0627..2f33cf2433e 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -24,6 +24,7 @@ final class ResultCache * @param array> $dependencies * @param array> $exportedNodes * @param array $projectExtensionFiles + * @param array $currentFileHashes */ public function __construct( private array $filesToAnalyse, @@ -38,6 +39,7 @@ public function __construct( private array $dependencies, private array $exportedNodes, private array $projectExtensionFiles, + private array $currentFileHashes, ) { } @@ -132,4 +134,12 @@ public function getProjectExtensionFiles(): array return $this->projectExtensionFiles; } + /** + * @return array + */ + public function getCurrentFileHashes(): array + { + return $this->currentFileHashes; + } + } diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 567b61f798d..099cb19e3a4 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -91,17 +91,21 @@ public function __construct( public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?array $projectConfigArray, Output $output): ResultCache { $startTime = microtime(true); + $currentFileHashes = []; + foreach ($allAnalysedFiles as $analysedFile) { + $currentFileHashes[$analysedFile] = $this->getFileHash($analysedFile); + } if ($debug) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because of debug mode.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); } if ($onlyFiles) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); } $cacheFilePath = $this->cacheFilePath; @@ -109,7 +113,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because the cache file does not exist.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); } try { @@ -121,7 +125,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? @unlink($cacheFilePath); - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); } if (!is_array($data)) { @@ -130,7 +134,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $output->writeLineFormatted('Result cache not used because the cache file is corrupted.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); } $meta = $this->getMeta($allAnalysedFiles, $projectConfigArray); @@ -139,15 +143,16 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $diffs = $this->getMetaKeyDifferences($data['meta'], $meta); $output->writeLineFormatted('Result cache not used because the metadata do not match: ' . implode(', ', $diffs)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); } if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because it\'s more than 7 days since last full analysis.'); } + // run full analysis if the result cache is older than 7 days - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); } /** @@ -162,7 +167,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); } if ($this->getFileHash($extensionFile) === $fileHash) { @@ -173,7 +178,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], []); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); } $invertedDependencies = $data['dependencies']; @@ -234,7 +239,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $cachedFileHash = $analysedFileData['fileHash']; $dependentFiles = $analysedFileData['dependentFiles']; $invertedDependenciesToReturn[$analysedFile] = $dependentFiles; - $currentFileHash = $this->getFileHash($analysedFile); + $currentFileHash = $currentFileHashes[$analysedFile]; if ($cachedFileHash === $currentFileHash) { continue; @@ -301,7 +306,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? )); } - return new ResultCache($filesToAnalyse, false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredLinesToIgnore, $filteredUnmatchedLineIgnores, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles']); + return new ResultCache($filesToAnalyse, false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredLinesToIgnore, $filteredUnmatchedLineIgnores, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles'], $currentFileHashes); } /** @@ -445,7 +450,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } } - $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $meta); + $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $resultCache->getCurrentFileHashes(), $meta); if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache is saved.'); @@ -702,6 +707,7 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres * @param array> $dependencies * @param array> $exportedNodes * @param array $projectExtensionFiles + * @param array $currentFileHashes * @param mixed[] $meta */ private function save( @@ -714,6 +720,7 @@ private function save( array $dependencies, array $exportedNodes, array $projectExtensionFiles, + array $currentFileHashes, array $meta, ): void { @@ -723,7 +730,7 @@ private function save( foreach ($fileDependencies as $fileDep) { if (!array_key_exists($fileDep, $invertedDependencies)) { $invertedDependencies[$fileDep] = [ - 'fileHash' => $this->getFileHash($fileDep), + 'fileHash' => $currentFileHashes[$fileDep] ?? $this->getFileHash($fileDep), 'dependentFiles' => [], ]; unset($filesNoOneIsDependingOn[$fileDep]); @@ -742,7 +749,7 @@ private function save( } $invertedDependencies[$file] = [ - 'fileHash' => $this->getFileHash($file), + 'fileHash' => $currentFileHashes[$file] ?? $this->getFileHash($file), 'dependentFiles' => [], ]; } From 408692e22ce16abc860c689a55bedb3da445bfc7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 14:29:20 +0200 Subject: [PATCH 1389/3097] Result cache should be invalidated when files change in paths excluded for analysis but not scanning --- .github/workflows/e2e-tests.yml | 8 +++++++ conf/config.neon | 1 + e2e/result-cache-scanned/patch.patch | 8 +++++++ e2e/result-cache-scanned/phpstan.neon | 8 +++++++ .../src/Generated/Foo.php | 4 ++++ e2e/result-cache-scanned/src/testcase.php | 3 +++ .../ResultCache/ResultCacheManager.php | 22 ++++++++++++++++--- 7 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 e2e/result-cache-scanned/patch.patch create mode 100644 e2e/result-cache-scanned/phpstan.neon create mode 100644 e2e/result-cache-scanned/src/Generated/Foo.php create mode 100644 e2e/result-cache-scanned/src/testcase.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4b90be1ec3c..f1968b6a592 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -181,6 +181,14 @@ jobs: - script: | cd e2e/env-int-key env 1=1 ../../bin/phpstan analyse test.php + - script: | + cd e2e/result-cache-scanned + ../../bin/phpstan + patch -b src/Generated/Foo.php < patch.patch + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") + echo "$OUTPUT" + ../bashunit -a contains 'Result cache not used because the metadata do not match: projectConfig, scannedFiles' "$OUTPUT" + ../bashunit -a contains 'Instantiated class ResultCacheE2EGenerated\Foo not found.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ diff --git a/conf/config.neon b/conf/config.neon index aab1b08dfc1..c4aecd15614 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -574,6 +574,7 @@ services: scanFileFinder: @fileFinderScan cacheFilePath: %resultCachePath% analysedPaths: %analysedPaths% + analysedPathsFromConfig: %analysedPathsFromConfig% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% usedLevel: %usedLevel% cliAutoloadFile: %cliAutoloadFile% diff --git a/e2e/result-cache-scanned/patch.patch b/e2e/result-cache-scanned/patch.patch new file mode 100644 index 00000000000..c23bf6bf67f --- /dev/null +++ b/e2e/result-cache-scanned/patch.patch @@ -0,0 +1,8 @@ +--- src/Generated/Foo.php 2025-05-18 14:26:01 ++++ src/Generated/Foo.php 2025-05-18 14:27:01 +@@ -1,4 +1,4 @@ + scanFiles; - foreach ($this->scanFileFinder->findFiles($this->scanDirectories)->getFiles() as $file) { - $scannedFiles[] = $file; + $analysedDirectories = []; + foreach (array_merge($this->analysedPaths, $this->analysedPathsFromConfig) as $analysedPath) { + if (is_file($analysedPath)) { + continue; + } + + if (!is_dir($analysedPath)) { + continue; + } + + $analysedDirectories[] = $analysedPath; } - $scannedFiles = array_unique($scannedFiles); + $directories = array_unique(array_merge($analysedDirectories, $this->scanDirectories)); + foreach ($this->scanFileFinder->findFiles($directories)->getFiles() as $file) { + $scannedFiles[] = $file; + } $hashes = []; foreach (array_diff($scannedFiles, $allAnalysedFiles) as $file) { From e8b46c65dee36518b2d15b01dd9afc33a76dc92d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 15:21:18 +0200 Subject: [PATCH 1390/3097] Fix --- src/Analyser/ResultCache/ResultCacheManager.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 0e1117a66c8..212d659037f 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -97,6 +97,9 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $startTime = microtime(true); $currentFileHashes = []; foreach ($allAnalysedFiles as $analysedFile) { + if (!is_file($analysedFile)) { + continue; + } $currentFileHashes[$analysedFile] = $this->getFileHash($analysedFile); } if ($debug) { From 7e3639b2287952a6f05a2befe59435791166873a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 15:37:41 +0200 Subject: [PATCH 1391/3097] PHPStan Pro - refresh errors when scanned file is changed --- conf/config.neon | 7 +++- src/Command/FixerApplication.php | 1 - src/File/FileMonitor.php | 66 +++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index c4aecd15614..ec557e3bbf2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -732,7 +732,12 @@ services: - class: PHPStan\File\FileMonitor arguments: - fileFinder: @fileFinderAnalyse + analyseFileFinder: @fileFinderAnalyse + scanFileFinder: @fileFinderScan + analysedPaths: %analysedPaths% + analysedPathsFromConfig: %analysedPathsFromConfig% + scanFiles: %scanFiles% + scanDirectories: %scanDirectories% - class: PHPStan\Parser\DeclarePositionVisitor diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 40668741d9b..6a5b2cc317d 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -147,7 +147,6 @@ public function run( }); $this->fileMonitor->initialize(array_merge( - $this->analysedPaths, $this->getComposerLocks(), $this->getComposerInstalled(), $this->getExecutedFiles(), diff --git a/src/File/FileMonitor.php b/src/File/FileMonitor.php index 6fd0eaf8ef9..d7b2d5c4031 100644 --- a/src/File/FileMonitor.php +++ b/src/File/FileMonitor.php @@ -3,9 +3,14 @@ namespace PHPStan\File; use PHPStan\ShouldNotHappenException; +use function array_diff; use function array_key_exists; use function array_keys; +use function array_merge; +use function array_unique; use function count; +use function is_dir; +use function is_file; use function sha1_file; final class FileMonitor @@ -15,39 +20,52 @@ final class FileMonitor private ?array $fileHashes = null; /** @var array|null */ - private ?array $paths = null; + private ?array $filePaths = null; - public function __construct(private FileFinder $fileFinder) + /** + * @param string[] $analysedPaths + * @param string[] $analysedPathsFromConfig + * @param string[] $scanFiles + * @param string[] $scanDirectories + */ + public function __construct( + private FileFinder $analyseFileFinder, + private FileFinder $scanFileFinder, + private array $analysedPaths, + private array $analysedPathsFromConfig, + private array $scanFiles, + private array $scanDirectories, + ) { } /** - * @param array $paths + * @param array $filePaths */ - public function initialize(array $paths): void + public function initialize(array $filePaths): void { - $finderResult = $this->fileFinder->findFiles($paths); + $finderResult = $this->analyseFileFinder->findFiles($this->analysedPaths); $fileHashes = []; - foreach ($finderResult->getFiles() as $filePath) { + foreach (array_merge($finderResult->getFiles(), $filePaths, $this->getScannedFiles($finderResult->getFiles())) as $filePath) { $fileHashes[$filePath] = $this->getFileHash($filePath); } $this->fileHashes = $fileHashes; - $this->paths = $paths; + $this->filePaths = $filePaths; } public function getChanges(): FileMonitorResult { - if ($this->fileHashes === null || $this->paths === null) { + if ($this->fileHashes === null || $this->filePaths === null) { throw new ShouldNotHappenException(); } - $finderResult = $this->fileFinder->findFiles($this->paths); + $finderResult = $this->analyseFileFinder->findFiles($this->analysedPaths); $oldFileHashes = $this->fileHashes; $fileHashes = []; $newFiles = []; $changedFiles = []; $deletedFiles = []; - foreach ($finderResult->getFiles() as $filePath) { + foreach (array_merge($finderResult->getFiles(), $this->filePaths, $this->getScannedFiles($finderResult->getFiles())) as $filePath) { if (!array_key_exists($filePath, $oldFileHashes)) { $newFiles[] = $filePath; $fileHashes[$filePath] = $this->getFileHash($filePath); @@ -90,4 +108,32 @@ private function getFileHash(string $filePath): string return $hash; } + /** + * @param string[] $allAnalysedFiles + * @return array + */ + private function getScannedFiles(array $allAnalysedFiles): array + { + $scannedFiles = $this->scanFiles; + $analysedDirectories = []; + foreach (array_merge($this->analysedPaths, $this->analysedPathsFromConfig) as $analysedPath) { + if (is_file($analysedPath)) { + continue; + } + + if (!is_dir($analysedPath)) { + continue; + } + + $analysedDirectories[] = $analysedPath; + } + + $directories = array_unique(array_merge($analysedDirectories, $this->scanDirectories)); + foreach ($this->scanFileFinder->findFiles($directories)->getFiles() as $file) { + $scannedFiles[] = $file; + } + + return array_diff($scannedFiles, $allAnalysedFiles); + } + } From a06aaa4c6635fa6c3aa6b0752174b9682b72d98f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 16:30:01 +0200 Subject: [PATCH 1392/3097] Revert "Cleanup" This reverts commit c1ec0bdbbc44fa710907fa75ea970723d59d1a92. --- src/Parser/PathRoutingParser.php | 6 +++++- .../BetterReflectionSourceLocatorFactory.php | 5 +++++ tests/PHPStan/Parser/CachedParserTest.php | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index 83fae0a8910..0eb55ab07d6 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -16,6 +16,8 @@ final class PathRoutingParser implements Parser { + private ?string $singleReflectionFile; + /** @var bool[] filePath(string) => bool(true) */ private array $analysedFiles = []; @@ -24,8 +26,10 @@ public function __construct( private Parser $currentPhpVersionRichParser, private Parser $currentPhpVersionSimpleParser, private Parser $php8Parser, + ?string $singleReflectionFile, ) { + $this->singleReflectionFile = $singleReflectionFile !== null ? $fileHelper->normalizePath($singleReflectionFile) : null; } /** @@ -47,7 +51,7 @@ public function parseFile(string $file): array } $file = $this->fileHelper->normalizePath($file); - if (!isset($this->analysedFiles[$file])) { + if (!isset($this->analysedFiles[$file]) && $file !== $this->singleReflectionFile) { // check symlinked file that still might be in analysedFiles $pathParts = explode(DIRECTORY_SEPARATOR, $file); for ($i = count($pathParts); $i > 1; $i--) { diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 8632d6b59c9..280954f1d7d 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -56,6 +56,7 @@ public function __construct( private array $composerAutoloaderProjectPaths, private array $analysedPathsFromConfig, private bool $playgroundMode, // makes all PHPStan classes in the PHAR discoverable with PSR-4 + private ?string $singleReflectionFile, ) { } @@ -64,6 +65,10 @@ public function create(): SourceLocator { $locators = []; + if ($this->singleReflectionFile !== null) { + $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($this->singleReflectionFile); + } + $astLocator = new Locator($this->parser); $locators[] = new AutoloadFunctionsSourceLocator( new AutoloadSourceLocator($this->fileNodesFetcher, false), diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 13505dbce79..48cae905adf 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -83,6 +83,7 @@ public function testParseTheSameFileWithDifferentMethod(): void self::getContainer()->getService('currentPhpVersionRichParser'), self::getContainer()->getService('currentPhpVersionSimpleDirectParser'), self::getContainer()->getService('php8Parser'), + null, ); $parser = new CachedParser($pathRoutingParser, 500); $path = $fileHelper->normalizePath(__DIR__ . '/data/test.php'); From 74b909aedcc6fd09721c02763ff2c440ce5d7999 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 16:32:58 +0200 Subject: [PATCH 1393/3097] Introduce `--tmp-file` and `--instead-of` CLI options for easier running PHPStan from editor/IDE --- .github/workflows/e2e-tests.yml | 15 +++++++ conf/config.neon | 4 ++ conf/parametersSchema.neon | 4 ++ e2e/editor-mode/differentFoo.php | 13 ++++++ e2e/editor-mode/phpstan.neon | 4 ++ e2e/editor-mode/src/Bar.php | 18 ++++++++ e2e/editor-mode/src/Foo.php | 13 ++++++ .../ResultCache/ResultCacheManager.php | 43 ++++++++++++++++++ .../ResultCache/ResultCacheManagerFactory.php | 5 ++- src/Command/AnalyseApplication.php | 14 +++++- src/Command/AnalyseCommand.php | 22 +++++++++ src/Command/AnalyserRunner.php | 33 ++++++++++++-- src/Command/CommandHelper.php | 32 ++++++++++++- src/Command/DumpParametersCommand.php | 4 ++ src/Command/FixerWorkerCommand.php | 6 ++- src/Command/InceptionResult.php | 12 +++++ src/Command/WorkerCommand.php | 45 +++++++++++++++++-- src/DependencyInjection/ContainerFactory.php | 4 ++ .../DerivativeContainerFactory.php | 4 ++ src/Parallel/ParallelAnalyser.php | 10 +++++ .../AnalyseApplicationIntegrationTest.php | 2 + 21 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 e2e/editor-mode/differentFoo.php create mode 100644 e2e/editor-mode/phpstan.neon create mode 100644 e2e/editor-mode/src/Bar.php create mode 100644 e2e/editor-mode/src/Foo.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f1968b6a592..4bbfc2f346b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -189,6 +189,21 @@ jobs: echo "$OUTPUT" ../bashunit -a contains 'Result cache not used because the metadata do not match: projectConfig, scannedFiles' "$OUTPUT" ../bashunit -a contains 'Instantiated class ResultCacheE2EGenerated\Foo not found.' "$OUTPUT" + - script: | + cd e2e/editor-mode + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") + echo "$OUTPUT" + ../bashunit -a contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, int given. [identifier=argument.type]' "$OUTPUT" + ../bashunit -a contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a contains 'Result cache is saved.' "$OUTPUT" + + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw --tmp-file differentFoo.php --instead-of src/Foo.php") + echo "$OUTPUT" + ../bashunit -a contains 'differentFoo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a not_contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, float given. [identifier=argument.type]' "$OUTPUT" + ../bashunit -a contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" + ../bashunit -a contains 'Result cache was not saved because of --tmp-file and --instead-of CLI options passed (editor mode).' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ diff --git a/conf/config.neon b/conf/config.neon index ec557e3bbf2..099a7e214bb 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -660,6 +660,8 @@ services: usedLevel: %usedLevel% generateBaselineFile: %generateBaselineFile% cliAutoloadFile: %cliAutoloadFile% + singleReflectionFile: %singleReflectionFile% + singleReflectionInsteadOfFile: %singleReflectionInsteadOfFile% - class: PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider @@ -2196,6 +2198,7 @@ services: composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% analysedPathsFromConfig: %analysedPathsFromConfig% playgroundMode: %sourceLocatorPlaygroundMode% + singleReflectionFile: %singleReflectionFile% - implement: PHPStan\Reflection\BetterReflection\BetterReflectionProviderFactory @@ -2243,6 +2246,7 @@ services: currentPhpVersionRichParser: @currentPhpVersionRichParser currentPhpVersionSimpleParser: @currentPhpVersionSimpleParser php8Parser: @php8Parser + singleReflectionFile: %singleReflectionFile% autowired: false phpstanDiagnoseExtension: diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index d3359c68678..caca4e17bd6 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -251,3 +251,7 @@ parametersSchema: analysedPathsFromConfig: listOf(string()) usedLevel: string() cliAutoloadFile: schema(string(), nullable()) + + # internal - editor mode + singleReflectionFile: schema(string(), nullable()) + singleReflectionInsteadOfFile: schema(string(), nullable()) diff --git a/e2e/editor-mode/differentFoo.php b/e2e/editor-mode/differentFoo.php new file mode 100644 index 00000000000..f6908fca5ff --- /dev/null +++ b/e2e/editor-mode/differentFoo.php @@ -0,0 +1,13 @@ +requireString($foo->doFoo()); + } + + public function requireString(string $s): void + { + + } + +} diff --git a/e2e/editor-mode/src/Foo.php b/e2e/editor-mode/src/Foo.php new file mode 100644 index 00000000000..a91890871f9 --- /dev/null +++ b/e2e/editor-mode/src/Foo.php @@ -0,0 +1,13 @@ + $fileReplacements */ public function __construct( private ExportedNodeFetcher $exportedNodeFetcher, @@ -83,6 +84,7 @@ public function __construct( private array $bootstrapFiles, private array $scanFiles, private array $scanDirectories, + private array $fileReplacements, private bool $checkDependenciesOfProjectExtensionFiles, ) { @@ -369,6 +371,9 @@ private function getMetaKeyDifferences(array $cachedMeta, array $currentMeta): a */ private function exportedNodesChanged(string $analysedFile, array $cachedFileExportedNodes): ?bool { + if (array_key_exists($analysedFile, $this->fileReplacements)) { + $analysedFile = $this->fileReplacements[$analysedFile]; + } $fileExportedNodes = $this->exportedNodeFetcher->fetchNodes($analysedFile); $cachedSymbols = []; @@ -443,6 +448,13 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache return false; } + if (count($this->fileReplacements) > 0) { + if ($output->isVeryVerbose()) { + $output->writeLineFormatted('Result cache was not saved because of --tmp-file and --instead-of CLI options passed (editor mode).'); + } + return false; + } + foreach ($errorsByFile as $errors) { foreach ($errors as $error) { if (!$error->hasNonIgnorableException()) { @@ -561,6 +573,10 @@ private function mergeErrors(ResultCache $resultCache, array $freshErrorsByFile) { $errorsByFile = $resultCache->getErrors(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($errorsByFile[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshErrorsByFile)) { unset($errorsByFile[$file]); continue; @@ -579,6 +595,10 @@ private function mergeLocallyIgnoredErrors(ResultCache $resultCache, array $fres { $errorsByFile = $resultCache->getLocallyIgnoredErrors(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($errorsByFile[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshLocallyIgnoredErrorsByFile)) { unset($errorsByFile[$file]); continue; @@ -597,6 +617,10 @@ private function mergeCollectedData(ResultCache $resultCache, array $freshCollec { $collectedDataByFile = $resultCache->getCollectedData(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($collectedDataByFile[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshCollectedDataByFile)) { unset($collectedDataByFile[$file]); continue; @@ -637,6 +661,10 @@ private function mergeDependencies(ResultCache $resultCache, ?array $freshDepend $newDependencies = $cachedDependencies; foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($newDependencies[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshDependencies)) { unset($newDependencies[$file]); continue; @@ -656,6 +684,10 @@ private function mergeExportedNodes(ResultCache $resultCache, array $freshExport { $newExportedNodes = $resultCache->getExportedNodes(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($newExportedNodes[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshExportedNodes)) { unset($newExportedNodes[$file]); continue; @@ -675,6 +707,10 @@ private function mergeLinesToIgnore(ResultCache $resultCache, array $freshLinesT { $newLinesToIgnore = $resultCache->getLinesToIgnore(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($newLinesToIgnore[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshLinesToIgnore)) { unset($newLinesToIgnore[$file]); continue; @@ -694,6 +730,10 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres { $newUnmatchedLineIgnores = $resultCache->getUnmatchedLineIgnores(); foreach ($resultCache->getFilesToAnalyse() as $file) { + if (array_key_exists($file, $this->fileReplacements)) { + unset($newUnmatchedLineIgnores[$file]); + $file = $this->fileReplacements[$file]; + } if (!array_key_exists($file, $freshUnmatchedLineIgnores)) { unset($newUnmatchedLineIgnores[$file]); continue; @@ -933,6 +973,9 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a private function getFileHash(string $path): string { + if (array_key_exists($path, $this->fileReplacements)) { + $path = $this->fileReplacements[$path]; + } if (array_key_exists($path, $this->fileHashes)) { return $this->fileHashes[$path]; } diff --git a/src/Analyser/ResultCache/ResultCacheManagerFactory.php b/src/Analyser/ResultCache/ResultCacheManagerFactory.php index 269f7450158..333bc6136e1 100644 --- a/src/Analyser/ResultCache/ResultCacheManagerFactory.php +++ b/src/Analyser/ResultCache/ResultCacheManagerFactory.php @@ -5,6 +5,9 @@ interface ResultCacheManagerFactory { - public function create(): ResultCacheManager; + /** + * @param array $fileReplacements + */ + public function create(array $fileReplacements): ResultCacheManager; } diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 88589db6cc6..ce858a37546 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -46,11 +46,17 @@ public function analyse( bool $debug, ?string $projectConfigFile, ?array $projectConfigArray, + ?string $tmpFile, + ?string $insteadOfFile, InputInterface $input, ): AnalysisResult { $isResultCacheUsed = false; - $resultCacheManager = $this->resultCacheManagerFactory->create(); + $fileReplacements = []; + if ($tmpFile !== null && $insteadOfFile !== null) { + $fileReplacements = [$insteadOfFile => $tmpFile]; + } + $resultCacheManager = $this->resultCacheManagerFactory->create($fileReplacements); $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); $fileSpecificErrors = []; @@ -71,6 +77,8 @@ public function analyse( $files, $debug, $projectConfigFile, + $tmpFile, + $insteadOfFile, $stdOutput, $errorOutput, $input, @@ -169,6 +177,8 @@ private function runAnalyser( array $allAnalysedFiles, bool $debug, ?string $projectConfigFile, + ?string $tmpFile, + ?string $insteadOfFile, Output $stdOutput, Output $errorOutput, InputInterface $input, @@ -212,7 +222,7 @@ private function runAnalyser( } } - $analyserResult = $this->analyserRunner->runAnalyser($files, $allAnalysedFiles, $preFileCallback, $postFileCallback, $debug, true, $projectConfigFile, $input); + $analyserResult = $this->analyserRunner->runAnalyser($files, $allAnalysedFiles, $preFileCallback, $postFileCallback, $debug, true, $projectConfigFile, $tmpFile, $insteadOfFile, $input); if (!$debug) { $errorOutput->getStyle()->progressFinish(); diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index a112f222352..b11dbf8d536 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -98,6 +98,8 @@ protected function configure(): void new InputOption('allow-empty-baseline', null, InputOption::VALUE_NONE, 'Do not error out when the generated baseline is empty'), new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), + new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED, '(Editor mode) Edited file used in place of --instead-of file'), + new InputOption('instead-of', null, InputOption::VALUE_REQUIRED, '(Editor mode) File being replaced by --tmp-file'), new InputOption('fix', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), new InputOption('watch', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), new InputOption('pro', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), @@ -147,12 +149,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $allowEmptyBaseline = (bool) $input->getOption('allow-empty-baseline'); + $tmpFile = $input->getOption('tmp-file'); + $insteadOfFile = $input->getOption('instead-of'); + if ( !is_array($paths) || (!is_string($memoryLimit) && $memoryLimit !== null) || (!is_string($autoloadFile) && $autoloadFile !== null) || (!is_string($configuration) && $configuration !== null) || (!is_string($level) && $level !== null) + || (!is_string($tmpFile) && $tmpFile !== null) + || (!is_string($insteadOfFile) && $insteadOfFile !== null) || (!is_bool($allowXdebug)) ) { throw new ShouldNotHappenException(); @@ -171,6 +178,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $level, $allowXdebug, $debugEnabled, + $tmpFile, + $insteadOfFile, ); } catch (InceptionNotSuccessfulException $e) { return 1; @@ -181,6 +190,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); } + if ($inceptionResult->getEditorModeTmpFile() !== null) { + if ($generateBaselineFile !== null) { + $inceptionResult->getStdOutput()->getStyle()->error('Editor mode options --tmp-file and --instead-of cannot be used when generating the baseline.'); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + if ($fix) { + $inceptionResult->getStdOutput()->getStyle()->error('Editor mode options --tmp-file and --instead-of cannot be used with PHPStan Pro.'); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + } + $errorOutput = $inceptionResult->getErrorOutput(); $obsoleteDockerImage = $_SERVER['PHPSTAN_OBSOLETE_DOCKER_IMAGE'] ?? 'false'; if ($obsoleteDockerImage === 'true') { @@ -306,6 +326,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $debug, $inceptionResult->getProjectConfigFile(), $inceptionResult->getProjectConfigArray(), + $inceptionResult->getEditorModeTmpFile(), + $inceptionResult->getEditorModeInsteadOfFile(), $input, ); } catch (Throwable $t) { diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index 5b885293828..b0e6a3c467c 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -11,6 +11,9 @@ use PHPStan\ShouldNotHappenException; use React\EventLoop\StreamSelectLoop; use Symfony\Component\Console\Input\InputInterface; +use function array_filter; +use function array_unshift; +use function array_values; use function count; use function function_exists; use function is_file; @@ -42,6 +45,8 @@ public function runAnalyser( bool $debug, bool $allowParallel, ?string $projectConfigFile, + ?string $tmpFile, + ?string $insteadOfFile, InputInterface $input, ): AnalyserResult { @@ -65,7 +70,7 @@ public function runAnalyser( ) { $loop = new StreamSelectLoop(); $result = null; - $promise = $this->parallelAnalyser->analyse($loop, $schedule, $mainScript, $postFileCallback, $projectConfigFile, $input, null); + $promise = $this->parallelAnalyser->analyse($loop, $schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input, null); $promise->then(static function (AnalyserResult $tmp) use (&$result): void { $result = $tmp; }); @@ -77,12 +82,34 @@ public function runAnalyser( } return $this->analyser->analyse( - $files, + $this->switchTmpFile($files, $insteadOfFile, $tmpFile), $preFileCallback, $postFileCallback, $debug, - $allAnalysedFiles, + $this->switchTmpFile($allAnalysedFiles, $insteadOfFile, $tmpFile), ); } + /** + * @param string[] $analysedFiles + * @return string[] + */ + private function switchTmpFile( + array $analysedFiles, + ?string $insteadOfFile, + ?string $tmpFile, + ): array + { + if ($insteadOfFile === null) { + return $analysedFiles; + } + $analysedFiles = array_values(array_filter($analysedFiles, static fn (string $file): bool => $file !== $insteadOfFile)); + + if ($tmpFile !== null) { + array_unshift($analysedFiles, $tmpFile); + } + + return $analysedFiles; + } + } diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index b0219c9fac6..5158174918e 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -90,6 +90,8 @@ public static function begin( ?string $level, bool $allowXdebug, bool $debugEnabled = false, + ?string $singleReflectionFile = null, + ?string $singleReflectionInsteadOfFile = null, bool $cleanupContainerCache = true, ): InceptionResult { @@ -203,6 +205,32 @@ public static function begin( $generateBaselineFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($generateBaselineFile)); } + if ($singleReflectionFile !== null) { + $singleReflectionFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($singleReflectionFile)); + if (!is_file($singleReflectionFile)) { + $errorOutput->writeLineFormatted(sprintf('File passed to --tmp-file option does not exist: %s', $singleReflectionFile)); + throw new InceptionNotSuccessfulException(); + } + + if ($singleReflectionInsteadOfFile === null) { + $errorOutput->writeLineFormatted('Both --tmp-file and --instead-of options must be passed at the same time for editor mode to work.'); + throw new InceptionNotSuccessfulException(); + } + } + + if ($singleReflectionInsteadOfFile !== null) { + $singleReflectionInsteadOfFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($singleReflectionInsteadOfFile)); + if (!is_file($singleReflectionInsteadOfFile)) { + $errorOutput->writeLineFormatted(sprintf('File passed to --instead-of option does not exist: %s', $singleReflectionFile)); + throw new InceptionNotSuccessfulException(); + } + + if ($singleReflectionFile === null) { + $errorOutput->writeLineFormatted('Both --tmp-file and --instead-of options must be passed at the same time for editor mode to work.'); + throw new InceptionNotSuccessfulException(); + } + } + $defaultLevelUsed = false; if ($projectConfigFile === null && $level === null) { $level = self::DEFAULT_LEVEL; @@ -352,7 +380,7 @@ public static function begin( } try { - $container = $containerFactory->create($tmpDir, $additionalConfigFiles, $paths, $composerAutoloaderProjectPaths, $analysedPathsFromConfig, $level ?? self::DEFAULT_LEVEL, $generateBaselineFile, $autoloadFile); + $container = $containerFactory->create($tmpDir, $additionalConfigFiles, $paths, $composerAutoloaderProjectPaths, $analysedPathsFromConfig, $level ?? self::DEFAULT_LEVEL, $generateBaselineFile, $autoloadFile, $singleReflectionFile, $singleReflectionInsteadOfFile); } catch (InvalidConfigurationException | AssertionException $e) { $errorOutput->writeLineFormatted('Invalid configuration:'); $errorOutput->writeLineFormatted($e->getMessage()); @@ -625,6 +653,8 @@ public static function begin( $projectConfigFile, $projectConfig, $generateBaselineFile, + $singleReflectionFile, + $singleReflectionInsteadOfFile, ); } diff --git a/src/Command/DumpParametersCommand.php b/src/Command/DumpParametersCommand.php index b3085db0c64..d473ca31710 100644 --- a/src/Command/DumpParametersCommand.php +++ b/src/Command/DumpParametersCommand.php @@ -96,6 +96,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int unset($parameters['tempDir']); unset($parameters['__validate']); + // internal - editor mode + unset($parameters['singleReflectionFile']); + unset($parameters['singleReflectionInsteadOfFile']); + if ($json) { $encoded = Json::encode($parameters, Json::PRETTY); } else { diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index 70a76244538..f4364f7ab2d 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -108,6 +108,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $level, $allowXdebug, false, + null, + null, false, ); } catch (InceptionNotSuccessfulException) { @@ -133,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int //$in = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, 128 * 1024 * 1024); /** @var ResultCacheManager $resultCacheManager */ - $resultCacheManager = $container->getByType(ResultCacheManagerFactory::class)->create(); + $resultCacheManager = $container->getByType(ResultCacheManagerFactory::class)->create([]); $projectConfigArray = $inceptionResult->getProjectConfigArray(); /** @var AnalyserResultFinalizer $analyserResultFinalizer */ @@ -406,6 +408,8 @@ private function runAnalyser(LoopInterface $loop, Container $container, array $f $mainScript, null, $configuration, + null, + null, $input, $onFileAnalysisHandler, ); diff --git a/src/Command/InceptionResult.php b/src/Command/InceptionResult.php index fc6056eccb1..572cb6635c5 100644 --- a/src/Command/InceptionResult.php +++ b/src/Command/InceptionResult.php @@ -32,6 +32,8 @@ public function __construct( private ?string $projectConfigFile, private ?array $projectConfigArray, private ?string $generateBaselineFile, + private ?string $editorModeTmpFile, + private ?string $editorModeInsteadOfFile, ) { $this->filesCallback = $filesCallback; @@ -88,6 +90,16 @@ public function getGenerateBaselineFile(): ?string return $this->generateBaselineFile; } + public function getEditorModeTmpFile(): ?string + { + return $this->editorModeTmpFile; + } + + public function getEditorModeInsteadOfFile(): ?string + { + return $this->editorModeInsteadOfFile; + } + public function handleReturn(int $exitCode, ?int $peakMemoryUsageBytes, float $analysisStartTime): int { if ($this->getErrorOutput()->isVerbose()) { diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 6e44a112177..487e9cf23e4 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -24,7 +24,10 @@ use Symfony\Component\Console\Output\OutputInterface; use Throwable; use function array_fill_keys; +use function array_filter; use function array_merge; +use function array_unshift; +use function array_values; use function defined; use function is_array; use function is_bool; @@ -62,6 +65,8 @@ protected function configure(): void new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), new InputOption('port', null, InputOption::VALUE_REQUIRED), new InputOption('identifier', null, InputOption::VALUE_REQUIRED), + new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), + new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), ]) ->setHidden(true); } @@ -76,6 +81,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $allowXdebug = $input->getOption('xdebug'); $port = $input->getOption('port'); $identifier = $input->getOption('identifier'); + $tmpFile = $input->getOption('tmp-file'); + $insteadOfFile = $input->getOption('instead-of'); if ( !is_array($paths) @@ -86,6 +93,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int || (!is_bool($allowXdebug)) || !is_string($port) || !is_string($identifier) + || (!is_string($tmpFile) && $tmpFile !== null) + || (!is_string($insteadOfFile) && $insteadOfFile !== null) ) { throw new ShouldNotHappenException(); } @@ -103,6 +112,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $level, $allowXdebug, false, + $tmpFile, + $insteadOfFile, false, ); } catch (InceptionNotSuccessfulException $e) { @@ -114,6 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { [$analysedFiles] = $inceptionResult->getFiles(); + $analysedFiles = $this->switchTmpFile($analysedFiles, $insteadOfFile, $tmpFile); } catch (PathNotFoundException $e) { $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); return 1; @@ -127,14 +139,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $analysedFiles = array_fill_keys($analysedFiles, true); $tcpConnector = new TcpConnector($loop); - $tcpConnector->connect(sprintf('127.0.0.1:%d', $port))->then(function (ConnectionInterface $connection) use ($container, $identifier, $output, $analysedFiles): void { + $tcpConnector->connect(sprintf('127.0.0.1:%d', $port))->then(function (ConnectionInterface $connection) use ($container, $identifier, $output, $analysedFiles, $tmpFile, $insteadOfFile): void { // phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly $jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; // phpcs:enable $out = new Encoder($connection, $jsonInvalidUtf8Ignore); $in = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, $container->getParameter('parallel')['buffer']); $out->write(['action' => 'hello', 'identifier' => $identifier]); - $this->runWorker($container, $out, $in, $output, $analysedFiles); + $this->runWorker($container, $out, $in, $output, $analysedFiles, $tmpFile, $insteadOfFile); }); $loop->run(); @@ -155,6 +167,8 @@ private function runWorker( ReadableStreamInterface $in, OutputInterface $output, array $analysedFiles, + ?string $tmpFile, + ?string $insteadOfFile, ): void { $handleError = function (Throwable $error) use ($out, $output): void { @@ -192,7 +206,7 @@ private function runWorker( $fileAnalyser = $container->getByType(FileAnalyser::class); $ruleRegistry = $container->getByType(RuleRegistry::class); $collectorRegistry = $container->getByType(CollectorRegistry::class); - $in->on('data', static function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles): void { + $in->on('data', static function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles, $tmpFile, $insteadOfFile): void { $action = $json['action']; if ($action !== 'analyse') { return; @@ -212,6 +226,9 @@ private function runWorker( $exportedNodes = []; foreach ($files as $file) { try { + if ($file === $insteadOfFile) { + $file = $tmpFile; + } $fileAnalyserResult = $fileAnalyser->analyseFile($file, $analysedFiles, $ruleRegistry, $collectorRegistry, null); $fileErrors = $fileAnalyserResult->getErrors(); $filteredPhpErrors = array_merge($filteredPhpErrors, $fileAnalyserResult->getFilteredPhpErrors()); @@ -262,4 +279,26 @@ private function runWorker( $in->on('error', $handleError); } + /** + * @param string[] $analysedFiles + * @return string[] + */ + private function switchTmpFile( + array $analysedFiles, + ?string $insteadOfFile, + ?string $tmpFile, + ): array + { + if ($insteadOfFile === null) { + return $analysedFiles; + } + $analysedFiles = array_values(array_filter($analysedFiles, static fn (string $file): bool => $file !== $insteadOfFile)); + + if ($tmpFile !== null) { + array_unshift($analysedFiles, $tmpFile); + } + + return $analysedFiles; + } + } diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 5c8dad2f238..670bd89f436 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -102,6 +102,8 @@ public function create( string $usedLevel = CommandHelper::DEFAULT_LEVEL, ?string $generateBaselineFile = null, ?string $cliAutoloadFile = null, + ?string $singleReflectionFile = null, + ?string $singleReflectionInsteadOfFile = null, ): Container { [$allConfigFiles, $projectConfig] = $this->detectDuplicateIncludedFiles( @@ -138,6 +140,8 @@ public function create( 'cliAutoloadFile' => $cliAutoloadFile, ]); $configurator->addDynamicParameters([ + 'singleReflectionFile' => $singleReflectionFile, + 'singleReflectionInsteadOfFile' => $singleReflectionInsteadOfFile, 'analysedPaths' => $analysedPaths, 'analysedPathsFromConfig' => $analysedPathsFromConfig, 'env' => getenv(), diff --git a/src/DependencyInjection/DerivativeContainerFactory.php b/src/DependencyInjection/DerivativeContainerFactory.php index a32bc2eb6a0..a00a4e0724d 100644 --- a/src/DependencyInjection/DerivativeContainerFactory.php +++ b/src/DependencyInjection/DerivativeContainerFactory.php @@ -23,6 +23,8 @@ public function __construct( private string $usedLevel, private ?string $generateBaselineFile, private ?string $cliAutoloadFile, + private ?string $singleReflectionFile, + private ?string $singleReflectionInsteadOfFile, ) { } @@ -45,6 +47,8 @@ public function create(array $additionalConfigFiles): Container $this->usedLevel, $this->generateBaselineFile, $this->cliAutoloadFile, + $this->singleReflectionFile, + $this->singleReflectionInsteadOfFile, ); } diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 4c31b630505..569f038aeaf 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -25,6 +25,7 @@ use function array_sum; use function count; use function defined; +use function escapeshellarg; use function ini_get; use function max; use function memory_get_usage; @@ -62,6 +63,8 @@ public function analyse( string $mainScript, ?Closure $postFileCallback, ?string $projectConfigFile, + ?string $tmpFile, + ?string $insteadOfFile, InputInterface $input, ?callable $onFileAnalysisHandler, ): PromiseInterface @@ -170,6 +173,13 @@ public function analyse( $processIdentifier, ]; + if ($tmpFile !== null && $insteadOfFile !== null) { + $commandOptions[] = '--tmp-file'; + $commandOptions[] = escapeshellarg($tmpFile); + $commandOptions[] = '--instead-of'; + $commandOptions[] = escapeshellarg($insteadOfFile); + } + $process = new Process(ProcessHelper::getWorkerCommand( $mainScript, 'worker', diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index a7cb8997d84..2a0189e964e 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -88,6 +88,8 @@ private function runPath(string $path, int $expectedStatusCode): string true, null, null, + null, + null, $this->createMock(InputInterface::class), ); if (file_exists($memoryLimitFile)) { From 9cd97ab455ee728b47023b60a1f0f4cef33f0604 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 23:03:25 +0200 Subject: [PATCH 1394/3097] Fix error message --- src/Command/CommandHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 5158174918e..088fce63532 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -221,7 +221,7 @@ public static function begin( if ($singleReflectionInsteadOfFile !== null) { $singleReflectionInsteadOfFile = $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($singleReflectionInsteadOfFile)); if (!is_file($singleReflectionInsteadOfFile)) { - $errorOutput->writeLineFormatted(sprintf('File passed to --instead-of option does not exist: %s', $singleReflectionFile)); + $errorOutput->writeLineFormatted(sprintf('File passed to --instead-of option does not exist: %s', $singleReflectionInsteadOfFile)); throw new InceptionNotSuccessfulException(); } From fea728fc4f8147d44a08a181b807d3be73ce106b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 May 2025 23:22:07 +0200 Subject: [PATCH 1395/3097] File passed to `--instead-of` must be in analysed project files --- src/Command/AnalyseCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index b11dbf8d536..81dedfb56e0 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -279,6 +279,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); } + if ($inceptionResult->getEditorModeInsteadOfFile() !== null) { + if (!in_array($inceptionResult->getEditorModeInsteadOfFile(), $files, true)) { + $inceptionResult->getStdOutput()->getStyle()->error(sprintf('File %s passed to --instead-of is not in analysed project files.', $inceptionResult->getEditorModeInsteadOfFile())); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + } + $analysedConfigFiles = array_intersect($files, $container->getParameter('allConfigFiles')); /** @var RelativePathHelper $relativePathHelper */ $relativePathHelper = $container->getService('relativePathHelper'); From 795cc663fb5977edd8eb76d303ff326241bfda87 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 20 May 2025 00:04:16 +0000 Subject: [PATCH 1396/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 11a1ee76b79..cf8e9e4c5b2 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#1af43913cbb6d4dd4c8b776caae02dae1a2f35de", + "jetbrains/phpstorm-stubs": "dev-master#56e49161f6f411647350b769efe7c640bd9010d1", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 59365ba683c..d5c883c79c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3048aa1c538e9ea0ccfcddbb6a95c705", + "content-hash": "6dbfcceae1655d0051d153fcb50b2c1f", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "1af43913cbb6d4dd4c8b776caae02dae1a2f35de" + "reference": "56e49161f6f411647350b769efe7c640bd9010d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/1af43913cbb6d4dd4c8b776caae02dae1a2f35de", - "reference": "1af43913cbb6d4dd4c8b776caae02dae1a2f35de", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/56e49161f6f411647350b769efe7c640bd9010d1", + "reference": "56e49161f6f411647350b769efe7c640bd9010d1", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-05-12T09:33:59+00:00" + "time": "2025-05-14T19:32:50+00:00" }, { "name": "nette/bootstrap", From dff492e14c586368429893111ec4c950b4bd1748 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 09:46:14 +0200 Subject: [PATCH 1397/3097] Invalidate result cache properly when property hook changes --- .../ExportedNode/ExportedPropertiesNode.php | 20 +++ .../ExportedNode/ExportedPropertyHookNode.php | 143 ++++++++++++++++++ src/Dependency/ExportedNodeResolver.php | 41 +++++ 3 files changed, 204 insertions(+) create mode 100644 src/Dependency/ExportedNode/ExportedPropertyHookNode.php diff --git a/src/Dependency/ExportedNode/ExportedPropertiesNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php index af58d51738e..41a4e4fb9fc 100644 --- a/src/Dependency/ExportedNode/ExportedPropertiesNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -15,6 +15,7 @@ final class ExportedPropertiesNode implements JsonSerializable, ExportedNode /** * @param string[] $names * @param ExportedAttributeNode[] $attributes + * @param ExportedPropertyHookNode[] $hooks */ public function __construct( private array $names, @@ -25,6 +26,7 @@ public function __construct( private bool $static, private bool $readonly, private array $attributes, + private array $hooks, ) { } @@ -67,6 +69,16 @@ public function equals(ExportedNode $node): bool } } + if (count($this->hooks) !== count($node->hooks)) { + return false; + } + + foreach ($this->hooks as $i => $hook) { + if (!$hook->equals($node->hooks[$i])) { + return false; + } + } + return $this->type === $node->type && $this->public === $node->public && $this->private === $node->private @@ -88,6 +100,7 @@ public static function __set_state(array $properties): self $properties['static'], $properties['readonly'], $properties['attributes'], + $properties['hooks'], ); } @@ -110,6 +123,12 @@ public static function decode(array $data): self } return ExportedAttributeNode::decode($attributeData['data']); }, $data['attributes']), + array_map(static function (array $attributeData): ExportedPropertyHookNode { + if ($attributeData['type'] !== ExportedPropertyHookNode::class) { + throw new ShouldNotHappenException(); + } + return ExportedPropertyHookNode::decode($attributeData['data']); + }, $data['hooks']), ); } @@ -130,6 +149,7 @@ public function jsonSerialize() 'static' => $this->static, 'readonly' => $this->readonly, 'attributes' => $this->attributes, + 'hooks' => $this->hooks, ], ]; } diff --git a/src/Dependency/ExportedNode/ExportedPropertyHookNode.php b/src/Dependency/ExportedNode/ExportedPropertyHookNode.php new file mode 100644 index 00000000000..9c3337fd400 --- /dev/null +++ b/src/Dependency/ExportedNode/ExportedPropertyHookNode.php @@ -0,0 +1,143 @@ +parameters) !== count($node->parameters)) { + return false; + } + + foreach ($this->parameters as $i => $ourParameter) { + $theirParameter = $node->parameters[$i]; + if (!$ourParameter->equals($theirParameter)) { + return false; + } + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $attribute) { + if (!$attribute->equals($node->attributes[$i])) { + return false; + } + } + + return $this->name === $node->name + && $this->byRef === $node->byRef + && $this->abstract === $node->abstract + && $this->final === $node->final + && $this->short === $node->short; + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['byRef'], + $properties['abstract'], + $properties['final'], + $properties['short'], + $properties['parameters'], + $properties['attributes'], + ); + } + + /** + * @return mixed + */ + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'byRef' => $this->byRef, + 'abstract' => $this->abstract, + 'final' => $this->final, + 'short' => $this->short, + 'parameters' => $this->parameters, + 'attributes' => $this->attributes, + ], + ]; + } + + /** + * @param mixed[] $data + */ + public static function decode(array $data): self + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['byRef'], + $data['abstract'], + $data['final'], + $data['short'], + array_map(static function (array $parameterData): ExportedParameterNode { + if ($parameterData['type'] !== ExportedParameterNode::class) { + throw new ShouldNotHappenException(); + } + return ExportedParameterNode::decode($parameterData['data']); + }, $data['parameters']), + array_map(static function (array $attributeData): ExportedAttributeNode { + if ($attributeData['type'] !== ExportedAttributeNode::class) { + throw new ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($attributeData['data']); + }, $data['attributes']), + ); + } + +} diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 441c785c99a..7856bf2d114 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -3,6 +3,7 @@ namespace PHPStan\Dependency; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; @@ -19,6 +20,7 @@ use PHPStan\Dependency\ExportedNode\ExportedParameterNode; use PHPStan\Dependency\ExportedNode\ExportedPhpDocNode; use PHPStan\Dependency\ExportedNode\ExportedPropertiesNode; +use PHPStan\Dependency\ExportedNode\ExportedPropertyHookNode; use PHPStan\Dependency\ExportedNode\ExportedTraitNode; use PHPStan\Dependency\ExportedNode\ExportedTraitUseAdaptation; use PHPStan\Node\Printer\ExprPrinter; @@ -27,6 +29,7 @@ use PHPStan\Type\FileTypeMapper; use function array_map; use function is_string; +use function sprintf; final class ExportedNodeResolver { @@ -307,6 +310,7 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $node->isStatic(), $node->isReadonly(), $this->exportAttributeNodes($node->attrGroups), + $this->exportPropertyHooks($node->hooks, $fileName, $namespacedName), ); } @@ -382,4 +386,41 @@ private function exportAttributeNodes(array $attributeGroups): array return $nodes; } + /** + * @param Node\PropertyHook[] $hooks + * @return ExportedPropertyHookNode[] + */ + private function exportPropertyHooks( + array $hooks, + string $fileName, + string $namespacedName, + ): array + { + $nodes = []; + foreach ($hooks as $hook) { + $docComment = $hook->getDocComment(); + $propertyName = $hook->getAttribute('propertyName'); + if ($propertyName === null) { + continue; + } + $nodes[] = new ExportedPropertyHookNode( + $hook->name->toString(), + $this->exportPhpDocNode( + $fileName, + $namespacedName, + sprintf('$%s::%s', $propertyName, $hook->name->toString()), + $docComment !== null ? $docComment->getText() : null, + ), + $hook->byRef, + $hook->body === null, + $hook->isFinal(), + $hook->body instanceof Expr, + $this->exportParameterNodes($hook->params), + $this->exportAttributeNodes($hook->attrGroups), + ); + } + + return $nodes; + } + } From 268d7c6388eaeef1675ef8eb807975bc42a4dc1a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 10:58:09 +0200 Subject: [PATCH 1398/3097] Result cache - add types mentioned in property hooks to dependencies --- src/Dependency/DependencyResolver.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 77a4f957fe5..eaded153940 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -15,6 +15,7 @@ use PHPStan\Node\ClassPropertyNode; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InFunctionNode; +use PHPStan\Node\InPropertyHookNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; @@ -83,6 +84,10 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies $this->addClassToDependencies($referencedClass, $dependenciesReflections); } } + } elseif ($node instanceof InPropertyHookNode) { + $nativeMethod = $node->getHookReflection(); + $this->extractThrowType($nativeMethod->getThrowType(), $dependenciesReflections); + $this->extractFromParametersAcceptor($nativeMethod, $dependenciesReflections); } elseif ($node instanceof ClassPropertyNode) { $nativeType = $node->getNativeType(); if ($nativeType !== null) { From 0ad2a6a6f207fa3a57ab0dd98c30459666dd152f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 11:16:51 +0200 Subject: [PATCH 1399/3097] Result cache - react to property having abstract/final/asymmetric visibility changed --- .../ExportedNode/ExportedPropertiesNode.php | 27 ++++++++++++++++++- src/Dependency/ExportedNodeResolver.php | 5 ++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Dependency/ExportedNode/ExportedPropertiesNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php index 41a4e4fb9fc..6f67e035202 100644 --- a/src/Dependency/ExportedNode/ExportedPropertiesNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -25,6 +25,11 @@ public function __construct( private bool $private, private bool $static, private bool $readonly, + private bool $abstract, + private bool $final, + private bool $publicSet, + private bool $protectedSet, + private bool $privateSet, private array $attributes, private array $hooks, ) @@ -83,7 +88,12 @@ public function equals(ExportedNode $node): bool && $this->public === $node->public && $this->private === $node->private && $this->static === $node->static - && $this->readonly === $node->readonly; + && $this->readonly === $node->readonly + && $this->abstract === $node->abstract + && $this->final === $node->final + && $this->publicSet === $node->publicSet + && $this->protectedSet === $node->protectedSet + && $this->privateSet === $node->privateSet; } /** @@ -99,6 +109,11 @@ public static function __set_state(array $properties): self $properties['private'], $properties['static'], $properties['readonly'], + $properties['abstract'], + $properties['final'], + $properties['publicSet'], + $properties['protectedSet'], + $properties['privateSet'], $properties['attributes'], $properties['hooks'], ); @@ -117,6 +132,11 @@ public static function decode(array $data): self $data['private'], $data['static'], $data['readonly'], + $data['abstract'], + $data['final'], + $data['publicSet'], + $data['protectedSet'], + $data['privateSet'], array_map(static function (array $attributeData): ExportedAttributeNode { if ($attributeData['type'] !== ExportedAttributeNode::class) { throw new ShouldNotHappenException(); @@ -148,6 +168,11 @@ public function jsonSerialize() 'private' => $this->private, 'static' => $this->static, 'readonly' => $this->readonly, + 'abstract' => $this->abstract, + 'final' => $this->final, + 'publicSet' => $this->publicSet, + 'protectedSet' => $this->protectedSet, + 'privateSet' => $this->privateSet, 'attributes' => $this->attributes, 'hooks' => $this->hooks, ], diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 7856bf2d114..8e963009e52 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -309,6 +309,11 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $node->isPrivate(), $node->isStatic(), $node->isReadonly(), + $node->isAbstract(), + $node->isFinal(), + $node->isPublicSet(), + $node->isProtectedSet(), + $node->isPrivateSet(), $this->exportAttributeNodes($node->attrGroups), $this->exportPropertyHooks($node->hooks, $fileName, $namespacedName), ); From e33a560c71a7f1a52ae5f96158393ef441626aa5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 11:22:07 +0200 Subject: [PATCH 1400/3097] Result cache - react to property being virtual changing --- .../ExportedNode/ExportedPropertiesNode.php | 7 ++++++- src/Dependency/ExportedNodeResolver.php | 19 +++++++++++++++++-- tests/PHPStan/Analyser/AnalyserTest.php | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedPropertiesNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php index 6f67e035202..e39adb451d9 100644 --- a/src/Dependency/ExportedNode/ExportedPropertiesNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -30,6 +30,7 @@ public function __construct( private bool $publicSet, private bool $protectedSet, private bool $privateSet, + private bool $virtual, private array $attributes, private array $hooks, ) @@ -93,7 +94,8 @@ public function equals(ExportedNode $node): bool && $this->final === $node->final && $this->publicSet === $node->publicSet && $this->protectedSet === $node->protectedSet - && $this->privateSet === $node->privateSet; + && $this->privateSet === $node->privateSet + && $this->virtual === $node->virtual; } /** @@ -114,6 +116,7 @@ public static function __set_state(array $properties): self $properties['publicSet'], $properties['protectedSet'], $properties['privateSet'], + $properties['virtual'], $properties['attributes'], $properties['hooks'], ); @@ -137,6 +140,7 @@ public static function decode(array $data): self $data['publicSet'], $data['protectedSet'], $data['privateSet'], + $data['virtual'], array_map(static function (array $attributeData): ExportedAttributeNode { if ($attributeData['type'] !== ExportedAttributeNode::class) { throw new ShouldNotHappenException(); @@ -173,6 +177,7 @@ public function jsonSerialize() 'publicSet' => $this->publicSet, 'protectedSet' => $this->protectedSet, 'privateSet' => $this->privateSet, + 'virtual' => $this->virtual, 'attributes' => $this->attributes, 'hooks' => $this->hooks, ], diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 8e963009e52..0c4e2eb65d2 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -25,6 +25,7 @@ use PHPStan\Dependency\ExportedNode\ExportedTraitUseAdaptation; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\Printer\NodeTypePrinter; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Type\FileTypeMapper; use function array_map; @@ -34,7 +35,11 @@ final class ExportedNodeResolver { - public function __construct(private FileTypeMapper $fileTypeMapper, private ExprPrinter $exprPrinter) + public function __construct( + private ReflectionProvider $reflectionProvider, + private FileTypeMapper $fileTypeMapper, + private ExprPrinter $exprPrinter, + ) { } @@ -296,8 +301,17 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $docComment = $node->getDocComment(); + $names = array_map(static fn (Node\PropertyItem $prop): string => $prop->name->toString(), $node->props); + $virtual = false; + if ($this->reflectionProvider->hasClass($namespacedName)) { + $classReflection = $this->reflectionProvider->getClass($namespacedName); + if ($classReflection->hasNativeProperty($names[0])) { + $virtual = $classReflection->getNativeProperty($names[0])->isVirtual()->yes(); + } + } + return new ExportedPropertiesNode( - array_map(static fn (Node\PropertyItem $prop): string => $prop->name->toString(), $node->props), + $names, $this->exportPhpDocNode( $fileName, $namespacedName, @@ -314,6 +328,7 @@ private function exportClassStatement(Node\Stmt $node, string $fileName, string $node->isPublicSet(), $node->isProtectedSet(), $node->isPrivateSet(), + $virtual, $this->exportAttributeNodes($node->attrGroups), $this->exportPropertyHooks($node->hooks, $fileName, $namespacedName), ); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 1b41db9157c..9877e01b77d 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -747,7 +747,7 @@ private function createAnalyser(): Analyser self::getContainer(), new IgnoreLexer(), ), - new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), + new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($reflectionProvider, $fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), new RuleErrorTransformer(), new LocalIgnoresProcessor(), From e5db864362bb19b2fb3c47188c117afd18e386f6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 14:05:04 +0200 Subject: [PATCH 1401/3097] Result cache - add traits to dependencies recursively --- .github/workflows/e2e-tests.yml | 7 +++++++ e2e/result-cache-traits/phpstan.neon | 5 +++++ e2e/result-cache-traits/renameFooTraitMethod.patch | 11 +++++++++++ e2e/result-cache-traits/src/BarTrait.php | 10 ++++++++++ .../src/ClassMentioningClassUsingBarTrait.php | 13 +++++++++++++ e2e/result-cache-traits/src/ClassUsingBarTrait.php | 10 ++++++++++ e2e/result-cache-traits/src/FooTrait.php | 13 +++++++++++++ e2e/result-cache-traits/tmp/.gitignore | 2 ++ src/Dependency/DependencyResolver.php | 2 +- 9 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 e2e/result-cache-traits/phpstan.neon create mode 100644 e2e/result-cache-traits/renameFooTraitMethod.patch create mode 100644 e2e/result-cache-traits/src/BarTrait.php create mode 100644 e2e/result-cache-traits/src/ClassMentioningClassUsingBarTrait.php create mode 100644 e2e/result-cache-traits/src/ClassUsingBarTrait.php create mode 100644 e2e/result-cache-traits/src/FooTrait.php create mode 100644 e2e/result-cache-traits/tmp/.gitignore diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4bbfc2f346b..9a351d63325 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -189,6 +189,13 @@ jobs: echo "$OUTPUT" ../bashunit -a contains 'Result cache not used because the metadata do not match: projectConfig, scannedFiles' "$OUTPUT" ../bashunit -a contains 'Instantiated class ResultCacheE2EGenerated\Foo not found.' "$OUTPUT" + - script: | + cd e2e/result-cache-traits + ../../bin/phpstan analyse + patch -b src/FooTrait.php < renameFooTraitMethod.patch + OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") + echo "$OUTPUT" + ../bashunit -a contains 'ClassMentioningClassUsingBarTrait.php:10:Call to an undefined method ResultCacheE2ETraits\ClassUsingBarTrait::doFooTrait(). [identifier=method.notFound]' "$OUTPUT" - script: | cd e2e/editor-mode OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw") diff --git a/e2e/result-cache-traits/phpstan.neon b/e2e/result-cache-traits/phpstan.neon new file mode 100644 index 00000000000..1796e7715c7 --- /dev/null +++ b/e2e/result-cache-traits/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + tmpDir: tmp diff --git a/e2e/result-cache-traits/renameFooTraitMethod.patch b/e2e/result-cache-traits/renameFooTraitMethod.patch new file mode 100644 index 00000000000..74db3f8f06c --- /dev/null +++ b/e2e/result-cache-traits/renameFooTraitMethod.patch @@ -0,0 +1,11 @@ +--- src/FooTrait.php 2025-05-20 14:00:38 ++++ src/FooTrait.php 2025-05-20 14:00:49 +@@ -5,7 +5,7 @@ + trait FooTrait + { + +- public function doFooTrait(): void ++ public function doFooTrait2(): void + { + + } diff --git a/e2e/result-cache-traits/src/BarTrait.php b/e2e/result-cache-traits/src/BarTrait.php new file mode 100644 index 00000000000..3357cbbc3c9 --- /dev/null +++ b/e2e/result-cache-traits/src/BarTrait.php @@ -0,0 +1,10 @@ +doFooTrait(); + } + +} diff --git a/e2e/result-cache-traits/src/ClassUsingBarTrait.php b/e2e/result-cache-traits/src/ClassUsingBarTrait.php new file mode 100644 index 00000000000..e8063edef40 --- /dev/null +++ b/e2e/result-cache-traits/src/ClassUsingBarTrait.php @@ -0,0 +1,10 @@ +getTraits() as $trait) { + foreach ($classReflection->getTraits(true) as $trait) { $dependenciesReflections[] = $trait; } From bd6fc4e266a94fafe3d12078f142db3f900fc1f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 14:39:08 +0200 Subject: [PATCH 1402/3097] Result cache traits optimization - analyse only files using trait when trait implementation changes Specifically, files dependending on classes using the trait will not be reanalysed in this situation. --- src/Analyser/Analyser.php | 3 + src/Analyser/AnalyserResult.php | 10 ++ src/Analyser/AnalyserResultFinalizer.php | 3 + src/Analyser/FileAnalyser.php | 14 +- src/Analyser/FileAnalyserResult.php | 10 ++ src/Analyser/ResultCache/ResultCache.php | 10 ++ .../ResultCache/ResultCacheManager.php | 94 ++++++++++--- src/Command/AnalyseApplication.php | 3 +- src/Command/AnalyserRunner.php | 2 +- src/Command/FixerWorkerCommand.php | 2 +- src/Command/WorkerCommand.php | 3 + src/Dependency/DependencyResolver.php | 11 ++ .../ExportedNode/ExportedTraitNode.php | 127 ++++++++++++++++-- src/Dependency/ExportedNodeResolver.php | 47 ++++++- src/Parallel/ParallelAnalyser.php | 14 +- 15 files changed, 314 insertions(+), 39 deletions(-) diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index 4ab99fc5d37..64215a4860f 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -65,6 +65,7 @@ public function analyse( $internalErrorsCount = 0; $reachedInternalErrorsCountLimit = false; $dependencies = []; + $usedTraitDependencies = []; $exportedNodes = []; foreach ($files as $file) { if ($preFileCallback !== null) { @@ -88,6 +89,7 @@ public function analyse( $unmatchedLineIgnores[$file] = $fileAnalyserResult->getUnmatchedLineIgnores(); $collectedData = array_merge($collectedData, $fileAnalyserResult->getCollectedData()); $dependencies[$file] = $fileAnalyserResult->getDependencies(); + $usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies(); $fileExportedNodes = $fileAnalyserResult->getExportedNodes(); if (count($fileExportedNodes) > 0) { @@ -127,6 +129,7 @@ public function analyse( [], $collectedData, $internalErrorsCount === 0 ? $dependencies : null, + $internalErrorsCount === 0 ? $usedTraitDependencies : null, $exportedNodes, $reachedInternalErrorsCountLimit, memory_get_peak_usage(true), diff --git a/src/Analyser/AnalyserResult.php b/src/Analyser/AnalyserResult.php index 212fdcf422e..eafa1a8aded 100644 --- a/src/Analyser/AnalyserResult.php +++ b/src/Analyser/AnalyserResult.php @@ -25,6 +25,7 @@ final class AnalyserResult * @param list $collectedData * @param list $internalErrors * @param array>|null $dependencies + * @param array>|null $usedTraitDependencies * @param array> $exportedNodes */ public function __construct( @@ -37,6 +38,7 @@ public function __construct( private array $internalErrors, private array $collectedData, private ?array $dependencies, + private ?array $usedTraitDependencies, private array $exportedNodes, private bool $reachedInternalErrorsCountLimit, private int $peakMemoryUsageBytes, @@ -140,6 +142,14 @@ public function getDependencies(): ?array return $this->dependencies; } + /** + * @return array>|null + */ + public function getUsedTraitDependencies(): ?array + { + return $this->usedTraitDependencies; + } + /** * @return array> */ diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index b88b3e0d310..aea76cc2b9f 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -129,6 +129,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ $internalErrors, $analyserResult->getCollectedData(), $analyserResult->getDependencies(), + $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $analyserResult->hasReachedInternalErrorsCountLimit(), $analyserResult->getPeakMemoryUsageBytes(), @@ -147,6 +148,7 @@ private function mergeFilteredPhpErrors(AnalyserResult $analyserResult): Analyse $analyserResult->getInternalErrors(), $analyserResult->getCollectedData(), $analyserResult->getDependencies(), + $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $analyserResult->hasReachedInternalErrorsCountLimit(), $analyserResult->getPeakMemoryUsageBytes(), @@ -210,6 +212,7 @@ private function addUnmatchedIgnoredErrors( $analyserResult->getInternalErrors(), $analyserResult->getCollectedData(), $analyserResult->getDependencies(), + $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $analyserResult->hasReachedInternalErrorsCountLimit(), $analyserResult->getPeakMemoryUsageBytes(), diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 8d8cf590162..a76c648e6bd 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -11,6 +11,7 @@ use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; use PHPStan\Node\FileNode; +use PHPStan\Node\InClassNode; use PHPStan\Node\InTraitNode; use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; @@ -78,6 +79,7 @@ public function analyseFile( $fileCollectedData = []; $fileDependencies = []; + $usedTraitFileDependencies = []; $exportedNodes = []; $linesToIgnore = []; $unmatchedLineIgnores = []; @@ -87,7 +89,7 @@ public function analyseFile( $parserNodes = $this->parser->parseFile($file); $linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; $temporaryFileErrors = []; - $nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors): void { + $nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors): void { if ($node instanceof Node\Stmt\Trait_) { foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) { if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) { @@ -205,6 +207,15 @@ public function analyseFile( } catch (UnableToCompileNode) { // pass } + + if (!$node instanceof InClassNode) { + return; + } + + $usedTraitDependencies = $this->dependencyResolver->resolveUsedTraitDependencies($node); + foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) { + $usedTraitFileDependencies[] = $dependentFile; + } }; $scope = $this->scopeFactory->create(ScopeContext::create($file)); @@ -287,6 +298,7 @@ public function analyseFile( $locallyIgnoredErrors, $fileCollectedData, array_values(array_unique($fileDependencies)), + array_values(array_unique($usedTraitFileDependencies)), $exportedNodes, $linesToIgnore, $unmatchedLineIgnores, diff --git a/src/Analyser/FileAnalyserResult.php b/src/Analyser/FileAnalyserResult.php index d1727f5824c..87b957e35c2 100644 --- a/src/Analyser/FileAnalyserResult.php +++ b/src/Analyser/FileAnalyserResult.php @@ -18,6 +18,7 @@ final class FileAnalyserResult * @param list $locallyIgnoredErrors * @param list $collectedData * @param list $dependencies + * @param list $usedTraitDependencies * @param list $exportedNodes * @param LinesToIgnore $linesToIgnore * @param LinesToIgnore $unmatchedLineIgnores @@ -29,6 +30,7 @@ public function __construct( private array $locallyIgnoredErrors, private array $collectedData, private array $dependencies, + private array $usedTraitDependencies, private array $exportedNodes, private array $linesToIgnore, private array $unmatchedLineIgnores, @@ -84,6 +86,14 @@ public function getDependencies(): array return $this->dependencies; } + /** + * @return list + */ + public function getUsedTraitDependencies(): array + { + return $this->usedTraitDependencies; + } + /** * @return list */ diff --git a/src/Analyser/ResultCache/ResultCache.php b/src/Analyser/ResultCache/ResultCache.php index 2f33cf2433e..f0b89456bbf 100644 --- a/src/Analyser/ResultCache/ResultCache.php +++ b/src/Analyser/ResultCache/ResultCache.php @@ -22,6 +22,7 @@ final class ResultCache * @param array $unmatchedLineIgnores * @param array> $collectedData * @param array> $dependencies + * @param array> $usedTraitDependencies * @param array> $exportedNodes * @param array $projectExtensionFiles * @param array $currentFileHashes @@ -37,6 +38,7 @@ public function __construct( private array $unmatchedLineIgnores, private array $collectedData, private array $dependencies, + private array $usedTraitDependencies, private array $exportedNodes, private array $projectExtensionFiles, private array $currentFileHashes, @@ -118,6 +120,14 @@ public function getDependencies(): array return $this->dependencies; } + /** + * @return array> + */ + public function getUsedTraitDependencies(): array + { + return $this->usedTraitDependencies; + } + /** * @return array> */ diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 8fb9e53476c..35831f2dcaa 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\FileAnalyserResult; use PHPStan\Collectors\CollectedData; use PHPStan\Command\Output; +use PHPStan\Dependency\ExportedNode\ExportedTraitNode; use PHPStan\Dependency\ExportedNodeFetcher; use PHPStan\Dependency\RootExportedNode; use PHPStan\DependencyInjection\ProjectConfigHelper; @@ -108,13 +109,13 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because of debug mode.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], [], $currentFileHashes); } if ($onlyFiles) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because only files were passed as analysed paths.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], [], $currentFileHashes); } $cacheFilePath = $this->cacheFilePath; @@ -122,7 +123,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache not used because the cache file does not exist.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], [], $currentFileHashes); } try { @@ -134,7 +135,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? @unlink($cacheFilePath); - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], [], $currentFileHashes); } if (!is_array($data)) { @@ -143,7 +144,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $output->writeLineFormatted('Result cache not used because the cache file is corrupted.'); } - return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $this->getMeta($allAnalysedFiles, $projectConfigArray), [], [], [], [], [], [], [], [], [], $currentFileHashes); } $meta = $this->getMeta($allAnalysedFiles, $projectConfigArray); @@ -152,7 +153,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $diffs = $this->getMetaKeyDifferences($data['meta'], $meta); $output->writeLineFormatted('Result cache not used because the metadata do not match: ' . implode(', ', $diffs)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], [], $currentFileHashes); } if (time() - $data['lastFullAnalysisTime'] >= 60 * 60 * 24 * 7) { @@ -161,7 +162,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? } // run full analysis if the result cache is older than 7 days - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], [], $currentFileHashes); } /** @@ -176,7 +177,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? if ($output->isVeryVerbose()) { $output->writeLineFormatted(sprintf('Result cache not used because extension file %s was not found.', $extensionFile)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], [], $currentFileHashes); } if ($this->getFileHash($extensionFile) === $fileHash) { @@ -187,13 +188,14 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $output->writeLineFormatted(sprintf('Result cache not used because extension file %s hash does not match.', $extensionFile)); } - return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], $currentFileHashes); + return new ResultCache($allAnalysedFiles, true, time(), $meta, [], [], [], [], [], [], [], [], [], $currentFileHashes); } $invertedDependencies = $data['dependencies']; $deletedFiles = array_fill_keys(array_keys($invertedDependencies), true); $filesToAnalyse = []; $invertedDependenciesToReturn = []; + $invertedUsedTraitDependenciesToReturn = []; $errors = $data['errorsCallback'](); $locallyIgnoredErrors = $data['locallyIgnoredErrorsCallback'](); $linesToIgnore = $data['linesToIgnore']; @@ -248,6 +250,10 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $cachedFileHash = $analysedFileData['fileHash']; $dependentFiles = $analysedFileData['dependentFiles']; $invertedDependenciesToReturn[$analysedFile] = $dependentFiles; + $usedTraitDependentFiles = $analysedFileData['usedTraitDependentFiles'] ?? []; + if (count($usedTraitDependentFiles) > 0) { + $invertedUsedTraitDependenciesToReturn[$analysedFile] = $usedTraitDependentFiles; + } $currentFileHash = $currentFileHashes[$analysedFile]; if ($cachedFileHash === $currentFileHash) { @@ -262,6 +268,25 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? $cachedFileExportedNodes = $filteredExportedNodes[$analysedFile]; $exportedNodesChanged = $this->exportedNodesChanged($analysedFile, $cachedFileExportedNodes); if ($exportedNodesChanged === null) { + if (count($cachedFileExportedNodes) === 0) { + continue; + } + foreach ($cachedFileExportedNodes as $exportedNode) { + if (!$exportedNode instanceof ExportedTraitNode) { + continue 2; + } + } + + // if the file changed but no exported nodes changed and the only exported nodes are traits + // reanalyse files with classes using those traits + // but not other dependent files + + foreach ($usedTraitDependentFiles as $usedTraitDependentFile) { + if (!is_file($usedTraitDependentFile)) { + continue; + } + $filesToAnalyse[] = $usedTraitDependentFile; + } continue; } @@ -315,7 +340,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ? )); } - return new ResultCache($filesToAnalyse, false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredLinesToIgnore, $filteredUnmatchedLineIgnores, $filteredCollectedData, $invertedDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles'], $currentFileHashes); + return new ResultCache($filesToAnalyse, false, $data['lastFullAnalysisTime'], $meta, $filteredErrors, $filteredLocallyIgnoredErrors, $filteredLinesToIgnore, $filteredUnmatchedLineIgnores, $filteredCollectedData, $invertedDependenciesToReturn, $invertedUsedTraitDependenciesToReturn, $filteredExportedNodes, $data['projectExtensionFiles'], $currentFileHashes); } /** @@ -427,7 +452,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache if ($projectConfigArray !== null) { $meta['projectConfig'] = Neon::encode($projectConfigArray); } - $doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, ?array $dependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { + $doSave = function (array $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, ?array $dependencies, ?array $usedTraitDependencies, array $exportedNodes, array $projectExtensionFiles) use ($internalErrors, $resultCache, $output, $onlyFiles, $meta): bool { if ($onlyFiles) { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because only files were passed as analysed paths.'); @@ -440,6 +465,12 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } return false; } + if ($usedTraitDependencies === null) { + if ($output->isVeryVerbose()) { + $output->writeLineFormatted('Result cache was not saved because of error in used trait dependencies.'); + } + return false; + } if (count($internalErrors) > 0) { if ($output->isVeryVerbose()) { @@ -469,7 +500,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache } } - $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles, $resultCache->getCurrentFileHashes(), $meta); + $this->save($resultCache->getLastFullAnalysisTime(), $errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles, $resultCache->getCurrentFileHashes(), $meta); if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache is saved.'); @@ -485,7 +516,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache if ($analyserResult->getDependencies() !== null) { $projectExtensionFiles = $this->getProjectExtensionFiles($projectConfigArray, $analyserResult->getDependencies()); } - $saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $analyserResult->getLinesToIgnore(), $analyserResult->getUnmatchedLineIgnores(), $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles); + $saved = $doSave($freshErrorsByFile, $freshLocallyIgnoredErrorsByFile, $analyserResult->getLinesToIgnore(), $analyserResult->getUnmatchedLineIgnores(), $freshCollectedDataByFile, $analyserResult->getDependencies(), $analyserResult->getUsedTraitDependencies(), $analyserResult->getExportedNodes(), $projectExtensionFiles); } else { if ($output->isVeryVerbose()) { $output->writeLineFormatted('Result cache was not saved because it was not requested.'); @@ -498,7 +529,8 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $errorsByFile = $this->mergeErrors($resultCache, $freshErrorsByFile); $locallyIgnoredErrorsByFile = $this->mergeLocallyIgnoredErrors($resultCache, $freshLocallyIgnoredErrorsByFile); $collectedDataByFile = $this->mergeCollectedData($resultCache, $freshCollectedDataByFile); - $dependencies = $this->mergeDependencies($resultCache, $analyserResult->getDependencies()); + $dependencies = $this->mergeDependencies($resultCache->getDependencies(), $resultCache->getFilesToAnalyse(), $analyserResult->getDependencies()); + $usedTraitDependencies = $this->mergeDependencies($resultCache->getUsedTraitDependencies(), $resultCache->getFilesToAnalyse(), $analyserResult->getUsedTraitDependencies()); $exportedNodes = $this->mergeExportedNodes($resultCache, $analyserResult->getExportedNodes()); $linesToIgnore = $this->mergeLinesToIgnore($resultCache, $analyserResult->getLinesToIgnore()); $unmatchedLineIgnores = $this->mergeUnmatchedLineIgnores($resultCache, $analyserResult->getUnmatchedLineIgnores()); @@ -525,7 +557,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $projectExtensionFiles[$file] = [$hash, true, $className]; } } - $saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $exportedNodes, $projectExtensionFiles); + $saved = $doSave($errorsByFile, $locallyIgnoredErrorsByFile, $linesToIgnore, $unmatchedLineIgnores, $collectedDataByFile, $dependencies, $usedTraitDependencies, $exportedNodes, $projectExtensionFiles); } $flatErrors = []; @@ -559,6 +591,7 @@ public function process(AnalyserResult $analyserResult, ResultCache $resultCache $internalErrors, $flatCollectedData, $dependencies, + $usedTraitDependencies, $exportedNodes, $analyserResult->hasReachedInternalErrorsCountLimit(), $analyserResult->getPeakMemoryUsageBytes(), @@ -632,17 +665,18 @@ private function mergeCollectedData(ResultCache $resultCache, array $freshCollec } /** + * @param array> $resultCacheDependencies + * @param string[] $filesToAnalyse * @param array>|null $freshDependencies * @return array>|null */ - private function mergeDependencies(ResultCache $resultCache, ?array $freshDependencies): ?array + private function mergeDependencies(array $resultCacheDependencies, array $filesToAnalyse, ?array $freshDependencies): ?array { if ($freshDependencies === null) { return null; } $cachedDependencies = []; - $resultCacheDependencies = $resultCache->getDependencies(); $filesNoOneIsDependingOn = array_fill_keys(array_keys($resultCacheDependencies), true); foreach ($resultCacheDependencies as $file => $filesDependingOnFile) { foreach ($filesDependingOnFile as $fileDependingOnFile) { @@ -660,7 +694,7 @@ private function mergeDependencies(ResultCache $resultCache, ?array $freshDepend } $newDependencies = $cachedDependencies; - foreach ($resultCache->getFilesToAnalyse() as $file) { + foreach ($filesToAnalyse as $file) { if (array_key_exists($file, $this->fileReplacements)) { unset($newDependencies[$file]); $file = $this->fileReplacements[$file]; @@ -752,6 +786,7 @@ private function mergeUnmatchedLineIgnores(ResultCache $resultCache, array $fres * @param array $unmatchedLineIgnores * @param array> $collectedData * @param array> $dependencies + * @param array> $usedTraitDependencies * @param array> $exportedNodes * @param array $projectExtensionFiles * @param array $currentFileHashes @@ -765,6 +800,7 @@ private function save( array $unmatchedLineIgnores, array $collectedData, array $dependencies, + array $usedTraitDependencies, array $exportedNodes, array $projectExtensionFiles, array $currentFileHashes, @@ -786,6 +822,20 @@ private function save( } } + foreach ($usedTraitDependencies as $file => $fileUsedTraitDependencies) { + foreach ($fileUsedTraitDependencies as $usedTraitFileDep) { + if (!array_key_exists($usedTraitFileDep, $invertedDependencies)) { + $invertedDependencies[$usedTraitFileDep] = [ + 'fileHash' => $currentFileHashes[$usedTraitFileDep] ?? $this->getFileHash($usedTraitFileDep), + 'dependentFiles' => [], + 'usedTraitDependentFiles' => [], + ]; + unset($filesNoOneIsDependingOn[$usedTraitFileDep]); + } + $invertedDependencies[$usedTraitFileDep]['usedTraitDependentFiles'][] = $file; + } + } + foreach (array_keys($filesNoOneIsDependingOn) as $file) { if (array_key_exists($file, $invertedDependencies)) { throw new ShouldNotHappenException(); @@ -812,6 +862,14 @@ private function save( $dependentFiles = $fileData['dependentFiles']; sort($dependentFiles); $invertedDependencies[$file]['dependentFiles'] = $dependentFiles; + + $usedTraitDependentFiles = $fileData['usedTraitDependentFiles'] ?? []; + if (count($usedTraitDependentFiles) === 0) { + continue; + } + + sort($usedTraitDependentFiles); + $invertedDependencies[$file]['usedTraitDependentFiles'] = $usedTraitDependentFiles; } ksort($exportedNodes); diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index ce858a37546..009bd17d972 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -103,6 +103,7 @@ public function analyse( $intermediateAnalyserResult->getInternalErrors(), $intermediateAnalyserResult->getCollectedData(), $intermediateAnalyserResult->getDependencies(), + $intermediateAnalyserResult->getUsedTraitDependencies(), $intermediateAnalyserResult->getExportedNodes(), $intermediateAnalyserResult->hasReachedInternalErrorsCountLimit(), $intermediateAnalyserResult->getPeakMemoryUsageBytes(), @@ -190,7 +191,7 @@ private function runAnalyser( $errorOutput->getStyle()->progressStart($allAnalysedFilesCount); $errorOutput->getStyle()->progressAdvance($allAnalysedFilesCount); $errorOutput->getStyle()->progressFinish(); - return new AnalyserResult([], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true)); + return new AnalyserResult([], [], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true)); } if (!$debug) { diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index b0e6a3c467c..5b97a115ea6 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -52,7 +52,7 @@ public function runAnalyser( { $filesCount = count($files); if ($filesCount === 0) { - return new AnalyserResult([], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true)); + return new AnalyserResult([], [], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true)); } $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files); diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index f4364f7ab2d..54e1da4c41b 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -387,7 +387,7 @@ private function runAnalyser(LoopInterface $loop, Container $container, array $f $parallelAnalyser = $container->getByType(ParallelAnalyser::class); $filesCount = count($files); if ($filesCount === 0) { - return resolve(new AnalyserResult([], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true))); + return resolve(new AnalyserResult([], [], [], [], [], [], [], [], [], [], [], false, memory_get_peak_usage(true))); } /** @var Scheduler $scheduler */ diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 487e9cf23e4..d6cd900c3c2 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -223,6 +223,7 @@ private function runWorker( $unmatchedLineIgnores = []; $collectedData = []; $dependencies = []; + $usedTraitDependencies = []; $exportedNodes = []; foreach ($files as $file) { try { @@ -236,6 +237,7 @@ private function runWorker( $linesToIgnore[$file] = $fileAnalyserResult->getLinesToIgnore(); $unmatchedLineIgnores[$file] = $fileAnalyserResult->getUnmatchedLineIgnores(); $dependencies[$file] = $fileAnalyserResult->getDependencies(); + $usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies(); $exportedNodes[$file] = $fileAnalyserResult->getExportedNodes(); foreach ($fileErrors as $fileError) { $errors[] = $fileError; @@ -271,6 +273,7 @@ private function runWorker( 'collectedData' => $collectedData, 'memoryUsage' => memory_get_peak_usage(true), 'dependencies' => $dependencies, + 'usedTraitDependencies' => $usedTraitDependencies, 'exportedNodes' => $exportedNodes, 'files' => $files, 'internalErrorsCount' => $internalErrorsCount, diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index da033d28ba1..464c421f346 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -14,6 +14,7 @@ use PHPStan\File\FileHelper; use PHPStan\Node\ClassPropertyNode; use PHPStan\Node\InClassMethodNode; +use PHPStan\Node\InClassNode; use PHPStan\Node\InFunctionNode; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; @@ -469,6 +470,16 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies return new NodeDependencies($this->fileHelper, $dependenciesReflections, $this->exportedNodeResolver->resolve($scope->getFile(), $node)); } + public function resolveUsedTraitDependencies(InClassNode $inClassNode): NodeDependencies + { + $dependenciesReflections = []; + foreach ($inClassNode->getClassReflection()->getTraits(true) as $trait) { + $dependenciesReflections[] = $trait; + } + + return new NodeDependencies($this->fileHelper, $dependenciesReflections, null); + } + private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): bool { $items = $arrayNode->items; diff --git a/src/Dependency/ExportedNode/ExportedTraitNode.php b/src/Dependency/ExportedNode/ExportedTraitNode.php index f0f47ae0219..1e46b05e6ac 100644 --- a/src/Dependency/ExportedNode/ExportedTraitNode.php +++ b/src/Dependency/ExportedNode/ExportedTraitNode.php @@ -5,18 +5,84 @@ use JsonSerializable; use PHPStan\Dependency\ExportedNode; use PHPStan\Dependency\RootExportedNode; +use PHPStan\ShouldNotHappenException; use ReturnTypeWillChange; +use function array_map; +use function count; final class ExportedTraitNode implements RootExportedNode, JsonSerializable { - public function __construct(private string $traitName) + /** + * @param string[] $usedTraits + * @param ExportedTraitUseAdaptation[] $traitUseAdaptations + * @param ExportedNode[] $statements + * @param ExportedAttributeNode[] $attributes + */ + public function __construct( + private string $name, + private ?ExportedPhpDocNode $phpDoc, + private array $usedTraits, + private array $traitUseAdaptations, + private array $statements, + private array $attributes, + ) { } public function equals(ExportedNode $node): bool { - return false; + if (!$node instanceof self) { + return false; + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + if (count($this->attributes) !== count($node->attributes)) { + return false; + } + + foreach ($this->attributes as $i => $attribute) { + if (!$attribute->equals($node->attributes[$i])) { + return false; + } + } + + if (count($this->traitUseAdaptations) !== count($node->traitUseAdaptations)) { + return false; + } + + foreach ($this->traitUseAdaptations as $i => $ourTraitUseAdaptation) { + $theirTraitUseAdaptation = $node->traitUseAdaptations[$i]; + if (!$ourTraitUseAdaptation->equals($theirTraitUseAdaptation)) { + return false; + } + } + + if (count($this->statements) !== count($node->statements)) { + return false; + } + + foreach ($this->statements as $i => $statement) { + if ($statement->equals($node->statements[$i])) { + continue; + } + + return false; + } + + return $this->name === $node->name + && $this->usedTraits === $node->usedTraits; } /** @@ -25,16 +91,14 @@ public function equals(ExportedNode $node): bool */ public static function __set_state(array $properties): ExportedNode { - return new self($properties['traitName']); - } - - /** - * @param mixed[] $data - * @return self - */ - public static function decode(array $data): ExportedNode - { - return new self($data['traitName']); + return new self( + $properties['name'], + $properties['phpDoc'], + $properties['usedTraits'], + $properties['traitUseAdaptations'], + $properties['statements'], + $properties['attributes'], + ); } /** @@ -46,11 +110,46 @@ public function jsonSerialize() return [ 'type' => self::class, 'data' => [ - 'traitName' => $this->traitName, + 'name' => $this->name, + 'phpDoc' => $this->phpDoc, + 'usedTraits' => $this->usedTraits, + 'traitUseAdaptations' => $this->traitUseAdaptations, + 'statements' => $this->statements, + 'attributes' => $this->attributes, ], ]; } + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + $data['name'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, + $data['usedTraits'], + array_map(static function (array $traitUseAdaptationData): ExportedTraitUseAdaptation { + if ($traitUseAdaptationData['type'] !== ExportedTraitUseAdaptation::class) { + throw new ShouldNotHappenException(); + } + return ExportedTraitUseAdaptation::decode($traitUseAdaptationData['data']); + }, $data['traitUseAdaptations']), + array_map(static function (array $node): ExportedNode { + $nodeType = $node['type']; + + return $nodeType::decode($node['data']); + }, $data['statements']), + array_map(static function (array $attributeData): ExportedAttributeNode { + if ($attributeData['type'] !== ExportedAttributeNode::class) { + throw new ShouldNotHappenException(); + } + return ExportedAttributeNode::decode($attributeData['data']); + }, $data['attributes']), + ); + } + /** * @return self::TYPE_TRAIT */ @@ -61,7 +160,7 @@ public function getType(): string public function getName(): string { - return $this->traitName; + return $this->name; } } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 8e6eb17f61e..8a2b062e716 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -145,7 +145,52 @@ public function resolve(string $fileName, Node $node): ?RootExportedNode } if ($node instanceof Node\Stmt\Trait_ && isset($node->namespacedName)) { - return new ExportedTraitNode($node->namespacedName->toString()); + $docComment = $node->getDocComment(); + $usedTraits = []; + $adaptations = []; + foreach ($node->getTraitUses() as $traitUse) { + foreach ($traitUse->traits as $usedTraitName) { + $usedTraits[] = $usedTraitName->toString(); + } + foreach ($traitUse->adaptations as $adaptation) { + $adaptations[] = $adaptation; + } + } + + $className = $node->namespacedName->toString(); + + return new ExportedTraitNode( + $className, + $this->exportPhpDocNode( + $fileName, + $className, + null, + $docComment !== null ? $docComment->getText() : null, + ), + $usedTraits, + array_map(static function (Node\Stmt\TraitUseAdaptation $adaptation): ExportedTraitUseAdaptation { + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + return ExportedTraitUseAdaptation::createAlias( + $adaptation->trait !== null ? $adaptation->trait->toString() : null, + $adaptation->method->toString(), + $adaptation->newModifier, + $adaptation->newName !== null ? $adaptation->newName->toString() : null, + ); + } + + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence) { + return ExportedTraitUseAdaptation::createPrecedence( + $adaptation->trait !== null ? $adaptation->trait->toString() : null, + $adaptation->method->toString(), + array_map(static fn (Name $name): string => $name->toString(), $adaptation->insteadof), + ); + } + + throw new ShouldNotHappenException(); + }, $adaptations), + $this->exportClassStatements($node->stmts, $fileName, $className), + $this->exportAttributeNodes($node->attrGroups), + ); } if ($node instanceof Function_) { diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 569f038aeaf..7d5819e9c74 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -84,6 +84,7 @@ public function analyse( $internalErrorsCount = 0; $collectedData = []; $dependencies = []; + $usedTraitDependencies = []; $reachedInternalErrorsCountLimit = false; $exportedNodes = []; @@ -91,7 +92,7 @@ public function analyse( $deferred = new Deferred(); $server = new TcpServer('127.0.0.1:0', $loop); - $this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$exportedNodes, &$peakMemoryUsages): void { + $this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$peakMemoryUsages): void { if (count($jobs) > 0 && $internalErrorsCount === 0) { $internalErrors[] = new InternalError( 'Some parallel worker jobs have not finished.', @@ -113,6 +114,7 @@ public function analyse( $internalErrors, $collectedData, $internalErrorsCount === 0 ? $dependencies : null, + $internalErrorsCount === 0 ? $usedTraitDependencies : null, $exportedNodes, $reachedInternalErrorsCountLimit, array_sum($peakMemoryUsages), // not 100% correct as the peak usages of workers might not have met @@ -187,7 +189,7 @@ public function analyse( $commandOptions, $input, ), $loop, $this->processTimeout); - $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$exportedNodes, &$peakMemoryUsages, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier, $onFileAnalysisHandler): void { + $process->start(function (array $json) use ($process, &$internalErrors, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$usedTraitDependencies, &$exportedNodes, &$peakMemoryUsages, &$jobs, $postFileCallback, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, $processIdentifier, $onFileAnalysisHandler): void { $fileErrors = []; foreach ($json['errors'] as $jsonError) { $fileErrors[] = Error::decode($jsonError); @@ -233,6 +235,14 @@ public function analyse( $dependencies[$file] = $fileDependencies; } + /** + * @var string $file + * @var array $fileUsedTraitDependencies + */ + foreach ($json['usedTraitDependencies'] as $file => $fileUsedTraitDependencies) { + $usedTraitDependencies[$file] = $fileUsedTraitDependencies; + } + foreach ($json['linesToIgnore'] as $file => $fileLinesToIgnore) { if (count($fileLinesToIgnore) === 0) { continue; From 275d3b5a85cc84459bca8d965ed1127837665b02 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 16:50:02 +0200 Subject: [PATCH 1403/3097] InitializerExprTypeResolver - optimize arithmetical and bitwise operations for large types --- .../InitializerExprTypeResolver.php | 350 +++++++++--------- 1 file changed, 170 insertions(+), 180 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index a55fd7831d4..6f08799d4a5 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -608,31 +608,30 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { - $resultType = $this->getTypeFromValue($leftTypeInner->getValue() & $rightTypeInner->getValue()); - } else { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { + $resultType = $this->getTypeFromValue($leftTypeInner->getValue() & $rightTypeInner->getValue()); + } else { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() & $rightNumberType->getValue()); - } - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() & $rightNumberType->getValue()); + } + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } + return TypeCombinator::union(...$resultTypes); } - return TypeCombinator::union(...$resultTypes); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -675,31 +674,30 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { - $resultType = $this->getTypeFromValue($leftTypeInner->getValue() | $rightTypeInner->getValue()); - } else { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { + $resultType = $this->getTypeFromValue($leftTypeInner->getValue() | $rightTypeInner->getValue()); + } else { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() | $rightNumberType->getValue()); - } - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() | $rightNumberType->getValue()); + } + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } + return TypeCombinator::union(...$resultTypes); } - return TypeCombinator::union(...$resultTypes); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -732,31 +730,30 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { - $resultType = $this->getTypeFromValue($leftTypeInner->getValue() ^ $rightTypeInner->getValue()); - } else { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + if ($leftTypeInner instanceof ConstantStringType && $rightTypeInner instanceof ConstantStringType) { + $resultType = $this->getTypeFromValue($leftTypeInner->getValue() ^ $rightTypeInner->getValue()); + } else { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() ^ $rightNumberType->getValue()); - } - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() ^ $rightNumberType->getValue()); + } + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } + return TypeCombinator::union(...$resultTypes); } - return TypeCombinator::union(...$resultTypes); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -818,31 +815,30 @@ public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - if (in_array($rightNumberType->getValue(), [0, 0.0], true)) { - return new ErrorType(); - } + if (in_array($rightNumberType->getValue(), [0, 0.0], true)) { + return new ErrorType(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() / $rightNumberType->getValue()); // @phpstan-ignore binaryOp.invalid + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } + return TypeCombinator::union(...$resultTypes); } - return TypeCombinator::union(...$resultTypes); } $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); @@ -878,32 +874,31 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback): if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $rightIntegerValue = (int) $rightNumberType->getValue(); - if ($rightIntegerValue === 0) { - return new ErrorType(); - } + $rightIntegerValue = (int) $rightNumberType->getValue(); + if ($rightIntegerValue === 0) { + return new ErrorType(); + } - $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue((int) $leftNumberType->getValue() % $rightIntegerValue); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } + return TypeCombinator::union(...$resultTypes); } - return TypeCombinator::union(...$resultTypes); } $integerType = $rightType->toInteger(); @@ -975,28 +970,27 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue()); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() + $rightNumberType->getValue()); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } - } - return TypeCombinator::union(...$resultTypes); + return TypeCombinator::union(...$resultTypes); + } } $leftConstantArrays = $leftType->getConstantArrays(); @@ -1137,28 +1131,27 @@ public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback) if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue()); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() - $rightNumberType->getValue()); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } - } - return TypeCombinator::union(...$resultTypes); + return TypeCombinator::union(...$resultTypes); + } } return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType); @@ -1179,28 +1172,27 @@ public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue()); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue($leftNumberType->getValue() * $rightNumberType->getValue()); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } - } - return TypeCombinator::union(...$resultTypes); + return TypeCombinator::union(...$resultTypes); + } } $leftNumberType = $leftType->toNumber(); @@ -1261,32 +1253,31 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - if ($rightNumberType->getValue() < 0) { - return new ErrorType(); - } + if ($rightNumberType->getValue() < 0) { + return new ErrorType(); + } - $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue())); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) << intval($rightNumberType->getValue())); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } - } - return TypeCombinator::union(...$resultTypes); + return TypeCombinator::union(...$resultTypes); + } } $leftNumberType = $leftType->toNumber(); @@ -1318,32 +1309,31 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; - foreach ($leftTypes as $leftTypeInner) { - foreach ($rightTypes as $rightTypeInner) { - $leftNumberType = $leftTypeInner->toNumber(); - $rightNumberType = $rightTypeInner->toNumber(); + if (!$generalize) { + foreach ($leftTypes as $leftTypeInner) { + foreach ($rightTypes as $rightTypeInner) { + $leftNumberType = $leftTypeInner->toNumber(); + $rightNumberType = $rightTypeInner->toNumber(); - if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { - return new ErrorType(); - } + if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) { + return new ErrorType(); + } - if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { - throw new ShouldNotHappenException(); - } + if (!$leftNumberType instanceof ConstantScalarType || !$rightNumberType instanceof ConstantScalarType) { + throw new ShouldNotHappenException(); + } - if ($rightNumberType->getValue() < 0) { - return new ErrorType(); - } + if ($rightNumberType->getValue() < 0) { + return new ErrorType(); + } - $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue())); - if ($generalize) { - $resultType = $resultType->generalize(GeneralizePrecision::lessSpecific()); + $resultType = $this->getTypeFromValue(intval($leftNumberType->getValue()) >> intval($rightNumberType->getValue())); + $resultTypes[] = $resultType; } - $resultTypes[] = $resultType; } - } - return TypeCombinator::union(...$resultTypes); + return TypeCombinator::union(...$resultTypes); + } } $leftNumberType = $leftType->toNumber(); From 572e0e8f26eb3618f22127f694170f5cc2f2c6cb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 19:35:51 +0200 Subject: [PATCH 1404/3097] Optimization --- .../InitializerExprTypeResolver.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 6f08799d4a5..83b3e4ac96f 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -632,6 +632,9 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall } return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -698,6 +701,9 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb } return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -754,6 +760,9 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall } return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } if ($leftType->isString()->yes() && $rightType->isString()->yes()) { @@ -839,6 +848,9 @@ public function getDivType(Expr $left, Expr $right, callable $getTypeCallback): } return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $rightScalarValues = $rightType->toNumber()->getConstantScalarValues(); @@ -899,6 +911,9 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback): } return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $integerType = $rightType->toInteger(); @@ -991,6 +1006,9 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $leftConstantArrays = $leftType->getConstantArrays(); @@ -1152,6 +1170,9 @@ public function getMinusType(Expr $left, Expr $right, callable $getTypeCallback) return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } return $this->resolveCommonMath(new BinaryOp\Minus($left, $right), $leftType, $rightType); @@ -1193,6 +1214,9 @@ public function getMulType(Expr $left, Expr $right, callable $getTypeCallback): return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $leftNumberType = $leftType->toNumber(); @@ -1278,6 +1302,9 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $leftNumberType = $leftType->toNumber(); @@ -1334,6 +1361,9 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall return TypeCombinator::union(...$resultTypes); } + + $leftType = $this->optimizeScalarType($leftType); + $rightType = $this->optimizeScalarType($rightType); } $leftNumberType = $leftType->toNumber(); @@ -1346,6 +1376,33 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall return $this->resolveCommonMath(new Expr\BinaryOp\ShiftRight($left, $right), $leftType, $rightType); } + private function optimizeScalarType(Type $type): Type + { + $types = []; + if ($type->isInteger()->yes()) { + $types[] = new IntegerType(); + } + if ($type->isString()->yes()) { + $types[] = new StringType(); + } + if ($type->isFloat()->yes()) { + $types[] = new FloatType(); + } + if ($type->isNull()->yes()) { + $types[] = new NullType(); + } + + if (count($types) === 0) { + return new ErrorType(); + } + + if (count($types) === 1) { + return $types[0]; + } + + return new UnionType($types); + } + /** * @return TypeResult */ From 80b40f2177aafedad4557363673126f00583c5ca Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 May 2025 20:26:23 +0200 Subject: [PATCH 1405/3097] Limit how big int-mask type can be --- src/PhpDoc/TypeNodeResolver.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index af83130ac1a..84bc69b35bd 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1234,6 +1234,10 @@ private function expandIntMaskToType(Type $type): ?Type return IntegerRangeType::fromInterval($min, $max); } + if (count($values) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) { + return IntegerRangeType::fromInterval($min, $max); + } + return TypeCombinator::union(...array_map(static fn ($value) => new ConstantIntegerType($value), $values)); } From f5f0b3758ea9a87ca3947d3636802a0d0cb6866a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 May 2025 11:44:00 +0200 Subject: [PATCH 1406/3097] Fix reported file path for `--tmp-file` and ignoring errors --- .github/workflows/e2e-tests.yml | 3 +- src/Command/AnalyseApplication.php | 142 ++++++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9a351d63325..eb5856de18f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -206,8 +206,9 @@ jobs: OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw --tmp-file differentFoo.php --instead-of src/Foo.php") echo "$OUTPUT" - ../bashunit -a contains 'differentFoo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return float but returns string. [identifier=return.type]' "$OUTPUT" ../bashunit -a not_contains 'Foo.php:10:Method EditorModeE2E\Foo::doFoo() should return int but returns string. [identifier=return.type]' "$OUTPUT" + ../bashunit -a not_contains 'differentFoo.php' "$OUTPUT" ../bashunit -a contains 'Bar.php:10:Parameter #1 $s of method EditorModeE2E\Bar::requireString() expects string, float given. [identifier=argument.type]' "$OUTPUT" ../bashunit -a contains 'Result cache restored. 2 files will be reanalysed.' "$OUTPUT" ../bashunit -a contains 'Result cache was not saved because of --tmp-file and --instead-of CLI options passed (editor mode).' "$OUTPUT" diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 009bd17d972..b96bc347df9 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -4,6 +4,8 @@ use PHPStan\Analyser\AnalyserResult; use PHPStan\Analyser\AnalyserResultFinalizer; +use PHPStan\Analyser\Error; +use PHPStan\Analyser\FileAnalyserResult; use PHPStan\Analyser\Ignore\IgnoredErrorHelper; use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory; use PHPStan\Internal\BytesHelper; @@ -19,6 +21,9 @@ use function sha1_file; use function sprintf; +/** + * @phpstan-import-type LinesToIgnore from FileAnalyserResult + */ final class AnalyseApplication { @@ -111,7 +116,11 @@ public function analyse( } $resultCacheResult = $resultCacheManager->process($intermediateAnalyserResult, $resultCache, $errorOutput, $onlyFiles, true); - $analyserResult = $this->analyserResultFinalizer->finalize($resultCacheResult->getAnalyserResult(), $onlyFiles, $debug)->getAnalyserResult(); + $analyserResult = $this->analyserResultFinalizer->finalize( + $this->switchTmpFileInAnalyserResult($resultCacheResult->getAnalyserResult(), $insteadOfFile, $tmpFile), + $onlyFiles, + $debug, + )->getAnalyserResult(); $internalErrors = $analyserResult->getInternalErrors(); $errors = array_merge( $analyserResult->getErrors(), @@ -232,4 +241,135 @@ private function runAnalyser( return $analyserResult; } + private function switchTmpFileInAnalyserResult( + AnalyserResult $analyserResult, + ?string $insteadOfFile, + ?string $tmpFile, + ): AnalyserResult + { + if ($insteadOfFile === null || $tmpFile === null) { + return $analyserResult; + } + + $collectedData = []; + foreach ($analyserResult->getCollectedData() as $data) { + if ($data->getFilePath() === $tmpFile) { + $data = $data->changeFilePath($insteadOfFile); + } + + $collectedData[] = $data; + } + + $dependencies = null; + if ($analyserResult->getDependencies() !== null) { + $dependencies = $this->switchTmpFileInDependencies($analyserResult->getDependencies(), $insteadOfFile, $tmpFile); + } + $usedTraitDependencies = null; + if ($analyserResult->getUsedTraitDependencies() !== null) { + $usedTraitDependencies = $this->switchTmpFileInDependencies($analyserResult->getUsedTraitDependencies(), $insteadOfFile, $tmpFile); + } + + $exportedNodes = []; + foreach ($analyserResult->getExportedNodes() as $file => $fileExportedNodes) { + if ($file === $tmpFile) { + $file = $insteadOfFile; + } + + $exportedNodes[$file] = $fileExportedNodes; + } + + return new AnalyserResult( + $this->switchTmpFileInErrors($analyserResult->getUnorderedErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getFilteredPhpErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getAllPhpErrors(), $insteadOfFile, $tmpFile), + $this->switchTmpFileInErrors($analyserResult->getLocallyIgnoredErrors(), $insteadOfFile, $tmpFile), + $this->swittchTmpFileInLinesToIgnore($analyserResult->getLinesToIgnore(), $insteadOfFile, $tmpFile), + $this->swittchTmpFileInLinesToIgnore($analyserResult->getUnmatchedLineIgnores(), $insteadOfFile, $tmpFile), + $analyserResult->getInternalErrors(), + $collectedData, + $dependencies, + $usedTraitDependencies, + $exportedNodes, + $analyserResult->hasReachedInternalErrorsCountLimit(), + $analyserResult->getPeakMemoryUsageBytes(), + ); + } + + /** + * @param array> $dependencies + * @return array> + */ + private function switchTmpFileInDependencies(array $dependencies, string $insteadOfFile, string $tmpFile): array + { + $newDependencies = []; + foreach ($dependencies as $dependencyFile => $dependentFiles) { + $new = []; + foreach ($dependentFiles as $file) { + if ($file === $tmpFile) { + $new[] = $insteadOfFile; + continue; + } + + $new[] = $file; + } + + $key = $dependencyFile; + if ($key === $tmpFile) { + $key = $insteadOfFile; + } + + $newDependencies[$key] = $new; + } + + return $newDependencies; + } + + /** + * @param list $errors + * @return list + */ + private function switchTmpFileInErrors(array $errors, string $insteadOfFile, string $tmpFile): array + { + $newErrors = []; + foreach ($errors as $error) { + if ($error->getFilePath() === $tmpFile) { + $error = $error->changeFilePath($insteadOfFile); + } + if ($error->getTraitFilePath() === $tmpFile) { + $error = $error->changeTraitFilePath($insteadOfFile); + } + + $newErrors[] = $error; + } + + return $newErrors; + } + + /** + * @param array $linesToIgnore + * @return array + */ + private function swittchTmpFileInLinesToIgnore(array $linesToIgnore, string $insteadOfFile, string $tmpFile): array + { + $newLinesToIgnore = []; + foreach ($linesToIgnore as $file => $lines) { + if ($file === $tmpFile) { + $file = $insteadOfFile; + } + + $newLines = []; + foreach ($lines as $f => $line) { + if ($f === $tmpFile) { + $f = $insteadOfFile; + } + + $newLines[$f] = $line; + } + + $newLinesToIgnore[$file] = $newLines; + } + + return $newLinesToIgnore; + } + } From bc6352b8edd931c6a7eb51d684becdee19becc88 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 May 2025 22:07:37 +0200 Subject: [PATCH 1407/3097] File passed to `--tmp-file` cannot be in project files --- src/Command/AnalyseCommand.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 81dedfb56e0..7452846e542 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -286,6 +286,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + if ($inceptionResult->getEditorModeTmpFile() !== null) { + if (in_array($inceptionResult->getEditorModeTmpFile(), $files, true)) { + $inceptionResult->getStdOutput()->getStyle()->error(sprintf('File %s passed to --tmp-file is already in analysed project files.', $inceptionResult->getEditorModeInsteadOfFile())); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + } + $analysedConfigFiles = array_intersect($files, $container->getParameter('allConfigFiles')); /** @var RelativePathHelper $relativePathHelper */ $relativePathHelper = $container->getService('relativePathHelper'); From 0c4660d8e18a6732365c907edfd2988e7063f0c9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 21 May 2025 22:18:26 +0200 Subject: [PATCH 1408/3097] Update errorCode return type --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f5d9a827157..60f67ce5e29 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8678,7 +8678,7 @@ 'PDO::beginTransaction' => ['bool'], 'PDO::commit' => ['bool'], 'PDO::cubrid_schema' => ['array', 'schema_type'=>'int', 'table_name='=>'string', 'col_name='=>'string'], -'PDO::errorCode' => ['string'], +'PDO::errorCode' => ['string|null'], 'PDO::errorInfo' => ['array'], 'PDO::exec' => ['int|false', 'query'=>'string'], 'PDO::getAttribute' => ['', 'attribute'=>'int'], @@ -8721,7 +8721,7 @@ 'PDOStatement::closeCursor' => ['bool'], 'PDOStatement::columnCount' => ['0|positive-int'], 'PDOStatement::debugDumpParams' => ['void'], -'PDOStatement::errorCode' => ['string'], +'PDOStatement::errorCode' => ['string|null'], 'PDOStatement::errorInfo' => ['array'], 'PDOStatement::execute' => ['bool', 'bound_input_params='=>'?array'], 'PDOStatement::fetch' => ['mixed', 'how='=>'int', 'orientation='=>'int', 'offset='=>'int'], From 33c2cb196595efa9e356dd6030f165bf2af447f7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 May 2025 11:42:21 +0200 Subject: [PATCH 1409/3097] Fix recursion with object shapes in `@property` referencing other class and then back in recursive manner --- src/Type/ObjectType.php | 20 +++++++++++++-- .../Analyser/AnalyserIntegrationTest.php | 6 +++++ tests/PHPStan/Analyser/data/bug-13057.php | 25 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-13057.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 96d30d09587..cdc66a14da6 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -24,11 +24,13 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; +use PHPStan\Reflection\Dummy\DummyPropertyReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; +use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnionTypeUnresolvedPropertyPrototypeReflection; @@ -159,7 +161,8 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createMaybe(); } - if ($classReflection->hasProperty($propertyName)) { + $classHasProperty = RecursionGuard::run($this, static fn (): bool => $classReflection->hasProperty($propertyName)); + if ($classHasProperty === true || $classHasProperty instanceof ErrorType) { return TrinaryLogic::createYes(); } @@ -225,7 +228,17 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ClassNotFoundException($this->className); } - $property = $nakedClassReflection->getProperty($propertyName, $scope); + $property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getProperty($propertyName, $scope)); + if ($property instanceof ErrorType) { + $property = new DummyPropertyReflection(); + + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); $resolvedClassReflection = null; @@ -247,6 +260,9 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + /** + * @deprecated Not in use anymore. + */ public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection { $classReflection = $this->getNakedClassReflection(); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 1e5491bbc3b..8f853887b3f 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -555,6 +555,12 @@ public function testBug6442(): void $this->assertSame(9, $errors[1]->getLine()); } + public function testBug13057(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-13057.php'); + $this->assertNoErrors($errors); + } + public function testBug6375(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-6375.php'); diff --git a/tests/PHPStan/Analyser/data/bug-13057.php b/tests/PHPStan/Analyser/data/bug-13057.php new file mode 100644 index 00000000000..9b9c4eabae9 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-13057.php @@ -0,0 +1,25 @@ +modelB->extra); + assertType('string', $b->modelA->extra); +}; From 812d7da97bbf0ee3a723f8183bf7109f7edd8c3a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 May 2025 12:06:08 +0200 Subject: [PATCH 1410/3097] Fix build --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 7 ++----- tests/PHPStan/Analyser/data/bug-6300.php | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 8f853887b3f..6d487c2dda5 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -509,12 +509,9 @@ public function testBug6255(): void public function testBug6300(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-6300.php'); - $this->assertCount(2, $errors); + $this->assertCount(1, $errors); $this->assertSame('Call to an undefined method Bug6300\Bar::get().', $errors[0]->getMessage()); - $this->assertSame(23, $errors[0]->getLine()); - - $this->assertSame('Access to an undefined property Bug6300\Bar::$fooProp.', $errors[1]->getMessage()); - $this->assertSame(24, $errors[1]->getLine()); + $this->assertSame(27, $errors[0]->getLine()); } public function testBug6466(): void diff --git a/tests/PHPStan/Analyser/data/bug-6300.php b/tests/PHPStan/Analyser/data/bug-6300.php index c9244c6cec1..71723a5afec 100644 --- a/tests/PHPStan/Analyser/data/bug-6300.php +++ b/tests/PHPStan/Analyser/data/bug-6300.php @@ -2,9 +2,12 @@ namespace Bug6300; +use AllowDynamicProperties; + /** * @mixin Bar */ +#[AllowDynamicProperties] class Foo { @@ -13,6 +16,7 @@ class Foo /** * @mixin Foo */ +#[AllowDynamicProperties] class Bar { From d4107e426d1dd9edfd4a232db2f1dc09372853ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 May 2025 12:06:58 +0200 Subject: [PATCH 1411/3097] Fix --- src/Type/ObjectType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 2adcf1b6ae2..e98bdb9626b 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -229,7 +229,7 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember $property = RecursionGuard::run($this, static fn () => $nakedClassReflection->getProperty($propertyName, $scope)); if ($property instanceof ErrorType) { - $property = new DummyPropertyReflection(); + $property = new DummyPropertyReflection($propertyName); return new CallbackUnresolvedPropertyPrototypeReflection( $property, From f356b1676f4dcef56290ca246dea3a3cf7a1bc3d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 22 May 2025 14:08:54 +0200 Subject: [PATCH 1412/3097] Fix `@var` PHPDoc type inheritance for class constants --- src/PhpDoc/PhpDocNodeResolver.php | 4 +-- src/PhpDoc/ResolvedPhpDocBlock.php | 2 +- src/PhpDoc/Tag/VarTag.php | 14 ++++++++-- src/Reflection/ClassReflection.php | 12 ++++++--- ...patibleClassConstantPhpDocTypeRuleTest.php | 9 +++++++ tests/PHPStan/Rules/PhpDoc/data/bug-10911.php | 27 +++++++++++++++++++ 6 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-10911.php diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 02fed04bcc6..91aaf7037cb 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -76,9 +76,9 @@ public function resolveVarTags(PhpDocNode $phpDocNode, NameScope $nameScope): ar } if ($tagValue->variableName !== '') { $variableName = substr($tagValue->variableName, 1); - $resolved[$variableName] = new VarTag($type); + $resolved[$variableName] = new VarTag($type, true); } else { - $varTag = new VarTag($type); + $varTag = new VarTag($type, true); $tagResolved[] = $varTag; } } diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index ed45d60e483..771490e7c98 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -887,7 +887,7 @@ private static function mergeVarTags(array $varTags, array $parents, array $pare private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array { foreach ($parent->getVarTags() as $key => $parentVarTag) { - return [$key => self::resolveTemplateTypeInTag($parentVarTag, $phpDocBlock, TemplateTypeVariance::createInvariant())]; + return [$key => self::resolveTemplateTypeInTag($parentVarTag->toImplicit(), $phpDocBlock, TemplateTypeVariance::createInvariant())]; } return null; diff --git a/src/PhpDoc/Tag/VarTag.php b/src/PhpDoc/Tag/VarTag.php index 0d93daeac81..b1ae823ea06 100644 --- a/src/PhpDoc/Tag/VarTag.php +++ b/src/PhpDoc/Tag/VarTag.php @@ -11,7 +11,7 @@ class VarTag implements TypedTag { - public function __construct(private Type $type) + public function __construct(private Type $type, private bool $isExplicit) { } @@ -25,7 +25,17 @@ public function getType(): Type */ public function withType(Type $type): TypedTag { - return new self($type); + return new self($type, $this->isExplicit); + } + + public function isExplicit(): bool + { + return $this->isExplicit; + } + + public function toImplicit(): self + { + return new self($this->type, false); } } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index d031cb88169..23f1432c3c3 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1094,10 +1094,6 @@ public function getConstant(string $name): ClassConstantReflection $isDeprecated = $resolvedPhpDoc->isDeprecated(); $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); - $varTags = $resolvedPhpDoc->getVarTags(); - if (isset($varTags[0]) && count($varTags) === 1) { - $phpDocType = $varTags[0]->getType(); - } $nativeType = null; if ($reflectionConstant->getType() !== null) { @@ -1106,6 +1102,14 @@ public function getConstant(string $name): ClassConstantReflection $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType']; } + $varTags = $resolvedPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $varTag = $varTags[0]; + if ($varTag->isExplicit() || $nativeType === null || $nativeType->isSuperTypeOf($varTag->getType())->yes()) { + $phpDocType = $varTag->getType(); + } + } + $this->constants[$name] = new ClassConstantReflection( $this->initializerExprTypeResolver, $declaringClass, diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php index a66be60a2d8..91a74e428b6 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php @@ -50,4 +50,13 @@ public function testNativeType(): void ]); } + public function testBug10911(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->analyse([__DIR__ . '/data/bug-10911.php'], []); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php b/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php new file mode 100644 index 00000000000..7cce5e8b884 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-10911.php @@ -0,0 +1,27 @@ += 8.3 + +namespace Bug10911; + +abstract class Model +{ + /** + * The name of the "created at" column. + * + * @var string|null + */ + const CREATED_AT = 'created_at'; + + /** + * The name of the "updated at" column. + * + * @var string|null + */ + const UPDATED_AT = 'updated_at'; +} + +class TestModel extends Model +{ + const string CREATED_AT = 'data_criacao'; + const string UPDATED_AT = 'data_alteracao'; + const DELETED_AT = null; +} From 02066c7350e4a60a5cdcabacc613bd62ae0bf907 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 May 2025 14:54:32 +0200 Subject: [PATCH 1413/3097] Fix ClosureType::equals() for pure/impure closures --- src/Type/ClosureType.php | 3 +- tests/PHPStan/Rules/DeadCode/NoopRuleTest.php | 5 ++++ .../PHPStan/Rules/DeadCode/data/bug-13067.php | 30 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-13067.php diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 9a12b00fbb0..b0a39b8ccf6 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -236,7 +236,8 @@ public function equals(Type $type): bool return false; } - return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise()); + return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise()) + && $this->isPure()->equals($type->isPure()); } public function describe(VerbosityLevel $level): string diff --git a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php index 2e082297d1c..5750c03c3e7 100644 --- a/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/NoopRuleTest.php @@ -153,4 +153,9 @@ public function testBug11361(): void $this->analyse([__DIR__ . '/data/bug-11361.php'], []); } + public function testBug13067(): void + { + $this->analyse([__DIR__ . '/data/bug-13067.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-13067.php b/tests/PHPStan/Rules/DeadCode/data/bug-13067.php new file mode 100644 index 00000000000..76386890bc7 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-13067.php @@ -0,0 +1,30 @@ + Date: Thu, 22 May 2025 16:57:21 +0200 Subject: [PATCH 1414/3097] Allow `getenv(null)` for PHP 8.0+ Co-authored-by: Ondrej Mirtes --- resources/functionMap_php80delta.php | 2 ++ tests/PHPStan/Analyser/nsrt/getenv-php74.php | 23 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/getenv-php80.php | 20 ++++++++++++++++ .../CallToFunctionParametersRuleTest.php | 13 +++++++++++ .../Rules/Functions/data/bug-13065.php | 15 ++++++++++++ 5 files changed, 73 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/getenv-php74.php create mode 100644 tests/PHPStan/Analyser/nsrt/getenv-php80.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-13065.php diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index c6a9dfe91ae..ec070388a3c 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -78,6 +78,8 @@ 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], + 'getenv' => ['string|false', 'varname'=>'string', 'local_only='=>'bool'], + 'getenv\'1' => ['array', 'varname='=>'null', 'local_only='=>'bool'], 'ldap_set_rebind_proc' => ['bool', 'ldap'=>'resource', 'callback'=>'?callable'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], 'mb_encoding_aliases' => ['list', 'encoding'=>'string'], diff --git a/tests/PHPStan/Analyser/nsrt/getenv-php74.php b/tests/PHPStan/Analyser/nsrt/getenv-php74.php new file mode 100644 index 00000000000..a100e476869 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/getenv-php74.php @@ -0,0 +1,23 @@ +', getenv()); + assertType('string|false', getenv('foo')); + + assertType('string|false', getenv($stringOrNull)); + assertType('string|false', getenv($mixed)); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/getenv-php80.php b/tests/PHPStan/Analyser/nsrt/getenv-php80.php new file mode 100644 index 00000000000..c5036fa153e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/getenv-php80.php @@ -0,0 +1,20 @@ += 8.0 + +namespace GetenvPHP80; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + public function test(string|null $stringOrNull, mixed $mixed) + { + assertType('array', getenv(null)); + assertType('array', getenv()); + assertType('string|false', getenv('foo')); + + assertType('array|string|false', getenv($stringOrNull)); + assertType('array|string|false', getenv($mixed)); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 31538252d86..9358802c16a 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -2102,4 +2102,17 @@ public function testBug12499(): void $this->analyse([__DIR__ . '/data/bug-12499.php'], []); } + public function testBug13065(): void + { + $errors = []; + if (PHP_VERSION_ID < 80000) { + $errors[] = [ + 'Parameter #1 $varname of function getenv expects string, null given.', + 10, + ]; + } + + $this->analyse([__DIR__ . '/data/bug-13065.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-13065.php b/tests/PHPStan/Rules/Functions/data/bug-13065.php new file mode 100644 index 00000000000..f63acf9989b --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-13065.php @@ -0,0 +1,15 @@ + Date: Sat, 24 May 2025 19:19:43 +0200 Subject: [PATCH 1415/3097] Moved the list of relative paths in parameters to parametersSchema.neon This can now be extended by extensions and project configs! --- build/ignore-by-architecture.neon.php | 11 +++---- conf/config.neon | 1 + conf/parametersSchema.neon | 19 ++++++++++++ src/Command/CommandHelper.php | 4 +++ src/DependencyInjection/ContainerFactory.php | 3 +- .../ExpandRelativePathExtension.php | 17 +++++++++++ src/DependencyInjection/LoaderFactory.php | 10 +++++-- src/DependencyInjection/NeonAdapter.php | 29 ++++++------------- 8 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 src/DependencyInjection/ExpandRelativePathExtension.php diff --git a/build/ignore-by-architecture.neon.php b/build/ignore-by-architecture.neon.php index a6c86b46cfd..4b2208f5feb 100644 --- a/build/ignore-by-architecture.neon.php +++ b/build/ignore-by-architecture.neon.php @@ -1,11 +1,12 @@ load(__DIR__ . '/baseline-32bit.neon'); + $config = []; + $config['includes'] = [ + __DIR__ . '/baseline-32bit.neon', + ]; + + return $config; } return []; diff --git a/conf/config.neon b/conf/config.neon index 2f436f5cd98..96115b942da 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -209,6 +209,7 @@ parameters: extensions: rules: PHPStan\DependencyInjection\RulesExtension + expandRelativePaths: PHPStan\DependencyInjection\ExpandRelativePathExtension conditionalTags: PHPStan\DependencyInjection\ConditionalTagsExtension parametersSchema: PHPStan\DependencyInjection\ParametersSchemaExtension validateIgnoredErrors: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index 3108f1ef95d..6af9e2737ec 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -197,3 +197,22 @@ parametersSchema: # internal - editor mode singleReflectionFile: schema(string(), nullable()) singleReflectionInsteadOfFile: schema(string(), nullable()) + +expandRelativePaths: + - '[parameters][paths][]' + - '[parameters][excludePaths][]' + - '[parameters][excludePaths][analyse][]' + - '[parameters][excludePaths][analyseAndScan][]' + - '[parameters][ignoreErrors][][paths][]' + - '[parameters][ignoreErrors][][path]' + - '[parameters][bootstrapFiles][]' + - '[parameters][scanFiles][]' + - '[parameters][scanDirectories][]' + - '[parameters][tmpDir]' + - '[parameters][pro][tmpDir]' + - '[parameters][memoryLimitFile]' + - '[parameters][benchmarkFile]' + - '[parameters][stubFiles][]' + - '[parameters][symfony][consoleApplicationLoader]' + - '[parameters][symfony][containerXmlPath]' + - '[parameters][doctrine][objectManagerLoader]' diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c981eab8753..aa091926666 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -257,6 +257,10 @@ public static function begin( $containerFactory->getRootDirectory(), $containerFactory->getCurrentWorkingDirectory(), $generateBaselineFile, + [ + '[parameters][paths][]', + '[parameters][tmpDir]', + ], ))->createLoader(); try { diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 7142c9c4e64..38b6d24f54f 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -120,6 +120,7 @@ public function create( $this->rootDirectory, $this->currentWorkingDirectory, $generateBaselineFile, + $projectConfig['expandRelativePaths'], ), $this->journalContainer); $configurator->defaultExtensions = [ 'php' => PhpExtension::class, @@ -222,7 +223,7 @@ private function detectDuplicateIncludedFiles( array $loaderParameters, ): array { - $neonAdapter = new NeonAdapter(); + $neonAdapter = new NeonAdapter([]); $phpAdapter = new PhpAdapter(); $allConfigFiles = []; $configArray = []; diff --git a/src/DependencyInjection/ExpandRelativePathExtension.php b/src/DependencyInjection/ExpandRelativePathExtension.php new file mode 100644 index 00000000000..70b69adf798 --- /dev/null +++ b/src/DependencyInjection/ExpandRelativePathExtension.php @@ -0,0 +1,17 @@ + $expandRelativePaths + */ public function __construct( private FileHelper $fileHelper, private string $rootDir, private string $currentWorkingDirectory, private ?string $generateBaselineFile, + private array $expandRelativePaths, ) { } public function createLoader(): Loader { + $neonAdapter = new NeonAdapter($this->expandRelativePaths); + $loader = new NeonLoader($this->fileHelper, $this->generateBaselineFile); - $loader->addAdapter('dist', NeonAdapter::class); - $loader->addAdapter('neon', NeonAdapter::class); + $loader->addAdapter('dist', $neonAdapter); + $loader->addAdapter('neon', $neonAdapter); $loader->setParameters([ 'rootDir' => $this->rootDir, 'currentWorkingDirectory' => $this->currentWorkingDirectory, diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index a6d189a7ba1..1efc99470e2 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -29,13 +29,20 @@ final class NeonAdapter implements Adapter { - public const CACHE_KEY = 'v30-no-underscore'; + public const CACHE_KEY = 'v31-expand-relative-paths'; private const PREVENT_MERGING_SUFFIX = '!'; /** @var FileHelper[] */ private array $fileHelpers = []; + /** + * @param list $expandRelativePaths + */ + public function __construct(private array $expandRelativePaths) + { + } + /** * @return mixed[] */ @@ -117,25 +124,7 @@ public function process(array $arr, string $fileKey, string $file): array } } - if (in_array($keyToResolve, [ - '[parameters][paths][]', - '[parameters][excludePaths][]', - '[parameters][excludePaths][analyse][]', - '[parameters][excludePaths][analyseAndScan][]', - '[parameters][ignoreErrors][][paths][]', - '[parameters][ignoreErrors][][path]', - '[parameters][bootstrapFiles][]', - '[parameters][scanFiles][]', - '[parameters][scanDirectories][]', - '[parameters][tmpDir]', - '[parameters][pro][tmpDir]', - '[parameters][memoryLimitFile]', - '[parameters][benchmarkFile]', - '[parameters][stubFiles][]', - '[parameters][symfony][consoleApplicationLoader]', - '[parameters][symfony][containerXmlPath]', - '[parameters][doctrine][objectManagerLoader]', - ], true) && is_string($val) && !str_contains($val, '%') && !str_starts_with($val, '*')) { + if (in_array($keyToResolve, $this->expandRelativePaths, true) && is_string($val) && !str_contains($val, '%') && !str_starts_with($val, '*')) { $fileHelper = $this->createFileHelperByFile($file); $val = $fileHelper->normalizePath($fileHelper->absolutizePath($val)); } From 7644bd01603f3ef8194b8497bad06f217de420fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 May 2025 19:29:32 +0200 Subject: [PATCH 1416/3097] More precise elapsed time if it's a low number --- src/Command/InceptionResult.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Command/InceptionResult.php b/src/Command/InceptionResult.php index 572cb6635c5..13ff361922b 100644 --- a/src/Command/InceptionResult.php +++ b/src/Command/InceptionResult.php @@ -103,9 +103,15 @@ public function getEditorModeInsteadOfFile(): ?string public function handleReturn(int $exitCode, ?int $peakMemoryUsageBytes, float $analysisStartTime): int { if ($this->getErrorOutput()->isVerbose()) { + $elapsedTime = round(microtime(true) - $analysisStartTime, 2); + if ($elapsedTime < 10) { + $elapsedTimeString = sprintf('%.2f seconds', $elapsedTime); + } else { + $elapsedTimeString = $this->formatDuration((int) $elapsedTime); + } $this->getErrorOutput()->writeLineFormatted(sprintf( 'Elapsed time: %s', - $this->formatDuration((int) round(microtime(true) - $analysisStartTime)), + $elapsedTimeString, )); } From a359cfcb4fdd032affa69ea81a56cbef7e2cbabf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 24 May 2025 19:39:22 +0200 Subject: [PATCH 1417/3097] Attribute `#[AutowiredService]` for slimmer config.neon --- compiler/build/box.json | 3 +- composer.json | 10 ++- composer.lock | 70 ++++++++++++++++++- conf/config.neon | 4 +- src/Dependency/DependencyResolver.php | 2 + .../AutowiredAttributeServicesExtension.php | 24 +++++++ src/DependencyInjection/AutowiredService.php | 11 +++ 7 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 src/DependencyInjection/AutowiredAttributeServicesExtension.php create mode 100644 src/DependencyInjection/AutowiredService.php diff --git a/compiler/build/box.json b/compiler/build/box.json index 9f0b7ee6c12..90bce8aff52 100644 --- a/compiler/build/box.json +++ b/compiler/build/box.json @@ -8,7 +8,8 @@ ], "files": [ "preload.php", - "vendor/composer/installed.php" + "vendor/composer/installed.php", + "vendor/attributes.php" ], "directories": [ "conf", diff --git a/composer.json b/composer.json index cf8e9e4c5b2..0cdcf970718 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", + "ondrejmirtes/composer-attribute-collector": "dev-main", "phpstan/php-8-stubs": "0.4.12", "phpstan/phpdoc-parser": "2.1.0", "psr/http-message": "^1.1", @@ -72,10 +73,17 @@ "platform-check": false, "sort-packages": true, "allow-plugins": { - "cweagans/composer-patches": true + "cweagans/composer-patches": true, + "ondrejmirtes/composer-attribute-collector": true, + "vaimo/composer-patches": true } }, "extra": { + "composer-attribute-collector": { + "include": [ + "src" + ] + }, "composer-exit-on-patch-failure": true, "patches": { "composer/ca-bundle": [ diff --git a/composer.lock b/composer.lock index d5c883c79c0..0324294e6b7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6dbfcceae1655d0051d153fcb50b2c1f", + "content-hash": "6421bc766815495eca7c67c0a988a455", "packages": [ { "name": "clue/ndjson-react", @@ -2256,6 +2256,71 @@ }, "time": "2025-02-12T21:16:38+00:00" }, + { + "name": "ondrejmirtes/composer-attribute-collector", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/ondrejmirtes/composer-attribute-collector.git", + "reference": "d285f54d139d1f14b5d4ae928c9fb45bff22fda1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ondrejmirtes/composer-attribute-collector/zipball/d285f54d139d1f14b5d4ae928c9fb45bff22fda1", + "reference": "d285f54d139d1f14b5d4ae928c9fb45bff22fda1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "nikic/php-parser": "^5.4", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "composer/composer": ">=2.4", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^9.5" + }, + "default-branch": true, + "type": "composer-plugin", + "extra": { + "class": "olvlvl\\ComposerAttributeCollector\\Plugin", + "composer-attribute-collector": { + "exclude": [ + "tests/Acme/PSR4/IncompatibleSignature.php" + ], + "include": [ + "tests" + ] + } + }, + "autoload": { + "psr-4": { + "olvlvl\\ComposerAttributeCollector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Olivier Laviale", + "email": "olivier.laviale@gmail.com", + "homepage": "https://olvlvl.com/", + "role": "Developer" + }, + { + "name": "Ondrej Mirtes", + "email": "ondrej@mirtes.cz", + "role": "Developer" + } + ], + "description": "A convenient and near zero-cost way to retrieve targets of PHP 8 attributes", + "support": { + "source": "https://github.com/ondrejmirtes/composer-attribute-collector/tree/main" + }, + "time": "2025-05-24T23:38:56+00:00" + }, { "name": "phpstan/php-8-stubs", "version": "0.4.12", @@ -6525,7 +6590,8 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20 + "jetbrains/phpstorm-stubs": 20, + "ondrejmirtes/composer-attribute-collector": 20 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/conf/config.neon b/conf/config.neon index 96115b942da..ecc7f612b77 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -210,6 +210,7 @@ parameters: extensions: rules: PHPStan\DependencyInjection\RulesExtension expandRelativePaths: PHPStan\DependencyInjection\ExpandRelativePathExtension + autowiredAttributeServices: PHPStan\DependencyInjection\AutowiredAttributeServicesExtension conditionalTags: PHPStan\DependencyInjection\ConditionalTagsExtension parametersSchema: PHPStan\DependencyInjection\ParametersSchemaExtension validateIgnoredErrors: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension @@ -575,9 +576,6 @@ services: editorUrl: %editorUrl% usedLevel: %usedLevel% - - - class: PHPStan\Dependency\DependencyResolver - - class: PHPStan\Dependency\ExportedNodeFetcher arguments: diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 6186f559f4c..e47528377ff 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\ClassNotFoundException; use PHPStan\Broker\FunctionNotFoundException; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\File\FileHelper; use PHPStan\Node\ClassPropertyNode; use PHPStan\Node\InClassMethodNode; @@ -28,6 +29,7 @@ use function array_merge; use function count; +#[AutowiredService] final class DependencyResolver { diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php new file mode 100644 index 00000000000..e79126a15bd --- /dev/null +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -0,0 +1,24 @@ +getContainerBuilder(); + + foreach ($autowiredServiceClasses as $class) { + $builder->addDefinition(null) + ->setType($class->name) + ->setAutowired(); + } + } + +} diff --git a/src/DependencyInjection/AutowiredService.php b/src/DependencyInjection/AutowiredService.php new file mode 100644 index 00000000000..0a4cad4e807 --- /dev/null +++ b/src/DependencyInjection/AutowiredService.php @@ -0,0 +1,11 @@ + Date: Sun, 25 May 2025 02:10:16 +0200 Subject: [PATCH 1418/3097] simple-downgrade is installed globally in GitHub Actions so that it can downgrade code before installin g phpstan-src dependencies and running attribute collector plugin --- .github/workflows/lint.yml | 13 +- .github/workflows/reflection-golden-test.yml | 24 +- .github/workflows/static-analysis.yml | 11 +- .github/workflows/tests.yml | 11 +- compiler/composer.json | 9 +- compiler/composer.lock | 988 +++++++++++++------ compiler/src/Console/PrepareCommand.php | 2 +- composer.json | 1 - composer.lock | 51 +- 9 files changed, 747 insertions(+), 363 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d93e59843f9..252d705fdf2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,16 +39,21 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" + + - name: "Transform source code" + if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' + run: | + composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" + simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + + - name: "Validate Composer" run: "composer validate" - name: "Install dependencies" run: "composer install --no-interaction --no-progress" - - name: "Transform source code" - if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" - - name: "Lint" run: "make lint" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 8d0050cfd32..1590a29d66a 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -96,13 +96,15 @@ jobs: ini-file: development ini-values: memory_limit=2G - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' - shell: bash - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + run: | + composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" + simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" - name: "Dump previous reflection data" run: "php tests/generate-reflection-test.php" @@ -115,13 +117,15 @@ jobs: - name: "Checkout" uses: actions/checkout@v4 - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' - shell: bash - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + run: | + composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" + simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" - name: "Reflection golden test" run: "make tests-golden-reflection || true" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 602152e12ff..14a7f79e421 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -48,13 +48,16 @@ jobs: ini-file: development extensions: mbstring - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + run: | + composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" + simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" - name: "PHPStan" run: "make phpstan" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d7c4673b404..510125a9ba0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,13 +54,16 @@ jobs: ini-file: development ini-values: memory_limit=2G - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash - run: "vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }}" + run: | + composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" + simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" - name: "Tests" run: "make tests" diff --git a/compiler/composer.json b/compiler/composer.json index 4da1b080764..337bd912129 100644 --- a/compiler/composer.json +++ b/compiler/composer.json @@ -6,11 +6,12 @@ "require": { "php": "^8.0", "nette/neon": "^3.0.0", + "ondrejmirtes/simple-downgrader": "^2.0", "seld/phar-utils": "^1.2", - "symfony/console": "^6.0.0", - "symfony/filesystem": "^6.0.0", - "symfony/finder": "^6.0.0", - "symfony/process": "^6.0.0" + "symfony/console": "^5.4.43", + "symfony/filesystem": "^5.4.43", + "symfony/finder": "^5.4.43", + "symfony/process": "^5.4.43" }, "autoload": { "psr-4": { diff --git a/compiler/composer.lock b/compiler/composer.lock index d970a7b6f78..677851641aa 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -4,25 +4,25 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7caab5611acadb20806eaeca4e294e98", + "content-hash": "78123aa3f1b04db29d1e84c086bd4259", "packages": [ { "name": "nette/neon", - "version": "v3.4.0", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "372d945c156ee7f35c953339fb164538339e6283" + "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/372d945c156ee7f35c953339fb164538339e6283", - "reference": "372d945c156ee7f35c953339fb164538339e6283", + "url": "https://api.github.com/repos/nette/neon/zipball/3411aa86b104e2d5b7e760da4600865ead963c3c", + "reference": "3411aa86b104e2d5b7e760da4600865ead963c3c", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=8.0 <8.3" + "php": "8.0 - 8.4" }, "require-dev": { "nette/tester": "^2.4", @@ -70,9 +70,249 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.4.0" + "source": "https://github.com/nette/neon/tree/v3.4.4" }, - "time": "2023-01-13T03:08:29+00:00" + "time": "2024-10-04T22:00:08+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.10", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", + "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.4" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.10" + }, + "time": "2023-07-30T15:38:18+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + }, + "time": "2024-12-30T11:07:19+00:00" + }, + { + "name": "ondrejmirtes/simple-downgrader", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ondrejmirtes/simple-downgrader.git", + "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fb8b7833034f0396d5e4518ed090e3d099b7d9bc", + "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2.5", + "nikic/php-parser": "^5.3.0", + "php": "^7.4|^8.0", + "phpstan/phpdoc-parser": "^2.0", + "symfony/console": "^5.4", + "symfony/finder": "^5.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "bin": [ + "bin/simple-downgrade" + ], + "type": "library", + "autoload": { + "psr-4": { + "SimpleDowngrader\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Simple Downgrader", + "support": { + "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.0.0" + }, + "time": "2024-10-09T14:55:47+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + }, + "time": "2025-02-19T13:28:12+00:00" }, { "name": "psr/container", @@ -177,42 +417,46 @@ }, { "name": "symfony/console", - "version": "v6.0.19", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c3ebc83d031b71c39da318ca8b7a07ecc67507ed" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c3ebc83d031b71c39da318ca8b7a07ecc67507ed", - "reference": "c3ebc83d031b71c39da318ca8b7a07ecc67507ed", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -247,12 +491,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.0.19" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -268,26 +512,97 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:36:10+00:00" + "time": "2024-11-06T11:30:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/filesystem", - "version": "v6.0.19", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "3d49eec03fda1f0fc19b7349fbbe55ebc1004214" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/3d49eec03fda1f0fc19b7349fbbe55ebc1004214", - "reference": "3d49eec03fda1f0fc19b7349fbbe55ebc1004214", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -315,7 +630,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.0.19" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -331,24 +646,26 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:44:14+00:00" + "time": "2024-10-22T13:05:35+00:00" }, { "name": "symfony/finder", - "version": "v6.0.19", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11" + "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5cc9cac6586fc0c28cd173780ca696e419fefa11", - "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -376,7 +693,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.0.19" + "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { @@ -392,24 +709,24 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:44:14+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -419,12 +736,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -458,7 +772,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -474,36 +788,33 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -539,7 +850,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -555,36 +866,33 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -623,7 +931,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -639,24 +947,25 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -666,12 +975,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -706,7 +1012,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -722,24 +1028,181 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/process", - "version": "v6.0.19", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "2114fd60f26a296cc403a7939ab91478475a33d4" + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/2114fd60f26a296cc403a7939ab91478475a33d4", - "reference": "2114fd60f26a296cc403a7939ab91478475a33d4", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -767,7 +1230,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.0.19" + "source": "https://github.com/symfony/process/tree/v5.4.47" }, "funding": [ { @@ -783,7 +1246,7 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:36:10+00:00" + "time": "2024-11-06T11:36:42+00:00" }, { "name": "symfony/service-contracts", @@ -811,12 +1274,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -1026,16 +1489,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -1043,11 +1506,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -1073,7 +1537,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -1081,80 +1545,25 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.17.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" - }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -1195,9 +1604,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -1252,16 +1667,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.15", + "version": "1.12.27", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd" + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/762c4dac4da6f8756eebb80e528c3a47855da9bd", - "reference": "762c4dac4da6f8756eebb80e528c3a47855da9bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a6e423c076ab39dfedc307e2ac627ef579db162", + "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162", "shasum": "" }, "require": { @@ -1304,31 +1719,27 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2023-05-09T15:28:01+00:00" + "time": "2025-05-21T20:51:45+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.3.13", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5" + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d8bdab0218c5eb0964338d24a8511b65e9c94fa5", - "reference": "d8bdab0218c5eb0964338d24a8511b65e9c94fa5", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/72a6721c9b64b3e4c9db55abbc38f790b318267e", + "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.12" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -1336,7 +1747,7 @@ "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.5.1", "phpunit/phpunit": "^9.5" }, "type": "phpstan-extension", @@ -1360,41 +1771,41 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.13" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.2" }, - "time": "2023-05-26T11:05:59+00:00" + "time": "2024-12-17T17:20:49+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.27", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1403,7 +1814,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -1432,7 +1843,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -1440,7 +1851,7 @@ "type": "github" } ], - "time": "2023-07-26T13:44:30+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1685,45 +2096,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.11", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1768,7 +2179,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -1779,25 +2190,33 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2023-08-19T07:10:56+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -1832,7 +2251,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1840,7 +2259,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -2029,20 +2448,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -2074,7 +2493,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -2082,20 +2501,20 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -2140,7 +2559,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -2148,7 +2567,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -2215,16 +2634,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -2280,7 +2699,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -2288,20 +2707,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -2344,7 +2763,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -2352,24 +2771,24 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -2401,7 +2820,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -2409,7 +2828,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -2588,16 +3007,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -2609,7 +3028,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2630,8 +3049,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -2639,7 +3057,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -2752,16 +3170,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2790,7 +3208,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2798,20 +3216,20 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.0.99" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/compiler/src/Console/PrepareCommand.php b/compiler/src/Console/PrepareCommand.php index 9dbc3e31923..15654b55f6d 100644 --- a/compiler/src/Console/PrepareCommand.php +++ b/compiler/src/Console/PrepareCommand.php @@ -220,7 +220,7 @@ private function deleteUnnecessaryVendorCode(): void private function transformSource(): void { chdir(__DIR__ . '/../../..'); - exec(escapeshellarg(__DIR__ . '/../../../vendor/bin/simple-downgrade') . ' downgrade -c ' . escapeshellarg('build/downgrade.php') . ' 7.2', $outputLines, $exitCode); + exec(escapeshellarg(__DIR__ . '/../../vendor/bin/simple-downgrade') . ' downgrade -c ' . escapeshellarg('build/downgrade.php') . ' 7.2', $outputLines, $exitCode); if ($exitCode === 0) { return; } diff --git a/composer.json b/composer.json index 0cdcf970718..6fa4ee2333e 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,6 @@ "require-dev": { "brianium/paratest": "^6.5", "cweagans/composer-patches": "^1.7.3", - "ondrejmirtes/simple-downgrader": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2.0", "phpstan/phpstan-deprecation-rules": "^2.0.2", "phpstan/phpstan-nette": "^2.0", diff --git a/composer.lock b/composer.lock index 0324294e6b7..3384613ba75 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6421bc766815495eca7c67c0a988a455", + "content-hash": "22eb9a4d7a19b311c2bfe0b1cd1d9251", "packages": [ { "name": "clue/ndjson-react", @@ -4506,55 +4506,6 @@ ], "time": "2025-04-29T12:36:36+00:00" }, - { - "name": "ondrejmirtes/simple-downgrader", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fb8b7833034f0396d5e4518ed090e3d099b7d9bc", - "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc", - "shasum": "" - }, - "require": { - "nette/utils": "^3.2.5", - "nikic/php-parser": "^5.3.0", - "php": "^7.4|^8.0", - "phpstan/phpdoc-parser": "^2.0", - "symfony/console": "^5.4", - "symfony/finder": "^5.4" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^9.6" - }, - "bin": [ - "bin/simple-downgrade" - ], - "type": "library", - "autoload": { - "psr-4": { - "SimpleDowngrader\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Simple Downgrader", - "support": { - "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.0.0" - }, - "time": "2024-10-09T14:55:47+00:00" - }, { "name": "phar-io/manifest", "version": "2.0.4", From e889abb2568868c43a346df9cd78ec84a8e18aee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 02:22:00 +0200 Subject: [PATCH 1419/3097] Update `ondrejmirtes/composer-attribute-collector` --- composer.json | 2 +- composer.lock | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 6fa4ee2333e..83086ff40fa 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", - "ondrejmirtes/composer-attribute-collector": "dev-main", + "ondrejmirtes/composer-attribute-collector": "^1.0.0", "phpstan/php-8-stubs": "0.4.12", "phpstan/phpdoc-parser": "2.1.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index 3384613ba75..03c1501ad46 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "22eb9a4d7a19b311c2bfe0b1cd1d9251", + "content-hash": "b235ba93272364ed2ad1aff93785d2ca", "packages": [ { "name": "clue/ndjson-react", @@ -2258,7 +2258,7 @@ }, { "name": "ondrejmirtes/composer-attribute-collector", - "version": "dev-main", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/composer-attribute-collector.git", @@ -2280,7 +2280,6 @@ "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "composer-plugin", "extra": { "class": "olvlvl\\ComposerAttributeCollector\\Plugin", @@ -2317,7 +2316,7 @@ ], "description": "A convenient and near zero-cost way to retrieve targets of PHP 8 attributes", "support": { - "source": "https://github.com/ondrejmirtes/composer-attribute-collector/tree/main" + "source": "https://github.com/ondrejmirtes/composer-attribute-collector/tree/1.0.0" }, "time": "2025-05-24T23:38:56+00:00" }, @@ -6541,8 +6540,7 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20, - "ondrejmirtes/composer-attribute-collector": 20 + "jetbrains/phpstorm-stubs": 20 }, "prefer-stable": true, "prefer-lowest": false, From 5d756a014dabe37119917b078078770e035c5905 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 02:23:46 +0200 Subject: [PATCH 1420/3097] A few more services introduced with `#[AutowiredService]` --- conf/config.neon | 9 --------- src/Node/Printer/ExprPrinter.php | 2 ++ src/PhpDoc/PhpDocInheritanceResolver.php | 2 ++ src/PhpDoc/PhpDocNodeResolver.php | 2 ++ 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ecc7f612b77..82cb3c8a4c0 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -350,9 +350,6 @@ services: tags: - phpstan.parser.richParserNodeVisitor - - - class: PHPStan\Node\Printer\ExprPrinter - - class: PHPStan\Node\Printer\Printer autowired: @@ -403,12 +400,6 @@ services: - class: PHPStan\PhpDocParser\Printer\Printer - - - class: PHPStan\PhpDoc\PhpDocInheritanceResolver - - - - class: PHPStan\PhpDoc\PhpDocNodeResolver - - class: PHPStan\PhpDoc\PhpDocStringResolver diff --git a/src/Node/Printer/ExprPrinter.php b/src/Node/Printer/ExprPrinter.php index 32505ef5681..7478addf8c4 100644 --- a/src/Node/Printer/ExprPrinter.php +++ b/src/Node/Printer/ExprPrinter.php @@ -3,10 +3,12 @@ namespace PHPStan\Node\Printer; use PhpParser\Node\Expr; +use PHPStan\DependencyInjection\AutowiredService; /** * @api */ +#[AutowiredService] final class ExprPrinter { diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index b240a793222..b72e7fe36b2 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -3,11 +3,13 @@ namespace PHPStan\PhpDoc; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\FileTypeMapper; use function array_map; use function strtolower; +#[AutowiredService] final class PhpDocInheritanceResolver { diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index aaace199101..5701f754037 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDoc; use PHPStan\Analyser\NameScope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDoc\Tag\AssertTag; use PHPStan\PhpDoc\Tag\AssertTagParameter; use PHPStan\PhpDoc\Tag\DeprecatedTag; @@ -49,6 +50,7 @@ use function str_starts_with; use function substr; +#[AutowiredService] final class PhpDocNodeResolver { From a5314740b3a547e064451d7ba34f9c88dcfd5826 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 07:59:11 +0200 Subject: [PATCH 1421/3097] AutowiredAttributeServicesExtension - add service tags based on implemented interfaces --- conf/config.neon | 5 ----- .../AutowiredAttributeServicesExtension.php | 19 ++++++++++++++++++- .../AbsFunctionDynamicReturnTypeExtension.php | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 82cb3c8a4c0..133e6b8de21 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1150,11 +1150,6 @@ services: - class: PHPStan\Type\BitwiseFlagHelper - - - class: PHPStan\Type\Php\AbsFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ArgumentBasedFunctionReturnTypeExtension tags: diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index e79126a15bd..05c91a4108d 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -4,6 +4,9 @@ use Nette\DI\CompilerExtension; use olvlvl\ComposerAttributeCollector\Attributes; +use PHPStan\Broker\BrokerFactory; +use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use ReflectionClass; final class AutowiredAttributeServicesExtension extends CompilerExtension { @@ -14,10 +17,24 @@ public function loadConfiguration(): void $autowiredServiceClasses = Attributes::findTargetClasses(AutowiredService::class); $builder = $this->getContainerBuilder(); + $interfaceToTag = [ + DynamicFunctionReturnTypeExtension::class => BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG, + ]; + foreach ($autowiredServiceClasses as $class) { - $builder->addDefinition(null) + $reflection = new ReflectionClass($class->name); + + $definition = $builder->addDefinition(null) ->setType($class->name) ->setAutowired(); + + foreach ($interfaceToTag as $interface => $tag) { + if (!$reflection->implementsInterface($interface)) { + continue; + } + + $definition->addTag($tag); + } } } diff --git a/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php index 5ce81dbe845..7de2eebbd70 100644 --- a/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/AbsFunctionDynamicReturnTypeExtension.php @@ -4,11 +4,13 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; +#[AutowiredService] final class AbsFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From 602881647c5acce036f6cce140b95bdce5a5ed45 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 08:09:55 +0200 Subject: [PATCH 1422/3097] Recompile DIC if vendor/attributes.php changes --- src/DependencyInjection/Configurator.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 9536925b9de..a5107334fb8 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -75,9 +75,18 @@ public function loadContainer(): string $this->staticParameters['debugMode'], ); + $attributesPhp = __DIR__ . '/../../vendor/attributes.php'; + $className = $loader->load( [$this, 'generateContainer'], - [$this->staticParameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY, $this->getAllConfigFilesHashes()], + [ + $this->staticParameters, + array_keys($this->dynamicParameters), + $this->configs, + PHP_VERSION_ID - PHP_RELEASE_VERSION, + is_file($attributesPhp) ? sha1_file($attributesPhp) : 'attributes-missing', + NeonAdapter::CACHE_KEY, $this->getAllConfigFilesHashes(), + ], ); if ($this->journalContainer) { From c5955037382e7edc18fe129c7187f860cc529d47 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 08:59:02 +0200 Subject: [PATCH 1423/3097] More service converted to `#[AutowiredService]` attribute usage --- conf/config.neon | 16 ---------------- .../AutowiredAttributeServicesExtension.php | 3 +++ src/Rules/Debug/DebugScopeRule.php | 2 ++ src/Rules/Debug/DumpPhpDocTypeRule.php | 2 ++ src/Rules/Debug/DumpTypeRule.php | 2 ++ src/Rules/Debug/FileAssertRule.php | 2 ++ .../RestrictedClassConstantUsageRule.php | 2 ++ .../RestrictedFunctionCallableUsageRule.php | 2 ++ .../RestrictedFunctionUsageRule.php | 2 ++ .../RestrictedMethodCallableUsageRule.php | 2 ++ .../RestrictedMethodUsageRule.php | 2 ++ .../RestrictedPropertyUsageRule.php | 2 ++ .../RestrictedStaticMethodCallableUsageRule.php | 2 ++ .../RestrictedStaticMethodUsageRule.php | 2 ++ .../RestrictedStaticPropertyUsageRule.php | 2 ++ ...RestrictedUsageOfDeprecatedStringCastRule.php | 2 ++ 16 files changed, 31 insertions(+), 16 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 133e6b8de21..cfbe7444576 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -216,22 +216,6 @@ extensions: validateIgnoredErrors: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension validateExcludePaths: PHPStan\DependencyInjection\ValidateExcludePathsExtension -rules: - - PHPStan\Rules\Debug\DebugScopeRule - - PHPStan\Rules\Debug\DumpPhpDocTypeRule - - PHPStan\Rules\Debug\DumpTypeRule - - PHPStan\Rules\Debug\FileAssertRule - - PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedFunctionCallableUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedMethodCallableUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedStaticMethodCallableUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedStaticPropertyUsageRule - - PHPStan\Rules\RestrictedUsage\RestrictedUsageOfDeprecatedStringCastRule - conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: phpstan.rules.rule: %exceptions.check.missingCheckedExceptionInThrows% diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index 05c91a4108d..cdc63fc0123 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -5,6 +5,8 @@ use Nette\DI\CompilerExtension; use olvlvl\ComposerAttributeCollector\Attributes; use PHPStan\Broker\BrokerFactory; +use PHPStan\Rules\LazyRegistry; +use PHPStan\Rules\Rule; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use ReflectionClass; @@ -19,6 +21,7 @@ public function loadConfiguration(): void $interfaceToTag = [ DynamicFunctionReturnTypeExtension::class => BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG, + Rule::class => LazyRegistry::RULE_TAG, ]; foreach ($autowiredServiceClasses as $class) { diff --git a/src/Rules/Debug/DebugScopeRule.php b/src/Rules/Debug/DebugScopeRule.php index 7f6a43930a8..16a06b4a38f 100644 --- a/src/Rules/Debug/DebugScopeRule.php +++ b/src/Rules/Debug/DebugScopeRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -16,6 +17,7 @@ /** * @implements Rule */ +#[AutowiredService] final class DebugScopeRule implements Rule { diff --git a/src/Rules/Debug/DumpPhpDocTypeRule.php b/src/Rules/Debug/DumpPhpDocTypeRule.php index be867366b03..1180469b0df 100644 --- a/src/Rules/Debug/DumpPhpDocTypeRule.php +++ b/src/Rules/Debug/DumpPhpDocTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDocParser\Printer\Printer; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -15,6 +16,7 @@ /** * @implements Rule */ +#[AutowiredService] final class DumpPhpDocTypeRule implements Rule { diff --git a/src/Rules/Debug/DumpTypeRule.php b/src/Rules/Debug/DumpTypeRule.php index f7d2a6dcb4e..5a628a95f5d 100644 --- a/src/Rules/Debug/DumpTypeRule.php +++ b/src/Rules/Debug/DumpTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -15,6 +16,7 @@ /** * @implements Rule */ +#[AutowiredService] final class DumpTypeRule implements Rule { diff --git a/src/Rules/Debug/FileAssertRule.php b/src/Rules/Debug/FileAssertRule.php index 769f37bd1cc..888b00a4459 100644 --- a/src/Rules/Debug/FileAssertRule.php +++ b/src/Rules/Debug/FileAssertRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; @@ -18,6 +19,7 @@ /** * @implements Rule */ +#[AutowiredService] final class FileAssertRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php index fce21cbbdcd..8f8ad162296 100644 --- a/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedClassConstantUsageRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -17,6 +18,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedClassConstantUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php index ba15cb4f9cd..b6840c712f1 100644 --- a/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedFunctionCallableUsageRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Node\FunctionCallableNode; use PHPStan\Reflection\ReflectionProvider; @@ -14,6 +15,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedFunctionCallableUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedFunctionUsageRule.php b/src/Rules/RestrictedUsage/RestrictedFunctionUsageRule.php index 17126e48144..969963ed2b1 100644 --- a/src/Rules/RestrictedUsage/RestrictedFunctionUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedFunctionUsageRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -13,6 +14,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedFunctionUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php index 66eb85f906a..c9f1861b7c9 100644 --- a/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedMethodCallableUsageRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Node\MethodCallableNode; use PHPStan\Reflection\ReflectionProvider; @@ -14,6 +15,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedMethodCallableUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php b/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php index 1dcd4708250..7c1b4fe37ca 100644 --- a/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -14,6 +15,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedMethodUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php b/src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php index 26ebbd2b465..75163a38a73 100644 --- a/src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -13,6 +14,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedPropertyUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php index b4c86efb475..22230ca299e 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodCallableUsageRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Node\StaticMethodCallableNode; use PHPStan\Reflection\ReflectionProvider; @@ -18,6 +19,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedStaticMethodCallableUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php index 024ab870161..898075b5147 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticMethodUsageRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -17,6 +18,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedStaticMethodUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php index 9b0c011e834..5d45cb56a65 100644 --- a/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php +++ b/src/Rules/RestrictedUsage/RestrictedStaticPropertyUsageRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -17,6 +18,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedStaticPropertyUsageRule implements Rule { diff --git a/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php b/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php index 938982fe565..006282c94d4 100644 --- a/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php +++ b/src/Rules/RestrictedUsage/RestrictedUsageOfDeprecatedStringCastRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\Cast; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -13,6 +14,7 @@ /** * @implements Rule */ +#[AutowiredService] final class RestrictedUsageOfDeprecatedStringCastRule implements Rule { From 339a29d3d0480b7c145e2d49127ae2b442c22daf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 09:05:23 +0200 Subject: [PATCH 1424/3097] Explaining doc comment --- src/DependencyInjection/AutowiredService.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/DependencyInjection/AutowiredService.php b/src/DependencyInjection/AutowiredService.php index 0a4cad4e807..8544459f9cd 100644 --- a/src/DependencyInjection/AutowiredService.php +++ b/src/DependencyInjection/AutowiredService.php @@ -4,6 +4,14 @@ use Attribute; +/** + * Registers a service in the DI container. + * + * Auto-adds service extension tags based on implemented interfaces. + * + * Works thanks to https://github.com/ondrejmirtes/composer-attribute-collector + * and AutowiredAttributeServicesExtension. + */ #[Attribute(flags: Attribute::TARGET_CLASS)] final class AutowiredService { From 6fbe3c4119e454cc682ee90713b4c673d3e34f45 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 25 May 2025 10:04:22 +0200 Subject: [PATCH 1425/3097] More DynamicFunctionReturnTypeExtension converted to #[AutowiredService] attribute usage --- conf/config.neon | 506 +----------------- ...gumentBasedFunctionReturnTypeExtension.php | 2 + ...angeKeyCaseFunctionReturnTypeExtension.php | 2 + .../ArrayChunkFunctionReturnTypeExtension.php | 2 + ...ArrayColumnFunctionReturnTypeExtension.php | 2 + src/Type/Php/ArrayColumnHelper.php | 2 + ...rrayCombineFunctionReturnTypeExtension.php | 2 + ...ArrayCurrentDynamicReturnTypeExtension.php | 2 + .../ArrayFillFunctionReturnTypeExtension.php | 2 + ...rayFillKeysFunctionReturnTypeExtension.php | 2 + ...ArrayFilterFunctionReturnTypeExtension.php | 2 + .../ArrayFilterFunctionReturnTypeHelper.php | 2 + .../ArrayFindFunctionReturnTypeExtension.php | 2 + ...rrayFindKeyFunctionReturnTypeExtension.php | 2 + .../ArrayFlipFunctionReturnTypeExtension.php | 2 + ...ntersectKeyFunctionReturnTypeExtension.php | 2 + .../ArrayKeyDynamicReturnTypeExtension.php | 2 + ...rrayKeyFirstDynamicReturnTypeExtension.php | 2 + ...ArrayKeyLastDynamicReturnTypeExtension.php | 2 + ...KeysFunctionDynamicReturnTypeExtension.php | 2 + .../ArrayMapFunctionReturnTypeExtension.php | 2 + ...ergeFunctionDynamicReturnTypeExtension.php | 2 + .../ArrayNextDynamicReturnTypeExtension.php | 2 + ...terFunctionsDynamicReturnTypeExtension.php | 2 + .../ArrayPopFunctionReturnTypeExtension.php | 2 + .../ArrayRandFunctionReturnTypeExtension.php | 2 + ...ArrayReduceFunctionReturnTypeExtension.php | 2 + ...rrayReplaceFunctionReturnTypeExtension.php | 2 + ...rrayReverseFunctionReturnTypeExtension.php | 2 + ...archFunctionDynamicReturnTypeExtension.php | 2 + .../ArrayShiftFunctionReturnTypeExtension.php | 2 + .../ArraySliceFunctionReturnTypeExtension.php | 2 + ...ArraySpliceFunctionReturnTypeExtension.php | 2 + ...ySumFunctionDynamicReturnTypeExtension.php | 2 + ...luesFunctionDynamicReturnTypeExtension.php | 2 + ...codeDynamicFunctionReturnTypeExtension.php | 2 + .../BcMathStringOrNullReturnTypeExtension.php | 2 + ...sImplementsFunctionReturnTypeExtension.php | 2 + .../ConstantFunctionReturnTypeExtension.php | 2 + ...harsFunctionDynamicReturnTypeExtension.php | 2 + .../Php/CountFunctionReturnTypeExtension.php | 2 + ...infoFunctionDynamicReturnTypeExtension.php | 2 + .../DateFormatFunctionReturnTypeExtension.php | 2 + .../Php/DateFunctionReturnTypeExtension.php | 2 + ...teTimeCreateDynamicReturnTypeExtension.php | 2 + .../DateTimeDynamicReturnTypeExtension.php | 2 + ...StatDynamicFunctionReturnTypeExtension.php | 2 + ...lodeFunctionDynamicReturnTypeExtension.php | 2 + .../Php/FilterFunctionReturnTypeHelper.php | 2 + .../FilterInputDynamicReturnTypeExtension.php | 2 + ...lterVarArrayDynamicReturnTypeExtension.php | 2 + .../FilterVarDynamicReturnTypeExtension.php | 2 + ...tCalledClassDynamicReturnTypeExtension.php | 2 + .../GetClassDynamicReturnTypeExtension.php | 2 + ...etDebugTypeFunctionReturnTypeExtension.php | 2 + ...DefinedVarsFunctionReturnTypeExtension.php | 2 + ...lassDynamicFunctionReturnTypeExtension.php | 2 + ...fdayDynamicFunctionReturnTypeExtension.php | 2 + .../GettypeFunctionReturnTypeExtension.php | 2 + .../Php/HashFunctionsReturnTypeExtension.php | 2 + ...hlightStringDynamicReturnTypeExtension.php | 2 + .../Php/HrtimeFunctionReturnTypeExtension.php | 2 + .../ImplodeFunctionReturnTypeExtension.php | 2 + src/Type/Php/IniGetReturnTypeExtension.php | 2 + ...atorToArrayFunctionReturnTypeExtension.php | 2 + ...ThrowOnErrorDynamicReturnTypeExtension.php | 2 + ...ertEncodingFunctionReturnTypeExtension.php | 2 + .../Php/MbFunctionsReturnTypeExtension.php | 2 + .../MbStrlenFunctionReturnTypeExtension.php | 2 + ...uteCharacterDynamicReturnTypeExtension.php | 2 + .../MicrotimeFunctionReturnTypeExtension.php | 2 + .../Php/MinMaxFunctionReturnTypeExtension.php | 2 + ...mptyStringFunctionsReturnTypeExtension.php | 2 + ...rmatFunctionDynamicReturnTypeExtension.php | 2 + ...eUrlFunctionDynamicReturnTypeExtension.php | 2 + ...infoFunctionDynamicReturnTypeExtension.php | 2 + .../Php/PowFunctionReturnTypeExtension.php | 2 + .../PregFilterFunctionReturnTypeExtension.php | 2 + .../PregSplitDynamicReturnTypeExtension.php | 2 + .../RandomIntFunctionReturnTypeExtension.php | 2 + .../Php/RangeFunctionReturnTypeExtension.php | 2 + ...aceFunctionsDynamicReturnTypeExtension.php | 2 + .../Php/RoundFunctionReturnTypeExtension.php | 2 + ...intfFunctionDynamicReturnTypeExtension.php | 2 + ...canfFunctionDynamicReturnTypeExtension.php | 2 + .../StrCaseFunctionsReturnTypeExtension.php | 2 + ...ntDecrementFunctionReturnTypeExtension.php | 2 + .../Php/StrPadFunctionReturnTypeExtension.php | 2 + .../StrRepeatFunctionReturnTypeExtension.php | 2 + .../StrSplitFunctionReturnTypeExtension.php | 2 + .../Php/StrTokFunctionReturnTypeExtension.php | 2 + ...ountFunctionDynamicReturnTypeExtension.php | 2 + .../Php/StrlenFunctionReturnTypeExtension.php | 2 + .../Php/StrrevFunctionReturnTypeExtension.php | 2 + .../StrtotimeFunctionReturnTypeExtension.php | 2 + ...trvalFamilyFunctionReturnTypeExtension.php | 2 + .../Php/SubstrDynamicReturnTypeExtension.php | 2 + ...TriggerErrorDynamicReturnTypeExtension.php | 2 + 98 files changed, 208 insertions(+), 492 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index cfbe7444576..7fe441dfc6e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1135,80 +1135,17 @@ services: class: PHPStan\Type\BitwiseFlagHelper - - class: PHPStan\Type\Php\ArgumentBasedFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayChangeKeyCaseFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayIntersectKeyFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayChunkFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayColumnHelper - - - - class: PHPStan\Type\Php\ArrayColumnFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayCombineFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayCurrentDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayFillFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayFillKeysFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeHelper - - - - class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayFlipFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayFindFunctionReturnTypeExtension + class: PHPStan\Type\Php\CompactFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + arguments: + checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables% - - class: PHPStan\Type\Php\ArrayFindKeyFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension + class: PHPStan\Type\Php\ConstantHelper - - class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension + class: PHPStan\Type\Php\DateFunctionReturnTypeHelper - class: PHPStan\Type\Php\ArrayKeyExistsFunctionTypeSpecifyingExtension @@ -1216,79 +1153,9 @@ services: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - class: PHPStan\Type\Php\ArrayKeyFirstDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayKeyLastDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayKeysFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayMapFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayMergeFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayNextDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayPopFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayRandFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayReduceFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayReplaceFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayReverseFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayShiftFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArraySliceFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArraySpliceFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArraySearchFunctionDynamicReturnTypeExtension + class: PHPStan\Type\Php\AssertThrowTypeExtension tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension + - phpstan.dynamicFunctionThrowTypeExtension - class: PHPStan\Type\Php\ArraySearchFunctionTypeSpecifyingExtension @@ -1296,39 +1163,14 @@ services: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - class: PHPStan\Type\Php\ArrayValuesFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArraySumFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\AssertThrowTypeExtension - tags: - - phpstan.dynamicFunctionThrowTypeExtension - - - - class: PHPStan\Type\Php\BackedEnumFromMethodDynamicReturnTypeExtension + class: PHPStan\Type\Php\ClosureBindDynamicReturnTypeExtension tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: PHPStan\Type\Php\Base64DecodeDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\BcMathStringOrNullReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ClosureBindDynamicReturnTypeExtension + class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - phpstan.typeSpecifier.functionTypeSpecifyingExtension - class: PHPStan\Type\Php\ClosureBindToDynamicReturnTypeExtension @@ -1341,58 +1183,15 @@ services: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: PHPStan\Type\Php\CompactFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - arguments: - checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables% - - - - class: PHPStan\Type\Php\ConstantFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ConstantHelper - - - - class: PHPStan\Type\Php\CountFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\CountCharsFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\CurlGetinfoFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\DateFunctionReturnTypeHelper - - - - class: PHPStan\Type\Php\DateFormatFunctionReturnTypeExtension + class: PHPStan\Type\Php\BackedEnumFromMethodDynamicReturnTypeExtension tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension + - phpstan.broker.dynamicStaticMethodReturnTypeExtension - class: PHPStan\Type\Php\DateFormatMethodReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: PHPStan\Type\Php\DateFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\DateIntervalConstructorThrowTypeExtension tags: @@ -1403,16 +1202,6 @@ services: tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - - class: PHPStan\Type\Php\DateTimeCreateDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\DateTimeDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension tags: @@ -1457,88 +1246,11 @@ services: tags: - phpstan.dynamicMethodThrowTypeExtension - - - class: PHPStan\Type\Php\DioStatDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ExplodeFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\FilterFunctionReturnTypeHelper - - - - class: PHPStan\Type\Php\FilterInputDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\FilterVarDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\FilterVarArrayDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GetCalledClassDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GetClassDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GetDebugTypeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GetDefinedVarsFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GettypeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\GettimeofdayDynamicFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\HashFunctionsReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\HighlightStringDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\IntdivThrowTypeExtension tags: - phpstan.dynamicFunctionThrowTypeExtension - - - class: PHPStan\Type\Php\IniGetReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\JsonThrowTypeExtension tags: @@ -1629,151 +1341,21 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - class: PHPStan\Type\Php\MinMaxFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\NumberFormatFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\PathinfoFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\PregFilterFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\PregSplitDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ReflectionClassIsSubclassOfTypeSpecifyingExtension tags: - phpstan.typeSpecifier.methodTypeSpecifyingExtension - - - class: PHPStan\Type\Php\ReplaceFunctionsDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ArrayPointerFunctionsDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\MbFunctionsReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\MbConvertEncodingFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\MbSubstituteCharacterDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\MbStrlenFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\MicrotimeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\HrtimeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\ImplodeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\NonEmptyStringFunctionsReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\SetTypeFunctionTypeSpecifyingExtension tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - class: PHPStan\Type\Php\StrCaseFunctionsReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrlenFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrIncrementDecrementFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrPadFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrRepeatFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrrevFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ThrowableReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\TriggerErrorDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension arguments: @@ -1781,31 +1363,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\PowFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\RoundFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrtotimeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\RandomIntFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\RangeFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\AssertFunctionTypeSpecifyingExtension tags: @@ -1816,11 +1373,6 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - class: PHPStan\Type\Php\ClassImplementsFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\DefineConstantTypeSpecifyingExtension tags: @@ -1861,11 +1413,6 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - class: PHPStan\Type\Php\IteratorToArrayFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\IsAFunctionTypeSpecifyingExtension tags: @@ -1879,11 +1426,6 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - class: PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\TypeSpecifyingFunctionsDynamicReturnTypeExtension arguments: @@ -1903,32 +1445,12 @@ services: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Php\StrSplitFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrTokFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\SprintfFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\SscanfFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\StrvalFamilyFunctionReturnTypeExtension + class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\StrWordCountFunctionDynamicReturnTypeExtension + class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index 6e3c75b9a15..b039b62d00e 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; @@ -13,6 +14,7 @@ use PHPStan\Type\TypeCombinator; use function array_key_exists; +#[AutowiredService] final class ArgumentBasedFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php index 5367744502b..d722c872830 100644 --- a/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChangeKeyCaseFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -29,6 +30,7 @@ use const CASE_LOWER; use const CASE_UPPER; +#[AutowiredService] final class ArrayChangeKeyCaseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php index 84ca5046dea..1028ae2fa17 100644 --- a/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class ArrayChunkFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 68272571798..4fec6cfe3eb 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayColumnHelper.php b/src/Type/Php/ArrayColumnHelper.php index ef72623879b..22e7eb69d0d 100644 --- a/src/Type/Php/ArrayColumnHelper.php +++ b/src/Type/Php/ArrayColumnHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Php; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -17,6 +18,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayColumnHelper { diff --git a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php index f809967ad8b..663859a0696 100644 --- a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -23,6 +24,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class ArrayCombineFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php index 9871a469c97..2514aad8f86 100644 --- a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayCurrentDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index fbadbac5938..63cf6d9cf43 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -20,6 +21,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArrayFillFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php index b8c7fdfe970..bf05500cba7 100644 --- a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -12,6 +13,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class ArrayFillKeysFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php index 62dc52abf0a..6979c237591 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeExtension.php @@ -4,10 +4,12 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; +#[AutowiredService] final class ArrayFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php index 30bbe167410..0a5f1f913ea 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php @@ -15,6 +15,7 @@ use PhpParser\Node\Stmt\Return_; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -38,6 +39,7 @@ use function sprintf; use function substr; +#[AutowiredService] final class ArrayFilterFunctionReturnTypeHelper { diff --git a/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php index 220d8fa0efa..3cf1f7c9b7b 100644 --- a/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; @@ -11,6 +12,7 @@ use function array_map; use function count; +#[AutowiredService] final class ArrayFindFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php index 97c514f427f..58faf9d8bf6 100644 --- a/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFindKeyFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; @@ -11,6 +12,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArrayFindKeyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php index b5b0eb1df89..3e82909d22d 100644 --- a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -14,6 +15,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArrayFlipFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php b/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php index e7436d82752..c6212c02cc0 100644 --- a/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayIntersectKeyFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -14,6 +15,7 @@ use function array_slice; use function count; +#[AutowiredService] final class ArrayIntersectKeyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php index fe418f4daa9..28447065292 100644 --- a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayKeyDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index bd9fdd6f0ab..64fea966d63 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayKeyFirstDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index a13714293c9..c3865ada3c5 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayKeyLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 74a2903ea55..550e6688938 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -13,6 +14,7 @@ use function count; use function strtolower; +#[AutowiredService] final class ArrayKeysFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 145756b971e..591d9c8bde2 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -23,6 +24,7 @@ use function array_slice; use function count; +#[AutowiredService] final class ArrayMapFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 712bd4edc3e..1d4f7a11225 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -22,6 +23,7 @@ use function count; use function in_array; +#[AutowiredService] final class ArrayMergeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php index 0f33b7f532f..a0f49b4ca30 100644 --- a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -11,6 +12,7 @@ use PHPStan\Type\TypeCombinator; use function in_array; +#[AutowiredService] final class ArrayNextDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index 4e49cd465f9..eef5642028e 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -12,6 +13,7 @@ use function count; use function in_array; +#[AutowiredService] final class ArrayPointerFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 540dda82bb3..61bfdf69e6e 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayPopFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php index 1d390b59d0d..f61d48033e1 100644 --- a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -16,6 +17,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class ArrayRandFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php index 970e2cb39f9..7f955e6baa4 100644 --- a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\TrinaryLogic; @@ -13,6 +14,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArrayReduceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php index e68f0338f71..68ab63375bc 100644 --- a/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReplaceFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; @@ -13,6 +14,7 @@ use function count; use function strtolower; +#[AutowiredService] final class ArrayReplaceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 825f7d6c1a8..0c90c7bfaad 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -12,6 +13,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; +#[AutowiredService] final class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php index 762e577211c..e1662dbe5fb 100644 --- a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArraySearchFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index cb6c35bbddc..b961e624e02 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ArrayShiftFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php index 75f6a14b639..5ac2ba46063 100644 --- a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -13,6 +14,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class ArraySliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php index 85def351d28..5dd3c941744 100644 --- a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\TrinaryLogic; @@ -13,6 +14,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class ArraySpliceFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php index 5185fbccc1f..061f3d4dff5 100644 --- a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Scalar\Int_; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\TypeExpr; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; @@ -16,6 +17,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ArraySumFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php index 2378a291b3c..794b8df7baf 100644 --- a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -13,6 +14,7 @@ use function count; use function strtolower; +#[AutowiredService] final class ArrayValuesFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php index 6e38a43a25f..bf91435e627 100644 --- a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -13,6 +14,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; +#[AutowiredService] final class Base64DecodeDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php index 2651cb6b90f..e34e4d38590 100644 --- a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php +++ b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\UnaryMinus; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -21,6 +22,7 @@ use function in_array; use function is_numeric; +#[AutowiredService] final class BcMathStringOrNullReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php index df1b7022ab2..3d1010aff03 100644 --- a/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php +++ b/src/Type/Php/ClassImplementsFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\TrinaryLogic; @@ -18,6 +19,7 @@ use function count; use function in_array; +#[AutowiredService] final class ClassImplementsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ConstantFunctionReturnTypeExtension.php b/src/Type/Php/ConstantFunctionReturnTypeExtension.php index 83cfb69ef57..f332c99fc9b 100644 --- a/src/Type/Php/ConstantFunctionReturnTypeExtension.php +++ b/src/Type/Php/ConstantFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\ErrorType; @@ -11,6 +12,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class ConstantFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php index e798af6bbd1..70d08ba9e00 100644 --- a/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/CountCharsFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ArrayType; @@ -18,6 +19,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class CountCharsFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index 9368cb42799..b87d34c466b 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -12,6 +13,7 @@ use function in_array; use const COUNT_RECURSIVE; +#[AutowiredService] final class CountFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/CurlGetinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/CurlGetinfoFunctionDynamicReturnTypeExtension.php index b5de9de5f1f..89970fad0b4 100644 --- a/src/Type/Php/CurlGetinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/CurlGetinfoFunctionDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; @@ -22,6 +23,7 @@ use PHPStan\Type\TypeUtils; use function count; +#[AutowiredService] final class CurlGetinfoFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/DateFormatFunctionReturnTypeExtension.php b/src/Type/Php/DateFormatFunctionReturnTypeExtension.php index 1a404526f35..666a376cdeb 100644 --- a/src/Type/Php/DateFormatFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFormatFunctionReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\StringType; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class DateFormatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/DateFunctionReturnTypeExtension.php b/src/Type/Php/DateFunctionReturnTypeExtension.php index 32113eb3124..c74867e1865 100644 --- a/src/Type/Php/DateFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFunctionReturnTypeExtension.php @@ -4,11 +4,13 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class DateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php index 9e3e5c892dc..c2782272cf4 100644 --- a/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeCreateDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -16,6 +17,7 @@ use function date_create; use function in_array; +#[AutowiredService] final class DateTimeCreateDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php index ef42505ede4..eedc6a2feec 100644 --- a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -15,6 +16,7 @@ use function count; use function in_array; +#[AutowiredService] final class DateTimeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php index 5de459fb730..e6ae81a8d42 100644 --- a/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/DioStatDynamicFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantStringType; @@ -12,6 +13,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class DioStatDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index d43c328df55..65cf25e8d56 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -25,6 +26,7 @@ use PHPStan\Type\TypeUtils; use function count; +#[AutowiredService] final class ExplodeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/FilterFunctionReturnTypeHelper.php b/src/Type/Php/FilterFunctionReturnTypeHelper.php index cebc1577596..44e3fb55b91 100644 --- a/src/Type/Php/FilterFunctionReturnTypeHelper.php +++ b/src/Type/Php/FilterFunctionReturnTypeHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Php; use PhpParser\Node; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; @@ -33,6 +34,7 @@ use function preg_match; use function sprintf; +#[AutowiredService] final class FilterFunctionReturnTypeHelper { diff --git a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php index 1a04c7d5b94..d1679bcddaf 100644 --- a/src/Type/Php/FilterInputDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterInputDynamicReturnTypeExtension.php @@ -4,11 +4,13 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class FilterInputDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php index 9f9a51cad3a..d92e4e5882c 100644 --- a/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarArrayDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -26,6 +27,7 @@ use function in_array; use function strtolower; +#[AutowiredService] final class FilterVarArrayDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index 26c27735557..1aeacdb0cfd 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use function count; use function strtolower; +#[AutowiredService] final class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php index 612c66d7862..e290bea8498 100644 --- a/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetCalledClassDynamicReturnTypeExtension.php @@ -6,11 +6,13 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; +#[AutowiredService] final class GetCalledClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index bd1f73b5c27..14a06d90c04 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -23,6 +24,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class GetClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php index 6860694293b..6223240bb1a 100644 --- a/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetDebugTypeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -15,6 +16,7 @@ use function array_map; use function count; +#[AutowiredService] final class GetDebugTypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php b/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php index 35999b424bf..e401fd3f315 100644 --- a/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetDefinedVarsFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -13,6 +14,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; +#[AutowiredService] final class GetDefinedVarsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php index b03a68655d8..e4db21dda94 100644 --- a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; @@ -18,6 +19,7 @@ use function array_map; use function count; +#[AutowiredService] final class GetParentClassDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php index 8b6a6133cf0..757ffdf2385 100644 --- a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantArrayType; @@ -15,6 +16,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; +#[AutowiredService] final class GettimeofdayDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/GettypeFunctionReturnTypeExtension.php b/src/Type/Php/GettypeFunctionReturnTypeExtension.php index 875529ae473..79c1c421246 100644 --- a/src/Type/Php/GettypeFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettypeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -15,6 +16,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class GettypeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 85ba080957d..7798d8f0e88 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -24,6 +25,7 @@ use function is_bool; use function strtolower; +#[AutowiredService] final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php index 12236c20cf1..71f8e6643fe 100644 --- a/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php +++ b/src/Type/Php/HighlightStringDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BooleanType; @@ -13,6 +14,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class HighlightStringDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php index fdbf7833781..80a49d7cfc2 100644 --- a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; @@ -16,6 +17,7 @@ use PHPStan\Type\TypeUtils; use function count; +#[AutowiredService] final class HrtimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 15d1d86706c..699b21c09c4 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -23,6 +24,7 @@ use function implode; use function in_array; +#[AutowiredService] final class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/IniGetReturnTypeExtension.php b/src/Type/Php/IniGetReturnTypeExtension.php index 15dc36a481a..21aab0b82f0 100644 --- a/src/Type/Php/IniGetReturnTypeExtension.php +++ b/src/Type/Php/IniGetReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -13,6 +14,7 @@ use function array_key_exists; use function count; +#[AutowiredService] final class IniGetReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php index 618fdb1fdc1..b4ed3b2d187 100644 --- a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php +++ b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; @@ -15,6 +16,7 @@ use PHPStan\Type\TypeCombinator; use function strtolower; +#[AutowiredService] final class IteratorToArrayFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php index 65d82ccd7bc..f9e551cc8a5 100644 --- a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; @@ -20,6 +21,7 @@ use function is_bool; use function json_decode; +#[AutowiredService] final class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index a09f0a823ab..4c18de01765 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -18,6 +19,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MbFunctionsReturnTypeExtension.php b/src/Type/Php/MbFunctionsReturnTypeExtension.php index dc27fe349b4..596e2ed8c1f 100644 --- a/src/Type/Php/MbFunctionsReturnTypeExtension.php +++ b/src/Type/Php/MbFunctionsReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -21,6 +22,7 @@ use function array_unique; use function count; +#[AutowiredService] final class MbFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php index 8f6fd258193..48d6b5e7129 100644 --- a/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbStrlenFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -32,6 +33,7 @@ use function sprintf; use function var_export; +#[AutowiredService] final class MbStrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php index a782db66866..7b14c0cce7f 100644 --- a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php +++ b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BooleanType; @@ -18,6 +19,7 @@ use function in_array; use function strtolower; +#[AutowiredService] final class MbSubstituteCharacterDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php index f0cc1561fb3..63ab8b26691 100644 --- a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -14,6 +15,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class MicrotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index 9faa3563c70..26864a30548 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Ternary; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; @@ -20,6 +21,7 @@ use function count; use function in_array; +#[AutowiredService] final class MinMaxFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index 893627aae2a..913f97a10cb 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; @@ -17,6 +18,7 @@ use function in_array; use const ENT_SUBSTITUTE; +#[AutowiredService] final class NonEmptyStringFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php b/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php index 1a564b358ca..8da8eda9865 100644 --- a/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantStringType; @@ -14,6 +15,7 @@ use PHPStan\Type\Type; use function in_array; +#[AutowiredService] final class NumberFormatFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index 19d6c20b26f..66035119d36 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -30,6 +31,7 @@ use const PHP_URL_SCHEME; use const PHP_URL_USER; +#[AutowiredService] final class ParseUrlFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index 60fac483585..49cb0dffdb6 100644 --- a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; @@ -17,6 +18,7 @@ use function count; use function sprintf; +#[AutowiredService] final class PathinfoFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/PowFunctionReturnTypeExtension.php b/src/Type/Php/PowFunctionReturnTypeExtension.php index 6be372d38fc..d2c0ba48305 100644 --- a/src/Type/Php/PowFunctionReturnTypeExtension.php +++ b/src/Type/Php/PowFunctionReturnTypeExtension.php @@ -5,11 +5,13 @@ use PhpParser\Node\Expr\BinaryOp\Pow; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class PowFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php index c2c23e4155e..6d6660d3627 100644 --- a/src/Type/Php/PregFilterFunctionReturnTypeExtension.php +++ b/src/Type/Php/PregFilterFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ArrayType; @@ -15,6 +16,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class PregFilterFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index b54b6676d06..ec1c814a474 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -32,6 +33,7 @@ use function preg_split; use function strtolower; +#[AutowiredService] final class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php index 3e0873ee118..35f978e6314 100644 --- a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php +++ b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -17,6 +18,7 @@ use function max; use function min; +#[AutowiredService] final class RandomIntFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 175370be905..de6e43bd794 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -27,6 +28,7 @@ use function is_array; use function range; +#[AutowiredService] final class RangeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 01bb1dd29e4..2885b42e684 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -22,6 +23,7 @@ use function count; use function in_array; +#[AutowiredService] final class ReplaceFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/RoundFunctionReturnTypeExtension.php b/src/Type/Php/RoundFunctionReturnTypeExtension.php index 8d064c1ef38..88f95bf3083 100644 --- a/src/Type/Php/RoundFunctionReturnTypeExtension.php +++ b/src/Type/Php/RoundFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -22,6 +23,7 @@ use function count; use function in_array; +#[AutowiredService] final class RoundFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index e379c4cc3cc..b819df44c26 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Internal\CombinationsHelper; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -36,6 +37,7 @@ use function substr; use function vsprintf; +#[AutowiredService] final class SprintfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php index 18a1275ca66..de22ba0a461 100644 --- a/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; @@ -21,6 +22,7 @@ use function in_array; use function preg_match_all; +#[AutowiredService] final class SscanfFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php index c6226f5a5f8..c965f8cd126 100644 --- a/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php +++ b/src/Type/Php/StrCaseFunctionsReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -26,6 +27,7 @@ use const MB_CASE_LOWER; use const MB_CASE_UPPER; +#[AutowiredService] final class StrCaseFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php index f44a76b71ce..5c936e5f723 100644 --- a/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -23,6 +24,7 @@ use function str_split; use function stripos; +#[AutowiredService] final class StrIncrementDecrementFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index d556fff1be8..a6e9f927c9c 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -17,6 +18,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class StrPadFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 98afacb7d71..585c02bf9b0 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use Nette\Utils\Strings; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -24,6 +25,7 @@ use function str_repeat; use function strlen; +#[AutowiredService] final class StrRepeatFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 9c28bc3c694..37d66aabc27 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; @@ -29,6 +30,7 @@ use function mb_str_split; use function str_split; +#[AutowiredService] final class StrSplitFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrTokFunctionReturnTypeExtension.php b/src/Type/Php/StrTokFunctionReturnTypeExtension.php index 85efaa5ad2a..01af6229321 100644 --- a/src/Type/Php/StrTokFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrTokFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use PHPStan\Type\Type; use function count; +#[AutowiredService] final class StrTokFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php index 33ed2457396..c6517e866dc 100644 --- a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -16,6 +17,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class StrWordCountFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index bc91f8595b6..c758d490951 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -21,6 +22,7 @@ use function sort; use function strlen; +#[AutowiredService] final class StrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrrevFunctionReturnTypeExtension.php b/src/Type/Php/StrrevFunctionReturnTypeExtension.php index 4d02ec8a8c4..4bbc2c8fb8b 100644 --- a/src/Type/Php/StrrevFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrrevFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -18,6 +19,7 @@ use function count; use function strrev; +#[AutowiredService] final class StrrevFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php index 084ee527afa..284759e59e6 100644 --- a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantBooleanType; @@ -21,6 +22,7 @@ use function min; use function strtotime; +#[AutowiredService] final class StrtotimeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php index 8ed6c637fcc..3fa0403bab8 100644 --- a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\DynamicFunctionReturnTypeExtension; @@ -15,6 +16,7 @@ use function count; use function in_array; +#[AutowiredService] final class StrvalFamilyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index df1d0cf0606..00bfb3f33bc 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -25,6 +26,7 @@ use function mb_substr; use function substr; +#[AutowiredService] final class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php index c05c6f6cefd..3f3e09242df 100644 --- a/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; @@ -18,6 +19,7 @@ use const E_USER_NOTICE; use const E_USER_WARNING; +#[AutowiredService] final class TriggerErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From da40fdbfba811ea49d67ccf3bad8d33e72b3741a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 25 May 2025 21:22:24 +0200 Subject: [PATCH 1426/3097] Map all BrokerFactory tags in AutowiredAttributeServicesExtension --- conf/config.neon | 332 ------------------ .../AutowiredAttributeServicesExtension.php | 80 +++++ src/PhpDoc/SocketSelectStubFilesExtension.php | 2 + .../ReflectionSourceStubberFactory.php | 2 + ...flectionEnumDynamicReturnTypeExtension.php | 2 + .../Deprecation/DeprecationProvider.php | 2 + ...llowedSubTypesClassReflectionExtension.php | 2 + src/Rules/Functions/PrintfHelper.php | 2 + .../Properties/PropertyReflectionFinder.php | 2 + src/Rules/Pure/FunctionPurityCheck.php | 2 + .../TooWideParameterOutTypeCheck.php | 2 + src/Type/BitwiseFlagHelper.php | 2 + src/Type/Constant/OversizedArrayBuilder.php | 2 + ...teIdentifierDynamicReturnTypeExtension.php | 2 + ...yExistsFunctionTypeSpecifyingExtension.php | 2 + ...ySearchFunctionTypeSpecifyingExtension.php | 2 + .../AssertFunctionTypeSpecifyingExtension.php | 2 + src/Type/Php/AssertThrowTypeExtension.php | 2 + ...umFromMethodDynamicReturnTypeExtension.php | 2 + ...hNumberOperatorTypeSpecifyingExtension.php | 2 + ...sExistsFunctionTypeSpecifyingExtension.php | 2 + .../ClosureBindDynamicReturnTypeExtension.php | 2 + ...losureBindToDynamicReturnTypeExtension.php | 2 + ...FromCallableDynamicReturnTypeExtension.php | 2 + src/Type/Php/ConstantHelper.php | 2 + .../CountFunctionTypeSpecifyingExtension.php | 2 + ...peDigitFunctionTypeSpecifyingExtension.php | 2 + .../DateFormatMethodReturnTypeExtension.php | 2 + src/Type/Php/DateFunctionReturnTypeHelper.php | 2 + ...eIntervalConstructorThrowTypeExtension.php | 2 + ...DateIntervalDynamicReturnTypeExtension.php | 2 + ...tePeriodConstructorReturnTypeExtension.php | 2 + .../DateTimeConstructorThrowTypeExtension.php | 2 + ...DateTimeModifyMethodThrowTypeExtension.php | 2 + .../DateTimeSubMethodThrowTypeExtension.php | 2 + ...eTimeZoneConstructorThrowTypeExtension.php | 2 + .../DefineConstantTypeSpecifyingExtension.php | 2 + ...DefinedConstantTypeSpecifyingExtension.php | 2 + .../DsMapDynamicMethodThrowTypeExtension.php | 2 + .../Php/DsMapDynamicReturnTypeExtension.php | 2 + ...nExistsFunctionTypeSpecifyingExtension.php | 2 + ...InArrayFunctionTypeSpecifyingExtension.php | 2 + src/Type/Php/IntdivThrowTypeExtension.php | 2 + .../IsAFunctionTypeSpecifyingExtension.php | 2 + .../Php/IsAFunctionTypeSpecifyingHelper.php | 2 + ...IsArrayFunctionTypeSpecifyingExtension.php | 2 + ...allableFunctionTypeSpecifyingExtension.php | 2 + ...terableFunctionTypeSpecifyingExtension.php | 2 + ...classOfFunctionTypeSpecifyingExtension.php | 2 + src/Type/Php/JsonThrowTypeExtension.php | 2 + .../MethodExistsTypeSpecifyingExtension.php | 2 + ...penSslEncryptParameterOutTypeExtension.php | 2 + .../Php/ParseStrParameterOutTypeExtension.php | 2 + .../PregMatchParameterOutTypeExtension.php | 2 + .../Php/PregMatchTypeSpecifyingExtension.php | 2 + ...regReplaceCallbackClosureTypeExtension.php | 2 + .../PropertyExistsTypeSpecifyingExtension.php | 2 + ...tionClassConstructorThrowTypeExtension.php | 2 + ...assIsSubclassOfTypeSpecifyingExtension.php | 2 + ...nFunctionConstructorThrowTypeExtension.php | 2 + ...ionMethodConstructorThrowTypeExtension.php | 2 + ...nPropertyConstructorThrowTypeExtension.php | 2 + src/Type/Php/RegexArrayShapeMatcher.php | 2 + ...SetTypeFunctionTypeSpecifyingExtension.php | 2 + ...LElementAsXMLMethodReturnTypeExtension.php | 2 + ...lementClassPropertyReflectionExtension.php | 2 + ...MLElementConstructorThrowTypeExtension.php | 2 + ...LElementXpathMethodReturnTypeExtension.php | 2 + .../Php/StatDynamicReturnTypeExtension.php | 2 + .../StrContainingTypeSpecifyingExtension.php | 2 + src/Type/Php/ThrowableReturnTypeExtension.php | 2 + .../Php/XMLReaderOpenReturnTypeExtension.php | 2 + src/Type/Regex/RegexExpressionHelper.php | 2 + src/Type/Regex/RegexGroupParser.php | 2 + 74 files changed, 224 insertions(+), 332 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 7fe441dfc6e..abdf0f60679 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -403,11 +403,6 @@ services: - class: PHPStan\PhpDoc\StubValidator - - - class: PHPStan\PhpDoc\SocketSelectStubFilesExtension - tags: - - phpstan.stubFilesExtension - - class: PHPStan\PhpDoc\DefaultStubFilesProvider arguments: @@ -753,11 +748,6 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Reflection\ConstructorsHelper arguments: @@ -793,11 +783,6 @@ services: - class: PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension - - - class: PHPStan\Reflection\Php\EnumAllowedSubTypesClassReflectionExtension - tags: - - phpstan.broker.allowedSubTypesClassReflectionExtension - - class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension tags: @@ -1071,10 +1056,6 @@ services: arguments: reportMagicProperties: %reportMagicProperties% checkDynamicProperties: %checkDynamicProperties% - - - class: PHPStan\Type\Php\BcMathNumberOperatorTypeSpecifyingExtension - tags: - - phpstan.broker.operatorTypeSpecifyingExtension - class: PHPStan\Rules\Properties\UninitializedPropertyRule @@ -1085,12 +1066,6 @@ services: - class: PHPStan\Rules\Properties\PropertyDescriptor - - - class: PHPStan\Rules\Properties\PropertyReflectionFinder - - - - class: PHPStan\Rules\Pure\FunctionPurityCheck - - class: PHPStan\Rules\RuleLevelHelper arguments: @@ -1107,9 +1082,6 @@ services: arguments: reportExactLine: %featureToggles.reportPreciseLineForUnusedFunctionParameter% - - - class: PHPStan\Rules\TooWideTypehints\TooWideParameterOutTypeCheck - - class: PHPStan\Type\FileTypeMapper arguments: @@ -1131,9 +1103,6 @@ services: class: PHPStan\Type\TypeAliasResolverProvider factory: PHPStan\Type\LazyTypeAliasResolverProvider - - - class: PHPStan\Type\BitwiseFlagHelper - - class: PHPStan\Type\Php\CompactFunctionReturnTypeExtension tags: @@ -1141,67 +1110,6 @@ services: arguments: checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables% - - - class: PHPStan\Type\Php\ConstantHelper - - - - class: PHPStan\Type\Php\DateFunctionReturnTypeHelper - - - - class: PHPStan\Type\Php\ArrayKeyExistsFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\AssertThrowTypeExtension - tags: - - phpstan.dynamicFunctionThrowTypeExtension - - - - class: PHPStan\Type\Php\ArraySearchFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\ClosureBindDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\ClosureBindToDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\ClosureFromCallableDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\BackedEnumFromMethodDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\DateFormatMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\DateIntervalConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\DateIntervalDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension tags: @@ -1216,146 +1124,6 @@ services: arguments: dateTimeClass: DateTimeImmutable - - - class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\DateTimeModifyMethodThrowTypeExtension - tags: - - phpstan.dynamicMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\DateTimeSubMethodThrowTypeExtension - tags: - - phpstan.dynamicMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\DateTimeZoneConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\DsMapDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\DsMapDynamicMethodThrowTypeExtension - tags: - - phpstan.dynamicMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\IntdivThrowTypeExtension - tags: - - phpstan.dynamicFunctionThrowTypeExtension - - - - class: PHPStan\Type\Php\JsonThrowTypeExtension - tags: - - phpstan.dynamicFunctionThrowTypeExtension - - - - class: PHPStan\Type\Php\OpenSslEncryptParameterOutTypeExtension - tags: - - phpstan.functionParameterOutTypeExtension - - - - class: PHPStan\Type\Php\ParseStrParameterOutTypeExtension - tags: - - phpstan.functionParameterOutTypeExtension - - - - class: PHPStan\Type\Php\PregMatchTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\PregMatchParameterOutTypeExtension - tags: - - phpstan.functionParameterOutTypeExtension - - - - class: PHPStan\Type\Php\PregReplaceCallbackClosureTypeExtension - tags: - - phpstan.functionParameterClosureTypeExtension - - - - class: PHPStan\Type\Php\RegexArrayShapeMatcher - - - - class: PHPStan\Type\Regex\RegexGroupParser - - - - class: PHPStan\Type\Regex\RegexExpressionHelper - - - - class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\ReflectionFunctionConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\ReflectionMethodConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\ReflectionPropertyConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\StrContainingTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\SimpleXMLElementClassPropertyReflectionExtension - tags: - - phpstan.broker.propertiesClassReflectionExtension - - - - class: PHPStan\Type\Php\SimpleXMLElementConstructorThrowTypeExtension - tags: - - phpstan.dynamicStaticMethodThrowTypeExtension - - - - class: PHPStan\Type\Php\StatDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\MethodExistsTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\PropertyExistsTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\ReflectionClassIsSubclassOfTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.methodTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\SetTypeFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\ThrowableReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension arguments: @@ -1363,69 +1131,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\AssertFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\ClassExistsFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\DefineConstantTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\DefinedConstantTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\FunctionExistsFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\InArrayFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsArrayFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsCallableFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsIterableFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsSubclassOfFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsAFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - - - class: PHPStan\Type\Php\IsAFunctionTypeSpecifyingHelper - - - - class: PHPStan\Type\Php\CtypeDigitFunctionTypeSpecifyingExtension - tags: - - phpstan.typeSpecifier.functionTypeSpecifyingExtension - - class: PHPStan\Type\Php\TypeSpecifyingFunctionsDynamicReturnTypeExtension arguments: @@ -1434,16 +1139,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\SimpleXMLElementAsXMLMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\SimpleXMLElementXpathMethodReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension tags: @@ -1454,12 +1149,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\XMLReaderOpenReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension arguments: @@ -1495,26 +1184,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - - class: PHPStan\Type\Php\DatePeriodConstructorReturnTypeExtension - tags: - - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - - - class: PHPStan\Type\PHPStan\ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - class: PHPStan\Type\ClosureTypeFactory arguments: parser: @currentPhpVersionPhpParser - - - class: PHPStan\Type\Constant\OversizedArrayBuilder - - - - class: PHPStan\Rules\Functions\PrintfHelper - exceptionTypeResolver: class: PHPStan\Rules\Exceptions\ExceptionTypeResolver factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver @@ -1682,12 +1356,6 @@ services: autowired: - PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber - - - class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory - - - - class: PHPStan\Reflection\Deprecation\DeprecationProvider - php8Lexer: class: PhpParser\Lexer\Emulative factory: @PHPStan\Parser\LexerFactory::createEmulative() diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index cdc63fc0123..f326b2d308f 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -4,10 +4,53 @@ use Nette\DI\CompilerExtension; use olvlvl\ComposerAttributeCollector\Attributes; +use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; +use PHPStan\Analyser\TypeSpecifierFactory; use PHPStan\Broker\BrokerFactory; +use PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider; +use PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider; +use PHPStan\DependencyInjection\Type\LazyParameterOutTypeExtensionProvider; +use PHPStan\PhpDoc\StubFilesExtension; +use PHPStan\PhpDoc\TypeNodeResolverExtension; +use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension; +use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension; +use PHPStan\Reflection\Deprecation\ClassDeprecationExtension; +use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension; +use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension; +use PHPStan\Reflection\Deprecation\MethodDeprecationExtension; +use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension; +use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\PropertiesClassReflectionExtension; +use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtension; +use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; use PHPStan\Rules\LazyRegistry; +use PHPStan\Rules\Methods\AlwaysUsedMethodExtension; +use PHPStan\Rules\Methods\AlwaysUsedMethodExtensionProvider; +use PHPStan\Rules\Properties\ReadWritePropertiesExtension; +use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; +use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; +use PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageExtension; use PHPStan\Rules\Rule; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\DynamicFunctionThrowTypeExtension; +use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\DynamicMethodThrowTypeExtension; +use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; +use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; +use PHPStan\Type\ExpressionTypeResolverExtension; +use PHPStan\Type\FunctionParameterClosureTypeExtension; +use PHPStan\Type\FunctionParameterOutTypeExtension; +use PHPStan\Type\FunctionTypeSpecifyingExtension; +use PHPStan\Type\MethodParameterClosureTypeExtension; +use PHPStan\Type\MethodParameterOutTypeExtension; +use PHPStan\Type\MethodTypeSpecifyingExtension; +use PHPStan\Type\OperatorTypeSpecifyingExtension; +use PHPStan\Type\StaticMethodParameterClosureTypeExtension; +use PHPStan\Type\StaticMethodParameterOutTypeExtension; +use PHPStan\Type\StaticMethodTypeSpecifyingExtension; use ReflectionClass; final class AutowiredAttributeServicesExtension extends CompilerExtension @@ -20,8 +63,45 @@ public function loadConfiguration(): void $builder = $this->getContainerBuilder(); $interfaceToTag = [ + PropertiesClassReflectionExtension::class => BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG, + MethodsClassReflectionExtension::class => BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG, + AllowedSubTypesClassReflectionExtension::class => BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG, + DynamicMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG, + DynamicStaticMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG, DynamicFunctionReturnTypeExtension::class => BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG, + OperatorTypeSpecifyingExtension::class => BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG, + ExpressionTypeResolverExtension::class => BrokerFactory::EXPRESSION_TYPE_RESOLVER_EXTENSION_TAG, + TypeNodeResolverExtension::class => TypeNodeResolverExtension::EXTENSION_TAG, Rule::class => LazyRegistry::RULE_TAG, + StubFilesExtension::class => StubFilesExtension::EXTENSION_TAG, + AlwaysUsedClassConstantsExtension::class => AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG, + AlwaysUsedMethodExtension::class => AlwaysUsedMethodExtensionProvider::EXTENSION_TAG, + ReadWritePropertiesExtension::class => ReadWritePropertiesExtensionProvider::EXTENSION_TAG, + FunctionTypeSpecifyingExtension::class => TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG, + MethodTypeSpecifyingExtension::class => TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG, + StaticMethodTypeSpecifyingExtension::class => TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG, + DynamicFunctionThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::FUNCTION_TAG, + DynamicMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::METHOD_TAG, + DynamicStaticMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::STATIC_METHOD_TAG, + FunctionParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG, + MethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::METHOD_TAG, + StaticMethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::STATIC_METHOD_TAG, + FunctionParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::FUNCTION_TAG, + MethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::METHOD_TAG, + StaticMethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG, + ResultCacheMetaExtension::class => ResultCacheMetaExtension::EXTENSION_TAG, + ClassConstantDeprecationExtension::class => ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG, + ClassDeprecationExtension::class => ClassDeprecationExtension::CLASS_EXTENSION_TAG, + EnumCaseDeprecationExtension::class => EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG, + FunctionDeprecationExtension::class => FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG, + MethodDeprecationExtension::class => MethodDeprecationExtension::METHOD_EXTENSION_TAG, + PropertyDeprecationExtension::class => PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG, + RestrictedMethodUsageExtension::class => RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG, + RestrictedClassNameUsageExtension::class => RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG, + RestrictedFunctionUsageExtension::class => RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG, + RestrictedPropertyUsageExtension::class => RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG, + RestrictedClassConstantUsageExtension::class => RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG, + ]; foreach ($autowiredServiceClasses as $class) { diff --git a/src/PhpDoc/SocketSelectStubFilesExtension.php b/src/PhpDoc/SocketSelectStubFilesExtension.php index c5d60ea909a..cd954ca6b08 100644 --- a/src/PhpDoc/SocketSelectStubFilesExtension.php +++ b/src/PhpDoc/SocketSelectStubFilesExtension.php @@ -2,8 +2,10 @@ namespace PHPStan\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class SocketSelectStubFilesExtension implements StubFilesExtension { diff --git a/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php b/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php index 8f7533ba5fc..0a1f1e9e6a9 100644 --- a/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php +++ b/src/Reflection/BetterReflection/SourceStubber/ReflectionSourceStubberFactory.php @@ -3,9 +3,11 @@ namespace PHPStan\Reflection\BetterReflection\SourceStubber; use PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\Printer; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class ReflectionSourceStubberFactory { diff --git a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php index dfa3218442f..2d3920e771c 100644 --- a/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php +++ b/src/Reflection/BetterReflection/Type/AdapterReflectionEnumDynamicReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -22,6 +23,7 @@ use PHPStan\Type\UnionType; use function in_array; +#[AutowiredService] final class AdapterReflectionEnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Reflection/Deprecation/DeprecationProvider.php b/src/Reflection/Deprecation/DeprecationProvider.php index 9c526121e90..ebd25cde0db 100644 --- a/src/Reflection/Deprecation/DeprecationProvider.php +++ b/src/Reflection/Deprecation/DeprecationProvider.php @@ -11,8 +11,10 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\BetterReflection\Reflection\ReflectionConstant; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class DeprecationProvider { diff --git a/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php index 04f4fbe8e74..d36306a1b51 100644 --- a/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php +++ b/src/Reflection/Php/EnumAllowedSubTypesClassReflectionExtension.php @@ -2,11 +2,13 @@ namespace PHPStan\Reflection\Php; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Enum\EnumCaseObjectType; use function array_keys; +#[AutowiredService] final class EnumAllowedSubTypesClassReflectionExtension implements AllowedSubTypesClassReflectionExtension { diff --git a/src/Rules/Functions/PrintfHelper.php b/src/Rules/Functions/PrintfHelper.php index a5d4571f762..7f68f17fa45 100644 --- a/src/Rules/Functions/PrintfHelper.php +++ b/src/Rules/Functions/PrintfHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Functions; use Nette\Utils\Strings; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use function array_filter; use function count; @@ -11,6 +12,7 @@ use function strlen; use const PREG_SET_ORDER; +#[AutowiredService] final class PrintfHelper { diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index 67b2785fa9c..3daa5ee867b 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -7,11 +7,13 @@ use PhpParser\Node\Scalar\String_; use PhpParser\Node\VarLikeIdentifier; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; use function array_map; use function count; +#[AutowiredService] final class PropertyReflectionFinder { diff --git a/src/Rules/Pure/FunctionPurityCheck.php b/src/Rules/Pure/FunctionPurityCheck.php index a799cd4351c..1e5f7a32d61 100644 --- a/src/Rules/Pure/FunctionPurityCheck.php +++ b/src/Rules/Pure/FunctionPurityCheck.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt; use PHPStan\Analyser\ImpurePoint; use PHPStan\Analyser\ThrowPoint; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\FunctionReflection; @@ -20,6 +21,7 @@ use function lcfirst; use function sprintf; +#[AutowiredService] final class FunctionPurityCheck { diff --git a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php index e891b65fb6f..47ff4287890 100644 --- a/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php +++ b/src/Rules/TooWideTypehints/TooWideParameterOutTypeCheck.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\ReturnStatement; use PHPStan\Reflection\ExtendedParameterReflection; @@ -14,6 +15,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; +#[AutowiredService] final class TooWideParameterOutTypeCheck { diff --git a/src/Type/BitwiseFlagHelper.php b/src/Type/BitwiseFlagHelper.php index 7d5e4c3db92..37e0a4a5cd0 100644 --- a/src/Type/BitwiseFlagHelper.php +++ b/src/Type/BitwiseFlagHelper.php @@ -7,10 +7,12 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Name\FullyQualified; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantIntegerType; +#[AutowiredService] final class BitwiseFlagHelper { diff --git a/src/Type/Constant/OversizedArrayBuilder.php b/src/Type/Constant/OversizedArrayBuilder.php index 026e305361d..530fe860465 100644 --- a/src/Type/Constant/OversizedArrayBuilder.php +++ b/src/Type/Constant/OversizedArrayBuilder.php @@ -5,6 +5,7 @@ use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\TypeExpr; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -19,6 +20,7 @@ use function array_values; use function count; +#[AutowiredService] final class OversizedArrayBuilder { diff --git a/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php index 0b847b8ca62..c15f13bf242 100644 --- a/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php +++ b/src/Type/PHPStan/ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -14,6 +15,7 @@ use function count; use function sort; +#[AutowiredService] final class ClassNameUsageLocationCreateIdentifierDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index 7d2eb3b2a4a..af63cc047d3 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -12,6 +12,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -24,6 +25,7 @@ use function count; use function in_array; +#[AutowiredService] final class ArrayKeyExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php index 68f2992096a..38d8909e9b1 100644 --- a/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArraySearchFunctionTypeSpecifyingExtension.php @@ -8,11 +8,13 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use function strtolower; +#[AutowiredService] final class ArraySearchFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php index d1458a27dd8..adc436e5841 100644 --- a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php @@ -8,9 +8,11 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\FunctionTypeSpecifyingExtension; +#[AutowiredService] final class AssertFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/AssertThrowTypeExtension.php b/src/Type/Php/AssertThrowTypeExtension.php index 2cf42260472..b30bab71cc4 100644 --- a/src/Type/Php/AssertThrowTypeExtension.php +++ b/src/Type/Php/AssertThrowTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionThrowTypeExtension; use PHPStan\Type\ObjectType; @@ -11,6 +12,7 @@ use Throwable; use function count; +#[AutowiredService] final class AssertThrowTypeExtension implements DynamicFunctionThrowTypeExtension { diff --git a/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php b/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php index c80bb0950b4..5ee4ab5cf05 100644 --- a/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php +++ b/src/Type/Php/BackedEnumFromMethodDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use BackedEnum; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\Enum\EnumCaseObjectType; @@ -14,6 +15,7 @@ use function count; use function in_array; +#[AutowiredService] final class BackedEnumFromMethodDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php b/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php index 3e0d793052d..c75ebd20567 100644 --- a/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php +++ b/src/Type/Php/BcMathNumberOperatorTypeSpecifyingExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Type\ErrorType; use PHPStan\Type\NeverType; @@ -10,6 +11,7 @@ use PHPStan\Type\Type; use function in_array; +#[AutowiredService] final class BcMathNumberOperatorTypeSpecifyingExtension implements OperatorTypeSpecifyingExtension { diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index e10e74a53ea..53cec9a0e6c 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -21,6 +22,7 @@ use function in_array; use function ltrim; +#[AutowiredService] final class ClassExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php index 719fd172d30..580595acd74 100644 --- a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php @@ -5,11 +5,13 @@ use Closure; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ClosureType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\Type; +#[AutowiredService] final class ClosureBindDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php index 9e495b3163e..3e275677e27 100644 --- a/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindToDynamicReturnTypeExtension.php @@ -5,11 +5,13 @@ use Closure; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ClosureType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Type; +#[AutowiredService] final class ClosureBindToDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index d579157160c..fd103b4f6a2 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use Closure; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ClosureType; @@ -14,6 +15,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +#[AutowiredService] final class ClosureFromCallableDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/ConstantHelper.php b/src/Type/Php/ConstantHelper.php index a3e8c5224f9..16e55861bc6 100644 --- a/src/Type/Php/ConstantHelper.php +++ b/src/Type/Php/ConstantHelper.php @@ -8,10 +8,12 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; +use PHPStan\DependencyInjection\AutowiredService; use function count; use function explode; use function ltrim; +#[AutowiredService] final class ConstantHelper { diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index b109b13f917..741c7cc880c 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -8,12 +8,14 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use function count; use function in_array; +#[AutowiredService] final class CountFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php index 837c3fb8900..04daf2ed552 100644 --- a/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CtypeDigitFunctionTypeSpecifyingExtension.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -21,6 +22,7 @@ use PHPStan\Type\UnionType; use function strtolower; +#[AutowiredService] final class CtypeDigitFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/DateFormatMethodReturnTypeExtension.php b/src/Type/Php/DateFormatMethodReturnTypeExtension.php index 34854d28cc1..55d9e28d74a 100644 --- a/src/Type/Php/DateFormatMethodReturnTypeExtension.php +++ b/src/Type/Php/DateFormatMethodReturnTypeExtension.php @@ -5,12 +5,14 @@ use DateTimeInterface; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\StringType; use PHPStan\Type\Type; use function count; +#[AutowiredService] final class DateFormatMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/DateFunctionReturnTypeHelper.php b/src/Type/Php/DateFunctionReturnTypeHelper.php index 7723ee2151e..bac485292b2 100644 --- a/src/Type/Php/DateFunctionReturnTypeHelper.php +++ b/src/Type/Php/DateFunctionReturnTypeHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; @@ -17,6 +18,7 @@ use function str_pad; use const STR_PAD_LEFT; +#[AutowiredService] final class DateFunctionReturnTypeHelper { diff --git a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php index 04c356151de..971f593bfc8 100644 --- a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php @@ -5,6 +5,7 @@ use DateInterval; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -14,6 +15,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class DateIntervalConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 563ade35891..c5f597fcdc4 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use DateInterval; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; @@ -14,6 +15,7 @@ use function count; use function in_array; +#[AutowiredService] final class DateIntervalDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php index e20c503c05b..bcdf1032c73 100644 --- a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -8,6 +8,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\Generic\GenericObjectType; @@ -17,6 +18,7 @@ use PHPStan\Type\Type; use function strtolower; +#[AutowiredService] final class DatePeriodConstructorReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php index 6bde75bc6d2..2facd039453 100644 --- a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -16,6 +17,7 @@ use function count; use function in_array; +#[AutowiredService] final class DateTimeConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/DateTimeModifyMethodThrowTypeExtension.php b/src/Type/Php/DateTimeModifyMethodThrowTypeExtension.php index d22637d4e5f..02c0099c4ee 100644 --- a/src/Type/Php/DateTimeModifyMethodThrowTypeExtension.php +++ b/src/Type/Php/DateTimeModifyMethodThrowTypeExtension.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodThrowTypeExtension; @@ -16,6 +17,7 @@ use function count; use function in_array; +#[AutowiredService] final class DateTimeModifyMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension { diff --git a/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php b/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php index ce6d31b581a..fc92e2b91d5 100644 --- a/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php +++ b/src/Type/Php/DateTimeSubMethodThrowTypeExtension.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodThrowTypeExtension; @@ -14,6 +15,7 @@ use function count; use function in_array; +#[AutowiredService] final class DateTimeSubMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension { diff --git a/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php index e1b35ac7eeb..0c4c0bd9dd9 100644 --- a/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeZoneConstructorThrowTypeExtension.php @@ -5,6 +5,7 @@ use DateTimeZone; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -14,6 +15,7 @@ use PHPStan\Type\TypeCombinator; use function count; +#[AutowiredService] final class DateTimeZoneConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 9d4ac3d682a..7364ff54e9f 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -9,11 +9,13 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use function count; +#[AutowiredService] final class DefineConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php index 01c310459bc..df0fe21f2bf 100644 --- a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php @@ -8,12 +8,14 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\MixedType; use function count; +#[AutowiredService] final class DefinedConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/DsMapDynamicMethodThrowTypeExtension.php b/src/Type/Php/DsMapDynamicMethodThrowTypeExtension.php index 3075827a962..8d2ceddc98c 100644 --- a/src/Type/Php/DsMapDynamicMethodThrowTypeExtension.php +++ b/src/Type/Php/DsMapDynamicMethodThrowTypeExtension.php @@ -4,12 +4,14 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodThrowTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\VoidType; use function count; +#[AutowiredService] final class DsMapDynamicMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension { diff --git a/src/Type/Php/DsMapDynamicReturnTypeExtension.php b/src/Type/Php/DsMapDynamicReturnTypeExtension.php index 50ecba6226b..6b833656d73 100644 --- a/src/Type/Php/DsMapDynamicReturnTypeExtension.php +++ b/src/Type/Php/DsMapDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\Type; @@ -11,6 +12,7 @@ use function count; use function in_array; +#[AutowiredService] final class DsMapDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php index 5d320104a72..330735d9e48 100644 --- a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\CallableType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -18,6 +19,7 @@ use PHPStan\Type\FunctionTypeSpecifyingExtension; use function ltrim; +#[AutowiredService] final class FunctionExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index d83e9f78ad5..c26d5500494 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\AlwaysRememberedExpr; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -21,6 +22,7 @@ use function count; use function strtolower; +#[AutowiredService] final class InArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/IntdivThrowTypeExtension.php b/src/Type/Php/IntdivThrowTypeExtension.php index 46920122a68..baab09248b2 100644 --- a/src/Type/Php/IntdivThrowTypeExtension.php +++ b/src/Type/Php/IntdivThrowTypeExtension.php @@ -6,6 +6,7 @@ use DivisionByZeroError; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionThrowTypeExtension; @@ -14,6 +15,7 @@ use function count; use const PHP_INT_MIN; +#[AutowiredService] final class IntdivThrowTypeExtension implements DynamicFunctionThrowTypeExtension { diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index bc4591b9a83..5d19e4950f6 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; @@ -15,6 +16,7 @@ use function count; use function strtolower; +#[AutowiredService] final class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingHelper.php b/src/Type/Php/IsAFunctionTypeSpecifyingHelper.php index d1f4a1bd82f..df33262d8d8 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingHelper.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; @@ -16,6 +17,7 @@ use function array_unique; use function array_values; +#[AutowiredService] final class IsAFunctionTypeSpecifyingHelper { diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index e31033185ed..0beb301d461 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ArrayType; @@ -15,6 +16,7 @@ use PHPStan\Type\MixedType; use function strtolower; +#[AutowiredService] final class IsArrayFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index 42c5fe85056..a0cde666e5b 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\CallableType; @@ -18,6 +19,7 @@ use function count; use function strtolower; +#[AutowiredService] final class IsCallableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php index a8404ef99f7..475f9a83cb0 100644 --- a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -15,6 +16,7 @@ use PHPStan\Type\MixedType; use function strtolower; +#[AutowiredService] final class IsIterableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index c62a11b1506..e910bd5ea1a 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -15,6 +16,7 @@ use function count; use function strtolower; +#[AutowiredService] final class IsSubclassOfFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/JsonThrowTypeExtension.php b/src/Type/Php/JsonThrowTypeExtension.php index 68e7855bb75..19cfcbaaea0 100644 --- a/src/Type/Php/JsonThrowTypeExtension.php +++ b/src/Type/Php/JsonThrowTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\BitwiseFlagHelper; @@ -13,6 +14,7 @@ use PHPStan\Type\Type; use function in_array; +#[AutowiredService] final class JsonThrowTypeExtension implements DynamicFunctionThrowTypeExtension { diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 751a69a41a6..3db54618e52 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\ClassStringType; @@ -20,6 +21,7 @@ use PHPStan\Type\UnionType; use function count; +#[AutowiredService] final class MethodExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php index 5d24f86f526..6784622a2a7 100644 --- a/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php +++ b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -17,6 +18,7 @@ use function strtolower; use function substr; +#[AutowiredService] final class OpenSslEncryptParameterOutTypeExtension implements FunctionParameterOutTypeExtension { diff --git a/src/Type/Php/ParseStrParameterOutTypeExtension.php b/src/Type/Php/ParseStrParameterOutTypeExtension.php index caabc319bf9..8e4a2de7d10 100644 --- a/src/Type/Php/ParseStrParameterOutTypeExtension.php +++ b/src/Type/Php/ParseStrParameterOutTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; @@ -20,6 +21,7 @@ use function in_array; use function strtolower; +#[AutowiredService] final class ParseStrParameterOutTypeExtension implements FunctionParameterOutTypeExtension { diff --git a/src/Type/Php/PregMatchParameterOutTypeExtension.php b/src/Type/Php/PregMatchParameterOutTypeExtension.php index 9066351d9fa..b8bd415b453 100644 --- a/src/Type/Php/PregMatchParameterOutTypeExtension.php +++ b/src/Type/Php/PregMatchParameterOutTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\TrinaryLogic; @@ -12,6 +13,7 @@ use function in_array; use function strtolower; +#[AutowiredService] final class PregMatchParameterOutTypeExtension implements FunctionParameterOutTypeExtension { diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index 09606087f1a..dca332f50e4 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -8,12 +8,14 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\FunctionTypeSpecifyingExtension; use function in_array; use function strtolower; +#[AutowiredService] final class PregMatchTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php b/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php index a7ea4dc1331..1be24ff645f 100644 --- a/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php +++ b/src/Type/Php/PregReplaceCallbackClosureTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\ParameterReflection; @@ -13,6 +14,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; +#[AutowiredService] final class PregReplaceCallbackClosureTypeExtension implements FunctionParameterClosureTypeExtension { diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 8b4d51142ae..e62a04d409c 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -11,6 +11,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\Accessory\HasPropertyType; @@ -21,6 +22,7 @@ use PHPStan\Type\ObjectWithoutClassType; use function count; +#[AutowiredService] final class PropertyExistsTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php index 487857213d9..94f0d95379a 100644 --- a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ClassStringType; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -13,6 +14,7 @@ use ReflectionClass; use function count; +#[AutowiredService] final class ReflectionClassConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index f472eab562f..2d830e15010 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MethodTypeSpecifyingExtension; @@ -15,6 +16,7 @@ use PHPStan\Type\TypeCombinator; use ReflectionClass; +#[AutowiredService] final class ReflectionClassIsSubclassOfTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php index 83df24904f3..01fe08f3549 100644 --- a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -14,6 +15,7 @@ use ReflectionFunction; use function count; +#[AutowiredService] final class ReflectionFunctionConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php index c0b0df2cf53..a2371b64347 100644 --- a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantStringType; @@ -16,6 +17,7 @@ use ReflectionMethod; use function count; +#[AutowiredService] final class ReflectionMethodConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php index b988a7883a8..22140eba684 100644 --- a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; @@ -13,6 +14,7 @@ use ReflectionProperty; use function count; +#[AutowiredService] final class ReflectionPropertyConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/RegexArrayShapeMatcher.php b/src/Type/Php/RegexArrayShapeMatcher.php index a23e3445d80..11c30872224 100644 --- a/src/Type/Php/RegexArrayShapeMatcher.php +++ b/src/Type/Php/RegexArrayShapeMatcher.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -32,6 +33,7 @@ /** * @api */ +#[AutowiredService] final class RegexArrayShapeMatcher { diff --git a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php index a9134026eae..32373fcf7af 100644 --- a/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/SetTypeFunctionTypeSpecifyingExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ErrorType; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -18,6 +19,7 @@ use function count; use function strtolower; +#[AutowiredService] final class SetTypeFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php index a0f33f18419..e308b376875 100644 --- a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use SimpleXMLElement; use function count; +#[AutowiredService] final class SimpleXMLElementAsXMLMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php index c6709148353..1bf6d9854b1 100644 --- a/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php +++ b/src/Type/Php/SimpleXMLElementClassPropertyReflectionExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Php; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Php\SimpleXMLElementProperty; use PHPStan\Reflection\PropertiesClassReflectionExtension; @@ -10,6 +11,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; +#[AutowiredService] final class SimpleXMLElementClassPropertyReflectionExtension implements PropertiesClassReflectionExtension { diff --git a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php index 28995db8ea2..78faf2d1d33 100644 --- a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; use PHPStan\Type\NeverType; @@ -14,6 +15,7 @@ use function extension_loaded; use function libxml_use_internal_errors; +#[AutowiredService] final class SimpleXMLElementConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension { diff --git a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php index 7255d648877..84dc243c24d 100644 --- a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -14,6 +15,7 @@ use SimpleXMLElement; use function extension_loaded; +#[AutowiredService] final class SimpleXMLElementXpathMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/StatDynamicReturnTypeExtension.php b/src/Type/Php/StatDynamicReturnTypeExtension.php index 4a0141b1063..b4dcd8bb7ef 100644 --- a/src/Type/Php/StatDynamicReturnTypeExtension.php +++ b/src/Type/Php/StatDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -18,6 +19,7 @@ use SplFileObject; use function in_array; +#[AutowiredService] final class StatDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/StrContainingTypeSpecifyingExtension.php b/src/Type/Php/StrContainingTypeSpecifyingExtension.php index 655e77ddd3b..2e24c9dcbd5 100644 --- a/src/Type/Php/StrContainingTypeSpecifyingExtension.php +++ b/src/Type/Php/StrContainingTypeSpecifyingExtension.php @@ -13,6 +13,7 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; @@ -25,6 +26,7 @@ use function count; use function strtolower; +#[AutowiredService] final class StrContainingTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { diff --git a/src/Type/Php/ThrowableReturnTypeExtension.php b/src/Type/Php/ThrowableReturnTypeExtension.php index 9437b82485a..af236b4f2df 100644 --- a/src/Type/Php/ThrowableReturnTypeExtension.php +++ b/src/Type/Php/ThrowableReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -18,6 +19,7 @@ use function in_array; use function strtolower; +#[AutowiredService] final class ThrowableReturnTypeExtension implements DynamicMethodReturnTypeExtension { diff --git a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php index 26551ff8306..e5283ba68eb 100644 --- a/src/Type/Php/XMLReaderOpenReturnTypeExtension.php +++ b/src/Type/Php/XMLReaderOpenReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -14,6 +15,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; +#[AutowiredService] final class XMLReaderOpenReturnTypeExtension implements DynamicMethodReturnTypeExtension, DynamicStaticMethodReturnTypeExtension { diff --git a/src/Type/Regex/RegexExpressionHelper.php b/src/Type/Regex/RegexExpressionHelper.php index 0df0dc011c3..0dd32830cdc 100644 --- a/src/Type/Regex/RegexExpressionHelper.php +++ b/src/Type/Regex/RegexExpressionHelper.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\BinaryOp\Concat; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; @@ -15,6 +16,7 @@ use function strrpos; use function substr; +#[AutowiredService] final class RegexExpressionHelper { diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 2f90e08c222..5a99743a181 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -9,6 +9,7 @@ use Hoa\File\Read; use Nette\Utils\RegexpException; use Nette\Utils\Strings; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; @@ -33,6 +34,7 @@ use function substr; use function trim; +#[AutowiredService] final class RegexGroupParser { From 6194cf23686591303eb43e655da526664a7d73b9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 25 May 2025 22:10:01 +0200 Subject: [PATCH 1427/3097] More service converted to `#[AutowiredService]` attribute usage --- conf/config.neon | 143 ------------------ src/Analyser/ConstantResolverFactory.php | 2 + src/Analyser/Ignore/IgnoreLexer.php | 2 + src/Analyser/IgnoreErrorExtensionProvider.php | 2 + src/Analyser/LocalIgnoresProcessor.php | 2 + src/Analyser/RicherScopeGetTypeHelper.php | 2 + src/Analyser/RuleErrorTransformer.php | 2 + src/Analyser/ScopeFactory.php | 3 + src/Collectors/RegistryFactory.php | 2 + src/Command/AnalyseApplication.php | 2 + src/Command/AnalyserRunner.php | 2 + src/Dependency/ExportedNodeResolver.php | 2 + src/Dependency/ExportedNodeVisitor.php | 2 + src/Parser/LexerFactory.php | 2 + src/PhpDoc/ConstExprNodeResolver.php | 2 + src/PhpDoc/JsonValidateStubFilesExtension.php | 2 + src/PhpDoc/PhpDocStringResolver.php | 2 + .../ReflectionClassStubFilesExtension.php | 2 + .../ReflectionEnumStubFilesExtension.php | 2 + src/PhpDoc/StubValidator.php | 2 + src/PhpDoc/TypeNodeResolver.php | 2 + src/PhpDoc/TypeStringResolver.php | 2 + src/Process/CpuCoreCounter.php | 2 + src/Reflection/AttributeReflectionFactory.php | 2 + .../SourceLocator/CachingVisitor.php | 2 + ...JsonAndInstalledJsonSourceLocatorMaker.php | 2 + ...imizedDirectorySourceLocatorRepository.php | 2 + ...mizedSingleFileSourceLocatorRepository.php | 2 + ...aysUsedClassConstantsExtensionProvider.php | 2 + src/Rules/FunctionReturnTypeCheck.php | 2 + .../Generics/CrossCheckInterfacesHelper.php | 2 + src/Rules/Generics/GenericObjectTypeCheck.php | 2 + .../Generics/MethodTagTemplateTypeCheck.php | 2 + src/Rules/Generics/VarianceCheck.php | 2 + .../RestrictedInternalUsageHelper.php | 2 + .../LazyAlwaysUsedMethodExtensionProvider.php | 2 + .../MethodParameterComparisonHelper.php | 2 + .../MethodVisibilityComparisonHelper.php | 2 + src/Rules/NullsafeCheck.php | 2 + src/Rules/ParameterCastableToStringCheck.php | 2 + .../ConditionalReturnTypeRuleHelper.php | 2 + .../PhpDoc/GenericCallableRuleHelper.php | 2 + .../PhpDoc/IncompatiblePhpDocTypeCheck.php | 2 + src/Rules/PhpDoc/UnresolvableTypeHelper.php | 2 + src/Rules/Playground/NeverRuleHelper.php | 2 + ...zyReadWritePropertiesExtensionProvider.php | 2 + src/Rules/Properties/PropertyDescriptor.php | 2 + 47 files changed, 93 insertions(+), 143 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index abdf0f60679..a81c5f69df3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -230,9 +230,6 @@ services: - class: PhpParser\BuilderFactory - - - class: PHPStan\Parser\LexerFactory - - class: PhpParser\NodeVisitor\NameResolver arguments: @@ -384,25 +381,10 @@ services: - class: PHPStan\PhpDocParser\Printer\Printer - - - class: PHPStan\PhpDoc\PhpDocStringResolver - - - - class: PHPStan\PhpDoc\ConstExprNodeResolver - - - - class: PHPStan\PhpDoc\TypeNodeResolver - - class: PHPStan\PhpDoc\TypeNodeResolverExtensionRegistryProvider factory: PHPStan\PhpDoc\LazyTypeNodeResolverExtensionRegistryProvider - - - class: PHPStan\PhpDoc\TypeStringResolver - - - - class: PHPStan\PhpDoc\StubValidator - - class: PHPStan\PhpDoc\DefaultStubFilesProvider arguments: @@ -411,21 +393,6 @@ services: autowired: - PHPStan\PhpDoc\StubFilesProvider - - - class: PHPStan\PhpDoc\JsonValidateStubFilesExtension - tags: - - phpstan.stubFilesExtension - - - - class: PHPStan\PhpDoc\ReflectionClassStubFilesExtension - tags: - - phpstan.stubFilesExtension - - - - class: PHPStan\PhpDoc\ReflectionEnumStubFilesExtension - tags: - - phpstan.stubFilesExtension - - class: PHPStan\Analyser\Analyser arguments: @@ -441,32 +408,17 @@ services: arguments: parser: @defaultAnalysisParser - - - class: PHPStan\Analyser\IgnoreErrorExtensionProvider - - - - class: PHPStan\Analyser\LocalIgnoresProcessor - - - - class: PHPStan\Analyser\RuleErrorTransformer - - class: PHPStan\Analyser\Ignore\IgnoredErrorHelper arguments: ignoreErrors: %ignoreErrors% reportUnmatchedIgnoredErrors: %reportUnmatchedIgnoredErrors% - - - class: PHPStan\Analyser\Ignore\IgnoreLexer - - class: PHPStan\Analyser\LazyInternalScopeFactory autowired: - PHPStan\Analyser\InternalScopeFactory - - - class: PHPStan\Analyser\ScopeFactory - - class: PHPStan\Analyser\NodeScopeResolver arguments: @@ -486,9 +438,6 @@ services: class: PHPStan\Analyser\ConstantResolver factory: @PHPStan\Analyser\ConstantResolverFactory::create() - - - class: PHPStan\Analyser\ConstantResolverFactory - - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory arguments: @@ -511,9 +460,6 @@ services: arguments: cacheFilePath: %resultCachePath% - - - class: PHPStan\Analyser\RicherScopeGetTypeHelper - - class: PHPStan\Cache\Cache arguments: @@ -523,15 +469,6 @@ services: class: PHPStan\Collectors\Registry factory: @PHPStan\Collectors\RegistryFactory::create - - - class: PHPStan\Collectors\RegistryFactory - - - - class: PHPStan\Command\AnalyseApplication - - - - class: PHPStan\Command\AnalyserRunner - - class: PHPStan\Command\FixerApplication arguments: @@ -551,12 +488,6 @@ services: arguments: parser: @defaultAnalysisParser - - - class: PHPStan\Dependency\ExportedNodeResolver - - - - class: PHPStan\Dependency\ExportedNodeVisitor - - class: PHPStan\DependencyInjection\Container factory: PHPStan\DependencyInjection\MemoizingContainer @@ -684,12 +615,6 @@ services: tags: - phpstan.diagnoseExtension - - - class: PHPStan\Process\CpuCoreCounter - - - - class: PHPStan\Reflection\AttributeReflectionFactory - - implement: PHPStan\Reflection\FunctionReflectionFactory arguments: @@ -706,34 +631,22 @@ services: - class: PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension - - - class: PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor - - class: PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher arguments: parser: @defaultAnalysisParser - - - class: PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker - - class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorFactory arguments: fileFinder: @fileFinderScan - - - class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository - - implement: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory - implement: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorFactory - - - class: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorRepository - - class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension arguments: @@ -946,37 +859,17 @@ services: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% - - - class: PHPStan\Rules\FunctionReturnTypeCheck - - - class: PHPStan\Rules\ParameterCastableToStringCheck - - - - class: PHPStan\Rules\Generics\CrossCheckInterfacesHelper - - class: PHPStan\Rules\Generics\GenericAncestorsCheck arguments: skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% checkMissingTypehints: %checkMissingTypehints% - - - class: PHPStan\Rules\Generics\GenericObjectTypeCheck - - - - class: PHPStan\Rules\Generics\MethodTagTemplateTypeCheck - - class: PHPStan\Rules\Generics\TemplateTypeCheck arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% - - - class: PHPStan\Rules\Generics\VarianceCheck - - - - class: PHPStan\Rules\InternalTag\RestrictedInternalUsageHelper - - class: PHPStan\Rules\IssetCheck arguments: @@ -1003,54 +896,24 @@ services: reportMaybes: %reportMaybesInMethodSignatures% reportStatic: %reportStaticMethodSignatures% - - - class: PHPStan\Rules\Methods\MethodParameterComparisonHelper - - - - class: PHPStan\Rules\Methods\MethodVisibilityComparisonHelper - - class: PHPStan\Rules\MissingTypehintCheck arguments: checkMissingCallableSignature: %checkMissingCallableSignature% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% - - - class: PHPStan\Rules\NullsafeCheck - - - - class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider - - - - class: PHPStan\Rules\Methods\LazyAlwaysUsedMethodExtensionProvider - - - - class: PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper - - class: PHPStan\Rules\PhpDoc\AssertRuleHelper arguments: checkMissingTypehints: %checkMissingTypehints% checkClassCaseSensitivity: %checkClassCaseSensitivity% - - - class: PHPStan\Rules\PhpDoc\UnresolvableTypeHelper - - - - class: PHPStan\Rules\PhpDoc\GenericCallableRuleHelper - - - - class: PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeCheck - - class: PHPStan\Rules\PhpDoc\VarTagTypeRuleHelper arguments: checkTypeAgainstPhpDocType: %reportWrongPhpDocTypeInVarTag% strictWideningCheck: %reportAnyTypeWideningInVarTag% - - - class: PHPStan\Rules\Playground\NeverRuleHelper - - class: PHPStan\Rules\Properties\AccessPropertiesCheck arguments: @@ -1060,12 +923,6 @@ services: - class: PHPStan\Rules\Properties\UninitializedPropertyRule - - - class: PHPStan\Rules\Properties\LazyReadWritePropertiesExtensionProvider - - - - class: PHPStan\Rules\Properties\PropertyDescriptor - - class: PHPStan\Rules\RuleLevelHelper arguments: diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php index 5ccc516e424..57a3284f762 100644 --- a/src/Analyser/ConstantResolverFactory.php +++ b/src/Analyser/ConstantResolverFactory.php @@ -2,10 +2,12 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Php\ComposerPhpVersionFactory; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; +#[AutowiredService] final class ConstantResolverFactory { diff --git a/src/Analyser/Ignore/IgnoreLexer.php b/src/Analyser/Ignore/IgnoreLexer.php index bcaf00ec107..dbfa2ebf42b 100644 --- a/src/Analyser/Ignore/IgnoreLexer.php +++ b/src/Analyser/Ignore/IgnoreLexer.php @@ -4,9 +4,11 @@ use Nette\Utils\Strings; use PHPStan\Analyser\Error; +use PHPStan\DependencyInjection\AutowiredService; use function implode; use const PREG_SET_ORDER; +#[AutowiredService] final class IgnoreLexer { diff --git a/src/Analyser/IgnoreErrorExtensionProvider.php b/src/Analyser/IgnoreErrorExtensionProvider.php index 79604a58458..ff5af371693 100644 --- a/src/Analyser/IgnoreErrorExtensionProvider.php +++ b/src/Analyser/IgnoreErrorExtensionProvider.php @@ -2,8 +2,10 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class IgnoreErrorExtensionProvider { diff --git a/src/Analyser/LocalIgnoresProcessor.php b/src/Analyser/LocalIgnoresProcessor.php index d5c0197dd64..192b9092511 100644 --- a/src/Analyser/LocalIgnoresProcessor.php +++ b/src/Analyser/LocalIgnoresProcessor.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\AutowiredService; use function array_key_exists; use function array_values; use function count; @@ -10,6 +11,7 @@ /** * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ +#[AutowiredService] final class LocalIgnoresProcessor { diff --git a/src/Analyser/RicherScopeGetTypeHelper.php b/src/Analyser/RicherScopeGetTypeHelper.php index ba7c6c618a1..e8c6fa2d915 100644 --- a/src/Analyser/RicherScopeGetTypeHelper.php +++ b/src/Analyser/RicherScopeGetTypeHelper.php @@ -5,12 +5,14 @@ use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\Variable; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\TypeResult; use function is_string; +#[AutowiredService] final class RicherScopeGetTypeHelper { diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index b45ce15acde..c7ab2604a8c 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PhpParser\Node; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\LineRuleError; @@ -11,6 +12,7 @@ use PHPStan\Rules\RuleError; use PHPStan\Rules\TipRuleError; +#[AutowiredService] final class RuleErrorTransformer { diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index ade6e1d8943..be9ca1982d9 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -2,9 +2,12 @@ namespace PHPStan\Analyser; +use PHPStan\DependencyInjection\AutowiredService; + /** * @api */ +#[AutowiredService] final class ScopeFactory { diff --git a/src/Collectors/RegistryFactory.php b/src/Collectors/RegistryFactory.php index 675740d99ae..a95dbe2a51d 100644 --- a/src/Collectors/RegistryFactory.php +++ b/src/Collectors/RegistryFactory.php @@ -2,8 +2,10 @@ namespace PHPStan\Collectors; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class RegistryFactory { diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 20450f9813a..07e36fb1b39 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -9,6 +9,7 @@ use PHPStan\Analyser\Ignore\IgnoredErrorHelper; use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory; use PHPStan\Collectors\CollectedData; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Internal\BytesHelper; use PHPStan\PhpDoc\StubFilesProvider; use PHPStan\PhpDoc\StubValidator; @@ -26,6 +27,7 @@ * @phpstan-import-type CollectorData from CollectedData * @phpstan-import-type LinesToIgnore from FileAnalyserResult */ +#[AutowiredService] final class AnalyseApplication { diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index 5b97a115ea6..f6ea231e84e 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -5,6 +5,7 @@ use Closure; use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResult; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Parallel\ParallelAnalyser; use PHPStan\Parallel\Scheduler; use PHPStan\Process\CpuCoreCounter; @@ -19,6 +20,7 @@ use function is_file; use function memory_get_peak_usage; +#[AutowiredService] final class AnalyserRunner { diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 38f1ceb616b..5a6afabe705 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -23,6 +23,7 @@ use PHPStan\Dependency\ExportedNode\ExportedPropertyHookNode; use PHPStan\Dependency\ExportedNode\ExportedTraitNode; use PHPStan\Dependency\ExportedNode\ExportedTraitUseAdaptation; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Node\Printer\NodeTypePrinter; use PHPStan\Reflection\ReflectionProvider; @@ -32,6 +33,7 @@ use function is_string; use function sprintf; +#[AutowiredService] final class ExportedNodeResolver { diff --git a/src/Dependency/ExportedNodeVisitor.php b/src/Dependency/ExportedNodeVisitor.php index 34dfd1efe18..f8027637044 100644 --- a/src/Dependency/ExportedNodeVisitor.php +++ b/src/Dependency/ExportedNodeVisitor.php @@ -5,8 +5,10 @@ use PhpParser\Node; use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\ShouldNotHappenException; +#[AutowiredService] final class ExportedNodeVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/LexerFactory.php b/src/Parser/LexerFactory.php index e02bc5ed2c4..1f844ff7cd8 100644 --- a/src/Parser/LexerFactory.php +++ b/src/Parser/LexerFactory.php @@ -3,9 +3,11 @@ namespace PHPStan\Parser; use PhpParser\Lexer; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use const PHP_VERSION_ID; +#[AutowiredService] final class LexerFactory { diff --git a/src/PhpDoc/ConstExprNodeResolver.php b/src/PhpDoc/ConstExprNodeResolver.php index 257883af2ce..7d1b0ad824f 100644 --- a/src/PhpDoc/ConstExprNodeResolver.php +++ b/src/PhpDoc/ConstExprNodeResolver.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDoc; use PHPStan\Analyser\NameScope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode; @@ -26,6 +27,7 @@ use PHPStan\Type\Type; use function strtolower; +#[AutowiredService] final class ConstExprNodeResolver { diff --git a/src/PhpDoc/JsonValidateStubFilesExtension.php b/src/PhpDoc/JsonValidateStubFilesExtension.php index 3bfcfe862c4..61ba6aca5db 100644 --- a/src/PhpDoc/JsonValidateStubFilesExtension.php +++ b/src/PhpDoc/JsonValidateStubFilesExtension.php @@ -2,8 +2,10 @@ namespace PHPStan\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class JsonValidateStubFilesExtension implements StubFilesExtension { diff --git a/src/PhpDoc/PhpDocStringResolver.php b/src/PhpDoc/PhpDocStringResolver.php index 7c8129a3cc5..30a6e1df0e6 100644 --- a/src/PhpDoc/PhpDocStringResolver.php +++ b/src/PhpDoc/PhpDocStringResolver.php @@ -2,11 +2,13 @@ namespace PHPStan\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\PhpDocParser\Parser\TokenIterator; +#[AutowiredService] final class PhpDocStringResolver { diff --git a/src/PhpDoc/ReflectionClassStubFilesExtension.php b/src/PhpDoc/ReflectionClassStubFilesExtension.php index 1abd672f68a..f47fe54bbae 100644 --- a/src/PhpDoc/ReflectionClassStubFilesExtension.php +++ b/src/PhpDoc/ReflectionClassStubFilesExtension.php @@ -2,8 +2,10 @@ namespace PHPStan\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class ReflectionClassStubFilesExtension implements StubFilesExtension { diff --git a/src/PhpDoc/ReflectionEnumStubFilesExtension.php b/src/PhpDoc/ReflectionEnumStubFilesExtension.php index ed9b43b6beb..d48519b7353 100644 --- a/src/PhpDoc/ReflectionEnumStubFilesExtension.php +++ b/src/PhpDoc/ReflectionEnumStubFilesExtension.php @@ -2,8 +2,10 @@ namespace PHPStan\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class ReflectionEnumStubFilesExtension implements StubFilesExtension { diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index b0737cbcc9e..3d1318fcd8d 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\InternalError; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Collectors\Registry as CollectorRegistry; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\DerivativeContainerFactory; use PHPStan\Php\PhpVersion; @@ -101,6 +102,7 @@ use function count; use function sprintf; +#[AutowiredService] final class StubValidator { diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 0ec112131f3..21284c91299 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -10,6 +10,7 @@ use PhpParser\Node\Name; use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\NameScope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; @@ -124,6 +125,7 @@ use function strtolower; use function substr; +#[AutowiredService] final class TypeNodeResolver { diff --git a/src/PhpDoc/TypeStringResolver.php b/src/PhpDoc/TypeStringResolver.php index 2bdb4ff94f0..9aa0b151bc5 100644 --- a/src/PhpDoc/TypeStringResolver.php +++ b/src/PhpDoc/TypeStringResolver.php @@ -3,11 +3,13 @@ namespace PHPStan\PhpDoc; use PHPStan\Analyser\NameScope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\TokenIterator; use PHPStan\PhpDocParser\Parser\TypeParser; use PHPStan\Type\Type; +#[AutowiredService] final class TypeStringResolver { diff --git a/src/Process/CpuCoreCounter.php b/src/Process/CpuCoreCounter.php index 2fd49e7cfa8..a0558bcbebc 100644 --- a/src/Process/CpuCoreCounter.php +++ b/src/Process/CpuCoreCounter.php @@ -4,7 +4,9 @@ use Fidry\CpuCoreCounter\CpuCoreCounter as FidryCpuCoreCounter; use Fidry\CpuCoreCounter\NumberOfCpuCoreNotFound; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class CpuCoreCounter { diff --git a/src/Reflection/AttributeReflectionFactory.php b/src/Reflection/AttributeReflectionFactory.php index 74a7d0efaa6..5cd5cd9cb91 100644 --- a/src/Reflection/AttributeReflectionFactory.php +++ b/src/Reflection/AttributeReflectionFactory.php @@ -6,12 +6,14 @@ use PhpParser\Node\Expr; use PHPStan\BetterReflection\Reflection\Adapter\FakeReflectionAttribute; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttribute; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; use PHPStan\Type\TypeCombinator; use function array_key_exists; use function count; use function is_int; +#[AutowiredService] final class AttributeReflectionFactory { diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 6eb1f576042..8d78fa4e1ee 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -9,9 +9,11 @@ use PHPStan\BetterReflection\Reflection\Exception\InvalidConstantNode; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; use PHPStan\BetterReflection\Util\ConstantNodeChecker; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ConstantNameHelper; use function strtolower; +#[AutowiredService] final class CachingVisitor extends NodeVisitorAbstract { diff --git a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php index 9e687100f7b..1195311fd22 100644 --- a/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php +++ b/src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php @@ -8,6 +8,7 @@ use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr0Mapping; use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\File\CouldNotReadFileException; use PHPStan\File\FileReader; use PHPStan\Internal\ComposerHelper; @@ -25,6 +26,7 @@ use function str_contains; use const GLOB_ONLYDIR; +#[AutowiredService] final class ComposerJsonAndInstalledJsonSourceLocatorMaker { diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php index f71d4dcf8a0..e0404ad6297 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorRepository.php @@ -2,8 +2,10 @@ namespace PHPStan\Reflection\BetterReflection\SourceLocator; +use PHPStan\DependencyInjection\AutowiredService; use function array_key_exists; +#[AutowiredService] final class OptimizedDirectorySourceLocatorRepository { diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php index bd857f74898..b97c230f798 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorRepository.php @@ -2,8 +2,10 @@ namespace PHPStan\Reflection\BetterReflection\SourceLocator; +use PHPStan\DependencyInjection\AutowiredService; use function array_key_exists; +#[AutowiredService] final class OptimizedSingleFileSourceLocatorRepository { diff --git a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php index e91391e8bb8..9425eb88494 100644 --- a/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php +++ b/src/Rules/Constants/LazyAlwaysUsedClassConstantsExtensionProvider.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Constants; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class LazyAlwaysUsedClassConstantsExtensionProvider implements AlwaysUsedClassConstantsExtensionProvider { diff --git a/src/Rules/FunctionReturnTypeCheck.php b/src/Rules/FunctionReturnTypeCheck.php index 994b3943f25..e61965ca3a3 100644 --- a/src/Rules/FunctionReturnTypeCheck.php +++ b/src/Rules/FunctionReturnTypeCheck.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\ErrorType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -13,6 +14,7 @@ use PHPStan\Type\VerbosityLevel; use function sprintf; +#[AutowiredService] final class FunctionReturnTypeCheck { diff --git a/src/Rules/Generics/CrossCheckInterfacesHelper.php b/src/Rules/Generics/CrossCheckInterfacesHelper.php index 3e9c477cb35..25b57e10e62 100644 --- a/src/Rules/Generics/CrossCheckInterfacesHelper.php +++ b/src/Rules/Generics/CrossCheckInterfacesHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -9,6 +10,7 @@ use function array_key_exists; use function sprintf; +#[AutowiredService] final class CrossCheckInterfacesHelper { diff --git a/src/Rules/Generics/GenericObjectTypeCheck.php b/src/Rules/Generics/GenericObjectTypeCheck.php index c0a4936bdc8..0ed2f741664 100644 --- a/src/Rules/Generics/GenericObjectTypeCheck.php +++ b/src/Rules/Generics/GenericObjectTypeCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; @@ -23,6 +24,7 @@ use function sprintf; use function strtolower; +#[AutowiredService] final class GenericObjectTypeCheck { diff --git a/src/Rules/Generics/MethodTagTemplateTypeCheck.php b/src/Rules/Generics/MethodTagTemplateTypeCheck.php index afa672f9837..31eb219ad8d 100644 --- a/src/Rules/Generics/MethodTagTemplateTypeCheck.php +++ b/src/Rules/Generics/MethodTagTemplateTypeCheck.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\IdentifierRuleError; @@ -15,6 +16,7 @@ use function array_merge; use function sprintf; +#[AutowiredService] final class MethodTagTemplateTypeCheck { diff --git a/src/Rules/Generics/VarianceCheck.php b/src/Rules/Generics/VarianceCheck.php index d01dbf75a37..cd8d7192bfa 100644 --- a/src/Rules/Generics/VarianceCheck.php +++ b/src/Rules/Generics/VarianceCheck.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Generics; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -10,6 +11,7 @@ use PHPStan\Type\Type; use function sprintf; +#[AutowiredService] final class VarianceCheck { diff --git a/src/Rules/InternalTag/RestrictedInternalUsageHelper.php b/src/Rules/InternalTag/RestrictedInternalUsageHelper.php index 1767c02fbb5..f5c6147b13c 100644 --- a/src/Rules/InternalTag/RestrictedInternalUsageHelper.php +++ b/src/Rules/InternalTag/RestrictedInternalUsageHelper.php @@ -3,10 +3,12 @@ namespace PHPStan\Rules\InternalTag; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use function array_slice; use function explode; use function str_starts_with; +#[AutowiredService] final class RestrictedInternalUsageHelper { diff --git a/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php index 6fca3226a5c..1a2b49ee389 100644 --- a/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php +++ b/src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class LazyAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider { diff --git a/src/Rules/Methods/MethodParameterComparisonHelper.php b/src/Rules/Methods/MethodParameterComparisonHelper.php index 34f66bb8bb1..a52dbe86f59 100644 --- a/src/Rules/Methods/MethodParameterComparisonHelper.php +++ b/src/Rules/Methods/MethodParameterComparisonHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -21,6 +22,7 @@ use function count; use function sprintf; +#[AutowiredService] final class MethodParameterComparisonHelper { diff --git a/src/Rules/Methods/MethodVisibilityComparisonHelper.php b/src/Rules/Methods/MethodVisibilityComparisonHelper.php index 4807453f2a9..f0e922deece 100755 --- a/src/Rules/Methods/MethodVisibilityComparisonHelper.php +++ b/src/Rules/Methods/MethodVisibilityComparisonHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Methods; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; @@ -9,6 +10,7 @@ use PHPStan\Rules\RuleErrorBuilder; use function sprintf; +#[AutowiredService] final class MethodVisibilityComparisonHelper { diff --git a/src/Rules/NullsafeCheck.php b/src/Rules/NullsafeCheck.php index a4424b69ac5..4bd8d724ad2 100644 --- a/src/Rules/NullsafeCheck.php +++ b/src/Rules/NullsafeCheck.php @@ -3,7 +3,9 @@ namespace PHPStan\Rules; use PhpParser\Node\Expr; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class NullsafeCheck { diff --git a/src/Rules/ParameterCastableToStringCheck.php b/src/Rules/ParameterCastableToStringCheck.php index 97538635577..244cd1ba4f2 100644 --- a/src/Rules/ParameterCastableToStringCheck.php +++ b/src/Rules/ParameterCastableToStringCheck.php @@ -5,12 +5,14 @@ use PhpParser\Node\Arg; use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ParameterReflection; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use function sprintf; +#[AutowiredService] final class ParameterCastableToStringCheck { diff --git a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php index f48a8abc7a5..af7cfda1556 100644 --- a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php +++ b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -17,6 +18,7 @@ use function sprintf; use function substr; +#[AutowiredService] final class ConditionalReturnTypeRuleHelper { diff --git a/src/Rules/PhpDoc/GenericCallableRuleHelper.php b/src/Rules/PhpDoc/GenericCallableRuleHelper.php index c32491fe427..d38279a6592 100644 --- a/src/Rules/PhpDoc/GenericCallableRuleHelper.php +++ b/src/Rules/PhpDoc/GenericCallableRuleHelper.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\Generics\TemplateTypeCheck; @@ -18,6 +19,7 @@ use function array_keys; use function sprintf; +#[AutowiredService] final class GenericCallableRuleHelper { diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php index 56c0ac529e7..8fcc1569d2b 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeCheck.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Internal\SprintfHelper; use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDoc\Tag\ParamOutTag; @@ -19,6 +20,7 @@ use function in_array; use function sprintf; +#[AutowiredService] final class IncompatiblePhpDocTypeCheck { diff --git a/src/Rules/PhpDoc/UnresolvableTypeHelper.php b/src/Rules/PhpDoc/UnresolvableTypeHelper.php index 25b485dfad0..69f539f3736 100644 --- a/src/Rules/PhpDoc/UnresolvableTypeHelper.php +++ b/src/Rules/PhpDoc/UnresolvableTypeHelper.php @@ -2,11 +2,13 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Type\ErrorType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; +#[AutowiredService] final class UnresolvableTypeHelper { diff --git a/src/Rules/Playground/NeverRuleHelper.php b/src/Rules/Playground/NeverRuleHelper.php index 9865d3d1ce1..a0870f7ce3d 100644 --- a/src/Rules/Playground/NeverRuleHelper.php +++ b/src/Rules/Playground/NeverRuleHelper.php @@ -3,10 +3,12 @@ namespace PHPStan\Rules\Playground; use PhpParser\Node; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\ReturnStatementsNode; use PHPStan\Type\NeverType; use PHPStan\Type\Type; +#[AutowiredService] final class NeverRuleHelper { diff --git a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php index f42cb7e7d5e..07dfafea6b7 100644 --- a/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php +++ b/src/Rules/Properties/LazyReadWritePropertiesExtensionProvider.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Properties; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; +#[AutowiredService] final class LazyReadWritePropertiesExtensionProvider implements ReadWritePropertiesExtensionProvider { diff --git a/src/Rules/Properties/PropertyDescriptor.php b/src/Rules/Properties/PropertyDescriptor.php index 8588a1a57a7..d9d6e10c995 100644 --- a/src/Rules/Properties/PropertyDescriptor.php +++ b/src/Rules/Properties/PropertyDescriptor.php @@ -4,11 +4,13 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\PropertyReflection; use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function sprintf; +#[AutowiredService] final class PropertyDescriptor { From 2776f5de0324535c074abc299e1a310998b06c78 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 25 May 2025 22:16:49 +0200 Subject: [PATCH 1428/3097] Added missing interface/tag combos to AutowiredAttributeServicesExtension --- conf/config.neon | 11 ++++++----- src/Dependency/ExportedNodeVisitor.php | 2 -- .../AutowiredAttributeServicesExtension.php | 9 ++++++++- src/Parser/LastConditionVisitor.php | 2 ++ .../BetterReflection/SourceLocator/CachingVisitor.php | 2 -- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index a81c5f69df3..207d0b7ae1d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -311,11 +311,6 @@ services: tags: - phpstan.parser.richParserNodeVisitor - - - class: PHPStan\Parser\LastConditionVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - class: PHPStan\Parser\TypeTraverserInstanceofVisitor tags: @@ -488,6 +483,9 @@ services: arguments: parser: @defaultAnalysisParser + - + class: PHPStan\Dependency\ExportedNodeVisitor + - class: PHPStan\DependencyInjection\Container factory: PHPStan\DependencyInjection\MemoizingContainer @@ -631,6 +629,9 @@ services: - class: PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension + - + class: PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor + - class: PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher arguments: diff --git a/src/Dependency/ExportedNodeVisitor.php b/src/Dependency/ExportedNodeVisitor.php index f8027637044..34dfd1efe18 100644 --- a/src/Dependency/ExportedNodeVisitor.php +++ b/src/Dependency/ExportedNodeVisitor.php @@ -5,10 +5,8 @@ use PhpParser\Node; use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; -use PHPStan\DependencyInjection\AutowiredService; use PHPStan\ShouldNotHappenException; -#[AutowiredService] final class ExportedNodeVisitor extends NodeVisitorAbstract { diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index f326b2d308f..39042f853b7 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -4,12 +4,17 @@ use Nette\DI\CompilerExtension; use olvlvl\ComposerAttributeCollector\Attributes; +use PhpParser\NodeVisitor; use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; use PHPStan\Analyser\TypeSpecifierFactory; use PHPStan\Broker\BrokerFactory; +use PHPStan\Collectors\Collector; +use PHPStan\Collectors\RegistryFactory as CollectorRegistryFactory; use PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\LazyParameterOutTypeExtensionProvider; +use PHPStan\Diagnose\DiagnoseExtension; +use PHPStan\Parser\RichParser; use PHPStan\PhpDoc\StubFilesExtension; use PHPStan\PhpDoc\TypeNodeResolverExtension; use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension; @@ -101,7 +106,9 @@ public function loadConfiguration(): void RestrictedFunctionUsageExtension::class => RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG, RestrictedPropertyUsageExtension::class => RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG, RestrictedClassConstantUsageExtension::class => RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG, - + NodeVisitor::class => RichParser::VISITOR_SERVICE_TAG, + Collector::class => CollectorRegistryFactory::COLLECTOR_TAG, + DiagnoseExtension::class => DiagnoseExtension::EXTENSION_TAG, ]; foreach ($autowiredServiceClasses as $class) { diff --git a/src/Parser/LastConditionVisitor.php b/src/Parser/LastConditionVisitor.php index d20a8f4b906..bfb9bc66c3e 100644 --- a/src/Parser/LastConditionVisitor.php +++ b/src/Parser/LastConditionVisitor.php @@ -4,8 +4,10 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function count; +#[AutowiredService] final class LastConditionVisitor extends NodeVisitorAbstract { diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 8d78fa4e1ee..6eb1f576042 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -9,11 +9,9 @@ use PHPStan\BetterReflection\Reflection\Exception\InvalidConstantNode; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; use PHPStan\BetterReflection\Util\ConstantNodeChecker; -use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ConstantNameHelper; use function strtolower; -#[AutowiredService] final class CachingVisitor extends NodeVisitorAbstract { From f375a814c453ee4220322e238d5d1ace59810f45 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 09:50:22 +0200 Subject: [PATCH 1429/3097] Validate registered services tags against implemented interface --- conf/config.neon | 1 + src/Command/CommandHelper.php | 7 + .../AutowiredAttributeServicesExtension.php | 98 +----------- ...ntedInterfaceInServiceWithTagException.php | 16 ++ .../ValidateServiceTagsExtension.php | 145 ++++++++++++++++++ .../TestedConditionalServiceDisabled.php | 19 ++- ...stedConditionalServiceDisabledDisabled.php | 19 ++- ...estedConditionalServiceDisabledEnabled.php | 19 ++- .../TestedConditionalServiceEnabled.php | 19 ++- ...estedConditionalServiceEnabledDisabled.php | 19 ++- ...TestedConditionalServiceEnabledEnabled.php | 19 ++- 11 files changed, 278 insertions(+), 103 deletions(-) create mode 100644 src/DependencyInjection/MissingImplementedInterfaceInServiceWithTagException.php create mode 100644 src/DependencyInjection/ValidateServiceTagsExtension.php diff --git a/conf/config.neon b/conf/config.neon index 207d0b7ae1d..429672258b3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -215,6 +215,7 @@ extensions: parametersSchema: PHPStan\DependencyInjection\ParametersSchemaExtension validateIgnoredErrors: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension validateExcludePaths: PHPStan\DependencyInjection\ValidateExcludePathsExtension + validateServiceTags: PHPStan\DependencyInjection\ValidateServiceTagsExtension conditionalTags: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule: diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index aa091926666..a599a83efaa 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -21,6 +21,7 @@ use PHPStan\DependencyInjection\InvalidExcludePathsException; use PHPStan\DependencyInjection\InvalidIgnoredErrorPatternsException; use PHPStan\DependencyInjection\LoaderFactory; +use PHPStan\DependencyInjection\MissingImplementedInterfaceInServiceWithTagException; use PHPStan\ExtensionInstaller\GeneratedConfig; use PHPStan\File\FileExcluder; use PHPStan\File\FileFinder; @@ -404,6 +405,12 @@ public static function begin( $errorOutput->writeLineFormatted('set reportUnmatchedIgnoredErrors: false in your configuration file.'); $errorOutput->writeLineFormatted(''); + throw new InceptionNotSuccessfulException(); + } catch (MissingImplementedInterfaceInServiceWithTagException $e) { + $errorOutput->writeLineFormatted('Invalid service:'); + $errorOutput->writeLineFormatted($e->getMessage()); + $errorOutput->writeLineFormatted(''); + throw new InceptionNotSuccessfulException(); } catch (InvalidExcludePathsException $e) { $errorOutput->writeLineFormatted(sprintf('Invalid %s in excludePaths:', count($e->getErrors()) === 1 ? 'entry' : 'entries')); diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index 39042f853b7..a02d84bf483 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -4,58 +4,6 @@ use Nette\DI\CompilerExtension; use olvlvl\ComposerAttributeCollector\Attributes; -use PhpParser\NodeVisitor; -use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; -use PHPStan\Analyser\TypeSpecifierFactory; -use PHPStan\Broker\BrokerFactory; -use PHPStan\Collectors\Collector; -use PHPStan\Collectors\RegistryFactory as CollectorRegistryFactory; -use PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\LazyParameterOutTypeExtensionProvider; -use PHPStan\Diagnose\DiagnoseExtension; -use PHPStan\Parser\RichParser; -use PHPStan\PhpDoc\StubFilesExtension; -use PHPStan\PhpDoc\TypeNodeResolverExtension; -use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension; -use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension; -use PHPStan\Reflection\Deprecation\ClassDeprecationExtension; -use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension; -use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension; -use PHPStan\Reflection\Deprecation\MethodDeprecationExtension; -use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension; -use PHPStan\Reflection\MethodsClassReflectionExtension; -use PHPStan\Reflection\PropertiesClassReflectionExtension; -use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtension; -use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; -use PHPStan\Rules\LazyRegistry; -use PHPStan\Rules\Methods\AlwaysUsedMethodExtension; -use PHPStan\Rules\Methods\AlwaysUsedMethodExtensionProvider; -use PHPStan\Rules\Properties\ReadWritePropertiesExtension; -use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; -use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageExtension; -use PHPStan\Rules\Rule; -use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\DynamicFunctionThrowTypeExtension; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\DynamicMethodThrowTypeExtension; -use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; -use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; -use PHPStan\Type\ExpressionTypeResolverExtension; -use PHPStan\Type\FunctionParameterClosureTypeExtension; -use PHPStan\Type\FunctionParameterOutTypeExtension; -use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\MethodParameterClosureTypeExtension; -use PHPStan\Type\MethodParameterOutTypeExtension; -use PHPStan\Type\MethodTypeSpecifyingExtension; -use PHPStan\Type\OperatorTypeSpecifyingExtension; -use PHPStan\Type\StaticMethodParameterClosureTypeExtension; -use PHPStan\Type\StaticMethodParameterOutTypeExtension; -use PHPStan\Type\StaticMethodTypeSpecifyingExtension; use ReflectionClass; final class AutowiredAttributeServicesExtension extends CompilerExtension @@ -67,50 +15,6 @@ public function loadConfiguration(): void $autowiredServiceClasses = Attributes::findTargetClasses(AutowiredService::class); $builder = $this->getContainerBuilder(); - $interfaceToTag = [ - PropertiesClassReflectionExtension::class => BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG, - MethodsClassReflectionExtension::class => BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG, - AllowedSubTypesClassReflectionExtension::class => BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG, - DynamicMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG, - DynamicStaticMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG, - DynamicFunctionReturnTypeExtension::class => BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG, - OperatorTypeSpecifyingExtension::class => BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG, - ExpressionTypeResolverExtension::class => BrokerFactory::EXPRESSION_TYPE_RESOLVER_EXTENSION_TAG, - TypeNodeResolverExtension::class => TypeNodeResolverExtension::EXTENSION_TAG, - Rule::class => LazyRegistry::RULE_TAG, - StubFilesExtension::class => StubFilesExtension::EXTENSION_TAG, - AlwaysUsedClassConstantsExtension::class => AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG, - AlwaysUsedMethodExtension::class => AlwaysUsedMethodExtensionProvider::EXTENSION_TAG, - ReadWritePropertiesExtension::class => ReadWritePropertiesExtensionProvider::EXTENSION_TAG, - FunctionTypeSpecifyingExtension::class => TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG, - MethodTypeSpecifyingExtension::class => TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG, - StaticMethodTypeSpecifyingExtension::class => TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG, - DynamicFunctionThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::FUNCTION_TAG, - DynamicMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::METHOD_TAG, - DynamicStaticMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::STATIC_METHOD_TAG, - FunctionParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG, - MethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::METHOD_TAG, - StaticMethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::STATIC_METHOD_TAG, - FunctionParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::FUNCTION_TAG, - MethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::METHOD_TAG, - StaticMethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG, - ResultCacheMetaExtension::class => ResultCacheMetaExtension::EXTENSION_TAG, - ClassConstantDeprecationExtension::class => ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG, - ClassDeprecationExtension::class => ClassDeprecationExtension::CLASS_EXTENSION_TAG, - EnumCaseDeprecationExtension::class => EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG, - FunctionDeprecationExtension::class => FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG, - MethodDeprecationExtension::class => MethodDeprecationExtension::METHOD_EXTENSION_TAG, - PropertyDeprecationExtension::class => PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG, - RestrictedMethodUsageExtension::class => RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG, - RestrictedClassNameUsageExtension::class => RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG, - RestrictedFunctionUsageExtension::class => RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG, - RestrictedPropertyUsageExtension::class => RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG, - RestrictedClassConstantUsageExtension::class => RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG, - NodeVisitor::class => RichParser::VISITOR_SERVICE_TAG, - Collector::class => CollectorRegistryFactory::COLLECTOR_TAG, - DiagnoseExtension::class => DiagnoseExtension::EXTENSION_TAG, - ]; - foreach ($autowiredServiceClasses as $class) { $reflection = new ReflectionClass($class->name); @@ -118,7 +22,7 @@ public function loadConfiguration(): void ->setType($class->name) ->setAutowired(); - foreach ($interfaceToTag as $interface => $tag) { + foreach (ValidateServiceTagsExtension::INTERFACE_TAG_MAPPING as $interface => $tag) { if (!$reflection->implementsInterface($interface)) { continue; } diff --git a/src/DependencyInjection/MissingImplementedInterfaceInServiceWithTagException.php b/src/DependencyInjection/MissingImplementedInterfaceInServiceWithTagException.php new file mode 100644 index 00000000000..2bfc4ac2ad7 --- /dev/null +++ b/src/DependencyInjection/MissingImplementedInterfaceInServiceWithTagException.php @@ -0,0 +1,16 @@ + BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG, + MethodsClassReflectionExtension::class => BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG, + AllowedSubTypesClassReflectionExtension::class => BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG, + DynamicMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG, + DynamicStaticMethodReturnTypeExtension::class => BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG, + DynamicFunctionReturnTypeExtension::class => BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG, + OperatorTypeSpecifyingExtension::class => BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG, + ExpressionTypeResolverExtension::class => BrokerFactory::EXPRESSION_TYPE_RESOLVER_EXTENSION_TAG, + TypeNodeResolverExtension::class => TypeNodeResolverExtension::EXTENSION_TAG, + Rule::class => LazyRegistry::RULE_TAG, + StubFilesExtension::class => StubFilesExtension::EXTENSION_TAG, + AlwaysUsedClassConstantsExtension::class => AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG, + AlwaysUsedMethodExtension::class => AlwaysUsedMethodExtensionProvider::EXTENSION_TAG, + ReadWritePropertiesExtension::class => ReadWritePropertiesExtensionProvider::EXTENSION_TAG, + FunctionTypeSpecifyingExtension::class => TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG, + MethodTypeSpecifyingExtension::class => TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG, + StaticMethodTypeSpecifyingExtension::class => TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG, + DynamicFunctionThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::FUNCTION_TAG, + DynamicMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::METHOD_TAG, + DynamicStaticMethodThrowTypeExtension::class => LazyDynamicThrowTypeExtensionProvider::STATIC_METHOD_TAG, + FunctionParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG, + MethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::METHOD_TAG, + StaticMethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::STATIC_METHOD_TAG, + FunctionParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::FUNCTION_TAG, + MethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::METHOD_TAG, + StaticMethodParameterOutTypeExtension::class => LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG, + ResultCacheMetaExtension::class => ResultCacheMetaExtension::EXTENSION_TAG, + ClassConstantDeprecationExtension::class => ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG, + ClassDeprecationExtension::class => ClassDeprecationExtension::CLASS_EXTENSION_TAG, + EnumCaseDeprecationExtension::class => EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG, + FunctionDeprecationExtension::class => FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG, + MethodDeprecationExtension::class => MethodDeprecationExtension::METHOD_EXTENSION_TAG, + PropertyDeprecationExtension::class => PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG, + RestrictedMethodUsageExtension::class => RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG, + RestrictedClassNameUsageExtension::class => RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG, + RestrictedFunctionUsageExtension::class => RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG, + RestrictedPropertyUsageExtension::class => RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG, + RestrictedClassConstantUsageExtension::class => RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG, + NodeVisitor::class => RichParser::VISITOR_SERVICE_TAG, + Collector::class => CollectorRegistryFactory::COLLECTOR_TAG, + DiagnoseExtension::class => DiagnoseExtension::EXTENSION_TAG, + ]; + + /** + * @throws MissingImplementedInterfaceInServiceWithTagException + */ + public function beforeCompile(): void + { + $builder = $this->getContainerBuilder(); + $mappingCount = count(self::INTERFACE_TAG_MAPPING); + $flippedMapping = array_flip(self::INTERFACE_TAG_MAPPING); + + if (count($flippedMapping) !== $mappingCount) { // @phpstan-ignore notIdentical.alwaysFalse + throw new ShouldNotHappenException('A tag is mapped to multiple interfaces'); + } + + foreach ($builder->getDefinitions() as $definition) { + /** @var class-string|null $className */ + $className = $definition->getType(); + if ($className === null) { + continue; + } + $reflection = new ReflectionClass($className); + foreach ($definition->getTags() as $tag => $attr) { + if (!array_key_exists($tag, $flippedMapping)) { + continue; + } + + if ($reflection->implementsInterface($flippedMapping[$tag])) { + continue; + } + + throw new MissingImplementedInterfaceInServiceWithTagException($className, $tag, $flippedMapping[$tag]); + } + } + } + +} diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabled.php index 0d0af6673ca..ded54328e95 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceDisabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceDisabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledDisabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledDisabled.php index ffba6df404b..636e786ea34 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledDisabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledDisabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceDisabledDisabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceDisabledDisabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledEnabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledEnabled.php index 9234d1a4bd4..e758ce8efee 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledEnabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceDisabledEnabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceDisabledEnabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceDisabledEnabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabled.php index a267a71129e..921f9a9590b 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceEnabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceEnabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledDisabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledDisabled.php index 9535501fb46..379700d04fc 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledDisabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledDisabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceEnabledDisabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceEnabledDisabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } diff --git a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledEnabled.php b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledEnabled.php index 270201cc4ec..d12d4121c88 100644 --- a/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledEnabled.php +++ b/tests/PHPStan/DependencyInjection/TestedConditionalServiceEnabledEnabled.php @@ -2,7 +2,24 @@ namespace PHPStan\DependencyInjection; -class TestedConditionalServiceEnabledEnabled +use PhpParser\Node; +use PHPStan\Analyser\Scope; +use PHPStan\Rules\Rule; + +/** + * @implements Rule + */ +class TestedConditionalServiceEnabledEnabled implements Rule { + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return []; + } + } From c512a4503b3d6a40f481f716566517f523ab045e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 10:37:02 +0200 Subject: [PATCH 1430/3097] Deduplicate list of extension tags between ConditionalTagsExtension and ValidateServiceTagsExtension --- .../ConditionalTagsExtension.php | 75 ++----------------- 1 file changed, 7 insertions(+), 68 deletions(-) diff --git a/src/DependencyInjection/ConditionalTagsExtension.php b/src/DependencyInjection/ConditionalTagsExtension.php index ccffd142900..78566591576 100644 --- a/src/DependencyInjection/ConditionalTagsExtension.php +++ b/src/DependencyInjection/ConditionalTagsExtension.php @@ -5,33 +5,10 @@ use Nette; use Nette\DI\CompilerExtension; use Nette\Schema\Expect; -use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; -use PHPStan\Analyser\TypeSpecifierFactory; -use PHPStan\Broker\BrokerFactory; -use PHPStan\Collectors\RegistryFactory as CollectorRegistryFactory; -use PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider; -use PHPStan\DependencyInjection\Type\LazyParameterOutTypeExtensionProvider; -use PHPStan\Diagnose\DiagnoseExtension; -use PHPStan\Parser\RichParser; -use PHPStan\PhpDoc\StubFilesExtension; -use PHPStan\PhpDoc\TypeNodeResolverExtension; -use PHPStan\Reflection\Deprecation\ClassConstantDeprecationExtension; -use PHPStan\Reflection\Deprecation\ClassDeprecationExtension; -use PHPStan\Reflection\Deprecation\EnumCaseDeprecationExtension; -use PHPStan\Reflection\Deprecation\FunctionDeprecationExtension; -use PHPStan\Reflection\Deprecation\MethodDeprecationExtension; -use PHPStan\Reflection\Deprecation\PropertyDeprecationExtension; -use PHPStan\Rules\Constants\AlwaysUsedClassConstantsExtensionProvider; -use PHPStan\Rules\LazyRegistry; -use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; -use PHPStan\Rules\RestrictedUsage\RestrictedClassConstantUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedFunctionUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedMethodUsageExtension; -use PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageExtension; use PHPStan\ShouldNotHappenException; +use function array_fill_keys; use function array_reduce; +use function array_values; use function count; use function is_array; use function sprintf; @@ -41,49 +18,11 @@ final class ConditionalTagsExtension extends CompilerExtension public function getConfigSchema(): Nette\Schema\Schema { - $bool = Expect::anyOf(Expect::bool(), Expect::listOf(Expect::bool())); - return Expect::arrayOf(Expect::structure([ - BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG => $bool, - BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG => $bool, - BrokerFactory::EXPRESSION_TYPE_RESOLVER_EXTENSION_TAG => $bool, - BrokerFactory::OPERATOR_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - BrokerFactory::ALLOWED_SUB_TYPES_CLASS_REFLECTION_EXTENSION_TAG => $bool, - LazyRegistry::RULE_TAG => $bool, - TypeNodeResolverExtension::EXTENSION_TAG => $bool, - StubFilesExtension::EXTENSION_TAG => $bool, - AlwaysUsedClassConstantsExtensionProvider::EXTENSION_TAG => $bool, - ReadWritePropertiesExtensionProvider::EXTENSION_TAG => $bool, - TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG => $bool, - RichParser::VISITOR_SERVICE_TAG => $bool, - CollectorRegistryFactory::COLLECTOR_TAG => $bool, - LazyDynamicThrowTypeExtensionProvider::FUNCTION_TAG => $bool, - LazyDynamicThrowTypeExtensionProvider::METHOD_TAG => $bool, - LazyDynamicThrowTypeExtensionProvider::STATIC_METHOD_TAG => $bool, - LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG => $bool, - LazyParameterClosureTypeExtensionProvider::METHOD_TAG => $bool, - LazyParameterClosureTypeExtensionProvider::STATIC_METHOD_TAG => $bool, - LazyParameterOutTypeExtensionProvider::FUNCTION_TAG => $bool, - LazyParameterOutTypeExtensionProvider::METHOD_TAG => $bool, - LazyParameterOutTypeExtensionProvider::STATIC_METHOD_TAG => $bool, - DiagnoseExtension::EXTENSION_TAG => $bool, - ResultCacheMetaExtension::EXTENSION_TAG => $bool, - ClassConstantDeprecationExtension::CLASS_CONSTANT_EXTENSION_TAG => $bool, - ClassDeprecationExtension::CLASS_EXTENSION_TAG => $bool, - EnumCaseDeprecationExtension::ENUM_CASE_EXTENSION_TAG => $bool, - FunctionDeprecationExtension::FUNCTION_EXTENSION_TAG => $bool, - MethodDeprecationExtension::METHOD_EXTENSION_TAG => $bool, - PropertyDeprecationExtension::PROPERTY_EXTENSION_TAG => $bool, - RestrictedMethodUsageExtension::METHOD_EXTENSION_TAG => $bool, - RestrictedClassNameUsageExtension::CLASS_NAME_EXTENSION_TAG => $bool, - RestrictedFunctionUsageExtension::FUNCTION_EXTENSION_TAG => $bool, - RestrictedPropertyUsageExtension::PROPERTY_EXTENSION_TAG => $bool, - RestrictedClassConstantUsageExtension::CLASS_CONSTANT_EXTENSION_TAG => $bool, - ])->min(1)); + $tags = array_values(ValidateServiceTagsExtension::INTERFACE_TAG_MAPPING); + + return Expect::arrayOf(Expect::structure( + array_fill_keys($tags, Expect::anyOf(Expect::bool(), Expect::listOf(Expect::bool()))), + )->min(1)); } public function beforeCompile(): void From c0ef34cd3ce67c275e9133266df67023c9788297 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 10:52:40 +0200 Subject: [PATCH 1431/3097] Moved richParserNodeVisitor services from `config.neon` to `#[AutowiredService]` attribute --- conf/config.neon | 90 ------------------- src/Parser/AnonymousClassVisitor.php | 2 + src/Parser/ArrayFilterArgVisitor.php | 2 + src/Parser/ArrayFindArgVisitor.php | 2 + src/Parser/ArrayMapArgVisitor.php | 2 + src/Parser/ArrayWalkArgVisitor.php | 2 + src/Parser/ArrowFunctionArgVisitor.php | 2 + src/Parser/ClosureArgVisitor.php | 2 + src/Parser/ClosureBindArgVisitor.php | 2 + src/Parser/ClosureBindToVarVisitor.php | 2 + src/Parser/CurlSetOptArgVisitor.php | 2 + .../MagicConstantParamDefaultVisitor.php | 2 + src/Parser/NewAssignedToPropertyVisitor.php | 2 + src/Parser/ParentStmtTypesVisitor.php | 2 + src/Parser/StandaloneThrowExprVisitor.php | 2 + src/Parser/TryCatchTypeVisitor.php | 2 + src/Parser/TypeTraverserInstanceofVisitor.php | 2 + src/Parser/VariadicFunctionsVisitor.php | 2 + src/Parser/VariadicMethodsVisitor.php | 2 + 19 files changed, 36 insertions(+), 90 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 429672258b3..d71681c9c9c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -237,96 +237,6 @@ services: options: preserveOriginalNames: true - - - class: PHPStan\Parser\AnonymousClassVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ArrayFilterArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ArrayFindArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ArrayMapArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ArrayWalkArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ClosureArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ClosureBindToVarVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ClosureBindArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\CurlSetOptArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ArrowFunctionArgVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\MagicConstantParamDefaultVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\NewAssignedToPropertyVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ParentStmtTypesVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\StandaloneThrowExprVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\TryCatchTypeVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\TypeTraverserInstanceofVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\VariadicMethodsVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\VariadicFunctionsVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - class: PHPStan\Node\Printer\Printer autowired: diff --git a/src/Parser/AnonymousClassVisitor.php b/src/Parser/AnonymousClassVisitor.php index 16fbe58dec7..78d5f870ea9 100644 --- a/src/Parser/AnonymousClassVisitor.php +++ b/src/Parser/AnonymousClassVisitor.php @@ -4,9 +4,11 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\AnonymousClassNode; use function count; +#[AutowiredService] final class AnonymousClassVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ArrayFilterArgVisitor.php b/src/Parser/ArrayFilterArgVisitor.php index a2730deb3d5..4aff628e4cb 100644 --- a/src/Parser/ArrayFilterArgVisitor.php +++ b/src/Parser/ArrayFilterArgVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class ArrayFilterArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ArrayFindArgVisitor.php b/src/Parser/ArrayFindArgVisitor.php index 0e798eb5c0b..b3cda234d7f 100644 --- a/src/Parser/ArrayFindArgVisitor.php +++ b/src/Parser/ArrayFindArgVisitor.php @@ -4,8 +4,10 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function in_array; +#[AutowiredService] final class ArrayFindArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ArrayMapArgVisitor.php b/src/Parser/ArrayMapArgVisitor.php index 0c62d0c7c4d..38d7f2c1f07 100644 --- a/src/Parser/ArrayMapArgVisitor.php +++ b/src/Parser/ArrayMapArgVisitor.php @@ -4,9 +4,11 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function array_slice; use function count; +#[AutowiredService] final class ArrayMapArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ArrayWalkArgVisitor.php b/src/Parser/ArrayWalkArgVisitor.php index ad776bb1751..8eca1930234 100644 --- a/src/Parser/ArrayWalkArgVisitor.php +++ b/src/Parser/ArrayWalkArgVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class ArrayWalkArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ArrowFunctionArgVisitor.php b/src/Parser/ArrowFunctionArgVisitor.php index 93cce45dd93..8091bdeda30 100644 --- a/src/Parser/ArrowFunctionArgVisitor.php +++ b/src/Parser/ArrowFunctionArgVisitor.php @@ -4,8 +4,10 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function count; +#[AutowiredService] final class ArrowFunctionArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ClosureArgVisitor.php b/src/Parser/ClosureArgVisitor.php index c9435f826eb..81e284c02f1 100644 --- a/src/Parser/ClosureArgVisitor.php +++ b/src/Parser/ClosureArgVisitor.php @@ -4,8 +4,10 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function count; +#[AutowiredService] final class ClosureArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ClosureBindArgVisitor.php b/src/Parser/ClosureBindArgVisitor.php index 291ede59b45..2436a3b2026 100644 --- a/src/Parser/ClosureBindArgVisitor.php +++ b/src/Parser/ClosureBindArgVisitor.php @@ -5,8 +5,10 @@ use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function count; +#[AutowiredService] final class ClosureBindArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ClosureBindToVarVisitor.php b/src/Parser/ClosureBindToVarVisitor.php index 7196d50093b..7a24da4d7d0 100644 --- a/src/Parser/ClosureBindToVarVisitor.php +++ b/src/Parser/ClosureBindToVarVisitor.php @@ -5,7 +5,9 @@ use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class ClosureBindToVarVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/CurlSetOptArgVisitor.php b/src/Parser/CurlSetOptArgVisitor.php index f9be2fd5c3c..eb9c2124327 100644 --- a/src/Parser/CurlSetOptArgVisitor.php +++ b/src/Parser/CurlSetOptArgVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class CurlSetOptArgVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/MagicConstantParamDefaultVisitor.php b/src/Parser/MagicConstantParamDefaultVisitor.php index 455c341e4e3..7c4542f506e 100644 --- a/src/Parser/MagicConstantParamDefaultVisitor.php +++ b/src/Parser/MagicConstantParamDefaultVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class MagicConstantParamDefaultVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/NewAssignedToPropertyVisitor.php b/src/Parser/NewAssignedToPropertyVisitor.php index 209e72730d7..f201e3fc3c8 100644 --- a/src/Parser/NewAssignedToPropertyVisitor.php +++ b/src/Parser/NewAssignedToPropertyVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class NewAssignedToPropertyVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ParentStmtTypesVisitor.php b/src/Parser/ParentStmtTypesVisitor.php index a7da560658e..a1c554ba8ee 100644 --- a/src/Parser/ParentStmtTypesVisitor.php +++ b/src/Parser/ParentStmtTypesVisitor.php @@ -4,10 +4,12 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function array_pop; use function count; use function get_class; +#[AutowiredService] final class ParentStmtTypesVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/StandaloneThrowExprVisitor.php b/src/Parser/StandaloneThrowExprVisitor.php index 386c903281a..222641aa627 100644 --- a/src/Parser/StandaloneThrowExprVisitor.php +++ b/src/Parser/StandaloneThrowExprVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class StandaloneThrowExprVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/TryCatchTypeVisitor.php b/src/Parser/TryCatchTypeVisitor.php index cca8bf4e3a3..37953cdf263 100644 --- a/src/Parser/TryCatchTypeVisitor.php +++ b/src/Parser/TryCatchTypeVisitor.php @@ -4,10 +4,12 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function array_pop; use function array_reverse; use function count; +#[AutowiredService] final class TryCatchTypeVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/TypeTraverserInstanceofVisitor.php b/src/Parser/TypeTraverserInstanceofVisitor.php index e353d31af3c..5be0b256fcd 100644 --- a/src/Parser/TypeTraverserInstanceofVisitor.php +++ b/src/Parser/TypeTraverserInstanceofVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class TypeTraverserInstanceofVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/VariadicFunctionsVisitor.php b/src/Parser/VariadicFunctionsVisitor.php index 5276d0eb47d..10973e4974d 100644 --- a/src/Parser/VariadicFunctionsVisitor.php +++ b/src/Parser/VariadicFunctionsVisitor.php @@ -5,11 +5,13 @@ use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ParametersAcceptor; use function array_filter; use function array_key_exists; use function in_array; +#[AutowiredService] final class VariadicFunctionsVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/VariadicMethodsVisitor.php b/src/Parser/VariadicMethodsVisitor.php index cc3821d9f21..40b0b16ad3b 100644 --- a/src/Parser/VariadicMethodsVisitor.php +++ b/src/Parser/VariadicMethodsVisitor.php @@ -6,6 +6,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ParametersAcceptor; use function array_key_exists; use function array_pop; @@ -13,6 +14,7 @@ use function in_array; use function sprintf; +#[AutowiredService] final class VariadicMethodsVisitor extends NodeVisitorAbstract { From ded58e8a33974033005a6acede400745dbef89fc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:01:32 +0200 Subject: [PATCH 1432/3097] More AutowiredService --- conf/config.neon | 10 ---------- src/Parser/DeclarePositionVisitor.php | 2 ++ src/Parser/ImmediatelyInvokedClosureVisitor.php | 2 ++ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index d71681c9c9c..c15ac39eb34 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -498,16 +498,6 @@ services: scanFiles: %scanFiles% scanDirectories: %scanDirectories% - - - class: PHPStan\Parser\DeclarePositionVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - - - class: PHPStan\Parser\ImmediatelyInvokedClosureVisitor - tags: - - phpstan.parser.richParserNodeVisitor - - class: PHPStan\Parallel\ParallelAnalyser arguments: diff --git a/src/Parser/DeclarePositionVisitor.php b/src/Parser/DeclarePositionVisitor.php index e0d523603f4..8f00712e05b 100644 --- a/src/Parser/DeclarePositionVisitor.php +++ b/src/Parser/DeclarePositionVisitor.php @@ -4,8 +4,10 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; use function str_starts_with; +#[AutowiredService] final class DeclarePositionVisitor extends NodeVisitorAbstract { diff --git a/src/Parser/ImmediatelyInvokedClosureVisitor.php b/src/Parser/ImmediatelyInvokedClosureVisitor.php index c77059e2145..68dc4b7021c 100644 --- a/src/Parser/ImmediatelyInvokedClosureVisitor.php +++ b/src/Parser/ImmediatelyInvokedClosureVisitor.php @@ -4,7 +4,9 @@ use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use PHPStan\DependencyInjection\AutowiredService; +#[AutowiredService] final class ImmediatelyInvokedClosureVisitor extends NodeVisitorAbstract { From 0210ec20770eb475ec36d2ca0f223fe58237da8d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:00:40 +0200 Subject: [PATCH 1433/3097] Separate services that will never use `#[AutowiredService]` into a separate config file --- conf/config.neon | 112 +------------------------------------------- conf/services.neon | 114 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 111 deletions(-) create mode 100644 conf/services.neon diff --git a/conf/config.neon b/conf/config.neon index c15ac39eb34..ead664995a6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1,5 +1,6 @@ includes: - parametersSchema.neon + - services.neon parameters: bootstrapFiles: @@ -228,14 +229,6 @@ conditionalTags: phpstan.rules.rule: %checkUninitializedProperties% services: - - - class: PhpParser\BuilderFactory - - - - class: PhpParser\NodeVisitor\NameResolver - arguments: - options: - preserveOriginalNames: true - class: PHPStan\Node\Printer\Printer @@ -266,27 +259,6 @@ services: arguments: composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - - - class: PHPStan\PhpDocParser\ParserConfig - arguments: - usedAttributes: - lines: true - - - - class: PHPStan\PhpDocParser\Lexer\Lexer - - - - class: PHPStan\PhpDocParser\Parser\TypeParser - - - - class: PHPStan\PhpDocParser\Parser\ConstExprParser - - - - class: PHPStan\PhpDocParser\Parser\PhpDocParser - - - - class: PHPStan\PhpDocParser\Printer\Printer - - class: PHPStan\PhpDoc\TypeNodeResolverExtensionRegistryProvider factory: PHPStan\PhpDoc\LazyTypeNodeResolverExtensionRegistryProvider @@ -394,9 +366,6 @@ services: arguments: parser: @defaultAnalysisParser - - - class: PHPStan\Dependency\ExportedNodeVisitor - - class: PHPStan\DependencyInjection\Container factory: PHPStan\DependencyInjection\MemoizingContainer @@ -524,15 +493,6 @@ services: arguments: usePathConstantsAsConstantString: %usePathConstantsAsConstantString% - - - class: PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension - - - - class: PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension - - - - class: PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor - - class: PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher arguments: @@ -568,36 +528,11 @@ services: arguments: additionalConstructors: %additionalConstructors% - - - class: PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension - - - - class: PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension - - - - class: PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension - arguments: - mixinExcludeClasses: %mixinExcludeClasses% - - - - class: PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension - arguments: - mixinExcludeClasses: %mixinExcludeClasses% - - - - class: PHPStan\Reflection\Php\PhpClassReflectionExtension - arguments: - parser: @defaultAnalysisParser - inferPrivatePropertyTypeFromConstructor: %inferPrivatePropertyTypeFromConstructor% - - implement: PHPStan\Reflection\Php\PhpMethodReflectionFactory arguments: parser: @defaultAnalysisParser - - - class: PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension - - class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension tags: @@ -908,41 +843,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension - arguments: - className: ReflectionClass - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension - arguments: - className: ReflectionClassConstant - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension - arguments: - className: ReflectionFunctionAbstract - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension - arguments: - className: ReflectionParameter - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension - arguments: - className: ReflectionProperty - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\ClosureTypeFactory arguments: @@ -1055,16 +955,6 @@ services: autowired: - PHPStan\Reflection\ReflectionProvider - betterReflectionSourceLocator: - class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator - factory: @PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory::create - autowired: false - - originalBetterReflectionReflector: - class: PHPStan\BetterReflection\Reflector\DefaultReflector - arguments: - sourceLocator: @betterReflectionSourceLocator - betterReflectionReflector: class: PHPStan\Reflection\BetterReflection\Reflector\MemoizingReflector arguments: diff --git a/conf/services.neon b/conf/services.neon new file mode 100644 index 00000000000..84f424fb71e --- /dev/null +++ b/conf/services.neon @@ -0,0 +1,114 @@ +# these services are not registered using an attribute for one reason or another + +services: + - + class: PhpParser\BuilderFactory + + - + class: PhpParser\NodeVisitor\NameResolver + arguments: + options: + preserveOriginalNames: true + + - + class: PHPStan\PhpDocParser\ParserConfig + arguments: + usedAttributes: + lines: true + + - + class: PHPStan\PhpDocParser\Lexer\Lexer + + - + class: PHPStan\PhpDocParser\Parser\TypeParser + + - + class: PHPStan\PhpDocParser\Parser\ConstExprParser + + - + class: PHPStan\PhpDocParser\Parser\PhpDocParser + + - + class: PHPStan\PhpDocParser\Printer\Printer + + - + class: PHPStan\Dependency\ExportedNodeVisitor + + - + class: PHPStan\Reflection\BetterReflection\SourceLocator\CachingVisitor + + - + class: PHPStan\Reflection\Php\PhpClassReflectionExtension + arguments: + parser: @defaultAnalysisParser + inferPrivatePropertyTypeFromConstructor: %inferPrivatePropertyTypeFromConstructor% + + - + class: PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension + + - + class: PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension + + - + class: PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension + arguments: + mixinExcludeClasses: %mixinExcludeClasses% + + - + class: PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension + arguments: + mixinExcludeClasses: %mixinExcludeClasses% + + - + class: PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension + + - + class: PHPStan\Reflection\RequireExtension\RequireExtendsMethodsClassReflectionExtension + + - + class: PHPStan\Reflection\RequireExtension\RequireExtendsPropertiesClassReflectionExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionClass + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionClassConstant + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionFunctionAbstract + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionParameter + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionProperty + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + betterReflectionSourceLocator: + class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator + factory: @PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory::create + autowired: false + + originalBetterReflectionReflector: + class: PHPStan\BetterReflection\Reflector\DefaultReflector + arguments: + sourceLocator: @betterReflectionSourceLocator From 61c78a6e29a3ebb29586b4e26922491e1931af6d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:02:57 +0200 Subject: [PATCH 1434/3097] More AutowiredService --- conf/config.neon | 10 ---------- src/Type/Php/LtrimFunctionReturnTypeExtension.php | 2 ++ .../Php/TrimFunctionDynamicReturnTypeExtension.php | 2 ++ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ead664995a6..91b787fb8e7 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -833,16 +833,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - - - class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\ClosureTypeFactory arguments: diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 284be58a82a..06cf7ae5efa 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantStringType; @@ -12,6 +13,7 @@ use function count; use function ltrim; +#[AutowiredService] final class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index df06d98b437..80a05fe837d 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; @@ -14,6 +15,7 @@ use function count; use function in_array; +#[AutowiredService] final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From 2ac279d79ac30efa0451975fbe58066c06f58a75 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:03:24 +0200 Subject: [PATCH 1435/3097] Move more to services.neon --- conf/config.neon | 44 -------------------------------------------- conf/services.neon | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 91b787fb8e7..654fba3695a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -509,20 +509,6 @@ services: - implement: PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocatorFactory - - - class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension - arguments: - class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - - - class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension - arguments: - class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Reflection\ConstructorsHelper arguments: @@ -540,22 +526,6 @@ services: arguments: classes: %universalObjectCratesClasses% - - - class: PHPStan\Reflection\PHPStan\NativeReflectionEnumReturnDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - arguments: - className: PHPStan\Reflection\ClassReflection - methodName: getNativeReflection - - - - class: PHPStan\Reflection\PHPStan\NativeReflectionEnumReturnDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - arguments: - className: PHPStan\Reflection\Php\BuiltinMethodReflection - methodName: getDeclaringClass - - class: PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider factory: PHPStan\Reflection\ReflectionProvider\LazyReflectionProviderProvider @@ -804,20 +774,6 @@ services: arguments: checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables% - - - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - arguments: - dateTimeClass: DateTime - - - - class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension - arguments: - dateTimeClass: DateTimeImmutable - - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension arguments: diff --git a/conf/services.neon b/conf/services.neon index 84f424fb71e..b8178f99e6c 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -103,6 +103,52 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + dateTimeClass: DateTime + + - + class: PHPStan\Reflection\PHPStan\NativeReflectionEnumReturnDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + className: PHPStan\Reflection\ClassReflection + methodName: getNativeReflection + + - + class: PHPStan\Reflection\PHPStan\NativeReflectionEnumReturnDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + className: PHPStan\Reflection\Php\BuiltinMethodReflection + methodName: getDeclaringClass + + - + class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension + arguments: + class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumBackedCase + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Reflection\BetterReflection\Type\AdapterReflectionEnumCaseDynamicReturnTypeExtension + arguments: + class: PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnumUnitCase + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + + - + class: PHPStan\Type\Php\DateTimeModifyReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + arguments: + dateTimeClass: DateTimeImmutable + + betterReflectionSourceLocator: class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator factory: @PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory::create From fc3a97b00d7f4f1109ddfddb6d69908fdb5b54c9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:05:15 +0200 Subject: [PATCH 1436/3097] More AutowiredService --- conf/config.neon | 27 ------------------- conf/services.neon | 11 ++++++++ .../SignatureMap/SignatureMapParser.php | 2 ++ .../SignatureMapProviderFactory.php | 2 ++ src/Rules/Api/ApiRuleHelper.php | 2 ++ src/Rules/ClassForbiddenNameCheck.php | 2 ++ src/Rules/ClassNameCheck.php | 2 ++ 7 files changed, 21 insertions(+), 27 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 654fba3695a..293ca28e0bd 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -535,9 +535,6 @@ services: arguments: reflector: @betterReflectionReflector - - - class: PHPStan\Reflection\SignatureMap\SignatureMapParser - - class: PHPStan\Reflection\SignatureMap\FunctionSignatureMapProvider arguments: @@ -550,16 +547,10 @@ services: autowired: - PHPStan\Reflection\SignatureMap\Php8SignatureMapProvider - - - class: PHPStan\Reflection\SignatureMap\SignatureMapProviderFactory - - class: PHPStan\Reflection\SignatureMap\SignatureMapProvider factory: @PHPStan\Reflection\SignatureMap\SignatureMapProviderFactory::create() - - - class: PHPStan\Rules\Api\ApiRuleHelper - - class: PHPStan\Rules\AttributesCheck arguments: @@ -572,17 +563,11 @@ services: reportPossiblyNonexistentGeneralArrayOffset: %reportPossiblyNonexistentGeneralArrayOffset% reportPossiblyNonexistentConstantArrayOffset: %reportPossiblyNonexistentConstantArrayOffset% - - - class: PHPStan\Rules\ClassNameCheck - - class: PHPStan\Rules\ClassCaseSensitivityCheck arguments: checkInternalClassCaseSensitivity: %checkInternalClassCaseSensitivity% - - - class: PHPStan\Rules\ClassForbiddenNameCheck - - class: PHPStan\Rules\Classes\LocalTypeAliasesCheck arguments: @@ -633,15 +618,6 @@ services: autowired: - PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver - - - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule - - - - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule - - - - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule - - class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInThrowsCheck arguments: @@ -727,9 +703,6 @@ services: reportMagicProperties: %reportMagicProperties% checkDynamicProperties: %checkDynamicProperties% - - - class: PHPStan\Rules\Properties\UninitializedPropertyRule - - class: PHPStan\Rules\RuleLevelHelper arguments: diff --git a/conf/services.neon b/conf/services.neon index b8178f99e6c..227d5787548 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -148,6 +148,17 @@ services: arguments: dateTimeClass: DateTimeImmutable + - + class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInFunctionThrowsRule + + - + class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInMethodThrowsRule + + - + class: PHPStan\Rules\Exceptions\MissingCheckedExceptionInPropertyHookThrowsRule + + - + class: PHPStan\Rules\Properties\UninitializedPropertyRule betterReflectionSourceLocator: class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator diff --git a/src/Reflection/SignatureMap/SignatureMapParser.php b/src/Reflection/SignatureMap/SignatureMapParser.php index e60cede66d5..fab2c15313e 100644 --- a/src/Reflection/SignatureMap/SignatureMapParser.php +++ b/src/Reflection/SignatureMap/SignatureMapParser.php @@ -4,6 +4,7 @@ use Nette\Utils\Strings; use PHPStan\Analyser\NameScope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\PassedByReference; use PHPStan\ShouldNotHappenException; @@ -13,6 +14,7 @@ use function str_starts_with; use function substr; +#[AutowiredService] final class SignatureMapParser { diff --git a/src/Reflection/SignatureMap/SignatureMapProviderFactory.php b/src/Reflection/SignatureMap/SignatureMapProviderFactory.php index 4aa6d510da2..5e2c6a3fb34 100644 --- a/src/Reflection/SignatureMap/SignatureMapProviderFactory.php +++ b/src/Reflection/SignatureMap/SignatureMapProviderFactory.php @@ -2,8 +2,10 @@ namespace PHPStan\Reflection\SignatureMap; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +#[AutowiredService] final class SignatureMapProviderFactory { diff --git a/src/Rules/Api/ApiRuleHelper.php b/src/Rules/Api/ApiRuleHelper.php index 8fe066e60b9..01801a49aa4 100644 --- a/src/Rules/Api/ApiRuleHelper.php +++ b/src/Rules/Api/ApiRuleHelper.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Api; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\File\ParentDirectoryRelativePathHelper; use function dirname; use function pathinfo; @@ -11,6 +12,7 @@ use function strtolower; use const PATHINFO_BASENAME; +#[AutowiredService] final class ApiRuleHelper { diff --git a/src/Rules/ClassForbiddenNameCheck.php b/src/Rules/ClassForbiddenNameCheck.php index f1f9f032a31..217c42443bf 100644 --- a/src/Rules/ClassForbiddenNameCheck.php +++ b/src/Rules/ClassForbiddenNameCheck.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules; use PHPStan\Classes\ForbiddenClassNameExtension; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use function array_map; use function array_merge; @@ -12,6 +13,7 @@ use function strpos; use function substr; +#[AutowiredService] final class ClassForbiddenNameCheck { diff --git a/src/Rules/ClassNameCheck.php b/src/Rules/ClassNameCheck.php index 7ce8b7a27ec..ba23578483c 100644 --- a/src/Rules/ClassNameCheck.php +++ b/src/Rules/ClassNameCheck.php @@ -3,10 +3,12 @@ namespace PHPStan\Rules; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\RestrictedUsage\RestrictedClassNameUsageExtension; +#[AutowiredService] final class ClassNameCheck { From c59262247acc3b653715e829c1cb5ececa323d70 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:19:32 +0200 Subject: [PATCH 1437/3097] Revert "More AutowiredService" This reverts commit 61c78a6e29a3ebb29586b4e26922491e1931af6d. --- conf/config.neon | 10 ++++++++++ src/Type/Php/LtrimFunctionReturnTypeExtension.php | 2 -- .../Php/TrimFunctionDynamicReturnTypeExtension.php | 2 -- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 293ca28e0bd..cdba212a4ff 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -762,6 +762,16 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + + - + class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\ClosureTypeFactory arguments: diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 06cf7ae5efa..284be58a82a 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -4,7 +4,6 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantStringType; @@ -13,7 +12,6 @@ use function count; use function ltrim; -#[AutowiredService] final class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index 80a05fe837d..df06d98b437 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -4,7 +4,6 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; @@ -15,7 +14,6 @@ use function count; use function in_array; -#[AutowiredService] final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From 60543dac2e3e9c3ca412f69ef885f62ab165cab7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:25:24 +0200 Subject: [PATCH 1438/3097] TrimFunctionDynamicReturnTypeExtension - register as AutowiredService --- conf/config.neon | 5 ----- src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index cdba212a4ff..1e1a39ace9d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -767,11 +767,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\TrimFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\ClosureTypeFactory arguments: diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index df06d98b437..80a05fe837d 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; @@ -14,6 +15,7 @@ use function count; use function in_array; +#[AutowiredService] final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From 17af999c6716a6daed9b7c879ba04e85659279e0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:25:39 +0200 Subject: [PATCH 1439/3097] TrimFunctionDynamicReturnTypeExtension does not handle ltrim anymore --- .../Php/LtrimFunctionReturnTypeExtension.php | 26 +++++++++++++++++-- ...TrimFunctionDynamicReturnTypeExtension.php | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index 284be58a82a..ceb87242c2d 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -5,9 +5,13 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLowercaseStringType; +use PHPStan\Type\Accessory\AccessoryUppercaseStringType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\IntersectionType; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use function count; use function ltrim; @@ -22,11 +26,29 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - if (count($functionCall->getArgs()) !== 2) { + if (count($functionCall->getArgs()) < 1) { return null; } $string = $scope->getType($functionCall->getArgs()[0]->value); + + $accessory = []; + $defaultType = new StringType(); + if ($string->isLowercaseString()->yes()) { + $accessory[] = new AccessoryLowercaseStringType(); + } + if ($string->isUppercaseString()->yes()) { + $accessory[] = new AccessoryUppercaseStringType(); + } + if (count($accessory) > 0) { + $accessory[] = new StringType(); + $defaultType = new IntersectionType($accessory); + } + + if (count($functionCall->getArgs()) !== 2) { + return $defaultType; + } + $trimChars = $scope->getType($functionCall->getArgs()[1]->value); if ($trimChars instanceof ConstantStringType && $trimChars->getValue() === '\\' && $string->isClassString()->yes()) { @@ -37,7 +59,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new ClassStringType(); } - return null; + return $defaultType; } } diff --git a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php index 80a05fe837d..3b6c27320a2 100644 --- a/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/TrimFunctionDynamicReturnTypeExtension.php @@ -21,7 +21,7 @@ final class TrimFunctionDynamicReturnTypeExtension implements DynamicFunctionRet public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return in_array($functionReflection->getName(), ['trim', 'rtrim', 'ltrim'], true); + return in_array($functionReflection->getName(), ['trim', 'rtrim'], true); } public function getTypeFromFunctionCall( From 3b4cbdaba44a0557ca5af4642656361f4a3200b2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 11:26:12 +0200 Subject: [PATCH 1440/3097] Register LtrimFunctionReturnTypeExtension with AutowiredService --- conf/config.neon | 5 ----- src/Type/Php/LtrimFunctionReturnTypeExtension.php | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 1e1a39ace9d..293ca28e0bd 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -762,11 +762,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\LtrimFunctionReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\ClosureTypeFactory arguments: diff --git a/src/Type/Php/LtrimFunctionReturnTypeExtension.php b/src/Type/Php/LtrimFunctionReturnTypeExtension.php index ceb87242c2d..701e474fe5e 100644 --- a/src/Type/Php/LtrimFunctionReturnTypeExtension.php +++ b/src/Type/Php/LtrimFunctionReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLowercaseStringType; use PHPStan\Type\Accessory\AccessoryUppercaseStringType; @@ -16,6 +17,7 @@ use function count; use function ltrim; +#[AutowiredService] final class LtrimFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { From f8d9e29cafdc5caaec06828f487476b5d720b914 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 13:12:19 +0200 Subject: [PATCH 1441/3097] simple-downgrader - pass path to composer.json --- build/downgrade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/build/downgrade.php b/build/downgrade.php index 7c117d13f56..437bfcf1cc8 100644 --- a/build/downgrade.php +++ b/build/downgrade.php @@ -1,6 +1,7 @@ __DIR__ . '/../composer.json', 'paths' => [ __DIR__ . '/../build/PHPStan', __DIR__ . '/../src', From 553d33b53b9367013db4819e1a61ec47da80ef25 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 17:49:29 +0200 Subject: [PATCH 1442/3097] Update simple-downgrader which can now downgrade named arguments! --- .github/workflows/lint.yml | 2 +- .github/workflows/reflection-golden-test.yml | 4 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- compiler/composer.json | 2 +- compiler/composer.lock | 134 ++++++++++++++++++- 6 files changed, 133 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 252d705fdf2..f0e44b822d3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + composer global require --dev ondrejmirtes/simple-downgrader:^2.1 echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 1590a29d66a..0931c2492af 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -99,7 +99,7 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + composer global require --dev ondrejmirtes/simple-downgrader:^2.1 echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} @@ -120,7 +120,7 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + composer global require --dev ondrejmirtes/simple-downgrader:^2.1 echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 14a7f79e421..8a72947e2c5 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -52,7 +52,7 @@ jobs: if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + composer global require --dev ondrejmirtes/simple-downgrader:^2.1 echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 510125a9ba0..50e7c470af2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.0 + composer global require --dev ondrejmirtes/simple-downgrader:^2.1 echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} diff --git a/compiler/composer.json b/compiler/composer.json index 337bd912129..e19f55e81ff 100644 --- a/compiler/composer.json +++ b/compiler/composer.json @@ -6,7 +6,7 @@ "require": { "php": "^8.0", "nette/neon": "^3.0.0", - "ondrejmirtes/simple-downgrader": "^2.0", + "ondrejmirtes/simple-downgrader": "^2.1", "seld/phar-utils": "^1.2", "symfony/console": "^5.4.43", "symfony/filesystem": "^5.4.43", diff --git a/compiler/composer.lock b/compiler/composer.lock index 677851641aa..e245a05f978 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -4,8 +4,56 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "78123aa3f1b04db29d1e84c086bd4259", + "content-hash": "6f5c87d0b49dd091a2fb504cb8ce59e1", "packages": [ + { + "name": "jetbrains/phpstorm-stubs", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-stubs.git", + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.64.0", + "nikic/php-parser": "^v5.3.1", + "phpdocumentor/reflection-docblock": "^5.6.0", + "phpunit/phpunit": "^11.4.3" + }, + "default-branch": true, + "type": "library", + "autoload": { + "files": [ + "PhpStormStubsMap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "PHP runtime & extensions header files for PhpStorm", + "homepage": "https://www.jetbrains.com/phpstorm", + "keywords": [ + "autocomplete", + "code", + "inference", + "inspection", + "jetbrains", + "phpstorm", + "stubs", + "type" + ], + "support": { + "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" + }, + "time": "2025-05-21T19:01:16+00:00" + }, { "name": "nette/neon", "version": "v3.4.4", @@ -218,23 +266,95 @@ }, "time": "2024-12-30T11:07:19+00:00" }, + { + "name": "ondrejmirtes/better-reflection", + "version": "6.57.0.0", + "source": { + "type": "git", + "url": "https://github.com/ondrejmirtes/BetterReflection.git", + "reference": "dcc22b90a63497f3450dd5eed62197bc46937297" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/dcc22b90a63497f3450dd5eed62197bc46937297", + "reference": "dcc22b90a63497f3450dd5eed62197bc46937297", + "shasum": "" + }, + "require": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "dev-master#dfcad4524db603bd20bdec3aab1a31c5f5128ea3", + "nikic/php-parser": "^5.4.0", + "php": "^7.4 || ^8.0" + }, + "conflict": { + "thecodingmachine/safe": "<1.1.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "phpstan/phpstan": "^1.10.60", + "phpstan/phpstan-phpunit": "^1.3.16", + "phpunit/phpunit": "^11.5.7", + "rector/rector": "1.2.10" + }, + "suggest": { + "composer/composer": "Required to use the ComposerSourceLocator" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\BetterReflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Titcumb", + "email": "james@asgrim.com", + "homepage": "https://github.com/asgrim" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + }, + { + "name": "Gary Hockin", + "email": "gary@roave.com", + "homepage": "https://github.com/geeh" + }, + { + "name": "Jaroslav Hanslík", + "email": "kukulich@kukulich.cz", + "homepage": "https://github.com/kukulich" + } + ], + "description": "Better Reflection - an improved code reflection API", + "support": { + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/6.57.0.0" + }, + "time": "2025-02-12T21:16:38+00:00" + }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.0.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc" + "reference": "0ce57fe11a7577f22752d9676263c2e3653a9c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fb8b7833034f0396d5e4518ed090e3d099b7d9bc", - "reference": "fb8b7833034f0396d5e4518ed090e3d099b7d9bc", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/0ce57fe11a7577f22752d9676263c2e3653a9c56", + "reference": "0ce57fe11a7577f22752d9676263c2e3653a9c56", "shasum": "" }, "require": { "nette/utils": "^3.2.5", "nikic/php-parser": "^5.3.0", + "ondrejmirtes/better-reflection": "^6.57", "php": "^7.4|^8.0", "phpstan/phpdoc-parser": "^2.0", "symfony/console": "^5.4", @@ -263,9 +383,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.0.0" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.2" }, - "time": "2024-10-09T14:55:47+00:00" + "time": "2025-05-26T16:02:34+00:00" }, { "name": "phpstan/phpdoc-parser", From 3b0638e352d42f818cf4b548516600350d9ef3e1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 18:07:28 +0200 Subject: [PATCH 1443/3097] Use named arguments in an example --- src/Analyser/MutatingScope.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 856bb901705..cbd92c0d3fd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2674,9 +2674,7 @@ private function createFirstClassCallable( $templateTags, $throwPoints, $impurePoints, - [], - [], - $acceptsNamedArguments, + acceptsNamedArguments: $acceptsNamedArguments, ); } From f7eee187916ea68f85cdeaf810e30b8a4c851afd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 18:13:25 +0200 Subject: [PATCH 1444/3097] Always use simple-downgrader installed in compiler --- .github/workflows/lint.yml | 5 ++--- .github/workflows/reflection-golden-test.yml | 10 ++++------ .github/workflows/static-analysis.yml | 5 ++--- .github/workflows/tests.yml | 5 ++--- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f0e44b822d3..d51170684a3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,9 +43,8 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.1 - echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" - simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer install --no-interaction --no-progress --working-dir=compiler + ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - name: "Validate Composer" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index 0931c2492af..b6df58b5f8a 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -99,9 +99,8 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.1 - echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" - simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer install --no-interaction --no-progress --working-dir=compiler + ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - name: "Install dependencies" run: "composer install --no-interaction --no-progress" @@ -120,9 +119,8 @@ jobs: - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.1 - echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" - simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer install --no-interaction --no-progress --working-dir=compiler + ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - name: "Install dependencies" run: "composer install --no-interaction --no-progress" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8a72947e2c5..0f3e8336db5 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -52,9 +52,8 @@ jobs: if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.1 - echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" - simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer install --no-interaction --no-progress --working-dir=compiler + ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - name: "Install dependencies" run: "composer install --no-interaction --no-progress" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 50e7c470af2..09f2b6b71ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,9 +58,8 @@ jobs: if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | - composer global require --dev ondrejmirtes/simple-downgrader:^2.1 - echo "$(composer global config bin-dir --absolute -q)" >> "$GITHUB_PATH" - simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer install --no-interaction --no-progress --working-dir=compiler + ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - name: "Install dependencies" run: "composer install --no-interaction --no-progress" From 3ab292a4738bda0c5c933ee9ba30f4067af26d6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 18:22:47 +0200 Subject: [PATCH 1445/3097] Fix build --- .github/workflows/lint.yml | 7 +++---- .github/workflows/reflection-golden-test.yml | 13 ++++++++----- .github/workflows/static-analysis.yml | 6 ++++-- .github/workflows/tests.yml | 6 ++++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d51170684a3..cb5ed5c78e3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,20 +39,19 @@ jobs: coverage: "none" php-version: "${{ matrix.php-version }}" + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | composer install --no-interaction --no-progress --working-dir=compiler ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - + composer dump - name: "Validate Composer" run: "composer validate" - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - name: "Lint" run: "make lint" diff --git a/.github/workflows/reflection-golden-test.yml b/.github/workflows/reflection-golden-test.yml index b6df58b5f8a..d0bf46d5cc0 100644 --- a/.github/workflows/reflection-golden-test.yml +++ b/.github/workflows/reflection-golden-test.yml @@ -96,14 +96,16 @@ jobs: ini-file: development ini-values: memory_limit=2G + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | composer install --no-interaction --no-progress --working-dir=compiler ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer dump - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - name: "Dump previous reflection data" run: "php tests/generate-reflection-test.php" @@ -116,14 +118,15 @@ jobs: - name: "Checkout" uses: actions/checkout@v4 + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' run: | composer install --no-interaction --no-progress --working-dir=compiler ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} - - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" + composer dump - name: "Reflection golden test" run: "make tests-golden-reflection || true" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0f3e8336db5..a85ecea67df 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -48,15 +48,17 @@ jobs: ini-file: development extensions: mbstring + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | composer install --no-interaction --no-progress --working-dir=compiler ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer dump - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - name: "PHPStan" run: "make phpstan" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 09f2b6b71ce..e77ec8b28a5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,15 +54,17 @@ jobs: ini-file: development ini-values: memory_limit=2G + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + - name: "Transform source code" if: matrix.php-version != '8.1' && matrix.php-version != '8.2' && matrix.php-version != '8.3' && matrix.php-version != '8.4' shell: bash run: | composer install --no-interaction --no-progress --working-dir=compiler ./compiler/vendor/bin/simple-downgrade downgrade -c build/downgrade.php ${{ matrix.php-version }} + composer dump - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - name: "Tests" run: "make tests" From 0e64495204972b61a3b47980e7c9bc9b2d793acd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 May 2025 18:26:05 +0200 Subject: [PATCH 1446/3097] Install PHPStan in compiler --- .github/workflows/phar.yml | 2 +- compiler/composer.json | 5 +++-- compiler/composer.lock | 37 +++++++++++++++++++------------------ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 0cf91034a36..98493f07374 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -49,7 +49,7 @@ jobs: - name: "Compiler PHPStan" working-directory: "compiler" - run: "../bin/phpstan analyse -l 8 src tests" + run: "vendor/bin/phpstan analyse -l 8 src tests" - name: "Prepare for PHAR compilation" working-directory: "compiler" diff --git a/compiler/composer.json b/compiler/composer.json index e19f55e81ff..e91dbb6c6a1 100644 --- a/compiler/composer.json +++ b/compiler/composer.json @@ -24,8 +24,9 @@ } }, "require-dev": { - "phpunit/phpunit": "^9.5.1", - "phpstan/phpstan-phpunit": "^1.0" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.1" }, "config": { "platform": { diff --git a/compiler/composer.lock b/compiler/composer.lock index e245a05f978..af66180aeb3 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6f5c87d0b49dd091a2fb504cb8ce59e1", + "content-hash": "1186b96250453fd96d9977f1136e6a2c", "packages": [ { "name": "jetbrains/phpstorm-stubs", @@ -1787,20 +1787,20 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.27", + "version": "2.1.17", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162" + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a6e423c076ab39dfedc307e2ac627ef579db162", - "reference": "3a6e423c076ab39dfedc307e2ac627ef579db162", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/89b5ef665716fa2a52ecd2633f21007a6a349053", + "reference": "89b5ef665716fa2a52ecd2633f21007a6a349053", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.4|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -1841,34 +1841,35 @@ "type": "github" } ], - "time": "2025-05-21T20:51:45+00:00" + "time": "2025-05-21T20:55:28+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "1.4.2", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e" + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/72a6721c9b64b3e4c9db55abbc38f790b318267e", - "reference": "72a6721c9b64b3e4c9db55abbc38f790b318267e", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6b92469f8a7995e626da3aa487099617b8dfa260", + "reference": "6b92469f8a7995e626da3aa487099617b8dfa260", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.12" + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^5", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.5" + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" }, "type": "phpstan-extension", "extra": { @@ -1891,9 +1892,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.2" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.6" }, - "time": "2024-12-17T17:20:49+00:00" + "time": "2025-03-26T12:47:06+00:00" }, { "name": "phpunit/php-code-coverage", From f001793b8bfba2ec241db57f024d501ce0d36a53 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 27 May 2025 00:04:10 +0000 Subject: [PATCH 1447/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 83086ff40fa..47d3d5c251b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#56e49161f6f411647350b769efe7c640bd9010d1", + "jetbrains/phpstorm-stubs": "dev-master#0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 03c1501ad46..97e7a2b8235 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b235ba93272364ed2ad1aff93785d2ca", + "content-hash": "1172f6270b6d9e610f731403af5e01e6", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "56e49161f6f411647350b769efe7c640bd9010d1" + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/56e49161f6f411647350b769efe7c640bd9010d1", - "reference": "56e49161f6f411647350b769efe7c640bd9010d1", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-05-14T19:32:50+00:00" + "time": "2025-05-21T19:01:16+00:00" }, { "name": "nette/bootstrap", From 676ad27fc14abf18ebd026c0247b61ad9d568d92 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 14:09:12 +0200 Subject: [PATCH 1448/3097] Internal PHPStan rule - attributes must have named arguments --- .../Build/AttributeNamedArgumentsRule.php | 41 +++++++++++++++++++ build/phpstan.neon | 1 + 2 files changed, 42 insertions(+) create mode 100644 build/PHPStan/Build/AttributeNamedArgumentsRule.php diff --git a/build/PHPStan/Build/AttributeNamedArgumentsRule.php b/build/PHPStan/Build/AttributeNamedArgumentsRule.php new file mode 100644 index 00000000000..5a582a24a6f --- /dev/null +++ b/build/PHPStan/Build/AttributeNamedArgumentsRule.php @@ -0,0 +1,41 @@ + + */ +final class AttributeNamedArgumentsRule implements Rule +{ + + public function getNodeType(): string + { + return Attribute::class; + } + + public function processNode(Node $node, Scope $scope): array + { + foreach ($node->args as $arg) { + if ($arg->name !== null) { + continue; + } + + return [ + RuleErrorBuilder::message(sprintf('Attribute %s is not using named arguments.', $node->name->toString())) + ->identifier('phpstan.attributeWithoutNamedArguments') + ->nonIgnorable() + ->build(), + ]; + } + + return []; + } + +} diff --git a/build/phpstan.neon b/build/phpstan.neon index 3d099857e2f..1e7f00b736a 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -117,6 +117,7 @@ parameters: rules: - PHPStan\Build\FinalClassRule + - PHPStan\Build\AttributeNamedArgumentsRule services: - From 7107c62cb8a7cb51d53c452e513b138081ecd218 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 13:48:09 +0000 Subject: [PATCH 1449/3097] Update dependency shipmonk/dead-code-detector to v0.12.2 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 47d3d5c251b..83086ff40fa 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "jetbrains/phpstorm-stubs": "dev-master#56e49161f6f411647350b769efe7c640bd9010d1", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 97e7a2b8235..017eaf08bed 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1172f6270b6d9e610f731403af5e01e6", + "content-hash": "b235ba93272364ed2ad1aff93785d2ca", "packages": [ { "name": "clue/ndjson-react", @@ -6355,16 +6355,16 @@ }, { "name": "shipmonk/dead-code-detector", - "version": "0.12.0", + "version": "0.12.2", "source": { "type": "git", "url": "https://github.com/shipmonk-rnd/dead-code-detector.git", - "reference": "1f0c70ec4e9868c785f6505592dfb01ef53af2ca" + "reference": "71b842269e9a29634e34074e723023e4e151518b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/1f0c70ec4e9868c785f6505592dfb01ef53af2ca", - "reference": "1f0c70ec4e9868c785f6505592dfb01ef53af2ca", + "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/71b842269e9a29634e34074e723023e4e151518b", + "reference": "71b842269e9a29634e34074e723023e4e151518b", "shasum": "" }, "require": { @@ -6424,9 +6424,9 @@ ], "support": { "issues": "https://github.com/shipmonk-rnd/dead-code-detector/issues", - "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/0.12.0" + "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/0.12.2" }, - "time": "2025-05-16T13:02:10+00:00" + "time": "2025-05-22T07:50:57+00:00" }, { "name": "shipmonk/name-collision-detector", From df9fa6e04b636bbcd16d49ec213f13fbb45026a6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 17:10:46 +0200 Subject: [PATCH 1450/3097] Only change exit code with `--fail-without-result-cache` when the error formatter returns 0 --- src/Command/AnalyseCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 2d3c5315595..312b95c9819 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -485,7 +485,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $exitCode = $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()); - if ($failWithoutResultCache && !$analysisResult->isResultCacheUsed()) { + if ($exitCode === 0 && $failWithoutResultCache && !$analysisResult->isResultCacheUsed()) { $exitCode = 2; } From 27dd6c016a7993c6a6f9b552900da5daa05d3db3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 20:38:20 +0200 Subject: [PATCH 1451/3097] generate-rule-error-classes.php - fix generating method without native return type --- bin/generate-rule-error-classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-rule-error-classes.php b/bin/generate-rule-error-classes.php index 633b1824b42..6b8284669e5 100755 --- a/bin/generate-rule-error-classes.php +++ b/bin/generate-rule-error-classes.php @@ -47,7 +47,7 @@ final class RuleError%s implements %s $typeCombination, implode(', ', $interfaces), implode("\n\n\t", array_map(static fn (array $property): string => sprintf('%spublic %s $%s;', $property[2] !== $property[1] ? sprintf("/** @var %s */\n\t", $property[2]) : '', $property[1], $property[0]), $properties)), - implode("\n\n\t", array_map(static fn (array $property): string => sprintf("%spublic function get%s(): %s\n\t{\n\t\treturn \$this->%s;\n\t}", $property[2] !== $property[1] ? sprintf("/**\n\t * @return %s\n\t */\n\t", $property[2]) : '', ucfirst($property[0]), $property[1], $property[0]), $properties)), + implode("\n\n\t", array_map(static fn (array $property): string => sprintf("%spublic function get%s()%s\n\t{\n\t\treturn \$this->%s;\n\t}", $property[2] !== $property[1] ? sprintf("/**\n\t * @return %s\n\t */\n\t", $property[2]) : '', ucfirst($property[0]), $property[1] === null ? '' : sprintf(': %s', $property[1]), $property[0]), $properties)), ); file_put_contents(__DIR__ . '/../src/Rules/RuleErrors/RuleError' . $typeCombination . '.php', $phpClass); From f8369ec6579c8975058e04341b9878cb557e906f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 20:37:51 +0200 Subject: [PATCH 1452/3097] FixableNodeRuleError interface with support in RuleErrorBuilder --- src/Rules/FixableNodeRuleError.php | 13 ++++ src/Rules/RuleErrorBuilder.php | 32 +++++++++- src/Rules/RuleErrors/RuleError129.php | 33 +++++++++++ src/Rules/RuleErrors/RuleError131.php | 41 +++++++++++++ src/Rules/RuleErrors/RuleError133.php | 48 +++++++++++++++ src/Rules/RuleErrors/RuleError135.php | 56 ++++++++++++++++++ src/Rules/RuleErrors/RuleError137.php | 41 +++++++++++++ src/Rules/RuleErrors/RuleError139.php | 49 +++++++++++++++ src/Rules/RuleErrors/RuleError141.php | 56 ++++++++++++++++++ src/Rules/RuleErrors/RuleError143.php | 64 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError145.php | 41 +++++++++++++ src/Rules/RuleErrors/RuleError147.php | 49 +++++++++++++++ src/Rules/RuleErrors/RuleError149.php | 56 ++++++++++++++++++ src/Rules/RuleErrors/RuleError151.php | 64 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError153.php | 49 +++++++++++++++ src/Rules/RuleErrors/RuleError155.php | 57 ++++++++++++++++++ src/Rules/RuleErrors/RuleError157.php | 64 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError159.php | 72 +++++++++++++++++++++++ src/Rules/RuleErrors/RuleError161.php | 45 ++++++++++++++ src/Rules/RuleErrors/RuleError163.php | 53 +++++++++++++++++ src/Rules/RuleErrors/RuleError165.php | 60 +++++++++++++++++++ src/Rules/RuleErrors/RuleError167.php | 68 +++++++++++++++++++++ src/Rules/RuleErrors/RuleError169.php | 53 +++++++++++++++++ src/Rules/RuleErrors/RuleError171.php | 61 +++++++++++++++++++ src/Rules/RuleErrors/RuleError173.php | 68 +++++++++++++++++++++ src/Rules/RuleErrors/RuleError175.php | 76 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError177.php | 53 +++++++++++++++++ src/Rules/RuleErrors/RuleError179.php | 61 +++++++++++++++++++ src/Rules/RuleErrors/RuleError181.php | 68 +++++++++++++++++++++ src/Rules/RuleErrors/RuleError183.php | 76 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError185.php | 61 +++++++++++++++++++ src/Rules/RuleErrors/RuleError187.php | 69 ++++++++++++++++++++++ src/Rules/RuleErrors/RuleError189.php | 76 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError191.php | 84 ++++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError193.php | 34 +++++++++++ src/Rules/RuleErrors/RuleError195.php | 42 +++++++++++++ src/Rules/RuleErrors/RuleError197.php | 49 +++++++++++++++ src/Rules/RuleErrors/RuleError199.php | 57 ++++++++++++++++++ src/Rules/RuleErrors/RuleError201.php | 42 +++++++++++++ src/Rules/RuleErrors/RuleError203.php | 50 ++++++++++++++++ src/Rules/RuleErrors/RuleError205.php | 57 ++++++++++++++++++ src/Rules/RuleErrors/RuleError207.php | 65 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError209.php | 42 +++++++++++++ src/Rules/RuleErrors/RuleError211.php | 50 ++++++++++++++++ src/Rules/RuleErrors/RuleError213.php | 57 ++++++++++++++++++ src/Rules/RuleErrors/RuleError215.php | 65 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError217.php | 50 ++++++++++++++++ src/Rules/RuleErrors/RuleError219.php | 58 ++++++++++++++++++ src/Rules/RuleErrors/RuleError221.php | 65 ++++++++++++++++++++ src/Rules/RuleErrors/RuleError223.php | 73 +++++++++++++++++++++++ src/Rules/RuleErrors/RuleError225.php | 46 +++++++++++++++ src/Rules/RuleErrors/RuleError227.php | 54 +++++++++++++++++ src/Rules/RuleErrors/RuleError229.php | 61 +++++++++++++++++++ src/Rules/RuleErrors/RuleError231.php | 69 ++++++++++++++++++++++ src/Rules/RuleErrors/RuleError233.php | 54 +++++++++++++++++ src/Rules/RuleErrors/RuleError235.php | 62 +++++++++++++++++++ src/Rules/RuleErrors/RuleError237.php | 69 ++++++++++++++++++++++ src/Rules/RuleErrors/RuleError239.php | 77 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError241.php | 54 +++++++++++++++++ src/Rules/RuleErrors/RuleError243.php | 62 +++++++++++++++++++ src/Rules/RuleErrors/RuleError245.php | 69 ++++++++++++++++++++++ src/Rules/RuleErrors/RuleError247.php | 77 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError249.php | 62 +++++++++++++++++++ src/Rules/RuleErrors/RuleError251.php | 70 ++++++++++++++++++++++ src/Rules/RuleErrors/RuleError253.php | 77 ++++++++++++++++++++++++ src/Rules/RuleErrors/RuleError255.php | 85 +++++++++++++++++++++++++++ 66 files changed, 3818 insertions(+), 3 deletions(-) create mode 100644 src/Rules/FixableNodeRuleError.php create mode 100644 src/Rules/RuleErrors/RuleError129.php create mode 100644 src/Rules/RuleErrors/RuleError131.php create mode 100644 src/Rules/RuleErrors/RuleError133.php create mode 100644 src/Rules/RuleErrors/RuleError135.php create mode 100644 src/Rules/RuleErrors/RuleError137.php create mode 100644 src/Rules/RuleErrors/RuleError139.php create mode 100644 src/Rules/RuleErrors/RuleError141.php create mode 100644 src/Rules/RuleErrors/RuleError143.php create mode 100644 src/Rules/RuleErrors/RuleError145.php create mode 100644 src/Rules/RuleErrors/RuleError147.php create mode 100644 src/Rules/RuleErrors/RuleError149.php create mode 100644 src/Rules/RuleErrors/RuleError151.php create mode 100644 src/Rules/RuleErrors/RuleError153.php create mode 100644 src/Rules/RuleErrors/RuleError155.php create mode 100644 src/Rules/RuleErrors/RuleError157.php create mode 100644 src/Rules/RuleErrors/RuleError159.php create mode 100644 src/Rules/RuleErrors/RuleError161.php create mode 100644 src/Rules/RuleErrors/RuleError163.php create mode 100644 src/Rules/RuleErrors/RuleError165.php create mode 100644 src/Rules/RuleErrors/RuleError167.php create mode 100644 src/Rules/RuleErrors/RuleError169.php create mode 100644 src/Rules/RuleErrors/RuleError171.php create mode 100644 src/Rules/RuleErrors/RuleError173.php create mode 100644 src/Rules/RuleErrors/RuleError175.php create mode 100644 src/Rules/RuleErrors/RuleError177.php create mode 100644 src/Rules/RuleErrors/RuleError179.php create mode 100644 src/Rules/RuleErrors/RuleError181.php create mode 100644 src/Rules/RuleErrors/RuleError183.php create mode 100644 src/Rules/RuleErrors/RuleError185.php create mode 100644 src/Rules/RuleErrors/RuleError187.php create mode 100644 src/Rules/RuleErrors/RuleError189.php create mode 100644 src/Rules/RuleErrors/RuleError191.php create mode 100644 src/Rules/RuleErrors/RuleError193.php create mode 100644 src/Rules/RuleErrors/RuleError195.php create mode 100644 src/Rules/RuleErrors/RuleError197.php create mode 100644 src/Rules/RuleErrors/RuleError199.php create mode 100644 src/Rules/RuleErrors/RuleError201.php create mode 100644 src/Rules/RuleErrors/RuleError203.php create mode 100644 src/Rules/RuleErrors/RuleError205.php create mode 100644 src/Rules/RuleErrors/RuleError207.php create mode 100644 src/Rules/RuleErrors/RuleError209.php create mode 100644 src/Rules/RuleErrors/RuleError211.php create mode 100644 src/Rules/RuleErrors/RuleError213.php create mode 100644 src/Rules/RuleErrors/RuleError215.php create mode 100644 src/Rules/RuleErrors/RuleError217.php create mode 100644 src/Rules/RuleErrors/RuleError219.php create mode 100644 src/Rules/RuleErrors/RuleError221.php create mode 100644 src/Rules/RuleErrors/RuleError223.php create mode 100644 src/Rules/RuleErrors/RuleError225.php create mode 100644 src/Rules/RuleErrors/RuleError227.php create mode 100644 src/Rules/RuleErrors/RuleError229.php create mode 100644 src/Rules/RuleErrors/RuleError231.php create mode 100644 src/Rules/RuleErrors/RuleError233.php create mode 100644 src/Rules/RuleErrors/RuleError235.php create mode 100644 src/Rules/RuleErrors/RuleError237.php create mode 100644 src/Rules/RuleErrors/RuleError239.php create mode 100644 src/Rules/RuleErrors/RuleError241.php create mode 100644 src/Rules/RuleErrors/RuleError243.php create mode 100644 src/Rules/RuleErrors/RuleError245.php create mode 100644 src/Rules/RuleErrors/RuleError247.php create mode 100644 src/Rules/RuleErrors/RuleError249.php create mode 100644 src/Rules/RuleErrors/RuleError251.php create mode 100644 src/Rules/RuleErrors/RuleError253.php create mode 100644 src/Rules/RuleErrors/RuleError255.php diff --git a/src/Rules/FixableNodeRuleError.php b/src/Rules/FixableNodeRuleError.php new file mode 100644 index 00000000000..9eb34697565 --- /dev/null +++ b/src/Rules/FixableNodeRuleError.php @@ -0,0 +1,13 @@ + [ + FixableNodeRuleError::class, + [ + [ + 'newNodeCallable', + null, + 'callable(\PhpParser\Node): \PhpParser\Node', + ], + ], + ], ]; } @@ -254,6 +266,20 @@ public function nonIgnorable(): self return $this; } + /** + * @internal Experimental + * @param callable(Node): Node $cb + * @phpstan-this-out self + * @return self + */ + public function fixNode(callable $cb): self + { + $this->properties['newNodeCallable'] = $cb; + $this->type |= self::TYPE_FIXABLE_NODE; + + return $this; + } + /** * @return T */ diff --git a/src/Rules/RuleErrors/RuleError129.php b/src/Rules/RuleErrors/RuleError129.php new file mode 100644 index 00000000000..f47d82502dd --- /dev/null +++ b/src/Rules/RuleErrors/RuleError129.php @@ -0,0 +1,33 @@ +message; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError131.php b/src/Rules/RuleErrors/RuleError131.php new file mode 100644 index 00000000000..ded63b6bb28 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError131.php @@ -0,0 +1,41 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError133.php b/src/Rules/RuleErrors/RuleError133.php new file mode 100644 index 00000000000..cdc253ee26b --- /dev/null +++ b/src/Rules/RuleErrors/RuleError133.php @@ -0,0 +1,48 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError135.php b/src/Rules/RuleErrors/RuleError135.php new file mode 100644 index 00000000000..02a1ba11997 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError135.php @@ -0,0 +1,56 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError137.php b/src/Rules/RuleErrors/RuleError137.php new file mode 100644 index 00000000000..93a02b903ad --- /dev/null +++ b/src/Rules/RuleErrors/RuleError137.php @@ -0,0 +1,41 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError139.php b/src/Rules/RuleErrors/RuleError139.php new file mode 100644 index 00000000000..94b105f9083 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError139.php @@ -0,0 +1,49 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError141.php b/src/Rules/RuleErrors/RuleError141.php new file mode 100644 index 00000000000..9ad81cdba41 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError141.php @@ -0,0 +1,56 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError143.php b/src/Rules/RuleErrors/RuleError143.php new file mode 100644 index 00000000000..2d70326499f --- /dev/null +++ b/src/Rules/RuleErrors/RuleError143.php @@ -0,0 +1,64 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError145.php b/src/Rules/RuleErrors/RuleError145.php new file mode 100644 index 00000000000..80de64966b6 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError145.php @@ -0,0 +1,41 @@ +message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError147.php b/src/Rules/RuleErrors/RuleError147.php new file mode 100644 index 00000000000..b9fdbb69efa --- /dev/null +++ b/src/Rules/RuleErrors/RuleError147.php @@ -0,0 +1,49 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError149.php b/src/Rules/RuleErrors/RuleError149.php new file mode 100644 index 00000000000..c5746ae657c --- /dev/null +++ b/src/Rules/RuleErrors/RuleError149.php @@ -0,0 +1,56 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError151.php b/src/Rules/RuleErrors/RuleError151.php new file mode 100644 index 00000000000..e0185758188 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError151.php @@ -0,0 +1,64 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError153.php b/src/Rules/RuleErrors/RuleError153.php new file mode 100644 index 00000000000..9c83ccaa710 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError153.php @@ -0,0 +1,49 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError155.php b/src/Rules/RuleErrors/RuleError155.php new file mode 100644 index 00000000000..8675da798db --- /dev/null +++ b/src/Rules/RuleErrors/RuleError155.php @@ -0,0 +1,57 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError157.php b/src/Rules/RuleErrors/RuleError157.php new file mode 100644 index 00000000000..a1d75fd5940 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError157.php @@ -0,0 +1,64 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError159.php b/src/Rules/RuleErrors/RuleError159.php new file mode 100644 index 00000000000..0161fbe77ea --- /dev/null +++ b/src/Rules/RuleErrors/RuleError159.php @@ -0,0 +1,72 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError161.php b/src/Rules/RuleErrors/RuleError161.php new file mode 100644 index 00000000000..ffbe00fb117 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError161.php @@ -0,0 +1,45 @@ +message; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError163.php b/src/Rules/RuleErrors/RuleError163.php new file mode 100644 index 00000000000..90055920f2d --- /dev/null +++ b/src/Rules/RuleErrors/RuleError163.php @@ -0,0 +1,53 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError165.php b/src/Rules/RuleErrors/RuleError165.php new file mode 100644 index 00000000000..78341db1dde --- /dev/null +++ b/src/Rules/RuleErrors/RuleError165.php @@ -0,0 +1,60 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError167.php b/src/Rules/RuleErrors/RuleError167.php new file mode 100644 index 00000000000..eb0bb13f096 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError167.php @@ -0,0 +1,68 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError169.php b/src/Rules/RuleErrors/RuleError169.php new file mode 100644 index 00000000000..c11b12daf87 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError169.php @@ -0,0 +1,53 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError171.php b/src/Rules/RuleErrors/RuleError171.php new file mode 100644 index 00000000000..046ea44bc54 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError171.php @@ -0,0 +1,61 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError173.php b/src/Rules/RuleErrors/RuleError173.php new file mode 100644 index 00000000000..562b87b7299 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError173.php @@ -0,0 +1,68 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError175.php b/src/Rules/RuleErrors/RuleError175.php new file mode 100644 index 00000000000..6abb2d23b67 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError175.php @@ -0,0 +1,76 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError177.php b/src/Rules/RuleErrors/RuleError177.php new file mode 100644 index 00000000000..59411c26df9 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError177.php @@ -0,0 +1,53 @@ +message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError179.php b/src/Rules/RuleErrors/RuleError179.php new file mode 100644 index 00000000000..f9a272fbb0d --- /dev/null +++ b/src/Rules/RuleErrors/RuleError179.php @@ -0,0 +1,61 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError181.php b/src/Rules/RuleErrors/RuleError181.php new file mode 100644 index 00000000000..c46cff34905 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError181.php @@ -0,0 +1,68 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError183.php b/src/Rules/RuleErrors/RuleError183.php new file mode 100644 index 00000000000..746ffca4e2d --- /dev/null +++ b/src/Rules/RuleErrors/RuleError183.php @@ -0,0 +1,76 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError185.php b/src/Rules/RuleErrors/RuleError185.php new file mode 100644 index 00000000000..963d9c68a55 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError185.php @@ -0,0 +1,61 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError187.php b/src/Rules/RuleErrors/RuleError187.php new file mode 100644 index 00000000000..48a048d5def --- /dev/null +++ b/src/Rules/RuleErrors/RuleError187.php @@ -0,0 +1,69 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError189.php b/src/Rules/RuleErrors/RuleError189.php new file mode 100644 index 00000000000..a7585053341 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError189.php @@ -0,0 +1,76 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError191.php b/src/Rules/RuleErrors/RuleError191.php new file mode 100644 index 00000000000..3a68cef638d --- /dev/null +++ b/src/Rules/RuleErrors/RuleError191.php @@ -0,0 +1,84 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError193.php b/src/Rules/RuleErrors/RuleError193.php new file mode 100644 index 00000000000..606f3f7e671 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError193.php @@ -0,0 +1,34 @@ +message; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError195.php b/src/Rules/RuleErrors/RuleError195.php new file mode 100644 index 00000000000..25725e50266 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError195.php @@ -0,0 +1,42 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError197.php b/src/Rules/RuleErrors/RuleError197.php new file mode 100644 index 00000000000..7d0631e5410 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError197.php @@ -0,0 +1,49 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError199.php b/src/Rules/RuleErrors/RuleError199.php new file mode 100644 index 00000000000..711079460fd --- /dev/null +++ b/src/Rules/RuleErrors/RuleError199.php @@ -0,0 +1,57 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError201.php b/src/Rules/RuleErrors/RuleError201.php new file mode 100644 index 00000000000..c2004b13905 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError201.php @@ -0,0 +1,42 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError203.php b/src/Rules/RuleErrors/RuleError203.php new file mode 100644 index 00000000000..cd2a8a5254c --- /dev/null +++ b/src/Rules/RuleErrors/RuleError203.php @@ -0,0 +1,50 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError205.php b/src/Rules/RuleErrors/RuleError205.php new file mode 100644 index 00000000000..6d16f8f6e25 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError205.php @@ -0,0 +1,57 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError207.php b/src/Rules/RuleErrors/RuleError207.php new file mode 100644 index 00000000000..063bf99ee18 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError207.php @@ -0,0 +1,65 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError209.php b/src/Rules/RuleErrors/RuleError209.php new file mode 100644 index 00000000000..a79f416b321 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError209.php @@ -0,0 +1,42 @@ +message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError211.php b/src/Rules/RuleErrors/RuleError211.php new file mode 100644 index 00000000000..685e06977fc --- /dev/null +++ b/src/Rules/RuleErrors/RuleError211.php @@ -0,0 +1,50 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError213.php b/src/Rules/RuleErrors/RuleError213.php new file mode 100644 index 00000000000..2995cf991bd --- /dev/null +++ b/src/Rules/RuleErrors/RuleError213.php @@ -0,0 +1,57 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError215.php b/src/Rules/RuleErrors/RuleError215.php new file mode 100644 index 00000000000..f741ded7017 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError215.php @@ -0,0 +1,65 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError217.php b/src/Rules/RuleErrors/RuleError217.php new file mode 100644 index 00000000000..b6af9245d0e --- /dev/null +++ b/src/Rules/RuleErrors/RuleError217.php @@ -0,0 +1,50 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError219.php b/src/Rules/RuleErrors/RuleError219.php new file mode 100644 index 00000000000..db5b302e1ad --- /dev/null +++ b/src/Rules/RuleErrors/RuleError219.php @@ -0,0 +1,58 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError221.php b/src/Rules/RuleErrors/RuleError221.php new file mode 100644 index 00000000000..603128daead --- /dev/null +++ b/src/Rules/RuleErrors/RuleError221.php @@ -0,0 +1,65 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError223.php b/src/Rules/RuleErrors/RuleError223.php new file mode 100644 index 00000000000..601584bf810 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError223.php @@ -0,0 +1,73 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError225.php b/src/Rules/RuleErrors/RuleError225.php new file mode 100644 index 00000000000..d04128179d1 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError225.php @@ -0,0 +1,46 @@ +message; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError227.php b/src/Rules/RuleErrors/RuleError227.php new file mode 100644 index 00000000000..580e7b3f457 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError227.php @@ -0,0 +1,54 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError229.php b/src/Rules/RuleErrors/RuleError229.php new file mode 100644 index 00000000000..7b6812c7114 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError229.php @@ -0,0 +1,61 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError231.php b/src/Rules/RuleErrors/RuleError231.php new file mode 100644 index 00000000000..beda4eb54e7 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError231.php @@ -0,0 +1,69 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError233.php b/src/Rules/RuleErrors/RuleError233.php new file mode 100644 index 00000000000..4f3d9079d73 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError233.php @@ -0,0 +1,54 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError235.php b/src/Rules/RuleErrors/RuleError235.php new file mode 100644 index 00000000000..574134cace2 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError235.php @@ -0,0 +1,62 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError237.php b/src/Rules/RuleErrors/RuleError237.php new file mode 100644 index 00000000000..f6da8b7bacd --- /dev/null +++ b/src/Rules/RuleErrors/RuleError237.php @@ -0,0 +1,69 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError239.php b/src/Rules/RuleErrors/RuleError239.php new file mode 100644 index 00000000000..e7c1d41c77e --- /dev/null +++ b/src/Rules/RuleErrors/RuleError239.php @@ -0,0 +1,77 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError241.php b/src/Rules/RuleErrors/RuleError241.php new file mode 100644 index 00000000000..8f9291d2e9d --- /dev/null +++ b/src/Rules/RuleErrors/RuleError241.php @@ -0,0 +1,54 @@ +message; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError243.php b/src/Rules/RuleErrors/RuleError243.php new file mode 100644 index 00000000000..2585591107b --- /dev/null +++ b/src/Rules/RuleErrors/RuleError243.php @@ -0,0 +1,62 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError245.php b/src/Rules/RuleErrors/RuleError245.php new file mode 100644 index 00000000000..18226093033 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError245.php @@ -0,0 +1,69 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError247.php b/src/Rules/RuleErrors/RuleError247.php new file mode 100644 index 00000000000..6ea594080f4 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError247.php @@ -0,0 +1,77 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError249.php b/src/Rules/RuleErrors/RuleError249.php new file mode 100644 index 00000000000..7c8f370035f --- /dev/null +++ b/src/Rules/RuleErrors/RuleError249.php @@ -0,0 +1,62 @@ +message; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError251.php b/src/Rules/RuleErrors/RuleError251.php new file mode 100644 index 00000000000..9d66058153c --- /dev/null +++ b/src/Rules/RuleErrors/RuleError251.php @@ -0,0 +1,70 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError253.php b/src/Rules/RuleErrors/RuleError253.php new file mode 100644 index 00000000000..ba542597509 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError253.php @@ -0,0 +1,77 @@ +message; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} diff --git a/src/Rules/RuleErrors/RuleError255.php b/src/Rules/RuleErrors/RuleError255.php new file mode 100644 index 00000000000..148045815f6 --- /dev/null +++ b/src/Rules/RuleErrors/RuleError255.php @@ -0,0 +1,85 @@ +message; + } + + public function getLine(): int + { + return $this->line; + } + + public function getFile(): string + { + return $this->file; + } + + public function getFileDescription(): string + { + return $this->fileDescription; + } + + public function getTip(): string + { + return $this->tip; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return mixed[] + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @return callable(Node): Node + */ + public function getNewNodeCallable(): callable + { + return $this->newNodeCallable; + } + +} From a079fecbc74bf80a6f6c64d944df4c6b2004a8ff Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 20:39:33 +0200 Subject: [PATCH 1453/3097] Scaffolding for auto-fixable errors (experimental) --- composer.json | 2 + composer.lock | 189 +++++++++++------- conf/config.neon | 5 + phpstan-baseline.neon | 48 +++++ src/Analyser/AnalyserResultFinalizer.php | 2 +- src/Analyser/Error.php | 32 +++ src/Analyser/FileAnalyser.php | 4 +- src/Analyser/FixedErrorDiff.php | 28 +++ src/Analyser/RuleErrorTransformer.php | 78 +++++++- src/Command/AnalyseCommand.php | 154 +++++++++++++- .../PhpPrinterIndentationDetectorVisitor.php | 81 ++++++++ src/Fixable/ReplacingNodeVisitor.php | 36 ++++ src/Testing/RuleTestCase.php | 4 +- tests/PHPStan/Analyser/AnalyserTest.php | 4 +- 14 files changed, 580 insertions(+), 87 deletions(-) create mode 100644 src/Analyser/FixedErrorDiff.php create mode 100644 src/Fixable/PhpPrinterIndentationDetectorVisitor.php create mode 100644 src/Fixable/ReplacingNodeVisitor.php diff --git a/composer.json b/composer.json index 83086ff40fa..8397cdaf5db 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "require": { "php": "^8.1", "composer-runtime-api": "^2.0", + "bircher/php-merge": "^4.0", "clue/ndjson-react": "^1.0", "composer/ca-bundle": "^1.2", "composer/semver": "^3.4", @@ -37,6 +38,7 @@ "react/promise": "^3.2", "react/socket": "^1.3", "react/stream": "^1.1", + "sebastian/diff": "^4.0", "symfony/console": "^5.4.3", "symfony/finder": "^5.4.3", "symfony/polyfill-intl-grapheme": "^1.23", diff --git a/composer.lock b/composer.lock index 017eaf08bed..f5f46a4fcbd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b235ba93272364ed2ad1aff93785d2ca", + "content-hash": "c5f97c88abdd81aefa9b1b3e48fd0999", "packages": [ + { + "name": "bircher/php-merge", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/bircher/php-merge.git", + "reference": "db19f6e02c606cba3f4e59b2189c0df89cd2570d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bircher/php-merge/zipball/db19f6e02c606cba3f4e59b2189c0df89cd2570d", + "reference": "db19f6e02c606cba3f4e59b2189c0df89cd2570d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^2.0|^3.0|^4.0" + }, + "require-dev": { + "escapestudios/symfony2-coding-standard": "^3.5", + "phpstan/phpstan": "~1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpunit/phpunit": "~6|~7|~8|~9", + "squizlabs/php_codesniffer": "~3", + "symplify/git-wrapper": "^9.1|^10.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PhpMerge": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabian Bircher", + "email": "opensource@fabianbircher.com" + } + ], + "description": "A PHP merge utility using the Diff php library or the command line git.", + "homepage": "https://github.com/bircher/php-merge", + "keywords": [ + "git", + "merge", + "php-merge" + ], + "support": { + "issues": "https://github.com/bircher/php-merge/issues", + "source": "https://github.com/bircher/php-merge/tree/4.0.0" + }, + "time": "2021-12-27T15:04:20+00:00" + }, { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -3240,6 +3295,72 @@ ], "time": "2024-06-11T12:45:25+00:00" }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, { "name": "symfony/console", "version": "v5.4.47", @@ -5622,72 +5743,6 @@ ], "time": "2023-12-22T06:19:30+00:00" }, - { - "name": "sebastian/diff", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:30:58+00:00" - }, { "name": "sebastian/environment", "version": "5.1.5", diff --git a/conf/config.neon b/conf/config.neon index 293ca28e0bd..357a2e1d059 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -286,6 +286,11 @@ services: arguments: parser: @defaultAnalysisParser + - + class: PHPStan\Analyser\RuleErrorTransformer + arguments: + parser: @currentPhpVersionPhpParser + - class: PHPStan\Analyser\Ignore\IgnoredErrorHelper arguments: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a07ea295562..d5a0f76ed1a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -60,6 +60,18 @@ parameters: count: 1 path: src/Analyser/RicherScopeGetTypeHelper.php + - + message: '#^Call to method __construct\(\) of internal class PhpParser\\Internal\\TokenStream from outside its root namespace PhpParser\.$#' + identifier: method.internalClass + count: 1 + path: src/Analyser/RuleErrorTransformer.php + + - + message: '#^Instantiation of internal class PhpParser\\Internal\\TokenStream\.$#' + identifier: new.internalClass + count: 1 + path: src/Analyser/RuleErrorTransformer.php + - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -102,6 +114,24 @@ parameters: count: 1 path: src/Collectors/Registry.php + - + message: '#^Call to method getContent\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' + identifier: method.internalClass + count: 2 + path: src/Command/AnalyseCommand.php + + - + message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Hunk from outside its root namespace PhpMerge\.$#' + identifier: staticMethod.internalClass + count: 2 + path: src/Command/AnalyseCommand.php + + - + message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' + identifier: staticMethod.internalClass + count: 5 + path: src/Command/AnalyseCommand.php + - message: '#^Anonymous function has an unused use \$container\.$#' identifier: closure.unusedUse @@ -204,6 +234,24 @@ parameters: count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php + - + message: '#^Call to method getTokenCode\(\) of internal class PhpParser\\Internal\\TokenStream from outside its root namespace PhpParser\.$#' + identifier: method.internalClass + count: 1 + path: src/Fixable/PhpPrinterIndentationDetectorVisitor.php + + - + message: '#^Parameter \$origTokens of method PHPStan\\Fixable\\PhpPrinterIndentationDetectorVisitor\:\:__construct\(\) has typehint with internal class PhpParser\\Internal\\TokenStream\.$#' + identifier: parameter.internalClass + count: 1 + path: src/Fixable/PhpPrinterIndentationDetectorVisitor.php + + - + message: '#^Property \$origTokens references internal class PhpParser\\Internal\\TokenStream in its type\.$#' + identifier: property.internalClass + count: 1 + path: src/Fixable/PhpPrinterIndentationDetectorVisitor.php + - message: '#^Access to property \$id of internal class Symfony\\Polyfill\\Php80\\PhpToken from outside its root namespace Symfony\.$#' identifier: property.internalClass diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index 61c151aa52e..c0f0c411e3d 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -89,7 +89,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ } foreach ($ruleErrors as $ruleError) { - $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, [], $node); if ($error->canBeIgnored()) { foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index 1ad85c60bec..5147fb0d970 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -38,6 +38,7 @@ public function __construct( private ?string $nodeType = null, private ?string $identifier = null, private array $metadata = [], + private ?FixedErrorDiff $fixedErrorDiff = null, ) { if ($this->identifier !== null && !self::validateIdentifier($this->identifier)) { @@ -82,6 +83,7 @@ public function changeFilePath(string $newFilePath): self $this->nodeType, $this->identifier, $this->metadata, + $this->fixedErrorDiff, ); } @@ -99,6 +101,7 @@ public function changeTraitFilePath(string $newFilePath): self $this->nodeType, $this->identifier, $this->metadata, + $this->fixedErrorDiff, ); } @@ -143,6 +146,9 @@ public function withoutTip(): self null, $this->nodeLine, $this->nodeType, + $this->identifier, + $this->metadata, + $this->fixedErrorDiff, ); } @@ -162,6 +168,9 @@ public function doNotIgnore(): self $this->tip, $this->nodeLine, $this->nodeType, + $this->identifier, + $this->metadata, + $this->fixedErrorDiff, ); } @@ -183,6 +192,7 @@ public function withIdentifier(string $identifier): self $this->nodeType, $identifier, $this->metadata, + $this->fixedErrorDiff, ); } @@ -207,6 +217,7 @@ public function withMetadata(array $metadata): self $this->nodeType, $this->identifier, $metadata, + $this->fixedErrorDiff, ); } @@ -241,12 +252,24 @@ public function getMetadata(): array return $this->metadata; } + public function getFixedErrorDiff(): ?FixedErrorDiff + { + return $this->fixedErrorDiff; + } + /** * @return mixed */ #[ReturnTypeWillChange] public function jsonSerialize() { + $fixedErrorDiffHash = null; + $fixedErrorDiffDiff = null; + if ($this->fixedErrorDiff !== null) { + $fixedErrorDiffHash = $this->fixedErrorDiff->originalHash; + $fixedErrorDiffDiff = $this->fixedErrorDiff->diff; + } + return [ 'message' => $this->message, 'file' => $this->file, @@ -259,6 +282,8 @@ public function jsonSerialize() 'nodeType' => $this->nodeType, 'identifier' => $this->identifier, 'metadata' => $this->metadata, + 'fixedErrorDiffHash' => $fixedErrorDiffHash, + 'fixedErrorDiffDiff' => $fixedErrorDiffDiff, ]; } @@ -267,6 +292,11 @@ public function jsonSerialize() */ public static function decode(array $json): self { + $fixedErrorDiff = null; + if ($json['fixedErrorDiffHash'] !== null && $json['fixedErrorDiffDiff'] !== null) { + $fixedErrorDiff = new FixedErrorDiff($json['fixedErrorDiffHash'], $json['fixedErrorDiffDiff']); + } + return new self( $json['message'], $json['file'], @@ -279,6 +309,7 @@ public static function decode(array $json): self $json['nodeType'] ?? null, $json['identifier'] ?? null, $json['metadata'] ?? [], + $fixedErrorDiff, ); } @@ -299,6 +330,7 @@ public static function __set_state(array $properties): self $properties['nodeType'] ?? null, $properties['identifier'] ?? null, $properties['metadata'] ?? [], + $properties['fixedErrorDiff'] ?? null, ); } diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 9e16ae43607..7256e6854e6 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -94,7 +94,7 @@ public function analyseFile( $parserNodes = $this->parser->parseFile($file); $linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)]; $temporaryFileErrors = []; - $nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors): void { + $nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors, $parserNodes): void { if ($node instanceof Node\Stmt\Trait_) { foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) { if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) { @@ -148,7 +148,7 @@ public function analyseFile( } foreach ($ruleErrors as $ruleError) { - $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $parserNodes, $node); if ($error->canBeIgnored()) { foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { diff --git a/src/Analyser/FixedErrorDiff.php b/src/Analyser/FixedErrorDiff.php new file mode 100644 index 00000000000..6bc05db91da --- /dev/null +++ b/src/Analyser/FixedErrorDiff.php @@ -0,0 +1,28 @@ + $diff + */ + public function __construct( + public readonly string $originalHash, + public readonly array $diff, + ) + { + } + + /** + * @param mixed[] $properties + */ + public static function __set_state(array $properties): self + { + return new self($properties['originalHash'], $properties['diff']); + } + +} diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index c7ab2604a8c..3afc2a59a6d 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -2,31 +2,51 @@ namespace PHPStan\Analyser; +use PhpParser\Internal\TokenStream; use PhpParser\Node; -use PHPStan\DependencyInjection\AutowiredService; +use PhpParser\Node\Stmt; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\CloningVisitor; +use PhpParser\Parser; +use PHPStan\File\FileReader; +use PHPStan\Fixable\PhpPrinterIndentationDetectorVisitor; +use PHPStan\Fixable\ReplacingNodeVisitor; +use PHPStan\Node\Printer\Printer; +use PHPStan\Node\VirtualNode; use PHPStan\Rules\FileRuleError; +use PHPStan\Rules\FixableNodeRuleError; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\LineRuleError; use PHPStan\Rules\MetadataRuleError; use PHPStan\Rules\NonIgnorableRuleError; use PHPStan\Rules\RuleError; use PHPStan\Rules\TipRuleError; +use PHPStan\ShouldNotHappenException; +use SebastianBergmann\Diff\Differ; +use function get_class; +use function sha1; +use function str_repeat; -#[AutowiredService] final class RuleErrorTransformer { + public function __construct( + private Parser $parser, + ) + { + } + /** - * @param class-string $nodeType + * @param Node\Stmt[] $fileNodes */ public function transform( RuleError $ruleError, Scope $scope, - string $nodeType, - int $nodeLine, + array $fileNodes, + Node $node, ): Error { - $line = $nodeLine; + $line = $node->getStartLine(); $canBeIgnored = true; $fileName = $scope->getFileDescription(); $filePath = $scope->getFile(); @@ -72,6 +92,47 @@ public function transform( $canBeIgnored = false; } + $fixedErrorDiff = null; + if ($ruleError instanceof FixableNodeRuleError) { + if ($node instanceof VirtualNode) { + throw new ShouldNotHappenException('Cannot fix virtual node'); + } + $fixingFile = $filePath; + if ($traitFilePath !== null) { + $fixingFile = $traitFilePath; + } + + $oldCode = FileReader::read($fixingFile); + + $this->parser->parse($oldCode); + $hash = sha1($oldCode); + $oldTokens = $this->parser->getTokens(); + + $indentTraverser = new NodeTraverser(); + $indentDetector = new PhpPrinterIndentationDetectorVisitor(new TokenStream($oldTokens, PhpPrinter::TAB_WIDTH)); + $indentTraverser->addVisitor($indentDetector); + $indentTraverser->traverse($fileNodes); + + $cloningTraverser = new NodeTraverser(); + $cloningTraverser->addVisitor(new CloningVisitor()); + + /** @var Stmt[] $newStmts */ + $newStmts = $cloningTraverser->traverse($fileNodes); + + $traverser = new NodeTraverser(); + $visitor = new ReplacingNodeVisitor($node, $ruleError->getNewNodeCallable()); + $traverser->addVisitor($visitor); + + /** @var Stmt[] $newStmts */ + $newStmts = $traverser->traverse($newStmts); + + $printer = new Printer(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); + $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); + $differ = new Differ(); + + $fixedErrorDiff = new FixedErrorDiff($hash, $differ->diffToArray($oldCode, $newCode)); + } + return new Error( $ruleError->getMessage(), $fileName, @@ -80,10 +141,11 @@ public function transform( $filePath, $traitFilePath, $tip, - $nodeLine, - $nodeType, + $node->getStartLine(), + get_class($node), $identifier, $metadata, + $fixedErrorDiff, ); } diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 312b95c9819..b8dae98d381 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -2,7 +2,12 @@ namespace PHPStan\Command; +use Nette\Utils\Strings; use OndraM\CiDetector\CiDetector; +use PhpMerge\internal\Hunk; +use PhpMerge\internal\Line; +use PhpMerge\MergeConflict; +use PhpMerge\PhpMerge; use PHPStan\Analyser\InternalError; use PHPStan\Command\ErrorFormatter\BaselineNeonErrorFormatter; use PHPStan\Command\ErrorFormatter\BaselinePhpErrorFormatter; @@ -23,6 +28,8 @@ use PHPStan\Internal\DirectoryCreator; use PHPStan\Internal\DirectoryCreatorException; use PHPStan\ShouldNotHappenException; +use ReflectionClass; +use SebastianBergmann\Diff\Differ; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -51,6 +58,7 @@ use function is_string; use function pathinfo; use function rewind; +use function sha1; use function sprintf; use function str_contains; use function stream_get_contents; @@ -58,6 +66,8 @@ use function substr; use const PATHINFO_BASENAME; use const PATHINFO_EXTENSION; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; /** * @phpstan-import-type Trace from InternalError as InternalErrorTrace @@ -100,7 +110,7 @@ protected function configure(): void new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED, '(Editor mode) Edited file used in place of --instead-of file'), new InputOption('instead-of', null, InputOption::VALUE_REQUIRED, '(Editor mode) File being replaced by --tmp-file'), - new InputOption('fix', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), + new InputOption('fix', null, InputOption::VALUE_NONE, 'Fix auto-fixable errors (experimental)'), new InputOption('watch', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), new InputOption('pro', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), new InputOption('fail-without-result-cache', null, InputOption::VALUE_NONE, 'Return non-zero exit code when result cache is not used'), @@ -136,7 +146,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $level = $input->getOption(self::OPTION_LEVEL); $allowXdebug = $input->getOption('xdebug'); $debugEnabled = (bool) $input->getOption('debug'); - $fix = (bool) $input->getOption('fix') || (bool) $input->getOption('watch') || (bool) $input->getOption('pro'); + $pro = (bool) $input->getOption('watch') || (bool) $input->getOption('pro'); + $fix = (bool) $input->getOption('fix'); $failWithoutResultCache = (bool) $input->getOption('fail-without-result-cache'); /** @var string|false|null $generateBaselineFile */ @@ -196,10 +207,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int $inceptionResult->getStdOutput()->getStyle()->error('Editor mode options --tmp-file and --instead-of cannot be used when generating the baseline.'); return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); } - if ($fix) { + if ($pro) { $inceptionResult->getStdOutput()->getStyle()->error('Editor mode options --tmp-file and --instead-of cannot be used with PHPStan Pro.'); return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); } + if ($fix) { + $inceptionResult->getStdOutput()->getStyle()->error('Editor mode options --tmp-file and --instead-of cannot be used with --fix.'); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + } + + if ($fix) { + if ($generateBaselineFile !== null) { + $inceptionResult->getStdOutput()->getStyle()->error('Errors cannot be fixed when generating the baseline.'); + return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); + } + + $inceptionResult->getErrorOutput()->getStyle()->note('The --fix CLI option no longer launches PHPStan Pro. Use --pro instead if you want to launch PHPStan Pro'); } $errorOutput = $inceptionResult->getErrorOutput(); @@ -296,7 +320,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); } - if ($fix) { + if ($pro) { if ($generateBaselineFile !== null) { $inceptionResult->getStdOutput()->getStyle()->error('You cannot pass the --generate-baseline option when running PHPStan Pro.'); return $inceptionResult->handleReturn(1, null, $this->analysisStartTime); @@ -484,7 +508,119 @@ protected function execute(InputInterface $input, OutputInterface $output): int ); } - $exitCode = $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()); + if ($fix) { + $fixableErrors = []; + foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { + if ($fileSpecificError->getFixedErrorDiff() === null) { + continue; + } + + $fixableErrors[] = $fileSpecificError; + } + + $fixableErrorsCount = count($fixableErrors); + if (count($fixableErrors) === 0) { + $inceptionResult->getStdOutput()->getStyle()->error('No fixable errors found'); + $exitCode = 1; + } else { + $skippedCount = 0; + $fixableErrorsByFile = []; + foreach ($fixableErrors as $fixableError) { + $fixFile = $fixableError->getFilePath(); + if ($fixableError->getTraitFilePath() !== null) { + $fixFile = $fixableError->getTraitFilePath(); + } + + $fixableErrorsByFile[$fixFile][] = $fixableError; + } + + $differ = new Differ(); + + foreach ($fixableErrorsByFile as $file => $fileFixableErrors) { + $fileContents = FileReader::read($file); + $fileHash = sha1($fileContents); + $diffHunks = []; + foreach ($fileFixableErrors as $fileFixableError) { + $diff = $fileFixableError->getFixedErrorDiff(); + if ($diff === null) { + throw new ShouldNotHappenException(); + } + if ($diff->originalHash !== $fileHash) { + $skippedCount++; + continue; + } + + $diffHunks[] = Hunk::createArray(Line::createArray($diff->diff)); + } + + if (count($diffHunks) === 0) { + continue; + } + + $baseLines = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + self::splitStringByLines($fileContents), + )); + + $refMerge = new ReflectionClass(PhpMerge::class); + $refMergeMethod = $refMerge->getMethod('mergeHunks'); + $refMergeMethod->setAccessible(true); + + $result = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + $refMergeMethod->invokeArgs(null, [ + $baseLines, + $diffHunks[0], + [], + ]), + )); + + for ($i = 0; $i < count($diffHunks); $i++) { + /** @var MergeConflict[] $conflicts */ + $conflicts = []; + $merged = $refMergeMethod->invokeArgs(null, [ + $baseLines, + Hunk::createArray(Line::createArray($differ->diffToArray($fileContents, implode('', array_map(static fn ($l) => $l->getContent(), $result))))), + $diffHunks[$i], + &$conflicts, + ]); + if (count($conflicts) > 0) { + $skippedCount += count($diffHunks); + continue 2; + } + + $result = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + $merged, + )); + + } + + $finalFileContents = implode('', array_map(static fn ($l) => $l->getContent(), $result)); + FileWriter::write($file, $finalFileContents); + } + + if ($skippedCount > 0) { + $inceptionResult->getStdOutput()->getStyle()->warning(sprintf( + '%d %s fixed, %d %s skipped', + $fixableErrorsCount, + $fixableErrorsCount === 1 ? 'error' : 'errors', + $skippedCount, + $skippedCount === 1 ? 'error' : 'errors', + )); + } else { + $inceptionResult->getStdOutput()->getStyle()->success(sprintf( + '%d %s fixed', + $fixableErrorsCount, + $fixableErrorsCount === 1 ? 'error' : 'errors', + )); + } + $exitCode = 0; + } + } else { + $exitCode = $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()); + } + if ($exitCode === 0 && $failWithoutResultCache && !$analysisResult->isResultCacheUsed()) { $exitCode = 2; } @@ -543,6 +679,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int ); } + /** + * @return string[] + */ + private static function splitStringByLines(string $input): array + { + return Strings::split($input, '/(.*\R)/', PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + private function createStreamOutput(): StreamOutput { $resource = fopen('php://memory', 'w', false); diff --git a/src/Fixable/PhpPrinterIndentationDetectorVisitor.php b/src/Fixable/PhpPrinterIndentationDetectorVisitor.php new file mode 100644 index 00000000000..7e423cc96db --- /dev/null +++ b/src/Fixable/PhpPrinterIndentationDetectorVisitor.php @@ -0,0 +1,81 @@ +stmts) || count($node->stmts) === 0) { + return null; + } + + $firstStmt = $node->stmts[0]; + if (!$firstStmt instanceof Node) { + return null; + } + $text = $this->origTokens->getTokenCode($node->getStartTokenPos(), $firstStmt->getStartTokenPos(), 0); + + $c = preg_match_all('~\n([\\x09\\x20]*)~', $text, $matches, PREG_SET_ORDER); + if ($c === 0 || $c === false) { + return null; + } + + $char = ''; + $size = 0; + foreach ($matches as $match) { + $l = strlen($match[1]); + if ($l === 0) { + continue; + } + + $char = $match[1]; + $size = $l; + break; + } + + if ($size > 0) { + $d = preg_match('~^(\\x20+)$~', $char); + if ($d !== false && $d > 0) { + $size = strlen($char); + $char = ' '; + } + + $this->indentCharacter = $char; + $this->indentSize = $size; + + return NodeVisitor::STOP_TRAVERSAL; + } + + return null; + } + +} diff --git a/src/Fixable/ReplacingNodeVisitor.php b/src/Fixable/ReplacingNodeVisitor.php new file mode 100644 index 00000000000..a6de9b8f1f8 --- /dev/null +++ b/src/Fixable/ReplacingNodeVisitor.php @@ -0,0 +1,36 @@ +getAttribute('origNode'); + if ($origNode !== $this->originalNode) { + return null; + } + + $callable = $this->newNodeCallable; + $newNode = $callable($node); + if ($newNode instanceof VirtualNode) { + throw new ShouldNotHappenException('Cannot print VirtualNode.'); + } + + return $newNode; + } + +} diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 026069146a1..8edc42c7f8e 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -120,7 +120,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser $this->getParser(), self::getContainer()->getByType(DependencyResolver::class), new IgnoreErrorExtensionProvider(self::getContainer()), - new RuleErrorTransformer(), + new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), new LocalIgnoresProcessor(), ); $this->analyser = new Analyser( @@ -237,7 +237,7 @@ private function gatherAnalyserErrorsWithDelayedErrors(array $files): array $finalizer = new AnalyserResultFinalizer( $ruleRegistry, new IgnoreErrorExtensionProvider(self::getContainer()), - new RuleErrorTransformer(), + new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), $this->createScopeFactory($reflectionProvider, $this->getTypeSpecifier()), new LocalIgnoresProcessor(), true, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 9877e01b77d..7f743fe95a6 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -670,7 +670,7 @@ private function runAnalyser( $finalizer = new AnalyserResultFinalizer( new DirectRuleRegistry([]), new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), - new RuleErrorTransformer(), + new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), $this->createScopeFactory( $this->createReflectionProvider(), self::getContainer()->getService('typeSpecifier'), @@ -749,7 +749,7 @@ private function createAnalyser(): Analyser ), new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($reflectionProvider, $fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), - new RuleErrorTransformer(), + new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), new LocalIgnoresProcessor(), ); From 6680175a30415b1d89af7d34836db19403f158f4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 22:59:56 +0200 Subject: [PATCH 1454/3097] Fix --- src/Analyser/RuleErrorTransformer.php | 4 +-- src/Fixable/PhpPrinter.php | 35 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/Fixable/PhpPrinter.php diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 3afc2a59a6d..f8b35d4aaec 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -9,9 +9,9 @@ use PhpParser\NodeVisitor\CloningVisitor; use PhpParser\Parser; use PHPStan\File\FileReader; +use PHPStan\Fixable\PhpPrinter; use PHPStan\Fixable\PhpPrinterIndentationDetectorVisitor; use PHPStan\Fixable\ReplacingNodeVisitor; -use PHPStan\Node\Printer\Printer; use PHPStan\Node\VirtualNode; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\FixableNodeRuleError; @@ -126,7 +126,7 @@ public function transform( /** @var Stmt[] $newStmts */ $newStmts = $traverser->traverse($newStmts); - $printer = new Printer(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); + $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); $differ = new Differ(); diff --git a/src/Fixable/PhpPrinter.php b/src/Fixable/PhpPrinter.php new file mode 100644 index 00000000000..c4f9080d62b --- /dev/null +++ b/src/Fixable/PhpPrinter.php @@ -0,0 +1,35 @@ +getAttribute(self::FUNC_ARGS_TRAILING_COMMA_ATTRIBUTE); + if ($trailingComma === false) { + $result = rtrim($result, ','); + } + + return $result; + } + +} From 1d4477f943cc95b2ac02b4943dd84b196ca6ecbf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 08:52:02 +0200 Subject: [PATCH 1455/3097] Auto-apply fixes for NamedArgumentsRule --- src/Analyser/Analyser.php | 2 +- src/Analyser/AnalyserResultFinalizer.php | 4 +- src/Analyser/FileAnalyser.php | 18 +++---- .../Ignore/IgnoredErrorHelperResult.php | 3 +- src/Analyser/MutatingScope.php | 49 +++++++---------- src/Analyser/TypeSpecifier.php | 8 +-- src/Command/AnalyseCommand.php | 24 ++++----- src/Command/ClearResultCacheCommand.php | 6 +-- src/Command/DiagnoseCommand.php | 4 +- src/Command/DumpParametersCommand.php | 6 +-- src/Command/FixerApplication.php | 6 +-- src/Command/FixerWorkerCommand.php | 6 +-- src/Command/WorkerCommand.php | 14 ++--- src/Parallel/ParallelAnalyser.php | 2 +- src/Parallel/Process.php | 2 +- src/Parser/CachedParser.php | 6 +-- src/PhpDoc/StubValidator.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 24 ++++----- src/Process/ProcessPromise.php | 2 +- .../SourceLocator/PhpFileCleaner.php | 2 +- src/Reflection/ClassReflection.php | 4 +- .../InitializerExprTypeResolver.php | 2 +- .../Native/NativeMethodReflection.php | 2 +- .../Php/PhpClassReflectionExtension.php | 3 +- src/Reflection/Php/PhpMethodReflection.php | 5 +- src/Reflection/Php/PhpParameterReflection.php | 5 +- src/Reflection/Php/PhpPropertyReflection.php | 3 +- src/Rules/Api/ApiInstanceofRule.php | 2 +- src/Rules/Arrays/AllowedArrayKeysTypes.php | 3 +- .../DeadCode/UnusedPrivateConstantRule.php | 2 +- .../DeadCode/UnusedPrivateMethodRule.php | 2 +- .../DeadCode/UnusedPrivatePropertyRule.php | 2 +- src/Rules/PhpDoc/AssertRuleHelper.php | 2 +- .../PhpDoc/IncompatibleSelfOutTypeRule.php | 2 +- .../Traits/ConflictingTraitConstantsRule.php | 4 +- src/Testing/ErrorFormatterTestCase.php | 4 +- .../Accessory/AccessoryLiteralStringType.php | 3 +- .../AccessoryLowercaseStringType.php | 3 +- .../Accessory/AccessoryNonEmptyStringType.php | 3 +- .../Accessory/AccessoryNonFalsyStringType.php | 3 +- .../Accessory/AccessoryNumericStringType.php | 3 +- .../AccessoryUppercaseStringType.php | 3 +- src/Type/BooleanType.php | 3 +- src/Type/ClosureType.php | 3 +- src/Type/ClosureTypeFactory.php | 2 +- src/Type/Enum/EnumCaseObjectType.php | 2 +- src/Type/FileTypeMapper.php | 13 ++--- src/Type/FloatType.php | 3 +- .../Generic/TemplateGenericObjectType.php | 2 +- src/Type/Generic/TemplateTypeFactory.php | 2 +- src/Type/IntegerType.php | 3 +- src/Type/NullType.php | 2 +- src/Type/ObjectType.php | 4 +- ...FromCallableDynamicReturnTypeExtension.php | 11 ++-- .../Php/HrtimeFunctionReturnTypeExtension.php | 2 +- .../StrSplitFunctionReturnTypeExtension.php | 2 +- src/Type/Regex/RegexGroupParser.php | 4 +- src/Type/ResourceType.php | 3 +- src/Type/StaticType.php | 3 +- src/Type/StaticTypeFactory.php | 2 +- src/Type/StringType.php | 3 +- src/Type/TypeCombinator.php | 2 +- src/Type/TypehintHelper.php | 4 +- .../ArgumentsNormalizerLegacyTest.php | 25 ++------- .../Analyser/ArgumentsNormalizerTest.php | 4 +- .../PHPStan/Analyser/StatementResultTest.php | 2 +- .../BaselineNeonErrorFormatterTest.php | 2 +- .../CheckstyleErrorFormatterTest.php | 9 ++-- .../ErrorFormatter/JsonErrorFormatterTest.php | 2 +- .../TableErrorFormatterTest.php | 6 +-- .../ReflectionProviderGoldenTest.php | 2 +- tests/PHPStan/Type/ArrayTypeTest.php | 4 +- tests/PHPStan/Type/CallableTypeTest.php | 44 ++++++++-------- tests/PHPStan/Type/ClosureTypeTest.php | 2 +- .../Type/Constant/ConstantArrayTypeTest.php | 22 ++++---- tests/PHPStan/Type/MixedTypeTest.php | 52 +++++++++---------- tests/PHPStan/Type/TypeGetFiniteTypesTest.php | 8 +-- tests/PHPStan/Type/TypeToPhpDocNodeTest.php | 6 +-- tests/PHPStan/Type/VerbosityLevelTest.php | 8 +-- 79 files changed, 235 insertions(+), 293 deletions(-) diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index 8eea5bacecb..9e4835d376f 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -103,7 +103,7 @@ public function analyse( throw $t; } $internalErrorsCount++; - $errors[] = (new Error($t->getMessage(), $file, null, $t)) + $errors[] = (new Error($t->getMessage(), $file, canBeIgnored: $t)) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($t), diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index c0f0c411e3d..c3d3877b137 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -50,7 +50,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ try { $ruleErrors = $rule->processNode($node, $scope); } catch (AnalysedCodeException $e) { - $tempCollectorErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip())) + $tempCollectorErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, tip: $e->getTip())) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -58,7 +58,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ ]); continue; } catch (IdentifierNotFound $e) { - $tempCollectorErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + $tempCollectorErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) ->withIdentifier('phpstan.reflection') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 7256e6854e6..340039bf4a9 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -122,7 +122,7 @@ public function analyseFile( } $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; - $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip())) + $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, tip: $e->getTip())) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -130,7 +130,7 @@ public function analyseFile( ]); continue; } catch (IdentifierNotFound $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) ->withIdentifier('phpstan.reflection') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -171,7 +171,7 @@ public function analyseFile( } $uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true; - $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip())) + $fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, tip: $e->getTip())) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -179,7 +179,7 @@ public function analyseFile( ]); continue; } catch (IdentifierNotFound $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) ->withIdentifier('phpstan.reflection') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -257,21 +257,21 @@ public function analyseFile( $fileErrors[] = (new Error($error->getMessage(), $e->getParsedFile() ?? $file, $error->getLine() !== -1 ? $error->getStartLine() : null, $e))->withIdentifier('phpstan.parse'); } } catch (AnalysedCodeException $e) { - $fileErrors[] = (new Error($e->getMessage(), $file, null, $e, null, null, $e->getTip())) + $fileErrors[] = (new Error($e->getMessage(), $file, canBeIgnored: $e, tip: $e->getTip())) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), ]); } catch (IdentifierNotFound $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, null, $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) + $fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, canBeIgnored: $e, tip: 'Learn more at https://phpstan.org/user-guide/discovering-symbols')) ->withIdentifier('phpstan.reflection') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), InternalError::STACK_TRACE_AS_STRING_METADATA_KEY => $e->getTraceAsString(), ]); } catch (UnableToCompileNode | CircularReference $e) { - $fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, null, $e)) + $fileErrors[] = (new Error(sprintf('Reflection error: %s', $e->getMessage()), $file, canBeIgnored: $e)) ->withIdentifier('phpstan.reflection') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), @@ -279,9 +279,9 @@ public function analyseFile( ]); } } elseif (is_dir($file)) { - $fileErrors[] = (new Error(sprintf('File %s is a directory.', $file), $file, null, false))->withIdentifier('phpstan.path'); + $fileErrors[] = (new Error(sprintf('File %s is a directory.', $file), $file, canBeIgnored: false))->withIdentifier('phpstan.path'); } else { - $fileErrors[] = (new Error(sprintf('File %s does not exist.', $file), $file, null, false))->withIdentifier('phpstan.path'); + $fileErrors[] = (new Error(sprintf('File %s does not exist.', $file), $file, canBeIgnored: false))->withIdentifier('phpstan.path'); } $this->restoreCollectErrorsHandler(); diff --git a/src/Analyser/Ignore/IgnoredErrorHelperResult.php b/src/Analyser/Ignore/IgnoredErrorHelperResult.php index 529e1ae4a46..3358c9e63ac 100644 --- a/src/Analyser/Ignore/IgnoredErrorHelperResult.php +++ b/src/Analyser/Ignore/IgnoredErrorHelperResult.php @@ -224,8 +224,7 @@ public function process( IgnoredError::stringifyPattern($unmatchedIgnoredError), ), $unmatchedIgnoredError['realPath'], - null, - false, + canBeIgnored: false, ))->withIdentifier('ignore.unmatched'); } elseif (!$onlyFiles) { $stringErrors[] = sprintf( diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cbd92c0d3fd..cdb919da1fa 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1415,12 +1415,11 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), TemplateTypeVarianceMap::createEmpty(), - [], - $cachedClosureData['throwPoints'], - $cachedClosureData['impurePoints'], - $cachedClosureData['invalidateExpressions'], - $cachedClosureData['usedVariables'], - TrinaryLogic::createYes(), + throwPoints: $cachedClosureData['throwPoints'], + impurePoints: $cachedClosureData['impurePoints'], + invalidateExpressions: $cachedClosureData['invalidateExpressions'], + usedVariables: $cachedClosureData['usedVariables'], + acceptsNamedArguments: TrinaryLogic::createYes(), ); } if (self::$resolveClosureTypeDepth >= 2) { @@ -1630,12 +1629,11 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), TemplateTypeVarianceMap::createEmpty(), - [], - $throwPointsForClosureType, - $impurePointsForClosureType, - $invalidateExpressions, - $usedVariables, - TrinaryLogic::createYes(), + throwPoints: $throwPointsForClosureType, + impurePoints: $impurePointsForClosureType, + invalidateExpressions: $invalidateExpressions, + usedVariables: $usedVariables, + acceptsNamedArguments: TrinaryLogic::createYes(), ); } elseif ($node instanceof New_) { if ($node->class instanceof Name) { @@ -3140,7 +3138,7 @@ public function enterPropertyHook( if ($hook->params === []) { $hook = clone $hook; $hook->params = [ - new Node\Param(new Variable('value'), null, $nativePropertyTypeNode), + new Node\Param(new Variable('value'), type: $nativePropertyTypeNode), ]; } @@ -5805,7 +5803,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return $methodResult; } - $objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName, null, $classReflection); + $objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName, classReflection: $classReflection); if (!$classReflection->isGeneric()) { return $objectType; } @@ -5831,7 +5829,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type if (count($classTemplateTypes) === count($originalClassTemplateTypes)) { $propertyType = TypeCombinator::removeNull($this->getType($assignedToProperty)); - $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, null, $nonFinalClassReflection); + $nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection); if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) { return $propertyType; } @@ -5852,8 +5850,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } @@ -5872,8 +5869,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } $newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap())); @@ -5892,8 +5888,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } $ancestorClassReflections = $ancestorType->getObjectClassReflections(); @@ -5911,8 +5906,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } @@ -5933,8 +5927,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } $newParentTypeClassReflection = $newParentTypeClassReflections[0]; @@ -5981,8 +5974,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type return new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); } @@ -5998,8 +5990,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type $newGenericType = new GenericObjectType( $resolvedClassName, $types, - null, - $classReflection->withTypes($types)->asFinal(), + classReflection: $classReflection->withTypes($types)->asFinal(), ); if ($isStatic) { $newGenericType = new GenericStaticType( diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 761aa267ae8..2b43724ff06 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1898,7 +1898,7 @@ private function createRangeTypes(?Expr $rootExpr, Expr $expr, Type $type, TypeS } } - return (new SpecifiedTypes([], $sureNotTypes))->setRootExpr($rootExpr); + return (new SpecifiedTypes(sureNotTypes: $sureNotTypes))->setRootExpr($rootExpr); } /** @@ -2263,7 +2263,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($rightType instanceof ConstantStringType && $this->reflectionProvider->hasClass($rightType->getValue())) { return $this->create( $unwrappedLeftExpr->getArgs()[0]->value, - new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), + new ObjectType($rightType->getValue(), classReflection: $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), $context, $scope, )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); @@ -2370,7 +2370,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($this->reflectionProvider->hasClass($rightType->getValue())) { return $this->create( $unwrappedLeftExpr->class, - new ObjectType($rightType->getValue(), null, $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), + new ObjectType($rightType->getValue(), classReflection: $this->reflectionProvider->getClass($rightType->getValue())->asFinal()), $context, $scope, )->unionWith($this->create($leftExpr, $rightType, $context, $scope))->setRootExpr($expr); @@ -2401,7 +2401,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty if ($this->reflectionProvider->hasClass($leftType->getValue())) { return $this->create( $unwrappedRightExpr->class, - new ObjectType($leftType->getValue(), null, $this->reflectionProvider->getClass($leftType->getValue())->asFinal()), + new ObjectType($leftType->getValue(), classReflection: $this->reflectionProvider->getClass($leftType->getValue())->asFinal()), $context, $scope, )->unionWith($this->create($rightExpr, $leftType, $context, $scope)->setRootExpr($expr)); diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index b8dae98d381..f7a4ba6138d 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -100,20 +100,20 @@ protected function configure(): void new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(self::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), - new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), - new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'), + new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, mode: InputOption::VALUE_NONE, description: 'Do not show progress bar, only results'), + new InputOption('debug', mode: InputOption::VALUE_NONE, description: 'Show debug information - which file is analysed, do not catch internal errors'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', null), + new InputOption('error-format', mode: InputOption::VALUE_REQUIRED, description: 'Format in which to print the result of the analysis'), new InputOption('generate-baseline', 'b', InputOption::VALUE_OPTIONAL, 'Path to a file where the baseline should be saved', false), - new InputOption('allow-empty-baseline', null, InputOption::VALUE_NONE, 'Do not error out when the generated baseline is empty'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), - new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED, '(Editor mode) Edited file used in place of --instead-of file'), - new InputOption('instead-of', null, InputOption::VALUE_REQUIRED, '(Editor mode) File being replaced by --tmp-file'), - new InputOption('fix', null, InputOption::VALUE_NONE, 'Fix auto-fixable errors (experimental)'), - new InputOption('watch', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), - new InputOption('pro', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), - new InputOption('fail-without-result-cache', null, InputOption::VALUE_NONE, 'Return non-zero exit code when result cache is not used'), + new InputOption('allow-empty-baseline', mode: InputOption::VALUE_NONE, description: 'Do not error out when the generated baseline is empty'), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for analysis'), + new InputOption('xdebug', mode: InputOption::VALUE_NONE, description: 'Allow running with Xdebug for debugging purposes'), + new InputOption('tmp-file', mode: InputOption::VALUE_REQUIRED, description: '(Editor mode) Edited file used in place of --instead-of file'), + new InputOption('instead-of', mode: InputOption::VALUE_REQUIRED, description: '(Editor mode) File being replaced by --tmp-file'), + new InputOption('fix', mode: InputOption::VALUE_NONE, description: 'Fix auto-fixable errors (experimental)'), + new InputOption('watch', mode: InputOption::VALUE_NONE, description: 'Launch PHPStan Pro'), + new InputOption('pro', mode: InputOption::VALUE_NONE, description: 'Launch PHPStan Pro'), + new InputOption('fail-without-result-cache', mode: InputOption::VALUE_NONE, description: 'Return non-zero exit code when result cache is not used'), ]); } diff --git a/src/Command/ClearResultCacheCommand.php b/src/Command/ClearResultCacheCommand.php index d78b3acf3a4..462bb3df6e5 100644 --- a/src/Command/ClearResultCacheCommand.php +++ b/src/Command/ClearResultCacheCommand.php @@ -33,9 +33,9 @@ protected function configure(): void ->setDefinition([ new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for clearing result cache'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), + new InputOption('debug', mode: InputOption::VALUE_NONE, description: 'Show debug information - which file is analysed, do not catch internal errors'), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for clearing result cache'), + new InputOption('xdebug', mode: InputOption::VALUE_NONE, description: 'Allow running with Xdebug for debugging purposes'), ]); } diff --git a/src/Command/DiagnoseCommand.php b/src/Command/DiagnoseCommand.php index 1df8f71a964..5df3f8bf15a 100644 --- a/src/Command/DiagnoseCommand.php +++ b/src/Command/DiagnoseCommand.php @@ -34,8 +34,8 @@ protected function configure(): void new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - do not catch internal errors'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for clearing result cache'), + new InputOption('debug', mode: InputOption::VALUE_NONE, description: 'Show debug information - do not catch internal errors'), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for clearing result cache'), ]); } diff --git a/src/Command/DumpParametersCommand.php b/src/Command/DumpParametersCommand.php index ceec5f7cfef..2ae6fc15d8e 100644 --- a/src/Command/DumpParametersCommand.php +++ b/src/Command/DumpParametersCommand.php @@ -34,9 +34,9 @@ protected function configure(): void new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for clearing result cache'), - new InputOption('json', null, InputOption::VALUE_NONE, 'Dump parameters as JSON instead of NEON'), + new InputOption('debug', mode: InputOption::VALUE_NONE, description: 'Show debug information - which file is analysed, do not catch internal errors'), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for clearing result cache'), + new InputOption('json', mode: InputOption::VALUE_NONE, description: 'Dump parameters as JSON instead of NEON'), ]); } diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index fd48030cf2c..e40d6560216 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -117,7 +117,7 @@ public function run( // phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly $jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; // phpcs:enable - $decoder = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, 128 * 1024 * 1024); + $decoder = new Decoder($connection, true, options: $jsonInvalidUtf8Ignore, maxlength: 128 * 1024 * 1024); $encoder = new Encoder($connection, $jsonInvalidUtf8Ignore); $encoder->write(['action' => 'initialData', 'data' => [ 'currentWorkingDirectory' => $this->currentWorkingDirectory, @@ -292,7 +292,7 @@ private function getFixerProcess(OutputInterface $output, int $serverPort): Proc } } - return new Process(sprintf('%s -d memory_limit=%s %s --port %d', escapeshellarg(PHP_BINARY), escapeshellarg(ini_get('memory_limit')), escapeshellarg($pharPath), $serverPort), null, $env, []); + return new Process(sprintf('%s -d memory_limit=%s %s --port %d', escapeshellarg(PHP_BINARY), escapeshellarg(ini_get('memory_limit')), escapeshellarg($pharPath), $serverPort), env: $env, fds: []); } private function downloadPhar( @@ -458,7 +458,7 @@ private function analyse( // phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly $jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; // phpcs:enable - $decoder = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, 128 * 1024 * 1024); + $decoder = new Decoder($connection, true, options: $jsonInvalidUtf8Ignore, maxlength: 128 * 1024 * 1024); $decoder->on('data', static function (array $data) use ($phpstanFixerEncoder): void { $phpstanFixerEncoder->write($data); }); diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index 54e1da4c41b..f2d8127b5c9 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -66,9 +66,9 @@ protected function configure(): void new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), - new InputOption('server-port', null, InputOption::VALUE_REQUIRED, 'Server port for FixerApplication'), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for analysis'), + new InputOption('xdebug', mode: InputOption::VALUE_NONE, description: 'Allow running with Xdebug for debugging purposes'), + new InputOption('server-port', mode: InputOption::VALUE_REQUIRED, description: 'Server port for FixerApplication'), ]) ->setHidden(true); } diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 0fa30e17552..6928dadffa6 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -61,12 +61,12 @@ protected function configure(): void new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with Xdebug for debugging purposes'), - new InputOption('port', null, InputOption::VALUE_REQUIRED), - new InputOption('identifier', null, InputOption::VALUE_REQUIRED), - new InputOption('tmp-file', null, InputOption::VALUE_REQUIRED), - new InputOption('instead-of', null, InputOption::VALUE_REQUIRED), + new InputOption('memory-limit', mode: InputOption::VALUE_REQUIRED, description: 'Memory limit for analysis'), + new InputOption('xdebug', mode: InputOption::VALUE_NONE, description: 'Allow running with Xdebug for debugging purposes'), + new InputOption('port', mode: InputOption::VALUE_REQUIRED), + new InputOption('identifier', mode: InputOption::VALUE_REQUIRED), + new InputOption('tmp-file', mode: InputOption::VALUE_REQUIRED), + new InputOption('instead-of', mode: InputOption::VALUE_REQUIRED), ]) ->setHidden(true); } @@ -144,7 +144,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; // phpcs:enable $out = new Encoder($connection, $jsonInvalidUtf8Ignore); - $in = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, $container->getParameter('parallel')['buffer']); + $in = new Decoder($connection, true, options: $jsonInvalidUtf8Ignore, maxlength: $container->getParameter('parallel')['buffer']); $out->write(['action' => 'hello', 'identifier' => $identifier]); $this->runWorker($container, $out, $in, $output, $analysedFiles, $tmpFile, $insteadOfFile); }); diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index b88724fbc7e..7e8bd11edd2 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -123,7 +123,7 @@ public function analyse( // phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly $jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0; // phpcs:enable - $decoder = new Decoder($connection, true, 512, $jsonInvalidUtf8Ignore, $this->decoderBufferSize); + $decoder = new Decoder($connection, true, options: $jsonInvalidUtf8Ignore, maxlength: $this->decoderBufferSize); $encoder = new Encoder($connection, $jsonInvalidUtf8Ignore); $decoder->on('data', function (array $data) use (&$jobs, $decoder, $encoder): void { if ($data['action'] !== 'hello') { diff --git a/src/Parallel/Process.php b/src/Parallel/Process.php index e5cf90566fe..0f51442a561 100644 --- a/src/Parallel/Process.php +++ b/src/Parallel/Process.php @@ -61,7 +61,7 @@ public function start(callable $onData, callable $onError, callable $onExit): vo } $this->stdOut = $tmpStdOut; $this->stdErr = $tmpStdErr; - $this->process = new \React\ChildProcess\Process($this->command, null, null, [ + $this->process = new \React\ChildProcess\Process($this->command, fds: [ 1 => $this->stdOut, 2 => $this->stdErr, ]); diff --git a/src/Parser/CachedParser.php b/src/Parser/CachedParser.php index 62bba62a2ba..400c21bf5ad 100644 --- a/src/Parser/CachedParser.php +++ b/src/Parser/CachedParser.php @@ -34,8 +34,7 @@ public function parseFile(string $file): array $this->cachedNodesByString = array_slice( $this->cachedNodesByString, 1, - null, - true, + preserve_keys: true, ); --$this->cachedNodesByStringCount; @@ -60,8 +59,7 @@ public function parseString(string $sourceCode): array $this->cachedNodesByString = array_slice( $this->cachedNodesByString, 1, - null, - true, + preserve_keys: true, ); --$this->cachedNodesByStringCount; diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 3d1318fcd8d..f7b1466f8ec 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -161,7 +161,7 @@ static function (): void { } $internalErrorMessage = sprintf('Internal error: %s', $e->getMessage()); - $errors[] = (new Error($internalErrorMessage, $stubFile, null, $e)) + $errors[] = (new Error($internalErrorMessage, $stubFile, canBeIgnored: $e)) ->withIdentifier('phpstan.internal') ->withMetadata([ InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e), diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 21284c91299..bbd3d0929a4 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -392,7 +392,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new CallableType(); case 'pure-callable': - return new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()); + return new CallableType(isPure: TrinaryLogic::createYes()); case 'pure-closure': return ClosureType::createPure(); @@ -836,7 +836,7 @@ static function (string $variance): TemplateTypeVariance { if ($mainTypeClassName !== null) { if (!$this->getReflectionProvider()->hasClass($mainTypeClassName)) { - return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances); + return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances); } $classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName); @@ -859,7 +859,7 @@ static function (string $variance): TemplateTypeVariance { return new GenericObjectType($mainTypeClassName, [ new MixedType(true), $genericTypes[0], - ], null, null, [ + ], variances: [ TemplateTypeVariance::createInvariant(), $variances[0], ]); @@ -869,7 +869,7 @@ static function (string $variance): TemplateTypeVariance { return new GenericObjectType($mainTypeClassName, [ $genericTypes[0], $genericTypes[1], - ], null, null, [ + ], variances: [ $variances[0], $variances[1], ]); @@ -883,7 +883,7 @@ static function (string $variance): TemplateTypeVariance { $genericTypes[0], $mixed, $mixed, - ], null, null, [ + ], variances: [ TemplateTypeVariance::createInvariant(), $variances[0], TemplateTypeVariance::createInvariant(), @@ -898,7 +898,7 @@ static function (string $variance): TemplateTypeVariance { $genericTypes[1], $mixed, $mixed, - ], null, null, [ + ], variances: [ $variances[0], $variances[1], TemplateTypeVariance::createInvariant(), @@ -908,14 +908,14 @@ static function (string $variance): TemplateTypeVariance { } if (!$mainType->isIterable()->yes()) { - return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances); + return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances); } if ( count($genericTypes) !== 1 || $classReflection->getTemplateTypeMap()->count() === 1 ) { - return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances); + return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances); } } } @@ -951,7 +951,7 @@ static function (string $variance): TemplateTypeVariance { } if ($mainTypeClassName !== null) { - return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances); + return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances); } return new ErrorType(); @@ -1017,13 +1017,13 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi return new ErrorType(); } - return new CallableType($parameters, $returnType, $isVariadic, $templateTypeMap, null, $templateTags, $pure); + return new CallableType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, isPure: $pure); } elseif ( $mainType instanceof ObjectType && $mainType->getClassName() === Closure::class ) { - return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], [ + return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: [ new SimpleImpurePoint( 'functionCall', 'call to a Closure', @@ -1031,7 +1031,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi ), ]); } elseif ($mainType instanceof ClosureType) { - $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], $mainType->getImpurePoints(), $mainType->getInvalidateExpressions(), $mainType->getUsedVariables(), $mainType->acceptsNamedArguments()); + $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments()); if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) { return new ErrorType(); } diff --git a/src/Process/ProcessPromise.php b/src/Process/ProcessPromise.php index afc50f087de..c4b152cd8bf 100644 --- a/src/Process/ProcessPromise.php +++ b/src/Process/ProcessPromise.php @@ -43,7 +43,7 @@ public function run(): PromiseInterface throw new ShouldNotHappenException('Failed creating temp file for stderr.'); } - $this->process = new Process($this->command, null, null, [ + $this->process = new Process($this->command, fds: [ 1 => $tmpStdOutResource, 2 => $tmpStdErrResource, ]); diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php index 84985abbce5..137941d0047 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php @@ -289,7 +289,7 @@ private function peek(string $char): bool */ private function match(string $regex, ?array &$match = null, ?int $offset = null): bool { - return preg_match($regex, $this->contents, $match, 0, $offset ?? $this->index) === 1; + return preg_match($regex, $this->contents, $match, offset: $offset ?? $this->index) === 1; } } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 7c11efcda25..45c0fd4bff6 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1118,7 +1118,7 @@ public function getConstant(string $name): ClassConstantReflection $nativeType = null; if ($reflectionConstant->getType() !== null) { - $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), null, $declaringClass); + $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $declaringClass); } elseif ($this->signatureMapProvider->hasClassConstantMetadata($declaringClass->getName(), $name)) { $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType']; } @@ -1371,7 +1371,7 @@ private function findAttributeFlags(): ?int if ($i === '') { throw new ShouldNotHappenException(); } - $arguments[] = new Arg($expression, false, false, [], is_int($i) ? null : new Identifier($i)); + $arguments[] = new Arg($expression, name: is_int($i) ? null : new Identifier($i)); } if (!$attributeClass->hasConstructor()) { diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index d6024cf3237..a4a36a77894 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2035,7 +2035,7 @@ function (Type $type, callable $traverse): Type { $constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null)); $nativeType = null; if ($reflectionConstant->getType() !== null) { - $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), null, $constantClassReflection); + $nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection); } $types[] = $this->constantResolver->resolveClassConstantType( $constantClassReflection->getName(), diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 76be89b4932..91fc46229dd 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -84,7 +84,7 @@ public function getPrototype(): ClassMemberReflection $tentativeReturnType = null; if ($prototypeMethod->getTentativeReturnType() !== null) { - $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), null, $prototypeDeclaringClass); + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass); } return new MethodPrototypeReflection( diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 19144986c48..157fc89adac 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -829,8 +829,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $nativeReturnType = TypehintHelper::decideTypeFromReflection( $methodReflection->getReturnType(), - null, - $actualDeclaringClass, + selfClass: $actualDeclaringClass, ); $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index f032585242c..8f63f01931d 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -125,7 +125,7 @@ public function getPrototype(): ClassMemberReflection $tentativeReturnType = null; if ($prototypeMethod->getTentativeReturnType() !== null) { - $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), null, $prototypeDeclaringClass); + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass); } return new MethodPrototypeReflection( @@ -345,8 +345,7 @@ private function getNativeReturnType(): Type if ($this->nativeReturnType === null) { $this->nativeReturnType = TypehintHelper::decideTypeFromReflection( $this->reflection->getReturnType(), - null, - $this->declaringClass, + selfClass: $this->declaringClass, ); } diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index 01f69e3ccb3..73293225ee3 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -107,9 +107,8 @@ public function getNativeType(): Type if ($this->nativeType === null) { $this->nativeType = TypehintHelper::decideTypeFromReflection( $this->reflection->getType(), - null, - $this->declaringClass, - $this->isVariadic(), + selfClass: $this->declaringClass, + isVariadic: $this->isVariadic(), ); } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 8c4ea07439a..23d8f50e63f 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -175,8 +175,7 @@ public function getNativeType(): Type if ($this->finalNativeType === null) { $this->finalNativeType = TypehintHelper::decideTypeFromReflection( $this->nativeType, - null, - $this->declaringClass, + selfClass: $this->declaringClass, ); } diff --git a/src/Rules/Api/ApiInstanceofRule.php b/src/Rules/Api/ApiInstanceofRule.php index a1769055631..db654923c2e 100644 --- a/src/Rules/Api/ApiInstanceofRule.php +++ b/src/Rules/Api/ApiInstanceofRule.php @@ -93,7 +93,7 @@ private function processCoveredClass(Node\Expr\Instanceof_ $node, Scope $scope, return []; } - $classType = new ObjectType($classReflection->getName(), null, $classReflection); + $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $exprType = $scope->getType($node->expr); if ($exprType instanceof UnionType) { diff --git a/src/Rules/Arrays/AllowedArrayKeysTypes.php b/src/Rules/Arrays/AllowedArrayKeysTypes.php index 2b15a4eb651..2178b579fb0 100644 --- a/src/Rules/Arrays/AllowedArrayKeysTypes.php +++ b/src/Rules/Arrays/AllowedArrayKeysTypes.php @@ -74,8 +74,7 @@ public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type } return new MixedType( - false, - new UnionType([ + subtractedType: new UnionType([ new ArrayType(new MixedType(), new MixedType()), new ObjectWithoutClassType(), new ResourceType(), diff --git a/src/Rules/DeadCode/UnusedPrivateConstantRule.php b/src/Rules/DeadCode/UnusedPrivateConstantRule.php index 2e860a391fa..597b2992780 100644 --- a/src/Rules/DeadCode/UnusedPrivateConstantRule.php +++ b/src/Rules/DeadCode/UnusedPrivateConstantRule.php @@ -33,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array } $classReflection = $node->getClassReflection(); - $classType = new ObjectType($classReflection->getName(), null, $classReflection); + $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $constants = []; foreach ($node->getConstants() as $constant) { diff --git a/src/Rules/DeadCode/UnusedPrivateMethodRule.php b/src/Rules/DeadCode/UnusedPrivateMethodRule.php index e5b561af65d..dd589494e61 100644 --- a/src/Rules/DeadCode/UnusedPrivateMethodRule.php +++ b/src/Rules/DeadCode/UnusedPrivateMethodRule.php @@ -38,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array return []; } $classReflection = $node->getClassReflection(); - $classType = new ObjectType($classReflection->getName(), null, $classReflection); + $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $constructor = null; if ($classReflection->hasConstructor()) { $constructor = $classReflection->getConstructor(); diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 239e7320561..907991fb756 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -50,7 +50,7 @@ public function processNode(Node $node, Scope $scope): array return []; } $classReflection = $node->getClassReflection(); - $classType = new ObjectType($classReflection->getName(), null, $classReflection); + $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $properties = []; foreach ($node->getProperties() as $property) { if (!$property->isPrivate()) { diff --git a/src/Rules/PhpDoc/AssertRuleHelper.php b/src/Rules/PhpDoc/AssertRuleHelper.php index 473036edb10..05d5db8cb4b 100644 --- a/src/Rules/PhpDoc/AssertRuleHelper.php +++ b/src/Rules/PhpDoc/AssertRuleHelper.php @@ -61,7 +61,7 @@ public function check( if ($reflection instanceof ExtendedMethodReflection && !$reflection->isStatic()) { $class = $reflection->getDeclaringClass(); - $parametersByName['this'] = new ObjectType($class->getName(), null, $class); + $parametersByName['this'] = new ObjectType($class->getName(), classReflection: $class); } $context = InitializerExprContext::createEmpty(); diff --git a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php index f7907395ad4..80a254073bc 100644 --- a/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleSelfOutTypeRule.php @@ -42,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array } $classReflection = $method->getDeclaringClass(); - $classType = new ObjectType($classReflection->getName(), null, $classReflection); + $classType = new ObjectType($classReflection->getName(), classReflection: $classReflection); $errors = []; if (!$classType->isSuperTypeOf($selfOutType)->yes()) { diff --git a/src/Rules/Traits/ConflictingTraitConstantsRule.php b/src/Rules/Traits/ConflictingTraitConstantsRule.php index 4e38e44bcb9..85ea054c4ea 100644 --- a/src/Rules/Traits/ConflictingTraitConstantsRule.php +++ b/src/Rules/Traits/ConflictingTraitConstantsRule.php @@ -190,7 +190,7 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect ->build(); } } elseif ($constantNativeType === null) { - $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $this->reflectionProvider->getClass($traitDeclaringClass->getName())); + $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, selfClass: $this->reflectionProvider->getClass($traitDeclaringClass->getName())); $errors[] = RuleErrorBuilder::message(sprintf( 'Constant %s::%s overriding constant %s::%s (%s) should also have native type %s.', $classReflection->getDisplayName(), @@ -204,7 +204,7 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect ->identifier('classConstant.missingNativeType') ->build(); } else { - $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, null, $this->reflectionProvider->getClass($traitDeclaringClass->getName())); + $traitNativeTypeType = TypehintHelper::decideTypeFromReflection($traitNativeType, selfClass: $this->reflectionProvider->getClass($traitDeclaringClass->getName())); $constantNativeTypeType = ParserNodeTypeToPHPStanType::resolve($constantNativeType, $classReflection); if (!$traitNativeTypeType->equals($constantNativeTypeType)) { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 1009a53a0c2..8512285772a 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -103,10 +103,10 @@ protected function getAnalysisResult(array|int $numFileErrors, int $numGenericEr $fileErrors = array_slice([ new Error('Foo', self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 4), new Error('Foo', self::DIRECTORY_PATH . '/foo.php', 1), - new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5, true, null, null, 'a tip'), + new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5, tip: 'a tip'), new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2), new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', null), - new Error('Foobar\\Buz', self::DIRECTORY_PATH . '/foo.php', 5, true, null, null, 'a tip', null, null, 'foobar.buz'), + new Error('Foobar\\Buz', self::DIRECTORY_PATH . '/foo.php', 5, tip: 'a tip', identifier: 'foobar.buz'), ], $offsetFileErrors, $numFileErrors); $genericErrors = array_slice([ diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 8bcc663327a..d8a02005d63 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -204,8 +204,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 1e3b55b0ee9..3e1383bdb4d 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -201,8 +201,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index f0992770979..a8b573809a2 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -204,8 +204,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index 6600512da1d..22c57206f27 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -203,8 +203,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index c006d9b84b1..c10dd280824 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -203,8 +203,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index 3fee19deb38..5688e62df7d 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -201,8 +201,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index a703decac4d..bbde2f2aa8b 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -101,8 +101,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index b0a39b8ccf6..292557f12ac 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -459,8 +459,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/ClosureTypeFactory.php b/src/Type/ClosureTypeFactory.php index fb36d04c44c..a4a848f545b 100644 --- a/src/Type/ClosureTypeFactory.php +++ b/src/Type/ClosureTypeFactory.php @@ -82,7 +82,7 @@ public function isOptional(): bool public function getType(): Type { - return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), null, null, $this->reflection->isVariadic()); + return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), isVariadic: $this->reflection->isVariadic()); } public function passedByReference(): PassedByReference diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 1093e24cb4c..302b93a8c83 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -36,7 +36,7 @@ public function __construct( ?ClassReflection $classReflection = null, ) { - parent::__construct($className, null, $classReflection); + parent::__construct($className, classReflection: $classReflection); } public function getEnumCaseName(): string diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index d3f6af21376..3cd18f611d8 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -154,8 +154,7 @@ private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameSco $this->resolvedPhpDocBlockCache = array_slice( $this->resolvedPhpDocBlockCache, 1, - null, - true, + preserve_keys: true, ); $this->resolvedPhpDocBlockCacheCount--; @@ -198,8 +197,7 @@ private function getNameScopeMap(string $fileName): array $this->memoryCache = array_slice( $this->memoryCache, 1, - null, - true, + preserve_keys: true, ); $this->memoryCacheCount--; } @@ -541,7 +539,7 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; $currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null; $typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? []; - $nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, false, $constUses, $lookForTrait); + $nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, constUses: $constUses, typeAliasClassName: $lookForTrait); $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); $templateTypeScope = $nameScope->getTemplateTypeScope(); if ($templateTypeScope === null) { @@ -586,9 +584,8 @@ function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFoun $functionName, ($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty()), $typeAliasesMap, - false, - $constUses, - $lookForTrait, + constUses: $constUses, + typeAliasClassName: $lookForTrait, ); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index e38e5be35a4..92ebb92cf17 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -133,8 +133,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/Generic/TemplateGenericObjectType.php b/src/Type/Generic/TemplateGenericObjectType.php index 0c58b3b41e7..8236a7094e1 100644 --- a/src/Type/Generic/TemplateGenericObjectType.php +++ b/src/Type/Generic/TemplateGenericObjectType.php @@ -25,7 +25,7 @@ public function __construct( ?Type $default, ) { - parent::__construct($bound->getClassName(), $bound->getTypes(), null, null, $bound->getVariances()); + parent::__construct($bound->getClassName(), $bound->getTypes(), variances: $bound->getVariances()); $this->scope = $scope; $this->strategy = $templateTypeStrategy; diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index dd4764ee666..0471bc249c4 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -117,7 +117,7 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): TemplateType { - return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance(), null, $tag->getDefault()); + return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance(), default: $tag->getDefault()); } } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index fcb6fcd8936..0dd713bee8d 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -88,8 +88,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index fd30ee8bd88..bd52f459116 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -366,7 +366,7 @@ public function getSmallerOrEqualType(PhpVersion $phpVersion): Type public function getGreaterType(PhpVersion $phpVersion): Type { // All truthy types, but also '0' - return new MixedType(false, new UnionType([ + return new MixedType(subtractedType: new UnionType([ new NullType(), new ConstantBooleanType(false), new ConstantIntegerType(0), diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index e98bdb9626b..edaead6006c 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -142,9 +142,7 @@ private static function createFromReflection(ClassReflection $reflection): self return new GenericObjectType( $reflection->getName(), $reflection->typeMapToList($reflection->getActiveTemplateTypeMap()), - null, - null, - $reflection->varianceMapToList($reflection->getCallSiteVarianceMap()), + variances: $reflection->varianceMapToList($reflection->getCallSiteVarianceMap()), ); } diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index fd103b4f6a2..6674b653746 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -50,12 +50,11 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $variant->getTemplateTypeMap(), $variant->getResolvedTemplateTypeMap(), $variant instanceof ExtendedParametersAcceptor ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), - [], - $variant->getThrowPoints(), - $variant->getImpurePoints(), - $variant->getInvalidateExpressions(), - $variant->getUsedVariables(), - $variant->acceptsNamedArguments(), + throwPoints: $variant->getThrowPoints(), + impurePoints: $variant->getImpurePoints(), + invalidateExpressions: $variant->getInvalidateExpressions(), + usedVariables: $variant->getUsedVariables(), + acceptsNamedArguments: $variant->acceptsNamedArguments(), ); } diff --git a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php index 80a49d7cfc2..5b4a28b2ec9 100644 --- a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php @@ -28,7 +28,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $arrayType = new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new IntegerType(), new IntegerType()], [2], [], TrinaryLogic::createYes()); + $arrayType = new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new IntegerType(), new IntegerType()], [2], isList: TrinaryLogic::createYes()); $numberType = TypeUtils::toBenevolentUnion(TypeCombinator::union(new IntegerType(), new FloatType())); if (count($functionCall->getArgs()) < 1) { diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 37d66aabc27..76d74b8477f 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -135,7 +135,7 @@ private static function createConstantArrayFrom(array $constantArray, Scope $sco $i++; } - return new ConstantArrayType($keyTypes, $valueTypes, $isList ? [$i] : [0], [], TrinaryLogic::createFromBoolean(array_is_list($constantArray))); + return new ConstantArrayType($keyTypes, $valueTypes, $isList ? [$i] : [0], isList: TrinaryLogic::createFromBoolean(array_is_list($constantArray))); } } diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 5a99743a181..832bb701401 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -130,7 +130,7 @@ public function parseGroups(string $regex): ?RegexAstWalkResult private function createEmptyTokenTreeNode(TreeNode $parentAst): TreeNode { - return new TreeNode('token', ['token' => 'literal', 'value' => '', 'namespace' => 'default'], [], $parentAst); + return new TreeNode('token', ['token' => 'literal', 'value' => '', 'namespace' => 'default'], parent: $parentAst); } private function updateAlternationAstRemoveVerticalBarsAndAddEmptyToken(TreeNode $ast): void @@ -168,7 +168,7 @@ private function updateCapturingAstAddEmptyToken(TreeNode $ast): void return; } - $emptyAlternationAst = new TreeNode('#alternation', null, [], $ast); + $emptyAlternationAst = new TreeNode('#alternation', parent: $ast); $emptyAlternationAst->setChildren([$this->createEmptyTokenTreeNode($emptyAlternationAst)]); $ast->setChildren([$emptyAlternationAst]); } diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index 4ed9c79c1c3..3e83e3d8195 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -81,8 +81,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 9db20e5b348..61e4c7925e8 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -88,8 +88,7 @@ public function getStaticObjectType(): ObjectType $this->classReflection->getName(), $this->classReflection->typeMapToList($typeMap), $this->subtractedType, - null, - $this->classReflection->varianceMapToList($varianceMap), + variances: $this->classReflection->varianceMapToList($varianceMap), ); } diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index 382e4208ad9..93fe12d5558 100644 --- a/src/Type/StaticTypeFactory.php +++ b/src/Type/StaticTypeFactory.php @@ -35,7 +35,7 @@ public static function truthy(): Type static $truthy; if ($truthy === null) { - $truthy = new MixedType(false, self::falsey()); + $truthy = new MixedType(subtractedType: self::falsey()); } return $truthy; diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 0f1778aa213..44fa96ab154 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -171,8 +171,7 @@ public function toArray(): Type [new ConstantIntegerType(0)], [$this], [1], - [], - TrinaryLogic::createYes(), + isList: TrinaryLogic::createYes(), ); } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index e6cf82bf5f9..1b460f8b68b 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -619,7 +619,7 @@ private static function intersectWithSubtractedType( if ($b->isSuperTypeOf($subtractedTypeTmp)->yes()) { return $a->getTypeWithoutSubtractedType(); } - $subtractedType = new MixedType(false, $b); + $subtractedType = new MixedType(subtractedType: $b); } } diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 68ab00e39cf..41c85112a78 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -36,7 +36,7 @@ public static function decideTypeFromReflection( } if ($reflectionType instanceof ReflectionUnionType) { - $type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, null, $selfClass, false), $reflectionType->getTypes())); + $type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, selfClass: $selfClass), $reflectionType->getTypes())); return self::decideType($type, $phpDocType); } @@ -44,7 +44,7 @@ public static function decideTypeFromReflection( if ($reflectionType instanceof ReflectionIntersectionType) { $types = []; foreach ($reflectionType->getTypes() as $innerReflectionType) { - $innerType = self::decideTypeFromReflection($innerReflectionType, null, $selfClass, false); + $innerType = self::decideTypeFromReflection($innerReflectionType, selfClass: $selfClass); if (!$innerType->isObject()->yes()) { return new NeverType(); } diff --git a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php index b7f4e23bf02..34b7c46232f 100644 --- a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php +++ b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php @@ -38,17 +38,11 @@ public function testArgumentReorderAllNamed(): void $args = [ new Arg( new LNumber(0), - false, - false, - [], - new Identifier('flags'), + name: new Identifier('flags'), ), new Arg( new String_('my json value'), - false, - false, - [], - new Identifier('value'), + name: new Identifier('value'), ), ]; $funcCall = new FuncCall($funcName, $args); @@ -88,17 +82,11 @@ public function testArgumentReorderAllNamedWithSkipped(): void $args = [ new Arg( new LNumber(128), - false, - false, - [], - new Identifier('depth'), + name: new Identifier('depth'), ), new Arg( new String_('my json value'), - false, - false, - [], - new Identifier('value'), + name: new Identifier('value'), ), ]; $funcCall = new FuncCall($funcName, $args); @@ -141,10 +129,7 @@ public function testMissingRequiredParameter(): void $args = [ new Arg( new LNumber(128), - false, - false, - [], - new Identifier('depth'), + name: new Identifier('depth'), ), ]; $funcCall = new FuncCall($funcName, $args); diff --git a/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php b/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php index 6ee268c32ab..329b84522c6 100644 --- a/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php +++ b/tests/PHPStan/Analyser/ArgumentsNormalizerTest.php @@ -270,7 +270,7 @@ public function testReorderValid( $arguments = []; foreach ($argumentSettings as [$type, $name]) { - $arguments[] = new Arg(new TypeExpr($type), false, false, [], $name === null ? null : new Identifier($name)); + $arguments[] = new Arg(new TypeExpr($type), name: $name === null ? null : new Identifier($name)); } $normalized = ArgumentsNormalizer::reorderFuncArguments( @@ -348,7 +348,7 @@ public function testReorderInvalid( $arguments = []; foreach ($argumentSettings as [$type, $name]) { - $arguments[] = new Arg(new TypeExpr($type), false, false, [], $name === null ? null : new Identifier($name)); + $arguments[] = new Arg(new TypeExpr($type), name: $name === null ? null : new Identifier($name)); } $normalized = ArgumentsNormalizer::reorderFuncArguments( diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index da7d60bd3da..c8509d48c8d 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -533,7 +533,7 @@ public function testIsAlwaysTerminating( ->assignVariable('cond', new MixedType(), new MixedType(), TrinaryLogic::createYes()) ->assignVariable('arr', new ArrayType(new MixedType(), new MixedType()), new ArrayType(new MixedType(), new MixedType()), TrinaryLogic::createYes()); $result = $nodeScopeResolver->processStmtNodes( - new Stmt\Namespace_(null, $stmts), + new Stmt\Namespace_(stmts: $stmts), $stmts, $scope, static function (): void { diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php index ace5a21c5b1..546eda16f84 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php @@ -424,7 +424,7 @@ public function testEndOfFileNewlines( if ($resource === false) { throw new ShouldNotHappenException(); } - $outputStream = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, false); + $outputStream = new StreamOutput($resource, decorated: false); $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $outputStream); $output = new SymfonyOutput($outputStream, new SymfonyStyle($errorConsoleStyle)); diff --git a/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php index 6618b6effea..4fb960aa662 100644 --- a/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/CheckstyleErrorFormatterTest.php @@ -141,9 +141,8 @@ public function testTraitPath(): void 'Foo', __DIR__ . '/FooTrait.php (in context of class Foo)', 5, - true, - __DIR__ . '/Foo.php', - __DIR__ . '/FooTrait.php', + filePath: __DIR__ . '/Foo.php', + traitFilePath: __DIR__ . '/FooTrait.php', ); $formatter->formatErrors(new AnalysisResult( [$error], @@ -172,9 +171,7 @@ public function testIdentifier(): void 'Foo', __DIR__ . '/FooTrait.php', 5, - true, - __DIR__ . '/Foo.php', - null, + filePath: __DIR__ . '/Foo.php', ))->withIdentifier('argument.type'); $formatter->formatErrors(new AnalysisResult( [$error], diff --git a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php index ff1626d7d29..a2baee1e863 100644 --- a/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/JsonErrorFormatterTest.php @@ -252,7 +252,7 @@ public function testFormatTip(string $tip, string $expectedTip): void { $formatter = new JsonErrorFormatter(false); $formatter->formatErrors(new AnalysisResult([ - new Error('Foo', '/foo/bar.php', 1, true, null, null, $tip), + new Error('Foo', '/foo/bar.php', 1, tip: $tip), ], [], [], [], [], false, null, true, 0, false, []), $this->getOutput()); $content = $this->getOutputContent(); diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 1ada3c46241..4384ae77b0d 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -259,7 +259,7 @@ public function testFormatErrors( public function testEditorUrlWithTrait(): void { $formatter = $this->createErrorFormatter('editor://%file%/%line%'); - $error = new Error('Test', 'Foo.php (in context of trait)', 12, true, 'Foo.php', 'Bar.php'); + $error = new Error('Test', 'Foo.php (in context of trait)', 12, filePath: 'Foo.php', traitFilePath: 'Bar.php'); $formatter->formatErrors(new AnalysisResult([$error], [], [], [], [], false, null, true, 0, false, []), $this->getOutput()); $this->assertStringContainsString('Bar.php', $this->getOutputContent()); @@ -272,7 +272,7 @@ public function testEditorUrlWithRelativePath(): void } $formatter = $this->createErrorFormatter('editor://custom/path/%relFile%/%line%'); - $error = new Error('Test', 'Foo.php', 12, true, self::DIRECTORY_PATH . '/rel/Foo.php'); + $error = new Error('Test', 'Foo.php', 12, filePath: self::DIRECTORY_PATH . '/rel/Foo.php'); $formatter->formatErrors(new AnalysisResult([$error], [], [], [], [], false, null, true, 0, false, []), $this->getOutput(true)); $this->assertStringContainsString('editor://custom/path/rel/Foo.php', $this->getOutputContent(true)); @@ -281,7 +281,7 @@ public function testEditorUrlWithRelativePath(): void public function testEditorUrlWithCustomTitle(): void { $formatter = $this->createErrorFormatter('editor://any', '%relFile%:%line%'); - $error = new Error('Test', 'Foo.php', 12, true, self::DIRECTORY_PATH . '/rel/Foo.php'); + $error = new Error('Test', 'Foo.php', 12, filePath: self::DIRECTORY_PATH . '/rel/Foo.php'); $formatter->formatErrors(new AnalysisResult([$error], [], [], [], [], false, null, true, 0, false, []), $this->getOutput(true)); $this->assertStringContainsString('rel/Foo.php:12', $this->getOutputContent(true)); diff --git a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php index b26eda58497..0c39f2271f5 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php @@ -491,7 +491,7 @@ public static function dumpInputSymbols(): void { $symbols = self::scrapeInputSymbols(); $symbolsFile = self::getPhpSymbolsFile(); - @mkdir(dirname($symbolsFile), 0777, true); + @mkdir(dirname($symbolsFile), recursive: true); $result = file_put_contents($symbolsFile, implode("\n", $symbols)); if ($result !== false) { diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index e701997c6cb..fe02aeb58e5 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -57,12 +57,12 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], [ - new ArrayType(new MixedType(), new MixedType(false, StaticTypeFactory::falsey())), + new ArrayType(new MixedType(), new MixedType(subtractedType: StaticTypeFactory::falsey())), new ConstantArrayType([], []), TrinaryLogic::createYes(), ], [ - new ArrayType(new MixedType(), new MixedType(false, new NullType())), + new ArrayType(new MixedType(), new MixedType(subtractedType: new NullType())), new ConstantArrayType([], []), TrinaryLogic::createYes(), ], diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index a59308f40c9..739267f0e53 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -355,58 +355,58 @@ public function dataAccepts(): array TrinaryLogic::createYes(), ], [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createNo()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createNo()), + new CallableType(isPure: TrinaryLogic::createNo()), + new CallableType(isPure: TrinaryLogic::createNo()), TrinaryLogic::createYes(), ], [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createNo()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createNo()), + new CallableType(isPure: TrinaryLogic::createYes()), TrinaryLogic::createYes(), ], [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), TrinaryLogic::createYes(), ], [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createNo()), + new CallableType(isPure: TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createNo()), TrinaryLogic::createNo(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createNo()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createNo()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createNo()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createNo()), TrinaryLogic::createYes(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createNo()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createYes()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createNo()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createYes()), TrinaryLogic::createNo(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createMaybe()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createYes()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createMaybe()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createYes()), TrinaryLogic::createYes(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createMaybe()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createNo()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createMaybe()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createNo()), TrinaryLogic::createYes(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createMaybe()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createMaybe()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createMaybe()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createMaybe()), TrinaryLogic::createYes(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createYes()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createYes()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createYes()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createYes()), TrinaryLogic::createYes(), ], [ - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createYes()), - new CallableType([], new VoidType(), false, null, null, [], TrinaryLogic::createNo()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createYes()), + new CallableType([], new VoidType(), false, isPure: TrinaryLogic::createNo()), TrinaryLogic::createNo(), ], ]; diff --git a/tests/PHPStan/Type/ClosureTypeTest.php b/tests/PHPStan/Type/ClosureTypeTest.php index ccaaa4ca78f..5602adf7b9e 100644 --- a/tests/PHPStan/Type/ClosureTypeTest.php +++ b/tests/PHPStan/Type/ClosureTypeTest.php @@ -24,7 +24,7 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], [ - new ClosureType([], new MixedType(), false, null, null, null, [], [], []), + new ClosureType([], new MixedType(), false, impurePoints: []), new ClosureType([], new MixedType(), false), TrinaryLogic::createMaybe(), ], diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index b047b86a691..2fca7b7d970 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -190,7 +190,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -226,7 +226,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], [0], [1]), + ], optionalKeys: [1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -242,7 +242,7 @@ public function dataAccepts(): iterable new ConstantStringType('limit'), ], [ new IntegerType(), - ], [0], [0]), + ], optionalKeys: [0]), new ConstantArrayType([ new ConstantStringType('limit'), ], [ @@ -272,7 +272,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -290,7 +290,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('color'), ], [ @@ -306,7 +306,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('sound'), ], [ @@ -322,14 +322,14 @@ public function dataAccepts(): iterable ], [ new StringType(), new StringType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('foo'), new ConstantStringType('bar'), ], [ new ConstantStringType('s'), new ConstantStringType('m'), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), TrinaryLogic::createYes(), ]; @@ -340,7 +340,7 @@ public function dataAccepts(): iterable ], [ new StringType(), new IntegerType(), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new ConstantArrayType([ new ConstantStringType('sorton'), new ConstantStringType('limit'), @@ -923,14 +923,14 @@ public function dataValuesArray(): iterable ], [ new ConstantStringType('a'), new ConstantStringType('b'), - ], [20], [], TrinaryLogic::createNo()), + ], [20], isList: TrinaryLogic::createNo()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantStringType('a'), new ConstantStringType('b'), - ], [2], [], TrinaryLogic::createYes()), + ], [2], isList: TrinaryLogic::createYes()), ]; yield 'optional-1' => [ diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 3ffc8f9db7b..53223859631 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -32,48 +32,48 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], 2 => [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new IntegerType(), TrinaryLogic::createNo(), ], 3 => [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new ConstantIntegerType(1), TrinaryLogic::createNo(), ], 4 => [ - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), new IntegerType(), TrinaryLogic::createMaybe(), ], 5 => [ - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), new MixedType(), TrinaryLogic::createMaybe(), ], 6 => [ new MixedType(), - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), TrinaryLogic::createYes(), ], 7 => [ - new MixedType(false, new ConstantIntegerType(1)), - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), TrinaryLogic::createYes(), ], 8 => [ - new MixedType(false, new IntegerType()), - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new IntegerType()), + new MixedType(subtractedType: new ConstantIntegerType(1)), TrinaryLogic::createMaybe(), ], 9 => [ - new MixedType(false, new ConstantIntegerType(1)), - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new ConstantIntegerType(1)), + new MixedType(subtractedType: new IntegerType()), TrinaryLogic::createYes(), ], 10 => [ - new MixedType(false, new StringType()), - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new StringType()), + new MixedType(subtractedType: new IntegerType()), TrinaryLogic::createMaybe(), ], 11 => [ @@ -82,53 +82,53 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], 12 => [ - new MixedType(false, new ObjectWithoutClassType()), + new MixedType(subtractedType: new ObjectWithoutClassType()), new ObjectWithoutClassType(), TrinaryLogic::createNo(), ], 13 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectWithoutClassType(), TrinaryLogic::createMaybe(), ], 14 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectWithoutClassType(new ObjectType('Exception')), TrinaryLogic::createYes(), ], 15 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectWithoutClassType(new ObjectType('InvalidArgumentException')), TrinaryLogic::createMaybe(), ], 16 => [ - new MixedType(false, new ObjectType('InvalidArgumentException')), + new MixedType(subtractedType: new ObjectType('InvalidArgumentException')), new ObjectWithoutClassType(new ObjectType('Exception')), TrinaryLogic::createYes(), ], 17 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectType('Exception'), TrinaryLogic::createNo(), ], 18 => [ - new MixedType(false, new ObjectType('InvalidArgumentException')), + new MixedType(subtractedType: new ObjectType('InvalidArgumentException')), new ObjectType('Exception'), TrinaryLogic::createMaybe(), ], 19 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectType('InvalidArgumentException'), TrinaryLogic::createNo(), ], 20 => [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new MixedType(), TrinaryLogic::createMaybe(), ], 21 => [ - new MixedType(false, new ObjectType('Exception')), - new MixedType(false, new ObjectType('stdClass')), + new MixedType(subtractedType: new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('stdClass')), TrinaryLogic::createMaybe(), ], 22 => [ @@ -137,7 +137,7 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], 23 => [ - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), new NeverType(), TrinaryLogic::createYes(), ], @@ -147,7 +147,7 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createYes(), ], 25 => [ - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), new UnionType([new StringType(), new IntegerType()]), TrinaryLogic::createYes(), ], diff --git a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php index 9770a62a725..758ecbbb69b 100644 --- a/tests/PHPStan/Type/TypeGetFiniteTypesTest.php +++ b/tests/PHPStan/Type/TypeGetFiniteTypesTest.php @@ -97,28 +97,28 @@ public function dataGetFiniteTypes(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [2], [], TrinaryLogic::createYes()), + ], [2], isList: TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(true), new ConstantBooleanType(false), - ], [2], [], TrinaryLogic::createYes()), + ], [2], isList: TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(true), - ], [2], [], TrinaryLogic::createYes()), + ], [2], isList: TrinaryLogic::createYes()), new ConstantArrayType([ new ConstantIntegerType(0), new ConstantIntegerType(1), ], [ new ConstantBooleanType(false), new ConstantBooleanType(false), - ], [2], [], TrinaryLogic::createYes()), + ], [2], isList: TrinaryLogic::createYes()), ], ]; } diff --git a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php index c630063120e..8947396deb8 100644 --- a/tests/PHPStan/Type/TypeToPhpDocNodeTest.php +++ b/tests/PHPStan/Type/TypeToPhpDocNodeTest.php @@ -66,7 +66,7 @@ public function dataToPhpDocNode(): iterable new ConstantIntegerType(2), new ConstantIntegerType(3), new ConstantIntegerType(4), - ], [0], [2]), + ], optionalKeys: [2]), 'array{foo: 1, bar: 2, baz?: 3, \'$ref\': 4}', ]; @@ -104,7 +104,7 @@ public function dataToPhpDocNode(): iterable new ConstantStringType('foo'), new ConstantStringType('bar'), new ConstantStringType('baz'), - ], [0], [2]), + ], optionalKeys: [2]), 'array{1: \'foo\', 2: \'bar\', 3?: \'baz\'}', ]; @@ -151,7 +151,7 @@ public function dataToPhpDocNode(): iterable new StringType(), new IntegerType(), new MixedType(), - ], null, null, [ + ], variances: [ TemplateTypeVariance::createInvariant(), TemplateTypeVariance::createContravariant(), TemplateTypeVariance::createBivariant(), diff --git a/tests/PHPStan/Type/VerbosityLevelTest.php b/tests/PHPStan/Type/VerbosityLevelTest.php index 0ff71d50257..220df284b8f 100644 --- a/tests/PHPStan/Type/VerbosityLevelTest.php +++ b/tests/PHPStan/Type/VerbosityLevelTest.php @@ -30,9 +30,7 @@ public function dataGetRecommendedLevelByType(): iterable new IntegerType(), new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]), ], - null, - null, - [TemplateTypeVariance::createInvariant(), TemplateTypeVariance::createInvariant()], + variances: [TemplateTypeVariance::createInvariant(), TemplateTypeVariance::createInvariant()], ), new GenericObjectType( 'ArrayAccess', @@ -40,9 +38,7 @@ public function dataGetRecommendedLevelByType(): iterable new IntegerType(), new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]), ], - null, - null, - [TemplateTypeVariance::createInvariant(), TemplateTypeVariance::createInvariant()], + variances: [TemplateTypeVariance::createInvariant(), TemplateTypeVariance::createInvariant()], ), VerbosityLevel::precise(), ]; From 24697cb1506384bc82eb34c8377e52fa6f5a0fd7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 09:22:59 +0200 Subject: [PATCH 1456/3097] Update simple-downgrader --- compiler/composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/composer.lock b/compiler/composer.lock index af66180aeb3..24613813618 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -339,16 +339,16 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "0ce57fe11a7577f22752d9676263c2e3653a9c56" + "reference": "fd1e6964abdd6594523093592afd637133fec329" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/0ce57fe11a7577f22752d9676263c2e3653a9c56", - "reference": "0ce57fe11a7577f22752d9676263c2e3653a9c56", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fd1e6964abdd6594523093592afd637133fec329", + "reference": "fd1e6964abdd6594523093592afd637133fec329", "shasum": "" }, "require": { @@ -383,9 +383,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.2" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.3" }, - "time": "2025-05-26T16:02:34+00:00" + "time": "2025-05-28T07:21:17+00:00" }, { "name": "phpstan/phpdoc-parser", From 079fa1bc50e2db6eda0dcbb995db0ed889b7beca Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 10:48:28 +0200 Subject: [PATCH 1457/3097] Update simple-downgrader --- compiler/composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/composer.lock b/compiler/composer.lock index 24613813618..57f9032dc42 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -339,16 +339,16 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "fd1e6964abdd6594523093592afd637133fec329" + "reference": "3dc5bb651487e8abda78d9371144939ba92c0829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/fd1e6964abdd6594523093592afd637133fec329", - "reference": "fd1e6964abdd6594523093592afd637133fec329", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/3dc5bb651487e8abda78d9371144939ba92c0829", + "reference": "3dc5bb651487e8abda78d9371144939ba92c0829", "shasum": "" }, "require": { @@ -383,9 +383,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.3" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.4" }, - "time": "2025-05-28T07:21:17+00:00" + "time": "2025-05-28T08:45:20+00:00" }, { "name": "phpstan/phpdoc-parser", From c3f27ec2358481f029b6916ab3f286d025397665 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 28 May 2025 11:09:15 +0200 Subject: [PATCH 1458/3097] Fix "Named arguments are supported only on PHP 8.0 and later." false positive --- .github/workflows/e2e-tests.yml | 3 +++ e2e/composer-version-named-args/test.php | 34 ++++++++++++++++++++++++ src/Analyser/ConstantResolver.php | 4 ++- src/Analyser/MutatingScope.php | 5 +++- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 e2e/composer-version-named-args/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b143c3e4466..faa4cb81a11 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -391,6 +391,9 @@ jobs: cd e2e/composer-version-config composer install ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/composer-version-named-args + ../../bin/phpstan analyze test.php --level=0 steps: - name: "Checkout" diff --git a/e2e/composer-version-named-args/test.php b/e2e/composer-version-named-args/test.php new file mode 100644 index 00000000000..97bae389919 --- /dev/null +++ b/e2e/composer-version-named-args/test.php @@ -0,0 +1,34 @@ += 80400) { + } else { + } + return [ + new Exception(previous: new Exception()), + ]; + } +} + +class HelloWorld2 +{ + /** @return mixed[] */ + public function sayHello(): array|null + { + return [ + PHP_VERSION_ID >= 80400 ? 1 : 0, + new Exception(previous: new Exception()), + ]; + } +} diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 846188b9858..52dec8da3b9 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -34,6 +34,8 @@ final class ConstantResolver { + public const PHP_MIN_ANALYZABLE_VERSION_ID = 50207; + /** @var array */ private array $currentlyResolving = []; @@ -141,7 +143,7 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type return $this->createInteger($minRelease, $maxRelease); } if ($resolvedConstantName === 'PHP_VERSION_ID') { - $minVersion = 50207; + $minVersion = self::PHP_MIN_ANALYZABLE_VERSION_ID; $maxVersion = null; if ($minPhpVersion !== null) { $minVersion = max($minVersion, $minPhpVersion->getVersionId()); diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cdb919da1fa..ea86730f309 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -52,6 +52,7 @@ use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; +use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\Assertions; @@ -6235,8 +6236,10 @@ public function getIterableValueType(Type $iteratee): Type public function getPhpVersion(): PhpVersions { + $overallPhpVersionRange = IntegerRangeType::fromInterval(ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, PhpVersionFactory::MAX_PHP_VERSION); + $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); - if ($constType !== null) { + if ($constType !== null && !$constType->equals($overallPhpVersionRange)) { return new PhpVersions($constType); } From 1c565b54a216b4b3b15a40bb9e385eed2a042de1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:24:03 +0200 Subject: [PATCH 1459/3097] Revert "Fix "Named arguments are supported only on PHP 8.0 and later." false positive" This reverts commit c3f27ec2358481f029b6916ab3f286d025397665. --- .github/workflows/e2e-tests.yml | 3 --- e2e/composer-version-named-args/test.php | 34 ------------------------ src/Analyser/ConstantResolver.php | 4 +-- src/Analyser/MutatingScope.php | 5 +--- 4 files changed, 2 insertions(+), 44 deletions(-) delete mode 100644 e2e/composer-version-named-args/test.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index faa4cb81a11..b143c3e4466 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -391,9 +391,6 @@ jobs: cd e2e/composer-version-config composer install ../../bin/phpstan analyze test.php --level=0 - - script: | - cd e2e/composer-version-named-args - ../../bin/phpstan analyze test.php --level=0 steps: - name: "Checkout" diff --git a/e2e/composer-version-named-args/test.php b/e2e/composer-version-named-args/test.php deleted file mode 100644 index 97bae389919..00000000000 --- a/e2e/composer-version-named-args/test.php +++ /dev/null @@ -1,34 +0,0 @@ -= 80400) { - } else { - } - return [ - new Exception(previous: new Exception()), - ]; - } -} - -class HelloWorld2 -{ - /** @return mixed[] */ - public function sayHello(): array|null - { - return [ - PHP_VERSION_ID >= 80400 ? 1 : 0, - new Exception(previous: new Exception()), - ]; - } -} diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 52dec8da3b9..846188b9858 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -34,8 +34,6 @@ final class ConstantResolver { - public const PHP_MIN_ANALYZABLE_VERSION_ID = 50207; - /** @var array */ private array $currentlyResolving = []; @@ -143,7 +141,7 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type return $this->createInteger($minRelease, $maxRelease); } if ($resolvedConstantName === 'PHP_VERSION_ID') { - $minVersion = self::PHP_MIN_ANALYZABLE_VERSION_ID; + $minVersion = 50207; $maxVersion = null; if ($minPhpVersion !== null) { $minVersion = max($minVersion, $minPhpVersion->getVersionId()); diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index ea86730f309..cdb919da1fa 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -52,7 +52,6 @@ use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; -use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\Assertions; @@ -6236,10 +6235,8 @@ public function getIterableValueType(Type $iteratee): Type public function getPhpVersion(): PhpVersions { - $overallPhpVersionRange = IntegerRangeType::fromInterval(ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, PhpVersionFactory::MAX_PHP_VERSION); - $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); - if ($constType !== null && !$constType->equals($overallPhpVersionRange)) { + if ($constType !== null) { return new PhpVersions($constType); } From dce77e1ea991f2ec51d2622b4249bf80f6988a39 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:15:39 +0200 Subject: [PATCH 1460/3097] Update simple-downgrader --- compiler/composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/composer.lock b/compiler/composer.lock index 57f9032dc42..73238849d28 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -339,16 +339,16 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "3dc5bb651487e8abda78d9371144939ba92c0829" + "reference": "6e40de0b168686ce500f29a49536a3c8fd25b982" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/3dc5bb651487e8abda78d9371144939ba92c0829", - "reference": "3dc5bb651487e8abda78d9371144939ba92c0829", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/6e40de0b168686ce500f29a49536a3c8fd25b982", + "reference": "6e40de0b168686ce500f29a49536a3c8fd25b982", "shasum": "" }, "require": { @@ -383,9 +383,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.4" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.5" }, - "time": "2025-05-28T08:45:20+00:00" + "time": "2025-05-28T09:11:05+00:00" }, { "name": "phpstan/phpdoc-parser", From d4fe552d292b85689039c17e59e222fec9e26dac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:17:12 +0200 Subject: [PATCH 1461/3097] Makefile target for `--fix` --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 9e007ae2cc1..273283a4af4 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,9 @@ phpstan: phpstan-result-cache: php -d memory_limit=448M bin/phpstan +phpstan-fix: + php -d memory_limit=2G bin/phpstan --fix + phpstan-generate-baseline: php -d memory_limit=448M bin/phpstan --generate-baseline From d30498a7a18f950173357a522efae404a88d8d53 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:30:55 +0200 Subject: [PATCH 1462/3097] Differ as a service --- conf/services.neon | 3 +++ src/Analyser/RuleErrorTransformer.php | 4 ++-- src/Command/AnalyseCommand.php | 2 +- src/Testing/RuleTestCase.php | 4 ++-- tests/PHPStan/Analyser/AnalyserTest.php | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/conf/services.neon b/conf/services.neon index 227d5787548..d1adbb4ae5e 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -160,6 +160,9 @@ services: - class: PHPStan\Rules\Properties\UninitializedPropertyRule + - + class: SebastianBergmann\Diff\Differ + betterReflectionSourceLocator: class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator factory: @PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory::create diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index f8b35d4aaec..439250fff46 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -32,6 +32,7 @@ final class RuleErrorTransformer public function __construct( private Parser $parser, + private Differ $differ, ) { } @@ -128,9 +129,8 @@ public function transform( $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); - $differ = new Differ(); - $fixedErrorDiff = new FixedErrorDiff($hash, $differ->diffToArray($oldCode, $newCode)); + $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diffToArray($oldCode, $newCode)); } return new Error( diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index f7a4ba6138d..b550210254a 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -534,7 +534,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $fixableErrorsByFile[$fixFile][] = $fixableError; } - $differ = new Differ(); + $differ = $container->getByType(Differ::class); foreach ($fixableErrorsByFile as $file => $fileFixableErrors) { $fileContents = FileReader::read($file); diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 8edc42c7f8e..792d7acf574 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -120,7 +120,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser $this->getParser(), self::getContainer()->getByType(DependencyResolver::class), new IgnoreErrorExtensionProvider(self::getContainer()), - new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), + self::getContainer()->getByType(RuleErrorTransformer::class), new LocalIgnoresProcessor(), ); $this->analyser = new Analyser( @@ -237,7 +237,7 @@ private function gatherAnalyserErrorsWithDelayedErrors(array $files): array $finalizer = new AnalyserResultFinalizer( $ruleRegistry, new IgnoreErrorExtensionProvider(self::getContainer()), - new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), + self::getContainer()->getByType(RuleErrorTransformer::class), $this->createScopeFactory($reflectionProvider, $this->getTypeSpecifier()), new LocalIgnoresProcessor(), true, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 7f743fe95a6..8855ddd8f69 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -670,7 +670,7 @@ private function runAnalyser( $finalizer = new AnalyserResultFinalizer( new DirectRuleRegistry([]), new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), - new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), + self::getContainer()->getByType(RuleErrorTransformer::class), $this->createScopeFactory( $this->createReflectionProvider(), self::getContainer()->getService('typeSpecifier'), @@ -749,7 +749,7 @@ private function createAnalyser(): Analyser ), new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($reflectionProvider, $fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), - new RuleErrorTransformer(self::getContainer()->getService('currentPhpVersionPhpParser')), + self::getContainer()->getByType(RuleErrorTransformer::class), new LocalIgnoresProcessor(), ); From 682b2b52674d39b4eca9caa9fb8c485bff0f1289 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 28 May 2025 13:36:32 +0200 Subject: [PATCH 1463/3097] Improved fix "Named arguments are supported only on PHP 8.0 and later" false positive --- src/Analyser/ConstantResolver.php | 4 +- src/Analyser/MutatingScope.php | 13 ++++- .../Rules/Classes/InstantiationRuleTest.php | 9 +++ .../data/named-arguments-phpversion.php | 57 +++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/named-arguments-phpversion.php diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 846188b9858..52dec8da3b9 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -34,6 +34,8 @@ final class ConstantResolver { + public const PHP_MIN_ANALYZABLE_VERSION_ID = 50207; + /** @var array */ private array $currentlyResolving = []; @@ -141,7 +143,7 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type return $this->createInteger($minRelease, $maxRelease); } if ($resolvedConstantName === 'PHP_VERSION_ID') { - $minVersion = 50207; + $minVersion = self::PHP_MIN_ANALYZABLE_VERSION_ID; $maxVersion = null; if ($minPhpVersion !== null) { $minVersion = max($minVersion, $minPhpVersion->getVersionId()); diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cdb919da1fa..051d15d960f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -52,6 +52,7 @@ use PHPStan\Parser\NewAssignedToPropertyVisitor; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; +use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Reflection\Assertions; @@ -6236,7 +6237,17 @@ public function getIterableValueType(Type $iteratee): Type public function getPhpVersion(): PhpVersions { $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); - if ($constType !== null) { + + $isOverallPhpVersionRange = false; + if ( + $constType instanceof IntegerRangeType + && $constType->getMin() === ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID + && ($constType->getMax() === null || $constType->getMax() === PhpVersionFactory::MAX_PHP_VERSION) + ) { + $isOverallPhpVersionRange = true; + } + + if ($constType !== null && !$isOverallPhpVersionRange) { return new PhpVersions($constType); } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 1a84e5e1708..0c361880e7e 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -586,4 +586,13 @@ public function testBug12951(): void ]); } + public function testNamedArgumentsPhpversion(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/named-arguments-phpversion.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/named-arguments-phpversion.php b/tests/PHPStan/Rules/Classes/data/named-arguments-phpversion.php new file mode 100644 index 00000000000..dbba8693986 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/named-arguments-phpversion.php @@ -0,0 +1,57 @@ += 8.0 + +declare(strict_types = 1); + +namespace NamedArgumentsPhpversion; + +use Exception; + +class HelloWorld +{ + /** @return mixed[] */ + public function sayHello(): array|null + { + if(PHP_VERSION_ID >= 80400) { + } else { + } + return [ + new Exception(previous: new Exception()), + ]; + } +} + +class HelloWorld2 +{ + /** @return mixed[] */ + public function sayHello(): array|null + { + return [ + PHP_VERSION_ID >= 80400 ? 1 : 0, + new Exception(previous: new Exception()), + ]; + } +} + +class HelloWorld3 +{ + /** @return mixed[] */ + public function sayHello(): array|null + { + return [ + PHP_VERSION_ID >= 70400 ? 1 : 0, + new Exception(previous: new Exception()), + ]; + } +} + +class HelloWorld4 +{ + /** @return mixed[] */ + public function sayHello(): array|null + { + return [ + PHP_VERSION_ID < 80000 ? 1 : 0, + new Exception(previous: new Exception()), + ]; + } +} From 35fbc87a4b05a2f4884480a3e0f5dbd61a8b49a5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 May 2025 20:58:47 +0200 Subject: [PATCH 1464/3097] Internal PHPStan rule - use named arguments instead of passing default parameter values --- build/PHPStan/Build/NamedArgumentsRule.php | 216 ++++++++++++++++++ build/phpstan.neon | 1 + .../PHPStan/Build/NamedArgumentsRuleTest.php | 42 ++++ tests/PHPStan/Build/data/named-arguments.php | 23 ++ 4 files changed, 282 insertions(+) create mode 100644 build/PHPStan/Build/NamedArgumentsRule.php create mode 100644 tests/PHPStan/Build/NamedArgumentsRuleTest.php create mode 100644 tests/PHPStan/Build/data/named-arguments.php diff --git a/build/PHPStan/Build/NamedArgumentsRule.php b/build/PHPStan/Build/NamedArgumentsRule.php new file mode 100644 index 00000000000..45c74f1027d --- /dev/null +++ b/build/PHPStan/Build/NamedArgumentsRule.php @@ -0,0 +1,216 @@ + + */ +final class NamedArgumentsRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Expr\CallLike::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->isFirstClassCallable()) { + return []; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + if ($this->reflectionProvider->hasFunction($node->name, $scope)) { + $function = $this->reflectionProvider->getFunction($node->name, $scope); + $variants = $function->getVariants(); + if (count($variants) !== 1) { + return []; + } + + return $this->processArgs($variants[0], $scope, $node); + } + } + + if ($node instanceof Node\Expr\New_ && $node->class instanceof Node\Name) { + if ($this->reflectionProvider->hasClass($node->class->toString())) { + $class = $this->reflectionProvider->getClass($node->class->toString()); + if ($class->hasConstructor()) { + $constructor = $class->getConstructor(); + $variants = $constructor->getVariants(); + if (count($variants) !== 1) { + return []; + } + + return $this->processArgs($variants[0], $scope, $node); + } + } + } + + if ($node instanceof Node\Expr\StaticCall && $node->class instanceof Node\Name && $node->name instanceof Node\Identifier) { + $className = $scope->resolveName($node->class); + if ($this->reflectionProvider->hasClass($className)) { + $class = $this->reflectionProvider->getClass($className); + if ($class->hasNativeMethod($node->name->toString())) { + $method = $class->getNativeMethod($node->name->toString()); + $variants = $method->getVariants(); + if (count($variants) !== 1) { + return []; + } + + return $this->processArgs($variants[0], $scope, $node); + } + } + } + + return []; + } + + /** + * @param Node\Expr\FuncCall|Node\Expr\New_|Node\Expr\StaticCall $node + * @return list + */ + private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, Node\Expr\CallLike $node): array + { + if ($acceptor->isVariadic()) { + return []; + } + $normalizedArgs = ArgumentsNormalizer::reorderArgs($acceptor, $node->getArgs()); + if ($normalizedArgs === null) { + return []; + } + + $hasNamedArgument = false; + foreach ($node->getArgs() as $arg) { + if ($arg->name === null) { + continue; + } + + $hasNamedArgument = true; + break; + } + + $errorBuilders = []; + $parameters = $acceptor->getParameters(); + $defaultValueWasPassed = []; + foreach ($normalizedArgs as $i => $normalizedArg) { + if ($normalizedArg->unpack) { + return []; + } + $parameter = $parameters[$i]; + if ($parameter->getDefaultValue() === null) { + continue; + } + $argValue = $scope->getType($normalizedArg->value); + if ($normalizedArg->name !== null) { + continue; + } + + /** @var Node\Arg|null $originalArg */ + $originalArg = $normalizedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); + if ($originalArg === null) { + if ($hasNamedArgument) { + // this is an optional parameter not passed by the user, but filled in by ArgumentsNormalizer + continue; + } + } + + if (!$argValue->equals($parameter->getDefaultValue())) { + if (count($defaultValueWasPassed) > 0) { + $errorBuilders[] = RuleErrorBuilder::message(sprintf( + 'You\'re passing a non-default value %s to parameter $%s but previous %s (%s). You can skip %s and use named argument for $%s instead.', + $argValue->describe(VerbosityLevel::precise()), + $parameter->getName(), + count($defaultValueWasPassed) === 1 ? 'argument is passing default value to its parameter' : 'arguments are passing default values to their parameters', + implode(', ', $defaultValueWasPassed), + count($defaultValueWasPassed) === 1 ? 'it' : 'them', + $parameter->getName(), + )) + ->identifier('phpstan.namedArgument') + ->line($normalizedArg->getStartLine()) + ->nonIgnorable(); + } + continue; + } else { + if ($originalArg !== null && $originalArg->name !== null) { + $errorBuilders[] = RuleErrorBuilder::message(sprintf('Named argument $%s can be omitted, type %s is the same as the default value.', $originalArg->name, $argValue->describe(VerbosityLevel::precise()))) + ->identifier('phpstan.namedArgumentWithDefaultValue') + ->nonIgnorable(); + continue; + } + } + + $defaultValueWasPassed[] = '$' . $parameter->getName(); + } + + if (count($errorBuilders) > 0) { + $errorBuilders[0]->fixNode(static function (Node $node) use ($acceptor, $hasNamedArgument, $parameters, $scope) { + /** @var Node\Expr\FuncCall|Node\Expr\New_|Node\Expr\StaticCall $node */ + $normalizedArgs = ArgumentsNormalizer::reorderArgs($acceptor, $node->getArgs()); + if ($normalizedArgs === null) { + return $node; + } + + $newArgs = []; + $skippedOptional = false; + foreach ($normalizedArgs as $i => $normalizedArg) { + /** @var Node\Arg|null $originalArg */ + $originalArg = $normalizedArg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE); + if ($originalArg === null) { + if ($hasNamedArgument) { + // this is an optional parameter not passed by the user, but filled in by ArgumentsNormalizer + continue; + } + + $originalArg = $normalizedArg; + } + $parameter = $parameters[$i]; + if ($parameter->getDefaultValue() === null) { + $newArgs[] = $originalArg; + continue; + } + $argValue = $scope->getType($normalizedArg->value); + if ($argValue->equals($parameter->getDefaultValue())) { + $skippedOptional = true; + continue; + } + + if ($skippedOptional) { + if ($parameter->getName() === '') { + throw new ShouldNotHappenException(); + } + + $newArgs[] = new Node\Arg($originalArg->value, $originalArg->byRef, $originalArg->unpack, $originalArg->getAttributes(), new Node\Identifier($parameter->getName())); + continue; + } + + $newArgs[] = $originalArg; + } + + $node->args = $newArgs; + + return $node; + }); + } + + return array_map(static fn ($builder) => $builder->build(), $errorBuilders); + } + +} diff --git a/build/phpstan.neon b/build/phpstan.neon index 1e7f00b736a..ffbbcfecac3 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -118,6 +118,7 @@ parameters: rules: - PHPStan\Build\FinalClassRule - PHPStan\Build\AttributeNamedArgumentsRule + - PHPStan\Build\NamedArgumentsRule services: - diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php new file mode 100644 index 00000000000..bf5da59ef4b --- /dev/null +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -0,0 +1,42 @@ + + */ +class NamedArgumentsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new NamedArgumentsRule($this->createReflectionProvider()); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/named-arguments.php'], [ + [ + 'You\'re passing a non-default value Exception to parameter $previous but previous argument is passing default value to its parameter ($code). You can skip it and use named argument for $previous instead.', + 14, + ], + [ + 'Named argument $code can be omitted, type 0 is the same as the default value.', + 16, + ], + [ + 'You\'re passing a non-default value Exception to parameter $previous but previous arguments are passing default values to their parameters ($message, $code). You can skip them and use named argument for $previous instead.', + 20, + ], + ]); + } + +} diff --git a/tests/PHPStan/Build/data/named-arguments.php b/tests/PHPStan/Build/data/named-arguments.php new file mode 100644 index 00000000000..d27bdc4de4d --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments.php @@ -0,0 +1,23 @@ += 8.0 + +namespace NamedArgumentRule; + +use Exception; + +class Foo +{ + + public function doFoo(): void + { + new Exception('foo', 0); + new Exception('foo', 0, null); + new Exception('foo', 0, new Exception('previous')); + new Exception('foo', previous: new Exception('previous')); + new Exception('foo', code: 0, previous: new Exception('previous')); + new Exception('foo', code: 1, previous: new Exception('previous')); + new Exception('foo', 1, new Exception('previous')); + new Exception('foo', 1); + new Exception('', 0, new Exception('previous')); + } + +} From bd75a4f42ab83c4c126074801ba719ce7390ef01 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:18:36 +0200 Subject: [PATCH 1465/3097] Auto-apply fixes from NamedArgumentsRule --- .../Type/Generic/GenericObjectTypeTest.php | 60 ++++---- tests/PHPStan/Type/TypeCombinatorTest.php | 134 +++++++++--------- 2 files changed, 95 insertions(+), 99 deletions(-) diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 3be9b7193d2..53988eac7a2 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -156,43 +156,43 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createNo(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createInvariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createInvariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createCovariant()]), TrinaryLogic::createNo(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createInvariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createContravariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createInvariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createContravariant()]), TrinaryLogic::createNo(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createCovariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createInvariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createInvariant()]), TrinaryLogic::createYes(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createCovariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createCovariant()]), TrinaryLogic::createYes(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createCovariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createContravariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createContravariant()]), TrinaryLogic::createNo(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createContravariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createInvariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createContravariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createInvariant()]), TrinaryLogic::createYes(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createContravariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createInvariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createContravariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createInvariant()]), TrinaryLogic::createYes(), ], [ - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], null, null, [TemplateTypeVariance::createContravariant()]), - new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], null, null, [TemplateTypeVariance::createCovariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')], variances: [TemplateTypeVariance::createContravariant()]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')], variances: [TemplateTypeVariance::createCovariant()]), TrinaryLogic::createNo(), ], ]; @@ -200,19 +200,19 @@ public function dataIsSuperTypeOf(): array public function dataTypeProjections(): array { - $invariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], null, null, [TemplateTypeVariance::createInvariant()]); - $invariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], null, null, [TemplateTypeVariance::createInvariant()]); - $invariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], null, null, [TemplateTypeVariance::createInvariant()]); + $invariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], variances: [TemplateTypeVariance::createInvariant()]); + $invariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], variances: [TemplateTypeVariance::createInvariant()]); + $invariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], variances: [TemplateTypeVariance::createInvariant()]); - $covariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], null, null, [TemplateTypeVariance::createCovariant()]); - $covariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], null, null, [TemplateTypeVariance::createCovariant()]); - $covariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], null, null, [TemplateTypeVariance::createCovariant()]); + $covariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], variances: [TemplateTypeVariance::createCovariant()]); + $covariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], variances: [TemplateTypeVariance::createCovariant()]); + $covariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], variances: [TemplateTypeVariance::createCovariant()]); - $contravariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], null, null, [TemplateTypeVariance::createContravariant()]); - $contravariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], null, null, [TemplateTypeVariance::createContravariant()]); - $contravariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], null, null, [TemplateTypeVariance::createContravariant()]); + $contravariantA = new GenericObjectType(E\Foo::class, [new ObjectType(E\A::class)], variances: [TemplateTypeVariance::createContravariant()]); + $contravariantB = new GenericObjectType(E\Foo::class, [new ObjectType(E\B::class)], variances: [TemplateTypeVariance::createContravariant()]); + $contravariantC = new GenericObjectType(E\Foo::class, [new ObjectType(E\C::class)], variances: [TemplateTypeVariance::createContravariant()]); - $bivariant = new GenericObjectType(E\Foo::class, [new MixedType(true)], null, null, [TemplateTypeVariance::createBivariant()]); + $bivariant = new GenericObjectType(E\Foo::class, [new MixedType(true)], variances: [TemplateTypeVariance::createBivariant()]); return [ [$invariantB, $invariantA, TrinaryLogic::createNo()], @@ -813,7 +813,7 @@ public function dataGetReferencedTypeArguments(): array TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), - ], null, null, [ + ], variances: [ TemplateTypeVariance::createCovariant(), ]), [ @@ -827,7 +827,7 @@ public function dataGetReferencedTypeArguments(): array TemplateTypeVariance::createContravariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), - ], null, null, [ + ], variances: [ TemplateTypeVariance::createContravariant(), ]), [ @@ -929,7 +929,7 @@ public function dataGetReferencedTypeArguments(): array TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), - ], null, null, [ + ], variances: [ TemplateTypeVariance::createCovariant(), ]), [ @@ -943,7 +943,7 @@ public function dataGetReferencedTypeArguments(): array TemplateTypeVariance::createCovariant(), new GenericObjectType(D\Invariant::class, [ $templateType('T'), - ], null, null, [ + ], variances: [ TemplateTypeVariance::createContravariant(), ]), [ diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index a85b6a47093..f7f7fceb128 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -995,16 +995,16 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new IntegerType()), - new MixedType(false, new StringType()), + new MixedType(subtractedType: new IntegerType()), + new MixedType(subtractedType: new StringType()), ], MixedType::class, 'mixed=implicit', ], [ [ - new MixedType(false, new IntegerType()), - new MixedType(false, new UnionType([ + new MixedType(subtractedType: new IntegerType()), + new MixedType(subtractedType: new UnionType([ new IntegerType(), new StringType(), ])), @@ -1014,8 +1014,8 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new IntegerType()), - new MixedType(false, new UnionType([ + new MixedType(subtractedType: new IntegerType()), + new MixedType(subtractedType: new UnionType([ new ConstantIntegerType(1), new StringType(), ])), @@ -1025,8 +1025,8 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ConstantIntegerType(2)), - new MixedType(false, new UnionType([ + new MixedType(subtractedType: new ConstantIntegerType(2)), + new MixedType(subtractedType: new UnionType([ new ConstantIntegerType(1), new StringType(), ])), @@ -1036,8 +1036,8 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new IntegerType()), - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new IntegerType()), + new MixedType(subtractedType: new ConstantIntegerType(1)), ], MixedType::class, 'mixed~1=implicit', @@ -1045,14 +1045,14 @@ public function dataUnion(): iterable [ [ new MixedType(false), - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), ], MixedType::class, 'mixed=implicit', ], [ [ - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), new UnionType([ new StringType(), new NullType(), @@ -1079,7 +1079,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new ObjectWithoutClassType(new ObjectType('A')), ], MixedType::class, @@ -1087,7 +1087,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ObjectType('A')), + new MixedType(subtractedType: new ObjectType('A')), new ObjectWithoutClassType(new ObjectType('A')), ], MixedType::class, @@ -1095,7 +1095,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), new NullType(), ], MixedType::class, @@ -1103,7 +1103,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new IntegerType(), ], MixedType::class, @@ -1111,7 +1111,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ConstantIntegerType(1)), + new MixedType(subtractedType: new ConstantIntegerType(1)), new ConstantIntegerType(1), ], MixedType::class, @@ -1119,7 +1119,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectType('Throwable'), ], MixedType::class, @@ -1127,7 +1127,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectType('Exception'), ], MixedType::class, @@ -1135,7 +1135,7 @@ public function dataUnion(): iterable ], [ [ - new MixedType(false, new ObjectType('Exception')), + new MixedType(subtractedType: new ObjectType('Exception')), new ObjectType('InvalidArgumentException'), ], MixedType::class, @@ -1144,7 +1144,7 @@ public function dataUnion(): iterable [ [ new NullType(), - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), ], MixedType::class, 'mixed=implicit', @@ -1152,7 +1152,7 @@ public function dataUnion(): iterable [ [ new MixedType(), - new MixedType(false, new NullType()), + new MixedType(subtractedType: new NullType()), ], MixedType::class, 'mixed=implicit', @@ -1802,7 +1802,7 @@ public function dataUnion(): iterable [ [ new StringType(), - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), ], MixedType::class, 'mixed=implicit', @@ -2220,7 +2220,7 @@ public function dataUnion(): iterable ]; yield [ [ - new MixedType(false, IntegerRangeType::fromInterval(17, null)), + new MixedType(subtractedType: IntegerRangeType::fromInterval(17, null)), IntegerRangeType::fromInterval(19, null), ], MixedType::class, @@ -2447,14 +2447,12 @@ public function dataUnion(): iterable new ConstantArrayType( [new ConstantStringType('default'), new ConstantStringType('range')], [new ObjectType(Foo::class), new ObjectType(Foo::class)], - [0], - [0, 1], + optionalKeys: [0, 1], ), new ConstantArrayType( [new ConstantStringType('range')], [new ObjectType(Foo::class)], - [0], - [0], + optionalKeys: [0], ), ], ConstantArrayType::class, @@ -2467,16 +2465,14 @@ public function dataUnion(): iterable new ConstantArrayType( [new ConstantStringType('default'), new ConstantStringType('range')], [new ObjectType(Foo::class), new ObjectType(Foo::class)], - [0], - [0, 1], + optionalKeys: [0, 1], ), new NonEmptyArrayType(), ]), new ConstantArrayType( [new ConstantStringType('range')], [new ObjectType(Foo::class)], - [0], - [0], + optionalKeys: [0], ), ], ConstantArrayType::class, @@ -2603,14 +2599,14 @@ public function dataUnion(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0]), + ], optionalKeys: [0]), new ConstantArrayType([ new ConstantStringType('a'), new ConstantStringType('c'), ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), ], UnionType::class, 'array{a?: true, b: true}|array{a?: true, c?: true}', @@ -2624,7 +2620,7 @@ public function dataUnion(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0]), + ], optionalKeys: [0]), new IntersectionType([ new ConstantArrayType([ new ConstantStringType('a'), @@ -2632,7 +2628,7 @@ public function dataUnion(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new NonEmptyArrayType(), ]), ], @@ -2669,7 +2665,7 @@ public function dataUnion(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), new CallableType(), ], CallableType::class, @@ -2677,7 +2673,7 @@ public function dataUnion(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), ClosureType::createPure(), ], CallableType::class, @@ -2685,15 +2681,15 @@ public function dataUnion(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createMaybe()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createMaybe()), + new CallableType(isPure: TrinaryLogic::createYes()), ], CallableType::class, 'callable(): mixed', ]; yield [ [ - new ClosureType([], new MixedType(), true, null, null, null, [], [], [ + new ClosureType([], new MixedType(), impurePoints: [ new SimpleImpurePoint('functionCall', 'foo', true), ]), ClosureType::createPure(), @@ -2703,7 +2699,7 @@ public function dataUnion(): iterable ]; yield [ [ - new ClosureType([], new MixedType(), true, null, null, null, [], [], [ + new ClosureType([], new MixedType(), impurePoints: [ new SimpleImpurePoint('functionCall', 'foo', false), ]), ClosureType::createPure(), @@ -2791,8 +2787,8 @@ public function dataUnion(): iterable yield [ [ - new ObjectType($finalClass->getName(), null, $finalClass), - new ObjectType($nonFinalClass->getName(), null, $nonFinalClass), + new ObjectType($finalClass->getName(), classReflection: $finalClass), + new ObjectType($nonFinalClass->getName(), classReflection: $nonFinalClass), ], ObjectType::class, $nonFinalClass->getDisplayName(), @@ -3494,7 +3490,7 @@ public function dataIntersect(): iterable ], [ [ - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), new StringType(), ], NeverType::class, @@ -3502,7 +3498,7 @@ public function dataIntersect(): iterable ], [ [ - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), new ConstantStringType('foo'), ], NeverType::class, @@ -3510,7 +3506,7 @@ public function dataIntersect(): iterable ], [ [ - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), new ConstantIntegerType(1), ], ConstantIntegerType::class, @@ -3518,8 +3514,8 @@ public function dataIntersect(): iterable ], [ [ - new MixedType(false, new StringType()), - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new StringType()), + new MixedType(subtractedType: new IntegerType()), ], MixedType::class, 'mixed~(int|string)=implicit', @@ -4226,7 +4222,7 @@ public function dataIntersect(): iterable ]; yield [ [ - new MixedType(false, IntegerRangeType::fromInterval(17, null)), + new MixedType(subtractedType: IntegerRangeType::fromInterval(17, null)), new MixedType(), ], MixedType::class, @@ -4271,7 +4267,7 @@ public function dataIntersect(): iterable yield [ [ new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), - new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), + new MixedType(subtractedType: new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), ], NeverType::class, '*NEVER*=implicit', @@ -4283,7 +4279,7 @@ public function dataIntersect(): iterable new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A'), new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'B'), ]), - new MixedType(false, new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), + new MixedType(subtractedType: new EnumCaseObjectType('PHPStan\Fixture\ManyCasesTestEnum', 'A')), ], EnumCaseObjectType::class, 'PHPStan\Fixture\ManyCasesTestEnum::B', @@ -4479,14 +4475,14 @@ public function dataIntersect(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0]), + ], optionalKeys: [0]), new ConstantArrayType([ new ConstantStringType('a'), new ConstantStringType('c'), ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), ]), new NonEmptyArrayType(), ], @@ -4509,7 +4505,7 @@ public function dataIntersect(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0]), + ], optionalKeys: [0]), new NonEmptyArrayType(), ], ConstantArrayType::class, @@ -4523,7 +4519,7 @@ public function dataIntersect(): iterable ], [ new ConstantBooleanType(true), new ConstantBooleanType(true), - ], [0], [0, 1]), + ], optionalKeys: [0, 1]), new NonEmptyArrayType(), ], IntersectionType::class, @@ -4531,7 +4527,7 @@ public function dataIntersect(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), new CallableType(), ], CallableType::class, @@ -4539,7 +4535,7 @@ public function dataIntersect(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createYes()), ClosureType::createPure(), ], ClosureType::class, @@ -4547,15 +4543,15 @@ public function dataIntersect(): iterable ]; yield [ [ - new CallableType(null, null, true, null, null, [], TrinaryLogic::createMaybe()), - new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes()), + new CallableType(isPure: TrinaryLogic::createMaybe()), + new CallableType(isPure: TrinaryLogic::createYes()), ], CallableType::class, 'pure-callable(): mixed', ]; yield [ [ - new ClosureType([], new MixedType(), true, null, null, null, [], [], [ + new ClosureType([], new MixedType(), impurePoints: [ new SimpleImpurePoint('functionCall', 'foo', true), ]), ClosureType::createPure(), @@ -4565,7 +4561,7 @@ public function dataIntersect(): iterable ]; yield [ [ - new ClosureType([], new MixedType(), true, null, null, null, [], [], [ + new ClosureType([], new MixedType(), impurePoints: [ new SimpleImpurePoint('functionCall', 'foo', false), ]), ClosureType::createPure(), @@ -4722,8 +4718,8 @@ public function dataIntersect(): iterable yield [ [ - new ObjectType($finalClass->getName(), null, $finalClass), - new ObjectType($nonFinalClass->getName(), null, $nonFinalClass), + new ObjectType($finalClass->getName(), classReflection: $finalClass), + new ObjectType($nonFinalClass->getName(), classReflection: $nonFinalClass), ], ObjectType::class, $nonFinalClass->getDisplayName() . '=final', @@ -5087,13 +5083,13 @@ public function dataRemove(): array 'mixed~int', ], [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new IntegerType(), MixedType::class, 'mixed~int', ], [ - new MixedType(false, new IntegerType()), + new MixedType(subtractedType: new IntegerType()), new StringType(), MixedType::class, 'mixed~(int|string)', @@ -5105,19 +5101,19 @@ public function dataRemove(): array '*NEVER*=implicit', ], [ - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), new MixedType(), NeverType::class, '*NEVER*=implicit', ], [ new MixedType(false), - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), StringType::class, 'string', ], [ - new MixedType(false, new StringType()), + new MixedType(subtractedType: new StringType()), new NeverType(), MixedType::class, 'mixed~string', From 52c90e5fbab4b7b51b483e9a71e2250a244b4c2c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:57:52 +0200 Subject: [PATCH 1466/3097] Refactor `--fix` logic from AnalyseCommand to Patcher class --- phpstan-baseline.neon | 36 +++++----- src/Command/AnalyseCommand.php | 95 ++++---------------------- src/Fixable/FileChangedException.php | 10 +++ src/Fixable/MergeConflictException.php | 10 +++ src/Fixable/Patcher.php | 95 ++++++++++++++++++++++++++ 5 files changed, 148 insertions(+), 98 deletions(-) create mode 100644 src/Fixable/FileChangedException.php create mode 100644 src/Fixable/MergeConflictException.php create mode 100644 src/Fixable/Patcher.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d5a0f76ed1a..2f815c02337 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -114,24 +114,6 @@ parameters: count: 1 path: src/Collectors/Registry.php - - - message: '#^Call to method getContent\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' - identifier: method.internalClass - count: 2 - path: src/Command/AnalyseCommand.php - - - - message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Hunk from outside its root namespace PhpMerge\.$#' - identifier: staticMethod.internalClass - count: 2 - path: src/Command/AnalyseCommand.php - - - - message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' - identifier: staticMethod.internalClass - count: 5 - path: src/Command/AnalyseCommand.php - - message: '#^Anonymous function has an unused use \$container\.$#' identifier: closure.unusedUse @@ -234,6 +216,24 @@ parameters: count: 1 path: src/Diagnose/PHPStanDiagnoseExtension.php + - + message: '#^Call to method getContent\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' + identifier: method.internalClass + count: 2 + path: src/Fixable/Patcher.php + + - + message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Hunk from outside its root namespace PhpMerge\.$#' + identifier: staticMethod.internalClass + count: 2 + path: src/Fixable/Patcher.php + + - + message: '#^Call to static method createArray\(\) of internal class PhpMerge\\internal\\Line from outside its root namespace PhpMerge\.$#' + identifier: staticMethod.internalClass + count: 5 + path: src/Fixable/Patcher.php + - message: '#^Call to method getTokenCode\(\) of internal class PhpParser\\Internal\\TokenStream from outside its root namespace PhpParser\.$#' identifier: method.internalClass diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index b550210254a..d71b29187bd 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -2,12 +2,7 @@ namespace PHPStan\Command; -use Nette\Utils\Strings; use OndraM\CiDetector\CiDetector; -use PhpMerge\internal\Hunk; -use PhpMerge\internal\Line; -use PhpMerge\MergeConflict; -use PhpMerge\PhpMerge; use PHPStan\Analyser\InternalError; use PHPStan\Command\ErrorFormatter\BaselineNeonErrorFormatter; use PHPStan\Command\ErrorFormatter\BaselinePhpErrorFormatter; @@ -24,12 +19,13 @@ use PHPStan\File\ParentDirectoryRelativePathHelper; use PHPStan\File\PathNotFoundException; use PHPStan\File\RelativePathHelper; +use PHPStan\Fixable\FileChangedException; +use PHPStan\Fixable\MergeConflictException; +use PHPStan\Fixable\Patcher; use PHPStan\Internal\BytesHelper; use PHPStan\Internal\DirectoryCreator; use PHPStan\Internal\DirectoryCreatorException; use PHPStan\ShouldNotHappenException; -use ReflectionClass; -use SebastianBergmann\Diff\Differ; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -58,7 +54,6 @@ use function is_string; use function pathinfo; use function rewind; -use function sha1; use function sprintf; use function str_contains; use function stream_get_contents; @@ -66,8 +61,6 @@ use function substr; use const PATHINFO_BASENAME; use const PATHINFO_EXTENSION; -use const PREG_SPLIT_DELIM_CAPTURE; -use const PREG_SPLIT_NO_EMPTY; /** * @phpstan-import-type Trace from InternalError as InternalErrorTrace @@ -524,79 +517,29 @@ protected function execute(InputInterface $input, OutputInterface $output): int $exitCode = 1; } else { $skippedCount = 0; - $fixableErrorsByFile = []; + $diffsByFile = []; foreach ($fixableErrors as $fixableError) { $fixFile = $fixableError->getFilePath(); if ($fixableError->getTraitFilePath() !== null) { $fixFile = $fixableError->getTraitFilePath(); } - $fixableErrorsByFile[$fixFile][] = $fixableError; - } - - $differ = $container->getByType(Differ::class); - - foreach ($fixableErrorsByFile as $file => $fileFixableErrors) { - $fileContents = FileReader::read($file); - $fileHash = sha1($fileContents); - $diffHunks = []; - foreach ($fileFixableErrors as $fileFixableError) { - $diff = $fileFixableError->getFixedErrorDiff(); - if ($diff === null) { - throw new ShouldNotHappenException(); - } - if ($diff->originalHash !== $fileHash) { - $skippedCount++; - continue; - } - - $diffHunks[] = Hunk::createArray(Line::createArray($diff->diff)); - } - - if (count($diffHunks) === 0) { - continue; + if ($fixableError->getFixedErrorDiff() === null) { + throw new ShouldNotHappenException(); } - $baseLines = Line::createArray(array_map( - static fn ($l) => [$l, Differ::OLD], - self::splitStringByLines($fileContents), - )); - - $refMerge = new ReflectionClass(PhpMerge::class); - $refMergeMethod = $refMerge->getMethod('mergeHunks'); - $refMergeMethod->setAccessible(true); - - $result = Line::createArray(array_map( - static fn ($l) => [$l, Differ::OLD], - $refMergeMethod->invokeArgs(null, [ - $baseLines, - $diffHunks[0], - [], - ]), - )); - - for ($i = 0; $i < count($diffHunks); $i++) { - /** @var MergeConflict[] $conflicts */ - $conflicts = []; - $merged = $refMergeMethod->invokeArgs(null, [ - $baseLines, - Hunk::createArray(Line::createArray($differ->diffToArray($fileContents, implode('', array_map(static fn ($l) => $l->getContent(), $result))))), - $diffHunks[$i], - &$conflicts, - ]); - if (count($conflicts) > 0) { - $skippedCount += count($diffHunks); - continue 2; - } - - $result = Line::createArray(array_map( - static fn ($l) => [$l, Differ::OLD], - $merged, - )); + $diffsByFile[$fixFile][] = $fixableError->getFixedErrorDiff(); + } + $patcher = $container->getByType(Patcher::class); + foreach ($diffsByFile as $file => $diffs) { + try { + $finalFileContents = $patcher->applyDiffs($file, $diffs); + } catch (FileChangedException | MergeConflictException) { + $skippedCount += count($diffs); + continue; } - $finalFileContents = implode('', array_map(static fn ($l) => $l->getContent(), $result)); FileWriter::write($file, $finalFileContents); } @@ -679,14 +622,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int ); } - /** - * @return string[] - */ - private static function splitStringByLines(string $input): array - { - return Strings::split($input, '/(.*\R)/', PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - } - private function createStreamOutput(): StreamOutput { $resource = fopen('php://memory', 'w', false); diff --git a/src/Fixable/FileChangedException.php b/src/Fixable/FileChangedException.php new file mode 100644 index 00000000000..5630b13e482 --- /dev/null +++ b/src/Fixable/FileChangedException.php @@ -0,0 +1,10 @@ +originalHash !== $fileHash) { + throw new FileChangedException(); + } + + $diffHunks[] = Hunk::createArray(Line::createArray($diff->diff)); + } + + if (count($diffHunks) === 0) { + return $fileContents; + } + + $baseLines = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + self::splitStringByLines($fileContents), + )); + + $refMerge = new ReflectionClass(PhpMerge::class); + $refMergeMethod = $refMerge->getMethod('mergeHunks'); + $refMergeMethod->setAccessible(true); + + $result = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + $refMergeMethod->invokeArgs(null, [ + $baseLines, + $diffHunks[0], + [], + ]), + )); + + for ($i = 0; $i < count($diffHunks); $i++) { + /** @var MergeConflict[] $conflicts */ + $conflicts = []; + $merged = $refMergeMethod->invokeArgs(null, [ + $baseLines, + Hunk::createArray(Line::createArray($this->differ->diffToArray($fileContents, implode('', array_map(static fn ($l) => $l->getContent(), $result))))), + $diffHunks[$i], + &$conflicts, + ]); + if (count($conflicts) > 0) { + throw new MergeConflictException(); + } + + $result = Line::createArray(array_map( + static fn ($l) => [$l, Differ::OLD], + $merged, + )); + + } + + return implode('', array_map(static fn ($l) => $l->getContent(), $result)); + } + + /** + * @return string[] + */ + private static function splitStringByLines(string $input): array + { + return Strings::split($input, '/(.*\R)/', PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + +} From cfbf11ce8f9d255a409baafa5a0edb172cc62dc0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 11:59:44 +0200 Subject: [PATCH 1467/3097] `RuleTestCase::fix()` --- src/Fixable/FileChangedException.php | 2 +- src/Fixable/MergeConflictException.php | 2 +- src/Fixable/Patcher.php | 6 +++++ src/Testing/RuleTestCase.php | 23 +++++++++++++++++++ .../PHPStan/Build/NamedArgumentsRuleTest.php | 18 +++++++++++++++ .../Build/data/named-arguments-no-errors.php | 10 ++++++++ 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Build/data/named-arguments-no-errors.php diff --git a/src/Fixable/FileChangedException.php b/src/Fixable/FileChangedException.php index 5630b13e482..89eaf7ac6f0 100644 --- a/src/Fixable/FileChangedException.php +++ b/src/Fixable/FileChangedException.php @@ -4,7 +4,7 @@ use Exception; -class FileChangedException extends Exception +final class FileChangedException extends Exception { } diff --git a/src/Fixable/MergeConflictException.php b/src/Fixable/MergeConflictException.php index 2717939b307..e1f377f66e6 100644 --- a/src/Fixable/MergeConflictException.php +++ b/src/Fixable/MergeConflictException.php @@ -4,7 +4,7 @@ use Exception; -class MergeConflictException extends Exception +final class MergeConflictException extends Exception { } diff --git a/src/Fixable/Patcher.php b/src/Fixable/Patcher.php index a202992b52c..b68de7a8218 100644 --- a/src/Fixable/Patcher.php +++ b/src/Fixable/Patcher.php @@ -12,6 +12,12 @@ use PHPStan\File\FileReader; use ReflectionClass; use SebastianBergmann\Diff\Differ; +use function array_map; +use function count; +use function implode; +use function sha1; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; #[AutowiredService] final class Patcher diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 792d7acf574..0b82bfb8fba 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -20,6 +20,8 @@ use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; use PHPStan\File\FileHelper; +use PHPStan\File\FileReader; +use PHPStan\Fixable\Patcher; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; @@ -34,6 +36,8 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder; use function array_map; use function array_merge; use function count; @@ -193,6 +197,25 @@ static function (Error $error) use ($strictlyTypedSprintf): string { $this->assertSame($expectedErrorsString, $actualErrorsString); } + public function fix(string $file, string $expectedDiff): void + { + [$errors] = $this->gatherAnalyserErrorsWithDelayedErrors([$file]); + $diffs = []; + foreach ($errors as $error) { + if ($error->getFixedErrorDiff() === null) { + continue; + } + $diffs[] = $error->getFixedErrorDiff(); + } + + $patcher = self::getContainer()->getByType(Patcher::class); + $originalFileContents = FileReader::read($file); + $newFileContents = $patcher->applyDiffs($file, $diffs); // @phpstan-ignore missingType.checkedException, missingType.checkedException + + $differ = new Differ(new DiffOnlyOutputBuilder('')); + $this->assertSame($expectedDiff, $differ->diff($originalFileContents, $newFileContents)); + } + /** * @param string[] $files * @return list diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index bf5da59ef4b..2ace8bc0777 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -39,4 +39,22 @@ public function testRule(): void ]); } + public function testNoFix(): void + { + $this->fix(__DIR__ . '/data/named-arguments-no-errors.php', ''); + } + + public function testFix(): void + { + $this->fix(__DIR__ . '/data/named-arguments.php', <<= 8.0 + +namespace NamedArgumentRuleNoErrors; + +use Exception; + +function (): void { + new Exception('foo', 0); + new Exception('foo', 0, null); +}; From 7b68603f8e5761c995481ecd62632ab52c45d4c8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 13:57:00 +0200 Subject: [PATCH 1468/3097] RuleTestCase - do not assert diffs, but whole files --- src/Testing/RuleTestCase.php | 10 ++++---- .../PHPStan/Build/NamedArgumentsRuleTest.php | 18 +++++++-------- .../Build/data/named-arguments.php.fixed | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 tests/PHPStan/Build/data/named-arguments.php.fixed diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 0b82bfb8fba..c718a8f9c9c 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -36,8 +36,6 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; -use SebastianBergmann\Diff\Differ; -use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder; use function array_map; use function array_merge; use function count; @@ -197,7 +195,7 @@ static function (Error $error) use ($strictlyTypedSprintf): string { $this->assertSame($expectedErrorsString, $actualErrorsString); } - public function fix(string $file, string $expectedDiff): void + public function fix(string $file, string $expectedFile): void { [$errors] = $this->gatherAnalyserErrorsWithDelayedErrors([$file]); $diffs = []; @@ -209,11 +207,11 @@ public function fix(string $file, string $expectedDiff): void } $patcher = self::getContainer()->getByType(Patcher::class); - $originalFileContents = FileReader::read($file); $newFileContents = $patcher->applyDiffs($file, $diffs); // @phpstan-ignore missingType.checkedException, missingType.checkedException - $differ = new Differ(new DiffOnlyOutputBuilder('')); - $this->assertSame($expectedDiff, $differ->diff($originalFileContents, $newFileContents)); + $fixedFileContents = FileReader::read($expectedFile); + + $this->assertSame($fixedFileContents, $newFileContents); } /** diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index 2ace8bc0777..c4282bf4d2a 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -41,20 +41,18 @@ public function testRule(): void public function testNoFix(): void { - $this->fix(__DIR__ . '/data/named-arguments-no-errors.php', ''); + $this->fix( + __DIR__ . '/data/named-arguments-no-errors.php', + __DIR__ . '/data/named-arguments-no-errors.php', + ); } public function testFix(): void { - $this->fix(__DIR__ . '/data/named-arguments.php', <<fix( + __DIR__ . '/data/named-arguments.php', + __DIR__ . '/data/named-arguments.php.fixed', + ); } } diff --git a/tests/PHPStan/Build/data/named-arguments.php.fixed b/tests/PHPStan/Build/data/named-arguments.php.fixed new file mode 100644 index 00000000000..b85af027992 --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments.php.fixed @@ -0,0 +1,23 @@ += 8.0 + +namespace NamedArgumentRule; + +use Exception; + +class Foo +{ + + public function doFoo(): void + { + new Exception('foo', 0); + new Exception('foo', 0, null); + new Exception('foo', previous: new Exception('previous')); + new Exception('foo', previous: new Exception('previous')); + new Exception(previous: new Exception('previous')); + new Exception('foo', code: 1, previous: new Exception('previous')); + new Exception('foo', 1, new Exception('previous')); + new Exception('foo', 1); + new Exception(previous: new Exception('previous')); + } + +} From 39659f411ce2db0c9d13c5b57a57c3558e122064 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 14:16:32 +0200 Subject: [PATCH 1469/3097] Smaller diff representation in result cache --- Makefile | 2 +- conf/services.neon | 8 ++++ src/Analyser/FixedErrorDiff.php | 7 +-- src/Analyser/RuleErrorTransformer.php | 2 +- src/Fixable/Patcher.php | 67 ++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 273283a4af4..9a00fc8586f 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,7 @@ phpstan-result-cache: php -d memory_limit=448M bin/phpstan phpstan-fix: - php -d memory_limit=2G bin/phpstan --fix + php -d memory_limit=448M bin/phpstan --fix phpstan-generate-baseline: php -d memory_limit=448M bin/phpstan --generate-baseline diff --git a/conf/services.neon b/conf/services.neon index d1adbb4ae5e..1d6457e123e 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -162,6 +162,14 @@ services: - class: SebastianBergmann\Diff\Differ + arguments: + outputBuilder: @SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder + + - + class: SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder + arguments: + header: '' + addLineNumbers: true betterReflectionSourceLocator: class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator diff --git a/src/Analyser/FixedErrorDiff.php b/src/Analyser/FixedErrorDiff.php index 6bc05db91da..af8755b2f88 100644 --- a/src/Analyser/FixedErrorDiff.php +++ b/src/Analyser/FixedErrorDiff.php @@ -2,17 +2,12 @@ namespace PHPStan\Analyser; -use SebastianBergmann\Diff\Differ; - final class FixedErrorDiff { - /** - * @param array $diff - */ public function __construct( public readonly string $originalHash, - public readonly array $diff, + public readonly string $diff, ) { } diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 439250fff46..a92c110fac4 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -130,7 +130,7 @@ public function transform( $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); - $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diffToArray($oldCode, $newCode)); + $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diff($oldCode, $newCode)); } return new Error( diff --git a/src/Fixable/Patcher.php b/src/Fixable/Patcher.php index b68de7a8218..bbec610dd57 100644 --- a/src/Fixable/Patcher.php +++ b/src/Fixable/Patcher.php @@ -16,6 +16,8 @@ use function count; use function implode; use function sha1; +use function str_starts_with; +use function substr; use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_NO_EMPTY; @@ -42,7 +44,7 @@ public function applyDiffs(string $fileName, array $diffs): string throw new FileChangedException(); } - $diffHunks[] = Hunk::createArray(Line::createArray($diff->diff)); + $diffHunks[] = Hunk::createArray(Line::createArray($this->reconstructFullDiff($fileContents, $diff->diff))); } if (count($diffHunks) === 0) { @@ -90,6 +92,69 @@ public function applyDiffs(string $fileName, array $diffs): string return implode('', array_map(static fn ($l) => $l->getContent(), $result)); } + /** + * @return array + */ + private function reconstructFullDiff(string $originalText, string $unifiedDiff): array + { + $originalLines = self::splitStringByLines($originalText); + $diffLines = self::splitStringByLines($unifiedDiff); + $result = []; + + $origLineNo = 0; + $diffPos = 0; + + while ($diffPos < count($diffLines)) { + $line = $diffLines[$diffPos]; + + $matches = Strings::match($line, '/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/'); + if ($matches !== null) { + // Parse hunk header + $origStart = (int) $matches[1] - 1; // 0-based + $diffPos++; + + // Emit kept lines before hunk + while ($origLineNo < $origStart) { + $result[] = [$originalLines[$origLineNo], Differ::OLD]; + $origLineNo++; + } + + // Process hunk + while ($diffPos < count($diffLines)) { + $line = $diffLines[$diffPos]; + if (str_starts_with($line, '@@')) { + break; // next hunk + } + + $prefix = $line[0] ?? ''; + $content = substr($line, 1); + + if ($prefix === ' ') { + $result[] = [$content, Differ::OLD]; + $origLineNo++; + } elseif ($prefix === '-') { + $result[] = [$content, Differ::REMOVED]; + $origLineNo++; + } elseif ($prefix === '+') { + $result[] = [$content, Differ::ADDED]; + } + + $diffPos++; + } + } else { + $diffPos++; + } + } + + // Emit remaining lines as kept + while ($origLineNo < count($originalLines)) { + $result[] = [$originalLines[$origLineNo], Differ::OLD]; + $origLineNo++; + } + + return $result; + } + /** * @return string[] */ From e39e9b7a9713a33b6d016629f18a47476904e2b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 15:09:11 +0200 Subject: [PATCH 1470/3097] Differ cannot be a service after all --- conf/services.neon | 11 ----------- src/Analyser/RuleErrorTransformer.php | 5 ++++- src/Fixable/Patcher.php | 5 ++++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/conf/services.neon b/conf/services.neon index 1d6457e123e..227d5787548 100644 --- a/conf/services.neon +++ b/conf/services.neon @@ -160,17 +160,6 @@ services: - class: PHPStan\Rules\Properties\UninitializedPropertyRule - - - class: SebastianBergmann\Diff\Differ - arguments: - outputBuilder: @SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder - - - - class: SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder - arguments: - header: '' - addLineNumbers: true - betterReflectionSourceLocator: class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator factory: @PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory::create diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index a92c110fac4..5b5b9b47264 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -23,6 +23,7 @@ use PHPStan\Rules\TipRuleError; use PHPStan\ShouldNotHappenException; use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; use function get_class; use function sha1; use function str_repeat; @@ -30,11 +31,13 @@ final class RuleErrorTransformer { + private Differ $differ; + public function __construct( private Parser $parser, - private Differ $differ, ) { + $this->differ = new Differ(new UnifiedDiffOutputBuilder('', addLineNumbers: true)); } /** diff --git a/src/Fixable/Patcher.php b/src/Fixable/Patcher.php index bbec610dd57..197a58b4425 100644 --- a/src/Fixable/Patcher.php +++ b/src/Fixable/Patcher.php @@ -25,8 +25,11 @@ final class Patcher { - public function __construct(private Differ $differ) + private Differ $differ; + + public function __construct() { + $this->differ = new Differ(); } /** From fb3d20fd653f50ac945a394c968dce534b81fa38 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 15:31:15 +0200 Subject: [PATCH 1471/3097] Use my own php-merge fork --- composer.json | 2 +- composer.lock | 117 ++++++++++++++++++++++++++------------------------ 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/composer.json b/composer.json index 8397cdaf5db..bb72a6ab2f7 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,6 @@ "require": { "php": "^8.1", "composer-runtime-api": "^2.0", - "bircher/php-merge": "^4.0", "clue/ndjson-react": "^1.0", "composer/ca-bundle": "^1.2", "composer/semver": "^3.4", @@ -27,6 +26,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", "ondrejmirtes/composer-attribute-collector": "^1.0.0", + "ondrejmirtes/php-merge": "^4.1", "phpstan/php-8-stubs": "0.4.12", "phpstan/phpdoc-parser": "2.1.0", "psr/http-message": "^1.1", diff --git a/composer.lock b/composer.lock index f5f46a4fcbd..767a57642b1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,63 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5f97c88abdd81aefa9b1b3e48fd0999", + "content-hash": "8c786f1dc6ae74db5aa83e606ca74f28", "packages": [ - { - "name": "bircher/php-merge", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/bircher/php-merge.git", - "reference": "db19f6e02c606cba3f4e59b2189c0df89cd2570d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bircher/php-merge/zipball/db19f6e02c606cba3f4e59b2189c0df89cd2570d", - "reference": "db19f6e02c606cba3f4e59b2189c0df89cd2570d", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^2.0|^3.0|^4.0" - }, - "require-dev": { - "escapestudios/symfony2-coding-standard": "^3.5", - "phpstan/phpstan": "~1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpunit/phpunit": "~6|~7|~8|~9", - "squizlabs/php_codesniffer": "~3", - "symplify/git-wrapper": "^9.1|^10.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "PhpMerge": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabian Bircher", - "email": "opensource@fabianbircher.com" - } - ], - "description": "A PHP merge utility using the Diff php library or the command line git.", - "homepage": "https://github.com/bircher/php-merge", - "keywords": [ - "git", - "merge", - "php-merge" - ], - "support": { - "issues": "https://github.com/bircher/php-merge/issues", - "source": "https://github.com/bircher/php-merge/tree/4.0.0" - }, - "time": "2021-12-27T15:04:20+00:00" - }, { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -2375,6 +2320,64 @@ }, "time": "2025-05-24T23:38:56+00:00" }, + { + "name": "ondrejmirtes/php-merge", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/ondrejmirtes/php-merge.git", + "reference": "1c8cd6d7d9d0e327a5f4fe7689de3dbf99a75003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ondrejmirtes/php-merge/zipball/1c8cd6d7d9d0e327a5f4fe7689de3dbf99a75003", + "reference": "1c8cd6d7d9d0e327a5f4fe7689de3dbf99a75003", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "sebastian/diff": "^2.0|^3.0|^4.0|^5.0" + }, + "require-dev": { + "escapestudios/symfony2-coding-standard": "^3.5", + "phpstan/phpstan": "~1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpunit/phpunit": "~6|~7|~8|~9|~10", + "squizlabs/php_codesniffer": "~3", + "symplify/git-wrapper": "^9.1|^10.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PhpMerge": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabian Bircher", + "email": "opensource@fabianbircher.com" + }, + { + "name": "Ondrej Mirtes", + "email": "ondrej@mirtes.cz" + } + ], + "description": "A PHP merge utility using the Diff php library or the command line git.", + "homepage": "https://github.com/bircher/php-merge", + "keywords": [ + "git", + "merge", + "php-merge" + ], + "support": { + "source": "https://github.com/ondrejmirtes/php-merge/tree/4.1.0" + }, + "time": "2025-05-28T13:29:07+00:00" + }, { "name": "phpstan/php-8-stubs", "version": "0.4.12", @@ -6603,7 +6606,7 @@ "php": "^8.1", "composer-runtime-api": "^2.0" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1.99" }, From 54edbfbd45d0dde37911bf1d8c1c9669f0a557a6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 15:32:26 +0200 Subject: [PATCH 1472/3097] Try fixing CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e77ec8b28a5..8009e7d6292 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -119,7 +119,7 @@ jobs: ini-values: memory_limit=1G - name: "Install PHPUnit 10.x" - run: "composer remove --dev brianium/paratest && composer require --dev --with-all-dependencies phpunit/phpunit:^10" + run: "composer remove --dev brianium/paratest && composer require --dev --with-all-dependencies phpunit/phpunit:^10 sebastian/diff:^5.0" - id: set-matrix run: echo "matrix=$(php .github/workflows/tests-levels-matrix.php)" >> $GITHUB_OUTPUT From 6615cdd21b4e24b5d6f454e8b0a55ff3bc61ce77 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 20:55:51 +0200 Subject: [PATCH 1473/3097] getFixedErrorDiff is experimental --- src/Analyser/Error.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index 5147fb0d970..e285b04a149 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -252,6 +252,9 @@ public function getMetadata(): array return $this->metadata; } + /** + * @internal Experimental + */ public function getFixedErrorDiff(): ?FixedErrorDiff { return $this->fixedErrorDiff; From 7fdd9c47a156d3846080d84e2f505a37ffebc573 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 20:56:57 +0200 Subject: [PATCH 1474/3097] Normalize line endings --- src/Testing/RuleTestCase.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index c718a8f9c9c..36c383bc568 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -41,6 +41,7 @@ use function count; use function implode; use function sprintf; +use function str_replace; /** * @api @@ -211,7 +212,12 @@ public function fix(string $file, string $expectedFile): void $fixedFileContents = FileReader::read($expectedFile); - $this->assertSame($fixedFileContents, $newFileContents); + $this->assertSame($this->normalizeLineEndings($fixedFileContents), $this->normalizeLineEndings($newFileContents)); + } + + private function normalizeLineEndings(string $string): string + { + return str_replace("\r\n", "\n", $string); } /** From d51b7ad2077b091ecb7a63be03e4f20dcb669e82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:03:18 +0200 Subject: [PATCH 1475/3097] Fix progress bar --- src/Command/AnalyseCommand.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index d71b29187bd..f56c6ce59a8 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -330,6 +330,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new ShouldNotHappenException(); } + if ($fix) { + $inceptionResult->getErrorOutput()->writeLineFormatted('Analysing files...'); + } + try { $analysisResult = $application->analyse( $files, @@ -512,7 +516,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $fixableErrorsCount = count($fixableErrors); - if (count($fixableErrors) === 0) { + if ($fixableErrorsCount === 0) { $inceptionResult->getStdOutput()->getStyle()->error('No fixable errors found'); $exitCode = 1; } else { @@ -531,18 +535,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int $diffsByFile[$fixFile][] = $fixableError->getFixedErrorDiff(); } + $inceptionResult->getErrorOutput()->writeLineFormatted('Fixing errors...'); + $errorOutput->getStyle()->progressStart($fixableErrorsCount); + $patcher = $container->getByType(Patcher::class); foreach ($diffsByFile as $file => $diffs) { + $diffsCount = count($diffs); try { $finalFileContents = $patcher->applyDiffs($file, $diffs); + $errorOutput->getStyle()->progressAdvance($diffsCount); } catch (FileChangedException | MergeConflictException) { - $skippedCount += count($diffs); + $skippedCount += $diffsCount; + $errorOutput->getStyle()->progressAdvance($diffsCount); continue; } FileWriter::write($file, $finalFileContents); } + $errorOutput->getStyle()->progressFinish(); + if ($skippedCount > 0) { $inceptionResult->getStdOutput()->getStyle()->warning(sprintf( '%d %s fixed, %d %s skipped', From 536d2f1f7f790c89d04774ceca4b6654c511976f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:04:33 +0200 Subject: [PATCH 1476/3097] Fix tip --- src/Command/ErrorFormatter/TableErrorFormatter.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index f69eca834e9..0c3b11d4a98 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -77,10 +77,15 @@ public function formatErrors( $fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError; } + $fixableErrorsCount = 0; foreach ($fileErrors as $file => $errors) { $rows = []; foreach ($errors as $error) { $message = $error->getMessage(); + if ($error->getFixedErrorDiff() !== null) { + $message .= ' 🔧'; + $fixableErrorsCount++; + } $filePath = $error->getTraitFilePath() ?? $error->getFilePath(); if ($error->getIdentifier() !== null && $error->canBeIgnored()) { $message .= "\n"; @@ -157,6 +162,11 @@ public function formatErrors( $style->warning($finalMessage); } + if ($fixableErrorsCount > 0) { + $output->writeLineFormatted(sprintf('🔧 %d %s can be fixed automatically. Run PHPStan again with --fix.', $fixableErrorsCount, $fixableErrorsCount === 1 ? 'error' : 'errors')); + $output->writeLineFormatted(''); + } + return $analysisResult->getTotalErrorsCount() > 0 ? 1 : 0; } From 42e9c2488efd35c17b3e8a1a7af9655f22542c8f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:11:15 +0200 Subject: [PATCH 1477/3097] AutowiredService - support named services --- conf/config.neon | 3 --- src/Command/ErrorFormatter/RawErrorFormatter.php | 2 ++ .../AutowiredAttributeServicesExtension.php | 3 ++- src/DependencyInjection/AutowiredService.php | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 357a2e1d059..ff554989013 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -983,9 +983,6 @@ services: autowired: - PHPStan\Command\ErrorFormatter\CiDetectedErrorFormatter - errorFormatter.raw: - class: PHPStan\Command\ErrorFormatter\RawErrorFormatter - errorFormatter.table: class: PHPStan\Command\ErrorFormatter\TableErrorFormatter arguments: diff --git a/src/Command/ErrorFormatter/RawErrorFormatter.php b/src/Command/ErrorFormatter/RawErrorFormatter.php index f761a5a47e2..a37dc421296 100644 --- a/src/Command/ErrorFormatter/RawErrorFormatter.php +++ b/src/Command/ErrorFormatter/RawErrorFormatter.php @@ -4,8 +4,10 @@ use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; +use PHPStan\DependencyInjection\AutowiredService; use function sprintf; +#[AutowiredService(name: 'errorFormatter.raw')] final class RawErrorFormatter implements ErrorFormatter { diff --git a/src/DependencyInjection/AutowiredAttributeServicesExtension.php b/src/DependencyInjection/AutowiredAttributeServicesExtension.php index a02d84bf483..b09bb8b1129 100644 --- a/src/DependencyInjection/AutowiredAttributeServicesExtension.php +++ b/src/DependencyInjection/AutowiredAttributeServicesExtension.php @@ -17,8 +17,9 @@ public function loadConfiguration(): void foreach ($autowiredServiceClasses as $class) { $reflection = new ReflectionClass($class->name); + $attribute = $class->attribute; - $definition = $builder->addDefinition(null) + $definition = $builder->addDefinition($attribute->name) ->setType($class->name) ->setAutowired(); diff --git a/src/DependencyInjection/AutowiredService.php b/src/DependencyInjection/AutowiredService.php index 8544459f9cd..c172ff021c2 100644 --- a/src/DependencyInjection/AutowiredService.php +++ b/src/DependencyInjection/AutowiredService.php @@ -16,4 +16,8 @@ final class AutowiredService { + public function __construct(public ?string $name = null) + { + } + } From ba0e3be1c741f1c4f61dec6e883bbea5bb26dd7f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:19:49 +0200 Subject: [PATCH 1478/3097] NamedArgumentsRule - report only on PHP 8.0+ --- build/PHPStan/Build/NamedArgumentsRule.php | 10 +++++++++- tests/PHPStan/Build/NamedArgumentsRuleTest.php | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/build/PHPStan/Build/NamedArgumentsRule.php b/build/PHPStan/Build/NamedArgumentsRule.php index 45c74f1027d..fda84e6d167 100644 --- a/build/PHPStan/Build/NamedArgumentsRule.php +++ b/build/PHPStan/Build/NamedArgumentsRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PHPStan\Analyser\ArgumentsNormalizer; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; @@ -22,7 +23,10 @@ final class NamedArgumentsRule implements Rule { - public function __construct(private ReflectionProvider $reflectionProvider) + public function __construct( + private ReflectionProvider $reflectionProvider, + private PhpVersion $phpVersion, + ) { } @@ -33,6 +37,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if (!$this->phpVersion->supportsNamedArguments()) { + return []; + } + if ($node->isFirstClassCallable()) { return []; } diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index c4282bf4d2a..c44c4419e5a 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Build; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -14,7 +15,7 @@ class NamedArgumentsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new NamedArgumentsRule($this->createReflectionProvider()); + return new NamedArgumentsRule($this->createReflectionProvider(), new PhpVersion(PHP_VERSION_ID)); } public function testRule(): void @@ -41,6 +42,10 @@ public function testRule(): void public function testNoFix(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->fix( __DIR__ . '/data/named-arguments-no-errors.php', __DIR__ . '/data/named-arguments-no-errors.php', @@ -49,6 +54,10 @@ public function testNoFix(): void public function testFix(): void { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->fix( __DIR__ . '/data/named-arguments.php', __DIR__ . '/data/named-arguments.php.fixed', From 0868c047893738fbd482e1121db4fae0ee6cc15a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:38:03 +0200 Subject: [PATCH 1479/3097] Try fixing PHAR --- .github/workflows/phar.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 98493f07374..a468bd523a2 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -55,6 +55,9 @@ jobs: working-directory: "compiler" run: "php bin/prepare" + - name: "Dump autoloader one more time for attributes" + run: "composer dump" + - name: "Compile PHAR" working-directory: "compiler/build" run: "php box.phar compile --no-parallel" From f396426b89d7e110c25f3a850eda81217aadf2e9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 21:48:04 +0200 Subject: [PATCH 1480/3097] Compiler downgrade to 7.4 --- compiler/src/Console/PrepareCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/Console/PrepareCommand.php b/compiler/src/Console/PrepareCommand.php index 15654b55f6d..cc8ddbac5d3 100644 --- a/compiler/src/Console/PrepareCommand.php +++ b/compiler/src/Console/PrepareCommand.php @@ -220,7 +220,7 @@ private function deleteUnnecessaryVendorCode(): void private function transformSource(): void { chdir(__DIR__ . '/../../..'); - exec(escapeshellarg(__DIR__ . '/../../vendor/bin/simple-downgrade') . ' downgrade -c ' . escapeshellarg('build/downgrade.php') . ' 7.2', $outputLines, $exitCode); + exec(escapeshellarg(__DIR__ . '/../../vendor/bin/simple-downgrade') . ' downgrade -c ' . escapeshellarg('build/downgrade.php') . ' 7.4', $outputLines, $exitCode); if ($exitCode === 0) { return; } From 9d1c245fd4344f189204da07093aba3cedf82af4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 28 May 2025 22:03:04 +0200 Subject: [PATCH 1481/3097] One more named service --- conf/config.neon | 3 --- src/Analyser/TypeSpecifierFactory.php | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ff554989013..5b983426050 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -780,9 +780,6 @@ services: class: PHPStan\Analyser\TypeSpecifier factory: @typeSpecifierFactory::create - typeSpecifierFactory: - class: PHPStan\Analyser\TypeSpecifierFactory - relativePathHelper: class: PHPStan\File\RelativePathHelper factory: PHPStan\File\FuzzyRelativePathHelper diff --git a/src/Analyser/TypeSpecifierFactory.php b/src/Analyser/TypeSpecifierFactory.php index 83315b6e0ce..5df7f61e07b 100644 --- a/src/Analyser/TypeSpecifierFactory.php +++ b/src/Analyser/TypeSpecifierFactory.php @@ -3,12 +3,14 @@ namespace PHPStan\Analyser; use PHPStan\Broker\BrokerFactory; +use PHPStan\DependencyInjection\AutowiredService; use PHPStan\DependencyInjection\Container; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use function array_merge; +#[AutowiredService(name: 'typeSpecifierFactory')] final class TypeSpecifierFactory { From 8f4c135cb50029d9700ecbd517da45f7b7d11c57 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 10:13:18 +0200 Subject: [PATCH 1482/3097] Pass original node to fixNode() --- build/PHPStan/Build/NamedArgumentsRule.php | 6 ++---- build/ignore-gte-php7.4-errors.neon | 2 +- src/Analyser/RuleErrorTransformer.php | 4 ++-- src/Rules/FixableNodeRuleError.php | 2 ++ src/Rules/RuleErrorBuilder.php | 12 ++++++++++-- src/Rules/RuleErrors/RuleError129.php | 7 +++++++ src/Rules/RuleErrors/RuleError131.php | 7 +++++++ src/Rules/RuleErrors/RuleError133.php | 7 +++++++ src/Rules/RuleErrors/RuleError135.php | 7 +++++++ src/Rules/RuleErrors/RuleError137.php | 7 +++++++ src/Rules/RuleErrors/RuleError139.php | 7 +++++++ src/Rules/RuleErrors/RuleError141.php | 7 +++++++ src/Rules/RuleErrors/RuleError143.php | 7 +++++++ src/Rules/RuleErrors/RuleError145.php | 7 +++++++ src/Rules/RuleErrors/RuleError147.php | 7 +++++++ src/Rules/RuleErrors/RuleError149.php | 7 +++++++ src/Rules/RuleErrors/RuleError151.php | 7 +++++++ src/Rules/RuleErrors/RuleError153.php | 7 +++++++ src/Rules/RuleErrors/RuleError155.php | 7 +++++++ src/Rules/RuleErrors/RuleError157.php | 7 +++++++ src/Rules/RuleErrors/RuleError159.php | 7 +++++++ src/Rules/RuleErrors/RuleError161.php | 7 +++++++ src/Rules/RuleErrors/RuleError163.php | 7 +++++++ src/Rules/RuleErrors/RuleError165.php | 7 +++++++ src/Rules/RuleErrors/RuleError167.php | 7 +++++++ src/Rules/RuleErrors/RuleError169.php | 7 +++++++ src/Rules/RuleErrors/RuleError171.php | 7 +++++++ src/Rules/RuleErrors/RuleError173.php | 7 +++++++ src/Rules/RuleErrors/RuleError175.php | 7 +++++++ src/Rules/RuleErrors/RuleError177.php | 7 +++++++ src/Rules/RuleErrors/RuleError179.php | 7 +++++++ src/Rules/RuleErrors/RuleError181.php | 7 +++++++ src/Rules/RuleErrors/RuleError183.php | 7 +++++++ src/Rules/RuleErrors/RuleError185.php | 7 +++++++ src/Rules/RuleErrors/RuleError187.php | 7 +++++++ src/Rules/RuleErrors/RuleError189.php | 7 +++++++ src/Rules/RuleErrors/RuleError191.php | 7 +++++++ src/Rules/RuleErrors/RuleError193.php | 7 +++++++ src/Rules/RuleErrors/RuleError195.php | 7 +++++++ src/Rules/RuleErrors/RuleError197.php | 7 +++++++ src/Rules/RuleErrors/RuleError199.php | 7 +++++++ src/Rules/RuleErrors/RuleError201.php | 7 +++++++ src/Rules/RuleErrors/RuleError203.php | 7 +++++++ src/Rules/RuleErrors/RuleError205.php | 7 +++++++ src/Rules/RuleErrors/RuleError207.php | 7 +++++++ src/Rules/RuleErrors/RuleError209.php | 7 +++++++ src/Rules/RuleErrors/RuleError211.php | 7 +++++++ src/Rules/RuleErrors/RuleError213.php | 7 +++++++ src/Rules/RuleErrors/RuleError215.php | 7 +++++++ src/Rules/RuleErrors/RuleError217.php | 7 +++++++ src/Rules/RuleErrors/RuleError219.php | 7 +++++++ src/Rules/RuleErrors/RuleError221.php | 7 +++++++ src/Rules/RuleErrors/RuleError223.php | 7 +++++++ src/Rules/RuleErrors/RuleError225.php | 7 +++++++ src/Rules/RuleErrors/RuleError227.php | 7 +++++++ src/Rules/RuleErrors/RuleError229.php | 7 +++++++ src/Rules/RuleErrors/RuleError231.php | 7 +++++++ src/Rules/RuleErrors/RuleError233.php | 7 +++++++ src/Rules/RuleErrors/RuleError235.php | 7 +++++++ src/Rules/RuleErrors/RuleError237.php | 7 +++++++ src/Rules/RuleErrors/RuleError239.php | 7 +++++++ src/Rules/RuleErrors/RuleError241.php | 7 +++++++ src/Rules/RuleErrors/RuleError243.php | 7 +++++++ src/Rules/RuleErrors/RuleError245.php | 7 +++++++ src/Rules/RuleErrors/RuleError247.php | 7 +++++++ src/Rules/RuleErrors/RuleError249.php | 7 +++++++ src/Rules/RuleErrors/RuleError251.php | 7 +++++++ src/Rules/RuleErrors/RuleError253.php | 7 +++++++ src/Rules/RuleErrors/RuleError255.php | 7 +++++++ 69 files changed, 465 insertions(+), 9 deletions(-) diff --git a/build/PHPStan/Build/NamedArgumentsRule.php b/build/PHPStan/Build/NamedArgumentsRule.php index fda84e6d167..250ef7ba979 100644 --- a/build/PHPStan/Build/NamedArgumentsRule.php +++ b/build/PHPStan/Build/NamedArgumentsRule.php @@ -92,10 +92,9 @@ public function processNode(Node $node, Scope $scope): array } /** - * @param Node\Expr\FuncCall|Node\Expr\New_|Node\Expr\StaticCall $node * @return list */ - private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, Node\Expr\CallLike $node): array + private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, Node\Expr\FuncCall|Node\Expr\New_|Node\Expr\StaticCall $node): array { if ($acceptor->isVariadic()) { return []; @@ -169,8 +168,7 @@ private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, } if (count($errorBuilders) > 0) { - $errorBuilders[0]->fixNode(static function (Node $node) use ($acceptor, $hasNamedArgument, $parameters, $scope) { - /** @var Node\Expr\FuncCall|Node\Expr\New_|Node\Expr\StaticCall $node */ + $errorBuilders[0]->fixNode($node, static function ($node) use ($acceptor, $hasNamedArgument, $parameters, $scope) { $normalizedArgs = ArgumentsNormalizer::reorderArgs($acceptor, $node->getArgs()); if ($normalizedArgs === null) { return $node; diff --git a/build/ignore-gte-php7.4-errors.neon b/build/ignore-gte-php7.4-errors.neon index d5ae8bada3e..112fb17e4cd 100644 --- a/build/ignore-gte-php7.4-errors.neon +++ b/build/ignore-gte-php7.4-errors.neon @@ -3,7 +3,7 @@ includes: parameters: ignoreErrors: - - '#^Class PHPStan\\Rules\\RuleErrors\\RuleError(?:\d+) has an uninitialized property (?:\$message|\$line|\$identifier|\$tip|\$file|\$metadata)#' + - '#^Class PHPStan\\Rules\\RuleErrors\\RuleError(?:\d+) has an uninitialized property (?:\$message|\$line|\$identifier|\$tip|\$file|\$metadata|\$originalNode)#' - '#Extension has an uninitialized property (?:\$typeSpecifier|\$broker)#' - message: '#has an uninitialized property#' diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 5b5b9b47264..c4985a4ad36 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -98,7 +98,7 @@ public function transform( $fixedErrorDiff = null; if ($ruleError instanceof FixableNodeRuleError) { - if ($node instanceof VirtualNode) { + if ($ruleError->getOriginalNode() instanceof VirtualNode) { throw new ShouldNotHappenException('Cannot fix virtual node'); } $fixingFile = $filePath; @@ -124,7 +124,7 @@ public function transform( $newStmts = $cloningTraverser->traverse($fileNodes); $traverser = new NodeTraverser(); - $visitor = new ReplacingNodeVisitor($node, $ruleError->getNewNodeCallable()); + $visitor = new ReplacingNodeVisitor($ruleError->getOriginalNode(), $ruleError->getNewNodeCallable()); $traverser->addVisitor($visitor); /** @var Stmt[] $newStmts */ diff --git a/src/Rules/FixableNodeRuleError.php b/src/Rules/FixableNodeRuleError.php index 9eb34697565..bdb5c973f10 100644 --- a/src/Rules/FixableNodeRuleError.php +++ b/src/Rules/FixableNodeRuleError.php @@ -7,6 +7,8 @@ interface FixableNodeRuleError extends RuleError { + public function getOriginalNode(): Node; + /** @return callable(Node): Node */ public function getNewNodeCallable(): callable; diff --git a/src/Rules/RuleErrorBuilder.php b/src/Rules/RuleErrorBuilder.php index db53567e7bb..ec48db9cf1a 100644 --- a/src/Rules/RuleErrorBuilder.php +++ b/src/Rules/RuleErrorBuilder.php @@ -120,6 +120,11 @@ public static function getRuleErrorTypes(): array self::TYPE_FIXABLE_NODE => [ FixableNodeRuleError::class, [ + [ + 'originalNode', + '\PhpParser\Node', + '\PhpParser\Node', + ], [ 'newNodeCallable', null, @@ -268,12 +273,15 @@ public function nonIgnorable(): self /** * @internal Experimental - * @param callable(Node): Node $cb + * @template TNode of Node + * @param TNode $node + * @param callable(TNode): Node $cb * @phpstan-this-out self * @return self */ - public function fixNode(callable $cb): self + public function fixNode(Node $node, callable $cb): self { + $this->properties['originalNode'] = $node; $this->properties['newNodeCallable'] = $cb; $this->type |= self::TYPE_FIXABLE_NODE; diff --git a/src/Rules/RuleErrors/RuleError129.php b/src/Rules/RuleErrors/RuleError129.php index f47d82502dd..bd003c4f5a2 100644 --- a/src/Rules/RuleErrors/RuleError129.php +++ b/src/Rules/RuleErrors/RuleError129.php @@ -14,6 +14,8 @@ final class RuleError129 implements RuleError, FixableNodeRuleError public string $message; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -22,6 +24,11 @@ public function getMessage(): string return $this->message; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError131.php b/src/Rules/RuleErrors/RuleError131.php index ded63b6bb28..ba607b957e3 100644 --- a/src/Rules/RuleErrors/RuleError131.php +++ b/src/Rules/RuleErrors/RuleError131.php @@ -17,6 +17,8 @@ final class RuleError131 implements RuleError, LineRuleError, FixableNodeRuleErr public int $line; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -30,6 +32,11 @@ public function getLine(): int return $this->line; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError133.php b/src/Rules/RuleErrors/RuleError133.php index cdc253ee26b..481c6f0bcf5 100644 --- a/src/Rules/RuleErrors/RuleError133.php +++ b/src/Rules/RuleErrors/RuleError133.php @@ -19,6 +19,8 @@ final class RuleError133 implements RuleError, FileRuleError, FixableNodeRuleErr public string $fileDescription; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -37,6 +39,11 @@ public function getFileDescription(): string return $this->fileDescription; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError135.php b/src/Rules/RuleErrors/RuleError135.php index 02a1ba11997..11f52de24ae 100644 --- a/src/Rules/RuleErrors/RuleError135.php +++ b/src/Rules/RuleErrors/RuleError135.php @@ -22,6 +22,8 @@ final class RuleError135 implements RuleError, LineRuleError, FileRuleError, Fix public string $fileDescription; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -45,6 +47,11 @@ public function getFileDescription(): string return $this->fileDescription; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError137.php b/src/Rules/RuleErrors/RuleError137.php index 93a02b903ad..47925389f35 100644 --- a/src/Rules/RuleErrors/RuleError137.php +++ b/src/Rules/RuleErrors/RuleError137.php @@ -17,6 +17,8 @@ final class RuleError137 implements RuleError, TipRuleError, FixableNodeRuleErro public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -30,6 +32,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError139.php b/src/Rules/RuleErrors/RuleError139.php index 94b105f9083..d40a1721d6f 100644 --- a/src/Rules/RuleErrors/RuleError139.php +++ b/src/Rules/RuleErrors/RuleError139.php @@ -20,6 +20,8 @@ final class RuleError139 implements RuleError, LineRuleError, TipRuleError, Fixa public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -38,6 +40,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError141.php b/src/Rules/RuleErrors/RuleError141.php index 9ad81cdba41..5713a025ba4 100644 --- a/src/Rules/RuleErrors/RuleError141.php +++ b/src/Rules/RuleErrors/RuleError141.php @@ -22,6 +22,8 @@ final class RuleError141 implements RuleError, FileRuleError, TipRuleError, Fixa public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -45,6 +47,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError143.php b/src/Rules/RuleErrors/RuleError143.php index 2d70326499f..1a08b2cf53f 100644 --- a/src/Rules/RuleErrors/RuleError143.php +++ b/src/Rules/RuleErrors/RuleError143.php @@ -25,6 +25,8 @@ final class RuleError143 implements RuleError, LineRuleError, FileRuleError, Tip public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -53,6 +55,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError145.php b/src/Rules/RuleErrors/RuleError145.php index 80de64966b6..f23ae4b9e7c 100644 --- a/src/Rules/RuleErrors/RuleError145.php +++ b/src/Rules/RuleErrors/RuleError145.php @@ -17,6 +17,8 @@ final class RuleError145 implements RuleError, IdentifierRuleError, FixableNodeR public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -30,6 +32,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError147.php b/src/Rules/RuleErrors/RuleError147.php index b9fdbb69efa..47a80d87d8d 100644 --- a/src/Rules/RuleErrors/RuleError147.php +++ b/src/Rules/RuleErrors/RuleError147.php @@ -20,6 +20,8 @@ final class RuleError147 implements RuleError, LineRuleError, IdentifierRuleErro public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -38,6 +40,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError149.php b/src/Rules/RuleErrors/RuleError149.php index c5746ae657c..802779e5600 100644 --- a/src/Rules/RuleErrors/RuleError149.php +++ b/src/Rules/RuleErrors/RuleError149.php @@ -22,6 +22,8 @@ final class RuleError149 implements RuleError, FileRuleError, IdentifierRuleErro public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -45,6 +47,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError151.php b/src/Rules/RuleErrors/RuleError151.php index e0185758188..284a5700c51 100644 --- a/src/Rules/RuleErrors/RuleError151.php +++ b/src/Rules/RuleErrors/RuleError151.php @@ -25,6 +25,8 @@ final class RuleError151 implements RuleError, LineRuleError, FileRuleError, Ide public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -53,6 +55,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError153.php b/src/Rules/RuleErrors/RuleError153.php index 9c83ccaa710..24052dc8c9c 100644 --- a/src/Rules/RuleErrors/RuleError153.php +++ b/src/Rules/RuleErrors/RuleError153.php @@ -20,6 +20,8 @@ final class RuleError153 implements RuleError, TipRuleError, IdentifierRuleError public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -38,6 +40,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError155.php b/src/Rules/RuleErrors/RuleError155.php index 8675da798db..038f19d0797 100644 --- a/src/Rules/RuleErrors/RuleError155.php +++ b/src/Rules/RuleErrors/RuleError155.php @@ -23,6 +23,8 @@ final class RuleError155 implements RuleError, LineRuleError, TipRuleError, Iden public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -46,6 +48,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError157.php b/src/Rules/RuleErrors/RuleError157.php index a1d75fd5940..a2a71b9acae 100644 --- a/src/Rules/RuleErrors/RuleError157.php +++ b/src/Rules/RuleErrors/RuleError157.php @@ -25,6 +25,8 @@ final class RuleError157 implements RuleError, FileRuleError, TipRuleError, Iden public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -53,6 +55,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError159.php b/src/Rules/RuleErrors/RuleError159.php index 0161fbe77ea..638054207c6 100644 --- a/src/Rules/RuleErrors/RuleError159.php +++ b/src/Rules/RuleErrors/RuleError159.php @@ -28,6 +28,8 @@ final class RuleError159 implements RuleError, LineRuleError, FileRuleError, Tip public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -61,6 +63,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError161.php b/src/Rules/RuleErrors/RuleError161.php index ffbe00fb117..008cfef2de8 100644 --- a/src/Rules/RuleErrors/RuleError161.php +++ b/src/Rules/RuleErrors/RuleError161.php @@ -18,6 +18,8 @@ final class RuleError161 implements RuleError, MetadataRuleError, FixableNodeRul /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -34,6 +36,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError163.php b/src/Rules/RuleErrors/RuleError163.php index 90055920f2d..840450cce04 100644 --- a/src/Rules/RuleErrors/RuleError163.php +++ b/src/Rules/RuleErrors/RuleError163.php @@ -21,6 +21,8 @@ final class RuleError163 implements RuleError, LineRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -42,6 +44,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError165.php b/src/Rules/RuleErrors/RuleError165.php index 78341db1dde..79eb32732d1 100644 --- a/src/Rules/RuleErrors/RuleError165.php +++ b/src/Rules/RuleErrors/RuleError165.php @@ -23,6 +23,8 @@ final class RuleError165 implements RuleError, FileRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -49,6 +51,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError167.php b/src/Rules/RuleErrors/RuleError167.php index eb0bb13f096..ccc7d2f3e83 100644 --- a/src/Rules/RuleErrors/RuleError167.php +++ b/src/Rules/RuleErrors/RuleError167.php @@ -26,6 +26,8 @@ final class RuleError167 implements RuleError, LineRuleError, FileRuleError, Met /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -57,6 +59,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError169.php b/src/Rules/RuleErrors/RuleError169.php index c11b12daf87..7f106fec80b 100644 --- a/src/Rules/RuleErrors/RuleError169.php +++ b/src/Rules/RuleErrors/RuleError169.php @@ -21,6 +21,8 @@ final class RuleError169 implements RuleError, TipRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -42,6 +44,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError171.php b/src/Rules/RuleErrors/RuleError171.php index 046ea44bc54..bd01419ffbb 100644 --- a/src/Rules/RuleErrors/RuleError171.php +++ b/src/Rules/RuleErrors/RuleError171.php @@ -24,6 +24,8 @@ final class RuleError171 implements RuleError, LineRuleError, TipRuleError, Meta /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -50,6 +52,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError173.php b/src/Rules/RuleErrors/RuleError173.php index 562b87b7299..17e1855459f 100644 --- a/src/Rules/RuleErrors/RuleError173.php +++ b/src/Rules/RuleErrors/RuleError173.php @@ -26,6 +26,8 @@ final class RuleError173 implements RuleError, FileRuleError, TipRuleError, Meta /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -57,6 +59,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError175.php b/src/Rules/RuleErrors/RuleError175.php index 6abb2d23b67..ed0f4965f7b 100644 --- a/src/Rules/RuleErrors/RuleError175.php +++ b/src/Rules/RuleErrors/RuleError175.php @@ -29,6 +29,8 @@ final class RuleError175 implements RuleError, LineRuleError, FileRuleError, Tip /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -65,6 +67,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError177.php b/src/Rules/RuleErrors/RuleError177.php index 59411c26df9..2acb39c8657 100644 --- a/src/Rules/RuleErrors/RuleError177.php +++ b/src/Rules/RuleErrors/RuleError177.php @@ -21,6 +21,8 @@ final class RuleError177 implements RuleError, IdentifierRuleError, MetadataRule /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -42,6 +44,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError179.php b/src/Rules/RuleErrors/RuleError179.php index f9a272fbb0d..128b9187435 100644 --- a/src/Rules/RuleErrors/RuleError179.php +++ b/src/Rules/RuleErrors/RuleError179.php @@ -24,6 +24,8 @@ final class RuleError179 implements RuleError, LineRuleError, IdentifierRuleErro /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -50,6 +52,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError181.php b/src/Rules/RuleErrors/RuleError181.php index c46cff34905..601aaf38c8e 100644 --- a/src/Rules/RuleErrors/RuleError181.php +++ b/src/Rules/RuleErrors/RuleError181.php @@ -26,6 +26,8 @@ final class RuleError181 implements RuleError, FileRuleError, IdentifierRuleErro /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -57,6 +59,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError183.php b/src/Rules/RuleErrors/RuleError183.php index 746ffca4e2d..61f5965218d 100644 --- a/src/Rules/RuleErrors/RuleError183.php +++ b/src/Rules/RuleErrors/RuleError183.php @@ -29,6 +29,8 @@ final class RuleError183 implements RuleError, LineRuleError, FileRuleError, Ide /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -65,6 +67,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError185.php b/src/Rules/RuleErrors/RuleError185.php index 963d9c68a55..14dd4f7a9d0 100644 --- a/src/Rules/RuleErrors/RuleError185.php +++ b/src/Rules/RuleErrors/RuleError185.php @@ -24,6 +24,8 @@ final class RuleError185 implements RuleError, TipRuleError, IdentifierRuleError /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -50,6 +52,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError187.php b/src/Rules/RuleErrors/RuleError187.php index 48a048d5def..a76455ad719 100644 --- a/src/Rules/RuleErrors/RuleError187.php +++ b/src/Rules/RuleErrors/RuleError187.php @@ -27,6 +27,8 @@ final class RuleError187 implements RuleError, LineRuleError, TipRuleError, Iden /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -58,6 +60,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError189.php b/src/Rules/RuleErrors/RuleError189.php index a7585053341..d6b044c4265 100644 --- a/src/Rules/RuleErrors/RuleError189.php +++ b/src/Rules/RuleErrors/RuleError189.php @@ -29,6 +29,8 @@ final class RuleError189 implements RuleError, FileRuleError, TipRuleError, Iden /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -65,6 +67,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError191.php b/src/Rules/RuleErrors/RuleError191.php index 3a68cef638d..55b9f266627 100644 --- a/src/Rules/RuleErrors/RuleError191.php +++ b/src/Rules/RuleErrors/RuleError191.php @@ -32,6 +32,8 @@ final class RuleError191 implements RuleError, LineRuleError, FileRuleError, Tip /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -73,6 +75,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError193.php b/src/Rules/RuleErrors/RuleError193.php index 606f3f7e671..452a3d7195b 100644 --- a/src/Rules/RuleErrors/RuleError193.php +++ b/src/Rules/RuleErrors/RuleError193.php @@ -15,6 +15,8 @@ final class RuleError193 implements RuleError, NonIgnorableRuleError, FixableNod public string $message; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -23,6 +25,11 @@ public function getMessage(): string return $this->message; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError195.php b/src/Rules/RuleErrors/RuleError195.php index 25725e50266..0c662f1da3d 100644 --- a/src/Rules/RuleErrors/RuleError195.php +++ b/src/Rules/RuleErrors/RuleError195.php @@ -18,6 +18,8 @@ final class RuleError195 implements RuleError, LineRuleError, NonIgnorableRuleEr public int $line; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -31,6 +33,11 @@ public function getLine(): int return $this->line; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError197.php b/src/Rules/RuleErrors/RuleError197.php index 7d0631e5410..e2b5af378ca 100644 --- a/src/Rules/RuleErrors/RuleError197.php +++ b/src/Rules/RuleErrors/RuleError197.php @@ -20,6 +20,8 @@ final class RuleError197 implements RuleError, FileRuleError, NonIgnorableRuleEr public string $fileDescription; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -38,6 +40,11 @@ public function getFileDescription(): string return $this->fileDescription; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError199.php b/src/Rules/RuleErrors/RuleError199.php index 711079460fd..c9ed6c09016 100644 --- a/src/Rules/RuleErrors/RuleError199.php +++ b/src/Rules/RuleErrors/RuleError199.php @@ -23,6 +23,8 @@ final class RuleError199 implements RuleError, LineRuleError, FileRuleError, Non public string $fileDescription; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -46,6 +48,11 @@ public function getFileDescription(): string return $this->fileDescription; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError201.php b/src/Rules/RuleErrors/RuleError201.php index c2004b13905..2ecf6acaadd 100644 --- a/src/Rules/RuleErrors/RuleError201.php +++ b/src/Rules/RuleErrors/RuleError201.php @@ -18,6 +18,8 @@ final class RuleError201 implements RuleError, TipRuleError, NonIgnorableRuleErr public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -31,6 +33,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError203.php b/src/Rules/RuleErrors/RuleError203.php index cd2a8a5254c..a04fc867b2c 100644 --- a/src/Rules/RuleErrors/RuleError203.php +++ b/src/Rules/RuleErrors/RuleError203.php @@ -21,6 +21,8 @@ final class RuleError203 implements RuleError, LineRuleError, TipRuleError, NonI public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -39,6 +41,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError205.php b/src/Rules/RuleErrors/RuleError205.php index 6d16f8f6e25..697c9eb673d 100644 --- a/src/Rules/RuleErrors/RuleError205.php +++ b/src/Rules/RuleErrors/RuleError205.php @@ -23,6 +23,8 @@ final class RuleError205 implements RuleError, FileRuleError, TipRuleError, NonI public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -46,6 +48,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError207.php b/src/Rules/RuleErrors/RuleError207.php index 063bf99ee18..8ee9144bc41 100644 --- a/src/Rules/RuleErrors/RuleError207.php +++ b/src/Rules/RuleErrors/RuleError207.php @@ -26,6 +26,8 @@ final class RuleError207 implements RuleError, LineRuleError, FileRuleError, Tip public string $tip; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -54,6 +56,11 @@ public function getTip(): string return $this->tip; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError209.php b/src/Rules/RuleErrors/RuleError209.php index a79f416b321..e007d8ed6c8 100644 --- a/src/Rules/RuleErrors/RuleError209.php +++ b/src/Rules/RuleErrors/RuleError209.php @@ -18,6 +18,8 @@ final class RuleError209 implements RuleError, IdentifierRuleError, NonIgnorable public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -31,6 +33,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError211.php b/src/Rules/RuleErrors/RuleError211.php index 685e06977fc..6e56086b320 100644 --- a/src/Rules/RuleErrors/RuleError211.php +++ b/src/Rules/RuleErrors/RuleError211.php @@ -21,6 +21,8 @@ final class RuleError211 implements RuleError, LineRuleError, IdentifierRuleErro public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -39,6 +41,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError213.php b/src/Rules/RuleErrors/RuleError213.php index 2995cf991bd..a9e6203b319 100644 --- a/src/Rules/RuleErrors/RuleError213.php +++ b/src/Rules/RuleErrors/RuleError213.php @@ -23,6 +23,8 @@ final class RuleError213 implements RuleError, FileRuleError, IdentifierRuleErro public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -46,6 +48,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError215.php b/src/Rules/RuleErrors/RuleError215.php index f741ded7017..01c82029576 100644 --- a/src/Rules/RuleErrors/RuleError215.php +++ b/src/Rules/RuleErrors/RuleError215.php @@ -26,6 +26,8 @@ final class RuleError215 implements RuleError, LineRuleError, FileRuleError, Ide public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -54,6 +56,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError217.php b/src/Rules/RuleErrors/RuleError217.php index b6af9245d0e..e78f445f2ba 100644 --- a/src/Rules/RuleErrors/RuleError217.php +++ b/src/Rules/RuleErrors/RuleError217.php @@ -21,6 +21,8 @@ final class RuleError217 implements RuleError, TipRuleError, IdentifierRuleError public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -39,6 +41,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError219.php b/src/Rules/RuleErrors/RuleError219.php index db5b302e1ad..2bdc632dc07 100644 --- a/src/Rules/RuleErrors/RuleError219.php +++ b/src/Rules/RuleErrors/RuleError219.php @@ -24,6 +24,8 @@ final class RuleError219 implements RuleError, LineRuleError, TipRuleError, Iden public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -47,6 +49,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError221.php b/src/Rules/RuleErrors/RuleError221.php index 603128daead..d43a6004fa6 100644 --- a/src/Rules/RuleErrors/RuleError221.php +++ b/src/Rules/RuleErrors/RuleError221.php @@ -26,6 +26,8 @@ final class RuleError221 implements RuleError, FileRuleError, TipRuleError, Iden public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -54,6 +56,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError223.php b/src/Rules/RuleErrors/RuleError223.php index 601584bf810..b3287c9a056 100644 --- a/src/Rules/RuleErrors/RuleError223.php +++ b/src/Rules/RuleErrors/RuleError223.php @@ -29,6 +29,8 @@ final class RuleError223 implements RuleError, LineRuleError, FileRuleError, Tip public string $identifier; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -62,6 +64,11 @@ public function getIdentifier(): string return $this->identifier; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError225.php b/src/Rules/RuleErrors/RuleError225.php index d04128179d1..06ef862f283 100644 --- a/src/Rules/RuleErrors/RuleError225.php +++ b/src/Rules/RuleErrors/RuleError225.php @@ -19,6 +19,8 @@ final class RuleError225 implements RuleError, MetadataRuleError, NonIgnorableRu /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -35,6 +37,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError227.php b/src/Rules/RuleErrors/RuleError227.php index 580e7b3f457..633cee8a5c4 100644 --- a/src/Rules/RuleErrors/RuleError227.php +++ b/src/Rules/RuleErrors/RuleError227.php @@ -22,6 +22,8 @@ final class RuleError227 implements RuleError, LineRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -43,6 +45,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError229.php b/src/Rules/RuleErrors/RuleError229.php index 7b6812c7114..42c17069737 100644 --- a/src/Rules/RuleErrors/RuleError229.php +++ b/src/Rules/RuleErrors/RuleError229.php @@ -24,6 +24,8 @@ final class RuleError229 implements RuleError, FileRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -50,6 +52,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError231.php b/src/Rules/RuleErrors/RuleError231.php index beda4eb54e7..bbbdb5a38f6 100644 --- a/src/Rules/RuleErrors/RuleError231.php +++ b/src/Rules/RuleErrors/RuleError231.php @@ -27,6 +27,8 @@ final class RuleError231 implements RuleError, LineRuleError, FileRuleError, Met /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -58,6 +60,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError233.php b/src/Rules/RuleErrors/RuleError233.php index 4f3d9079d73..b80cc0c36de 100644 --- a/src/Rules/RuleErrors/RuleError233.php +++ b/src/Rules/RuleErrors/RuleError233.php @@ -22,6 +22,8 @@ final class RuleError233 implements RuleError, TipRuleError, MetadataRuleError, /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -43,6 +45,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError235.php b/src/Rules/RuleErrors/RuleError235.php index 574134cace2..aaf1c80deeb 100644 --- a/src/Rules/RuleErrors/RuleError235.php +++ b/src/Rules/RuleErrors/RuleError235.php @@ -25,6 +25,8 @@ final class RuleError235 implements RuleError, LineRuleError, TipRuleError, Meta /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -51,6 +53,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError237.php b/src/Rules/RuleErrors/RuleError237.php index f6da8b7bacd..1d4abc43324 100644 --- a/src/Rules/RuleErrors/RuleError237.php +++ b/src/Rules/RuleErrors/RuleError237.php @@ -27,6 +27,8 @@ final class RuleError237 implements RuleError, FileRuleError, TipRuleError, Meta /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -58,6 +60,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError239.php b/src/Rules/RuleErrors/RuleError239.php index e7c1d41c77e..5ee0033dc21 100644 --- a/src/Rules/RuleErrors/RuleError239.php +++ b/src/Rules/RuleErrors/RuleError239.php @@ -30,6 +30,8 @@ final class RuleError239 implements RuleError, LineRuleError, FileRuleError, Tip /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -66,6 +68,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError241.php b/src/Rules/RuleErrors/RuleError241.php index 8f9291d2e9d..65d853915a1 100644 --- a/src/Rules/RuleErrors/RuleError241.php +++ b/src/Rules/RuleErrors/RuleError241.php @@ -22,6 +22,8 @@ final class RuleError241 implements RuleError, IdentifierRuleError, MetadataRule /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -43,6 +45,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError243.php b/src/Rules/RuleErrors/RuleError243.php index 2585591107b..e5762ee32b3 100644 --- a/src/Rules/RuleErrors/RuleError243.php +++ b/src/Rules/RuleErrors/RuleError243.php @@ -25,6 +25,8 @@ final class RuleError243 implements RuleError, LineRuleError, IdentifierRuleErro /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -51,6 +53,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError245.php b/src/Rules/RuleErrors/RuleError245.php index 18226093033..369e7cbee33 100644 --- a/src/Rules/RuleErrors/RuleError245.php +++ b/src/Rules/RuleErrors/RuleError245.php @@ -27,6 +27,8 @@ final class RuleError245 implements RuleError, FileRuleError, IdentifierRuleErro /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -58,6 +60,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError247.php b/src/Rules/RuleErrors/RuleError247.php index 6ea594080f4..0a482fa833b 100644 --- a/src/Rules/RuleErrors/RuleError247.php +++ b/src/Rules/RuleErrors/RuleError247.php @@ -30,6 +30,8 @@ final class RuleError247 implements RuleError, LineRuleError, FileRuleError, Ide /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -66,6 +68,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError249.php b/src/Rules/RuleErrors/RuleError249.php index 7c8f370035f..ed86576c33f 100644 --- a/src/Rules/RuleErrors/RuleError249.php +++ b/src/Rules/RuleErrors/RuleError249.php @@ -25,6 +25,8 @@ final class RuleError249 implements RuleError, TipRuleError, IdentifierRuleError /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -51,6 +53,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError251.php b/src/Rules/RuleErrors/RuleError251.php index 9d66058153c..b77d8060f82 100644 --- a/src/Rules/RuleErrors/RuleError251.php +++ b/src/Rules/RuleErrors/RuleError251.php @@ -28,6 +28,8 @@ final class RuleError251 implements RuleError, LineRuleError, TipRuleError, Iden /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -59,6 +61,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError253.php b/src/Rules/RuleErrors/RuleError253.php index ba542597509..17fad64bc19 100644 --- a/src/Rules/RuleErrors/RuleError253.php +++ b/src/Rules/RuleErrors/RuleError253.php @@ -30,6 +30,8 @@ final class RuleError253 implements RuleError, FileRuleError, TipRuleError, Iden /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -66,6 +68,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ diff --git a/src/Rules/RuleErrors/RuleError255.php b/src/Rules/RuleErrors/RuleError255.php index 148045815f6..61d6fbd5055 100644 --- a/src/Rules/RuleErrors/RuleError255.php +++ b/src/Rules/RuleErrors/RuleError255.php @@ -33,6 +33,8 @@ final class RuleError255 implements RuleError, LineRuleError, FileRuleError, Tip /** @var mixed[] */ public array $metadata; + public Node $originalNode; + /** @var callable(Node): Node */ public $newNodeCallable; @@ -74,6 +76,11 @@ public function getMetadata(): array return $this->metadata; } + public function getOriginalNode(): Node + { + return $this->originalNode; + } + /** * @return callable(Node): Node */ From e10458be3002c78afaf56f0dd6dd0367cce3973c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 10:34:20 +0200 Subject: [PATCH 1483/3097] FinalClassRule now fixable --- build/PHPStan/Build/FinalClassRule.php | 24 +++++++++---- tests/PHPStan/Build/FinalClassRuleTest.php | 35 +++++++++++++++++++ tests/PHPStan/Build/data/final-class-rule.php | 32 +++++++++++++++++ .../Build/data/final-class-rule.php.fixed | 32 +++++++++++++++++ 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Build/FinalClassRuleTest.php create mode 100644 tests/PHPStan/Build/data/final-class-rule.php create mode 100644 tests/PHPStan/Build/data/final-class-rule.php.fixed diff --git a/build/PHPStan/Build/FinalClassRule.php b/build/PHPStan/Build/FinalClassRule.php index 018e1da4f8e..85b313bb03b 100644 --- a/build/PHPStan/Build/FinalClassRule.php +++ b/build/PHPStan/Build/FinalClassRule.php @@ -2,6 +2,7 @@ namespace PHPStan\Build; +use PhpParser\Modifiers; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\File\FileHelper; @@ -23,7 +24,7 @@ final class FinalClassRule implements Rule { - public function __construct(private FileHelper $fileHelper) + public function __construct(private FileHelper $fileHelper, private bool $skipTests = true) { } @@ -58,16 +59,25 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (str_starts_with($this->fileHelper->normalizePath($scope->getFile()), $this->fileHelper->normalizePath(dirname(__DIR__, 3) . '/tests'))) { + if ($this->skipTests && str_starts_with($this->fileHelper->normalizePath($scope->getFile()), $this->fileHelper->normalizePath(dirname(__DIR__, 3) . '/tests'))) { return []; } + $errorBuilder = RuleErrorBuilder::message( + sprintf('Class %s must be abstract or final.', $classReflection->getDisplayName()), + )->identifier('phpstan.finalClass'); + + $originalNode = $node->getOriginalNode(); + if ($originalNode instanceof Node\Stmt\Class_ && $originalNode->name !== null) { + $errorBuilder->fixNode($originalNode, static function ($classNode) { + $classNode->flags |= Modifiers::FINAL; + + return $classNode; + }); + } + return [ - RuleErrorBuilder::message( - sprintf('Class %s must be abstract or final.', $classReflection->getDisplayName()), - ) - ->identifier('phpstan.finalClass') - ->build(), + $errorBuilder->build(), ]; } diff --git a/tests/PHPStan/Build/FinalClassRuleTest.php b/tests/PHPStan/Build/FinalClassRuleTest.php new file mode 100644 index 00000000000..1889040248c --- /dev/null +++ b/tests/PHPStan/Build/FinalClassRuleTest.php @@ -0,0 +1,35 @@ + + */ +class FinalClassRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new FinalClassRule(self::getContainer()->getByType(FileHelper::class), skipTests: false); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/final-class-rule.php'], [ + [ + 'Class FinalClassRule\Baz must be abstract or final.', + 29, + ], + ]); + } + + public function testFix(): void + { + $this->fix(__DIR__ . '/data/final-class-rule.php', __DIR__ . '/data/final-class-rule.php.fixed'); + } + +} diff --git a/tests/PHPStan/Build/data/final-class-rule.php b/tests/PHPStan/Build/data/final-class-rule.php new file mode 100644 index 00000000000..512d7a6bcf9 --- /dev/null +++ b/tests/PHPStan/Build/data/final-class-rule.php @@ -0,0 +1,32 @@ + Date: Thu, 29 May 2025 10:39:09 +0200 Subject: [PATCH 1484/3097] Do not say that an error is fixable if the old code is same as new code --- src/Analyser/RuleErrorTransformer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index c4985a4ad36..dc211b05321 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -133,7 +133,9 @@ public function transform( $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); - $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diff($oldCode, $newCode)); + if ($oldCode !== $newCode) { + $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diff($oldCode, $newCode)); + } } return new Error( From 9b6bb2f4d88bc1a0aade80601bfc02f45d719d1b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 10:43:41 +0200 Subject: [PATCH 1485/3097] AttributeNamedArgumentsRule made fixable --- .../Build/AttributeNamedArgumentsRule.php | 43 ++++++++++++++++++- .../Build/AttributeNamedArgumentsRuleTest.php | 34 +++++++++++++++ .../Build/data/attribute-arguments.php | 17 ++++++++ .../Build/data/attribute-arguments.php.fixed | 17 ++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Build/AttributeNamedArgumentsRuleTest.php create mode 100644 tests/PHPStan/Build/data/attribute-arguments.php create mode 100644 tests/PHPStan/Build/data/attribute-arguments.php.fixed diff --git a/build/PHPStan/Build/AttributeNamedArgumentsRule.php b/build/PHPStan/Build/AttributeNamedArgumentsRule.php index 5a582a24a6f..1ff8295289d 100644 --- a/build/PHPStan/Build/AttributeNamedArgumentsRule.php +++ b/build/PHPStan/Build/AttributeNamedArgumentsRule.php @@ -5,8 +5,11 @@ use PhpParser\Node; use PhpParser\Node\Attribute; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\ShouldNotHappenException; +use function count; use function sprintf; /** @@ -15,6 +18,10 @@ final class AttributeNamedArgumentsRule implements Rule { + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + public function getNodeType(): string { return Attribute::class; @@ -22,15 +29,49 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + $attributeName = $node->name->toString(); + if (!$this->reflectionProvider->hasClass($attributeName)) { + return []; + } + + $attributeReflection = $this->reflectionProvider->getClass($attributeName); + if (!$attributeReflection->hasConstructor()) { + return []; + } + $constructor = $attributeReflection->getConstructor(); + $variants = $constructor->getVariants(); + if (count($variants) !== 1) { + return []; + } + + $parameters = $variants[0]->getParameters(); + foreach ($node->args as $arg) { if ($arg->name !== null) { - continue; + break; } return [ RuleErrorBuilder::message(sprintf('Attribute %s is not using named arguments.', $node->name->toString())) ->identifier('phpstan.attributeWithoutNamedArguments') ->nonIgnorable() + ->fixNode($node, static function (Node $node) use ($parameters) { + $args = $node->args; + foreach ($args as $i => $arg) { + if ($arg->name !== null) { + break; + } + + $parameterName = $parameters[$i]->getName(); + if ($parameterName === '') { + throw new ShouldNotHappenException(); + } + + $arg->name = new Node\Identifier($parameterName); + } + + return $node; + }) ->build(), ]; } diff --git a/tests/PHPStan/Build/AttributeNamedArgumentsRuleTest.php b/tests/PHPStan/Build/AttributeNamedArgumentsRuleTest.php new file mode 100644 index 00000000000..94953f813d2 --- /dev/null +++ b/tests/PHPStan/Build/AttributeNamedArgumentsRuleTest.php @@ -0,0 +1,34 @@ + + */ +class AttributeNamedArgumentsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new AttributeNamedArgumentsRule($this->createReflectionProvider()); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/attribute-arguments.php'], [ + [ + 'Attribute PHPStan\DependencyInjection\AutowiredService is not using named arguments.', + 13, + ], + ]); + } + + public function testFix(): void + { + $this->fix(__DIR__ . '/data/attribute-arguments.php', __DIR__ . '/data/attribute-arguments.php.fixed'); + } + +} diff --git a/tests/PHPStan/Build/data/attribute-arguments.php b/tests/PHPStan/Build/data/attribute-arguments.php new file mode 100644 index 00000000000..12ca9cbceaa --- /dev/null +++ b/tests/PHPStan/Build/data/attribute-arguments.php @@ -0,0 +1,17 @@ + Date: Thu, 29 May 2025 11:03:49 +0200 Subject: [PATCH 1486/3097] Fix - cannot downgrade named arguments in MethodCall --- tests/PHPStan/Build/FinalClassRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Build/FinalClassRuleTest.php b/tests/PHPStan/Build/FinalClassRuleTest.php index 1889040248c..4a09327a381 100644 --- a/tests/PHPStan/Build/FinalClassRuleTest.php +++ b/tests/PHPStan/Build/FinalClassRuleTest.php @@ -14,7 +14,7 @@ class FinalClassRuleTest extends RuleTestCase protected function getRule(): Rule { - return new FinalClassRule(self::getContainer()->getByType(FileHelper::class), skipTests: false); + return new FinalClassRule(self::getContainer()->getByType(FileHelper::class), false); } public function testRule(): void From 07486f502e4a581c8d5602017b759fdeab765afc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 11:32:48 +0200 Subject: [PATCH 1487/3097] Fix fixing nodes in used traits --- src/Analyser/FileAnalyser.php | 1 + src/Analyser/NodeScopeResolver.php | 11 ++++++----- src/Node/InTraitNode.php | 13 ++++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 340039bf4a9..6c07b2cc991 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -107,6 +107,7 @@ public function analyseFile( if ($node instanceof InTraitNode) { $traitNode = $node->getOriginalNode(); $linesToIgnore[$scope->getFileDescription()] = $this->getLinesToIgnoreFromTokens([$traitNode]); + $parserNodes = $node->getParserNodes(); } if ($outerNodeCallback !== null) { $outerNodeCallback($node, $scope); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8c070bb0400..23b25a55399 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6291,16 +6291,17 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS $adaptations[] = $adaptation; } $parserNodes = $this->parser->parseFile($fileName); - $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); } } /** + * @param Node\Stmt[] $parserNodes * @param Node[]|Node|scalar|null $node * @param Node\Stmt\TraitUseAdaptation[] $adaptations * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void + private function processNodesForTraitUse(array $parserNodes, $node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void { if ($node instanceof Node) { if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { @@ -6347,7 +6348,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection throw new ShouldNotHappenException(); } $traitScope = $scope->enterTrait($traitReflection); - $nodeCallback(new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope); + $nodeCallback(new InTraitNode($node, $parserNodes, $traitReflection, $scope->getClassReflection()), $traitScope); $this->processStmtNodes($node, $stmts, $traitScope, $nodeCallback, StatementContext::createTopLevel()); return; } @@ -6359,11 +6360,11 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection } foreach ($node->getSubNodeNames() as $subNodeName) { $subNode = $node->{$subNodeName}; - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } elseif (is_array($node)) { foreach ($node as $subNode) { - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } } diff --git a/src/Node/InTraitNode.php b/src/Node/InTraitNode.php index b7834e713fb..4d03716b025 100644 --- a/src/Node/InTraitNode.php +++ b/src/Node/InTraitNode.php @@ -11,7 +11,10 @@ final class InTraitNode extends Node\Stmt implements VirtualNode { - public function __construct(private Node\Stmt\Trait_ $originalNode, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) + /** + * @param Node\Stmt[] $parserNodes + */ + public function __construct(private Node\Stmt\Trait_ $originalNode, private array $parserNodes, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) { parent::__construct($originalNode->getAttributes()); } @@ -21,6 +24,14 @@ public function getOriginalNode(): Node\Stmt\Trait_ return $this->originalNode; } + /** + * @return Node\Stmt[] + */ + public function getParserNodes(): array + { + return $this->parserNodes; + } + public function getTraitReflection(): ClassReflection { return $this->traitReflection; From acc0fd9097a29bde3ec0ba5b23b19b2de2ba5f05 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 12:13:10 +0200 Subject: [PATCH 1488/3097] Do not attempt to pretty-print new code if node was not found --- src/Analyser/RuleErrorTransformer.php | 10 ++++++---- src/Fixable/ReplacingNodeVisitor.php | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index dc211b05321..146c5d4acb8 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -130,11 +130,13 @@ public function transform( /** @var Stmt[] $newStmts */ $newStmts = $traverser->traverse($newStmts); - $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); - $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); + if ($visitor->isFound()) { + $printer = new PhpPrinter(['indent' => str_repeat($indentDetector->indentCharacter, $indentDetector->indentSize)]); + $newCode = $printer->printFormatPreserving($newStmts, $fileNodes, $oldTokens); - if ($oldCode !== $newCode) { - $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diff($oldCode, $newCode)); + if ($oldCode !== $newCode) { + $fixedErrorDiff = new FixedErrorDiff($hash, $this->differ->diff($oldCode, $newCode)); + } } } diff --git a/src/Fixable/ReplacingNodeVisitor.php b/src/Fixable/ReplacingNodeVisitor.php index a6de9b8f1f8..345869fcb3f 100644 --- a/src/Fixable/ReplacingNodeVisitor.php +++ b/src/Fixable/ReplacingNodeVisitor.php @@ -10,6 +10,8 @@ final class ReplacingNodeVisitor extends NodeVisitorAbstract { + private bool $found = false; + /** * @param callable(Node): Node $newNodeCallable */ @@ -24,6 +26,8 @@ public function enterNode(Node $node): ?Node return null; } + $this->found = true; + $callable = $this->newNodeCallable; $newNode = $callable($node); if ($newNode instanceof VirtualNode) { @@ -33,4 +37,9 @@ public function enterNode(Node $node): ?Node return $newNode; } + public function isFound(): bool + { + return $this->found; + } + } From dc8c831f1af7618ed38034ba62ae0f693a972bb5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 May 2025 21:05:32 +0200 Subject: [PATCH 1489/3097] Unwrap virtual nodes in AST when fixing code --- src/Analyser/RuleErrorTransformer.php | 2 ++ src/Fixable/UnwrapVirtualNodesVisitor.php | 27 +++++++++++++++++++ .../PHPStan/Build/NamedArgumentsRuleTest.php | 12 +++++++++ .../Build/data/named-arguments-match.php | 16 +++++++++++ .../data/named-arguments-match.php.fixed | 16 +++++++++++ 5 files changed, 73 insertions(+) create mode 100644 src/Fixable/UnwrapVirtualNodesVisitor.php create mode 100644 tests/PHPStan/Build/data/named-arguments-match.php create mode 100644 tests/PHPStan/Build/data/named-arguments-match.php.fixed diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 146c5d4acb8..0176d537a9c 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -12,6 +12,7 @@ use PHPStan\Fixable\PhpPrinter; use PHPStan\Fixable\PhpPrinterIndentationDetectorVisitor; use PHPStan\Fixable\ReplacingNodeVisitor; +use PHPStan\Fixable\UnwrapVirtualNodesVisitor; use PHPStan\Node\VirtualNode; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\FixableNodeRuleError; @@ -118,6 +119,7 @@ public function transform( $indentTraverser->traverse($fileNodes); $cloningTraverser = new NodeTraverser(); + $cloningTraverser->addVisitor(new UnwrapVirtualNodesVisitor()); $cloningTraverser->addVisitor(new CloningVisitor()); /** @var Stmt[] $newStmts */ diff --git a/src/Fixable/UnwrapVirtualNodesVisitor.php b/src/Fixable/UnwrapVirtualNodesVisitor.php new file mode 100644 index 00000000000..1b315b32b8c --- /dev/null +++ b/src/Fixable/UnwrapVirtualNodesVisitor.php @@ -0,0 +1,27 @@ +cond instanceof AlwaysRememberedExpr) { + return null; + } + + $node->cond = $node->cond->expr; + + return $node; + } + +} diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index c44c4419e5a..e71c37e6170 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -64,4 +64,16 @@ public function testFix(): void ); } + public function testFixFileWithMatch(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->fix( + __DIR__ . '/data/named-arguments-match.php', + __DIR__ . '/data/named-arguments-match.php.fixed', + ); + } + } diff --git a/tests/PHPStan/Build/data/named-arguments-match.php b/tests/PHPStan/Build/data/named-arguments-match.php new file mode 100644 index 00000000000..f53c87832cc --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments-match.php @@ -0,0 +1,16 @@ += 8.0 + +namespace NamedArgumentsMatchRule; + +use Exception; + +function (bool $a, bool $b): void { + foreach ([1, 2, 3] as $v) { + match (true) { + $a => 1, + $b => 2, + default => 3, + }; + new Exception('foo', 0, new Exception('prev')); + } +}; diff --git a/tests/PHPStan/Build/data/named-arguments-match.php.fixed b/tests/PHPStan/Build/data/named-arguments-match.php.fixed new file mode 100644 index 00000000000..9cec06b484f --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments-match.php.fixed @@ -0,0 +1,16 @@ += 8.0 + +namespace NamedArgumentsMatchRule; + +use Exception; + +function (bool $a, bool $b): void { + foreach ([1, 2, 3] as $v) { + match (true) { + $a => 1, + $b => 2, + default => 3, + }; + new Exception('foo', previous: new Exception('prev')); + } +}; From efcc982fdbbc34322ad27bdd95d308d986058c1e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 30 May 2025 09:31:11 +0200 Subject: [PATCH 1490/3097] Normalize arguments before calling into TypeSpecifyingExtensions --- src/Analyser/ArgumentsNormalizer.php | 20 +++++++++++ src/Analyser/TypeSpecifier.php | 42 ++++++++++++++++++----- tests/PHPStan/Analyser/nsrt/bug-13088.php | 23 +++++++++++++ 3 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13088.php diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index da37080cfa8..1cdd33ebf77 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -98,6 +98,11 @@ public static function reorderFuncArguments( return null; } + // return identical object if not reordered, as TypeSpecifier relies on object identity + if ($reorderedArgs === $functionCall->getArgs()) { + return $functionCall; + } + return new FuncCall( $functionCall->name, $reorderedArgs, @@ -116,6 +121,11 @@ public static function reorderMethodArguments( return null; } + // return identical object if not reordered, as TypeSpecifier relies on object identity + if ($reorderedArgs === $methodCall->getArgs()) { + return $methodCall; + } + return new MethodCall( $methodCall->var, $methodCall->name, @@ -135,6 +145,11 @@ public static function reorderStaticCallArguments( return null; } + // return identical object if not reordered, as TypeSpecifier relies on object identity + if ($reorderedArgs === $staticCall->getArgs()) { + return $staticCall; + } + return new StaticCall( $staticCall->class, $staticCall->name, @@ -154,6 +169,11 @@ public static function reorderNewArguments( return null; } + // return identical object if not reordered, as TypeSpecifier relies on object identity + if ($reorderedArgs === $new->getArgs()) { + return $new; + } + return new New_( $new->class, $reorderedArgs, diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 2b43724ff06..12bd0d2153a 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -476,7 +476,15 @@ public function specifyTypesInCondition( } elseif ($expr instanceof FuncCall && $expr->name instanceof Name) { if ($this->reflectionProvider->hasFunction($expr->name, $scope)) { + // lazy create parametersAcceptor, as creation can be expensive + $parametersAcceptor = null; + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + if (count($expr->getArgs()) > 0) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $functionReflection->getVariants(), $functionReflection->getNamedArgumentsVariants()); + $expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr; + } + foreach ($this->getFunctionTypeSpecifyingExtensions() as $extension) { if (!$extension->isFunctionSupported($functionReflection, $expr, $context)) { continue; @@ -485,10 +493,10 @@ public function specifyTypesInCondition( return $extension->specifyTypes($functionReflection, $expr, $scope, $context); } - // lazy create parametersAcceptor, as creation can be expensive - $parametersAcceptor = null; if (count($expr->getArgs()) > 0) { - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $functionReflection->getVariants(), $functionReflection->getNamedArgumentsVariants()); + if ($parametersAcceptor === null) { + throw new ShouldNotHappenException(); + } $specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope); if ($specifiedTypes !== null) { @@ -518,6 +526,14 @@ public function specifyTypesInCondition( $methodCalledOnType = $scope->getType($expr->var); $methodReflection = $scope->getMethodReflection($methodCalledOnType, $expr->name->name); if ($methodReflection !== null) { + // lazy create parametersAcceptor, as creation can be expensive + $parametersAcceptor = null; + + if (count($expr->getArgs()) > 0) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $methodReflection->getVariants(), $methodReflection->getNamedArgumentsVariants()); + $expr = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $expr) ?? $expr; + } + $referencedClasses = $methodCalledOnType->getObjectClassNames(); if ( count($referencedClasses) === 1 @@ -533,10 +549,10 @@ public function specifyTypesInCondition( } } - // lazy create parametersAcceptor, as creation can be expensive - $parametersAcceptor = null; if (count($expr->getArgs()) > 0) { - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $methodReflection->getVariants(), $methodReflection->getNamedArgumentsVariants()); + if ($parametersAcceptor === null) { + throw new ShouldNotHappenException(); + } $specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope); if ($specifiedTypes !== null) { @@ -571,6 +587,14 @@ public function specifyTypesInCondition( $staticMethodReflection = $scope->getMethodReflection($calleeType, $expr->name->name); if ($staticMethodReflection !== null) { + // lazy create parametersAcceptor, as creation can be expensive + $parametersAcceptor = null; + + if (count($expr->getArgs()) > 0) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $staticMethodReflection->getVariants(), $staticMethodReflection->getNamedArgumentsVariants()); + $expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr; + } + $referencedClasses = $calleeType->getObjectClassNames(); if ( count($referencedClasses) === 1 @@ -586,10 +610,10 @@ public function specifyTypesInCondition( } } - // lazy create parametersAcceptor, as creation can be expensive - $parametersAcceptor = null; if (count($expr->getArgs()) > 0) { - $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $expr->getArgs(), $staticMethodReflection->getVariants(), $staticMethodReflection->getNamedArgumentsVariants()); + if ($parametersAcceptor === null) { + throw new ShouldNotHappenException(); + } $specifiedTypes = $this->specifyTypesFromConditionalReturnType($context, $expr, $parametersAcceptor, $scope); if ($specifiedTypes !== null) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-13088.php b/tests/PHPStan/Analyser/nsrt/bug-13088.php new file mode 100644 index 00000000000..29630344961 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13088.php @@ -0,0 +1,23 @@ += 8.0 + +namespace Bug13088; + +use function PHPStan\dumpType; +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + public function sayHello(string $s, int $offset): void + { + if (preg_match('~msgstr "(.*)"\n~', $s, $matches, 0, $offset) === 1) { + assertType('array{non-falsy-string, string}', $matches); + } + } + + public function sayHello2(string $s, int $offset): void + { + if (preg_match('~msgstr "(.*)"\n~', $s, $matches, offset: $offset) === 1) { + assertType('array{non-falsy-string, string}', $matches); + } + } +} From 772d3aeaa2cb5ae240394ff88d082d1698168751 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 May 2025 09:56:19 +0200 Subject: [PATCH 1491/3097] NamedArgumentsRule - do not touch by-ref arguments --- build/PHPStan/Build/NamedArgumentsRule.php | 7 +++++++ .../PHPStan/Build/NamedArgumentsRuleTest.php | 4 ++++ tests/PHPStan/Build/data/named-arguments.php | 21 +++++++++++++++++++ .../Build/data/named-arguments.php.fixed | 21 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/build/PHPStan/Build/NamedArgumentsRule.php b/build/PHPStan/Build/NamedArgumentsRule.php index 250ef7ba979..14895bed7ec 100644 --- a/build/PHPStan/Build/NamedArgumentsRule.php +++ b/build/PHPStan/Build/NamedArgumentsRule.php @@ -125,6 +125,9 @@ private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, if ($parameter->getDefaultValue() === null) { continue; } + if (!$parameter->passedByReference()->no()) { + continue; + } $argValue = $scope->getType($normalizedArg->value); if ($normalizedArg->name !== null) { continue; @@ -192,6 +195,10 @@ private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, $newArgs[] = $originalArg; continue; } + if (!$parameter->passedByReference()->no()) { + $newArgs[] = $originalArg; + continue; + } $argValue = $scope->getType($normalizedArg->value); if ($argValue->equals($parameter->getDefaultValue())) { $skippedOptional = true; diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index e71c37e6170..72c5e506a93 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -37,6 +37,10 @@ public function testRule(): void 'You\'re passing a non-default value Exception to parameter $previous but previous arguments are passing default values to their parameters ($message, $code). You can skip them and use named argument for $previous instead.', 20, ], + [ + 'You\'re passing a non-default value 3 to parameter $yetAnother but previous argument is passing default value to its parameter ($another). You can skip it and use named argument for $yetAnother instead.', + 41, + ], ]); } diff --git a/tests/PHPStan/Build/data/named-arguments.php b/tests/PHPStan/Build/data/named-arguments.php index d27bdc4de4d..db471495aac 100644 --- a/tests/PHPStan/Build/data/named-arguments.php +++ b/tests/PHPStan/Build/data/named-arguments.php @@ -21,3 +21,24 @@ public function doFoo(): void } } + +function (): void { + $output = null; + exec('exec', $output, $exitCode); +}; + +class Bar +{ + + public static function doFoo($a, &$byRef = null, int $another = 1, int $yetAnother = 2): void + { + + } + + public function doBar(): void + { + $byRef = null; + self::doFoo('a', $byRef, 1, 3); + } + +} diff --git a/tests/PHPStan/Build/data/named-arguments.php.fixed b/tests/PHPStan/Build/data/named-arguments.php.fixed index b85af027992..f216ff85fd2 100644 --- a/tests/PHPStan/Build/data/named-arguments.php.fixed +++ b/tests/PHPStan/Build/data/named-arguments.php.fixed @@ -21,3 +21,24 @@ class Foo } } + +function (): void { + $output = null; + exec('exec', $output, $exitCode); +}; + +class Bar +{ + + public static function doFoo($a, &$byRef = null, int $another = 1, int $yetAnother = 2): void + { + + } + + public function doBar(): void + { + $byRef = null; + self::doFoo('a', $byRef, yetAnother: 3); + } + +} From fabf273fd422fed5be7e7a33ef3acd6e3d0f2d13 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 May 2025 11:16:02 +0200 Subject: [PATCH 1492/3097] Fix too greedy removal of named arguments --- src/Analyser/ArgumentsNormalizer.php | 11 +++++++++- .../PHPStan/Build/NamedArgumentsRuleTest.php | 4 ++++ tests/PHPStan/Build/data/named-arguments.php | 19 +++++++++++++++++ .../Build/data/named-arguments.php.fixed | 21 ++++++++++++++++++- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index 1cdd33ebf77..4a4844cd8cd 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -222,7 +222,16 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array foreach ($callArgs as $i => $arg) { if ($arg->name === null) { // add regular args as is - $reorderedArgs[$i] = $arg; + + $attributes = $arg->getAttributes(); + $attributes[self::ORIGINAL_ARG_ATTRIBUTE] = $arg; + $reorderedArgs[$i] = new Arg( + $arg->value, + $arg->byRef, + $arg->unpack, + $attributes, + null, + ); } elseif (array_key_exists($arg->name->toString(), $argumentPositions)) { $argName = $arg->name->toString(); // order named args into the position the signature expects them diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index 72c5e506a93..596023259c8 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -41,6 +41,10 @@ public function testRule(): void 'You\'re passing a non-default value 3 to parameter $yetAnother but previous argument is passing default value to its parameter ($another). You can skip it and use named argument for $yetAnother instead.', 41, ], + [ + 'Named argument $priority can be omitted, type 1 is the same as the default value.', + 59, + ], ]); } diff --git a/tests/PHPStan/Build/data/named-arguments.php b/tests/PHPStan/Build/data/named-arguments.php index db471495aac..d8db59f36f5 100644 --- a/tests/PHPStan/Build/data/named-arguments.php +++ b/tests/PHPStan/Build/data/named-arguments.php @@ -42,3 +42,22 @@ public function doBar(): void } } + +class Baz +{ + + public const HIGH = 1; + public const MEDIUM = 2; + + public static function send(Bar $message, ?string $queueName = null, ?Bar $mode = null, int $priority = self::HIGH) + { + + } + + public function doFoo(Bar $message): void + { + self::send($message, 'queue', priority: self::HIGH); + self::send($message, 'queue', priority: self::MEDIUM); + } + +} diff --git a/tests/PHPStan/Build/data/named-arguments.php.fixed b/tests/PHPStan/Build/data/named-arguments.php.fixed index f216ff85fd2..cf1838943c7 100644 --- a/tests/PHPStan/Build/data/named-arguments.php.fixed +++ b/tests/PHPStan/Build/data/named-arguments.php.fixed @@ -13,7 +13,7 @@ class Foo new Exception('foo', 0, null); new Exception('foo', previous: new Exception('previous')); new Exception('foo', previous: new Exception('previous')); - new Exception(previous: new Exception('previous')); + new Exception('foo', previous: new Exception('previous')); new Exception('foo', code: 1, previous: new Exception('previous')); new Exception('foo', 1, new Exception('previous')); new Exception('foo', 1); @@ -42,3 +42,22 @@ class Bar } } + +class Baz +{ + + public const HIGH = 1; + public const MEDIUM = 2; + + public static function send(Bar $message, ?string $queueName = null, ?Bar $mode = null, int $priority = self::HIGH) + { + + } + + public function doFoo(Bar $message): void + { + self::send($message, 'queue'); + self::send($message, 'queue', priority: self::MEDIUM); + } + +} From cda009b48a983382a0f39b9ced937734c538b180 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 May 2025 12:07:50 +0200 Subject: [PATCH 1493/3097] Revert "Fix fixing nodes in used traits" This reverts commit 07486f502e4a581c8d5602017b759fdeab765afc. --- src/Analyser/FileAnalyser.php | 1 - src/Analyser/NodeScopeResolver.php | 11 +++++------ src/Node/InTraitNode.php | 13 +------------ 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 6c07b2cc991..340039bf4a9 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -107,7 +107,6 @@ public function analyseFile( if ($node instanceof InTraitNode) { $traitNode = $node->getOriginalNode(); $linesToIgnore[$scope->getFileDescription()] = $this->getLinesToIgnoreFromTokens([$traitNode]); - $parserNodes = $node->getParserNodes(); } if ($outerNodeCallback !== null) { $outerNodeCallback($node, $scope); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 23b25a55399..8c070bb0400 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6291,17 +6291,16 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS $adaptations[] = $adaptation; } $parserNodes = $this->parser->parseFile($fileName); - $this->processNodesForTraitUse($parserNodes, $parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $adaptations, $nodeCallback); } } /** - * @param Node\Stmt[] $parserNodes * @param Node[]|Node|scalar|null $node * @param Node\Stmt\TraitUseAdaptation[] $adaptations * @param callable(Node $node, Scope $scope): void $nodeCallback */ - private function processNodesForTraitUse(array $parserNodes, $node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void + private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void { if ($node instanceof Node) { if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { @@ -6348,7 +6347,7 @@ private function processNodesForTraitUse(array $parserNodes, $node, ClassReflect throw new ShouldNotHappenException(); } $traitScope = $scope->enterTrait($traitReflection); - $nodeCallback(new InTraitNode($node, $parserNodes, $traitReflection, $scope->getClassReflection()), $traitScope); + $nodeCallback(new InTraitNode($node, $traitReflection, $scope->getClassReflection()), $traitScope); $this->processStmtNodes($node, $stmts, $traitScope, $nodeCallback, StatementContext::createTopLevel()); return; } @@ -6360,11 +6359,11 @@ private function processNodesForTraitUse(array $parserNodes, $node, ClassReflect } foreach ($node->getSubNodeNames() as $subNodeName) { $subNode = $node->{$subNodeName}; - $this->processNodesForTraitUse($parserNodes, $subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } elseif (is_array($node)) { foreach ($node as $subNode) { - $this->processNodesForTraitUse($parserNodes, $subNode, $traitReflection, $scope, $adaptations, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } } diff --git a/src/Node/InTraitNode.php b/src/Node/InTraitNode.php index 4d03716b025..b7834e713fb 100644 --- a/src/Node/InTraitNode.php +++ b/src/Node/InTraitNode.php @@ -11,10 +11,7 @@ final class InTraitNode extends Node\Stmt implements VirtualNode { - /** - * @param Node\Stmt[] $parserNodes - */ - public function __construct(private Node\Stmt\Trait_ $originalNode, private array $parserNodes, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) + public function __construct(private Node\Stmt\Trait_ $originalNode, private ClassReflection $traitReflection, private ClassReflection $implementingClassReflection) { parent::__construct($originalNode->getAttributes()); } @@ -24,14 +21,6 @@ public function getOriginalNode(): Node\Stmt\Trait_ return $this->originalNode; } - /** - * @return Node\Stmt[] - */ - public function getParserNodes(): array - { - return $this->parserNodes; - } - public function getTraitReflection(): ClassReflection { return $this->traitReflection; From 5e9bf864938f31271d13f409810706953548e583 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 May 2025 12:10:34 +0200 Subject: [PATCH 1494/3097] Fix fixing nodes in used traits --- src/Analyser/FileAnalyser.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 340039bf4a9..0c1f208d825 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -108,6 +108,15 @@ public function analyseFile( $traitNode = $node->getOriginalNode(); $linesToIgnore[$scope->getFileDescription()] = $this->getLinesToIgnoreFromTokens([$traitNode]); } + + if ($scope->isInTrait()) { + $traitReflection = $scope->getTraitReflection(); + if ($traitReflection->getFileName() !== null) { + $traitFilePath = $traitReflection->getFileName(); + $parserNodes = $this->parser->parseFile($traitFilePath); + } + } + if ($outerNodeCallback !== null) { $outerNodeCallback($node, $scope); } From d04535df48b1bd76b9232fce3947ac968c02b24e Mon Sep 17 00:00:00 2001 From: Felix Bernhard Date: Tue, 27 May 2025 18:38:07 +0200 Subject: [PATCH 1495/3097] Revert "use one instead of two spaces" This reverts commit 59a003a4bf84b319d94fd2a4ff43a5870c316923. --- .../ErrorFormatter/TableErrorFormatter.php | 8 ++--- .../TableErrorFormatterTest.php | 36 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index 0c3b11d4a98..b195e03a347 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -89,7 +89,7 @@ public function formatErrors( $filePath = $error->getTraitFilePath() ?? $error->getFilePath(); if ($error->getIdentifier() !== null && $error->canBeIgnored()) { $message .= "\n"; - $message .= '🪪 ' . $error->getIdentifier(); + $message .= '🪪 ' . $error->getIdentifier(); } if ($error->getTip() !== null) { $tip = $error->getTip(); @@ -99,11 +99,11 @@ public function formatErrors( if (str_contains($tip, "\n")) { $lines = explode("\n", $tip); foreach ($lines as $line) { - $message .= '💡 ' . ltrim($line, ' •') . "\n"; + $message .= '💡 ' . ltrim($line, ' •') . "\n"; } $message = rtrim($message, "\n"); } else { - $message .= '💡 ' . $tip; + $message .= '💡 ' . $tip; } } if (is_string($this->editorUrl)) { @@ -123,7 +123,7 @@ public function formatErrors( $title = $this->relativePathHelper->getRelativePath($filePath); } - $message .= "\n✏️ ' . $title . ''; + $message .= "\n✏️ ' . $title . ''; } if ( diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 4384ae77b0d..4515aefa07c 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -94,14 +94,14 @@ public function dataFormatterOutputProvider(): iterable 4 Foo ------ ------------------------------------------------------------------- - ------ ---------- + ------ ----------- Line foo.php - ------ ---------- + ------ ----------- 1 Foo 5 Bar Bar2 - 💡 a tip - ------ ---------- + 💡 a tip + ------ ----------- [ERROR] Found 4 errors @@ -143,14 +143,14 @@ public function dataFormatterOutputProvider(): iterable 4 Foo ------ ------------------------------------------------------------------- - ------ ---------- + ------ ----------- Line foo.php - ------ ---------- + ------ ----------- 1 Foo 5 Bar Bar2 - 💡 a tip - ------ ---------- + 💡 a tip + ------ ----------- -- ----------------------- Error @@ -190,13 +190,13 @@ public function dataFormatterOutputProvider(): iterable 'numGenericErrors' => 0, 'verbose' => false, 'extraEnvVars' => [], - 'expected' => ' ------ --------------- + 'expected' => ' ------ ---------------- Line foo.php - ------ --------------- + ------ ---------------- 5 Foobar\Buz - 🪪 foobar.buz - 💡 a tip - ------ --------------- + 🪪 foobar.buz + 💡 a tip + ------ ---------------- [ERROR] Found 1 error @@ -211,13 +211,13 @@ public function dataFormatterOutputProvider(): iterable 'numGenericErrors' => 0, 'verbose' => true, 'extraEnvVars' => [], - 'expected' => ' ------ --------------- + 'expected' => ' ------ ---------------- Line foo.php - ------ --------------- + ------ ---------------- 5 Foobar\Buz - 🪪 foobar.buz - 💡 a tip - ------ --------------- + 🪪 foobar.buz + 💡 a tip + ------ ---------------- [ERROR] Found 1 error From cfa029941414a746b3d74de90b8acebd5c9ed3a7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 31 May 2025 12:03:51 +0200 Subject: [PATCH 1496/3097] Fixed false-positive with by-ref parameters and array-modifying functions --- src/Analyser/NodeScopeResolver.php | 22 ++++---- .../TypesAssignedToPropertiesRuleTest.php | 35 ++++++++++++- .../Rules/Properties/data/bug-12675.php | 37 ++++++++++++++ .../Rules/Properties/data/bug-13093c.php | 49 ++++++++++++++++++ .../Rules/Properties/data/bug-13093d.php | 50 +++++++++++++++++++ .../Rules/Properties/data/bug-7844.php | 20 ++++++++ .../Rules/Properties/data/bug-7844b.php | 39 +++++++++++++++ .../Rules/Properties/data/bug-8825.php | 24 +++++++++ .../ParameterOutAssignedTypeRuleTest.php | 10 ++++ .../Rules/Variables/data/bug-13093.php | 40 +++++++++++++++ .../Rules/Variables/data/bug-13093b.php | 26 ++++++++++ 11 files changed, 342 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12675.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-13093c.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-13093d.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-7844.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-7844b.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-8825.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-13093.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-13093b.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8c070bb0400..18ea0112e90 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5392,6 +5392,7 @@ private function processAssignVar( } } + $scopeBeforeAssignEval = $scope; $scope = $result->getScope(); $truthySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createTruthy()); $falseySpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $assignedExpr, TypeSpecifierContext::createFalsey()); @@ -5404,7 +5405,7 @@ private function processAssignVar( $conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); $conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType); - $nodeCallback(new VariableAssignNode($var, $assignedExpr), $result->getScope()); + $nodeCallback(new VariableAssignNode($var, $assignedExpr), $scopeBeforeAssignEval); $scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr), TrinaryLogic::createYes()); foreach ($conditionalExpressions as $exprString => $holders) { $scope = $scope->addConditionalExpressions($exprString, $holders); @@ -5487,6 +5488,7 @@ private function processAssignVar( $nativeValueToWrite = $scope->getNativeType($assignedExpr); $originalValueToWrite = $valueToWrite; $originalNativeValueToWrite = $nativeValueToWrite; + $scopeBeforeAssignEval = $scope; // 3. eval assigned expr $result = $processExprCallback($scope); @@ -5542,11 +5544,11 @@ private function processAssignVar( if ($varType->isArray()->yes() || !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->yes()) { if ($var instanceof Variable && is_string($var->name)) { - $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); + $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scopeBeforeAssignEval); $scope = $scope->assignVariable($var->name, $valueToWrite, $nativeValueToWrite, TrinaryLogic::createYes()); } else { if ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { - $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scopeBeforeAssignEval); if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) { $scope = $scope->assignInitializedProperty($scope->getType($var->var), $var->name->toString()); } @@ -5574,9 +5576,9 @@ private function processAssignVar( } } else { if ($var instanceof Variable) { - $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scope); + $nodeCallback(new VariableAssignNode($var, $assignedPropertyExpr), $scopeBeforeAssignEval); } elseif ($var instanceof PropertyFetch || $var instanceof StaticPropertyFetch) { - $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedPropertyExpr, $isAssignOp), $scopeBeforeAssignEval); if ($var instanceof PropertyFetch && $var->name instanceof Node\Identifier && !$isAssignOp) { $scope = $scope->assignInitializedProperty($scope->getType($var->var), $var->name->toString()); } @@ -5611,6 +5613,7 @@ static function (): void { $scope = $propertyNameResult->getScope(); } + $scopeBeforeAssignEval = $scope; $result = $processExprCallback($scope); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -5625,7 +5628,7 @@ static function (): void { if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { $propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); $assignedExprType = $scope->getType($assignedExpr); - $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); if ($propertyReflection->canChangeTypeAfterAssignment()) { if ($propertyReflection->hasNativeType()) { $propertyNativeType = $propertyReflection->getNativeType(); @@ -5671,7 +5674,7 @@ static function (): void { } else { // fallback $assignedExprType = $scope->getType($assignedExpr); - $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); // simulate dynamic property assign by __set to get throw points if (!$propertyHolderType->hasMethod('__set')->no()) { @@ -5705,6 +5708,7 @@ static function (): void { $scope = $propertyNameResult->getScope(); } + $scopeBeforeAssignEval = $scope; $result = $processExprCallback($scope); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -5714,7 +5718,7 @@ static function (): void { if ($propertyName !== null) { $propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName); $assignedExprType = $scope->getType($assignedExpr); - $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { if ($propertyReflection->hasNativeType()) { $propertyNativeType = $propertyReflection->getNativeType(); @@ -5745,7 +5749,7 @@ static function (): void { } else { // fallback $assignedExprType = $scope->getType($assignedExpr); - $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); + $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scopeBeforeAssignEval); $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); } } elseif ($var instanceof List_) { diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 8d050e7636c..4260da643c5 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -15,9 +15,11 @@ class TypesAssignedToPropertiesRuleTest extends RuleTestCase private bool $checkExplicitMixed = false; + private bool $checkImplicitMixed = false; + protected function getRule(): Rule { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false, false, true), new PropertyReflectionFinder()); + return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true), new PropertyReflectionFinder()); } public function testTypesAssignedToProperties(): void @@ -779,4 +781,35 @@ public function testPropertyHooks(): void ]); } + public function testBug13093c(): void + { + $this->analyse([__DIR__ . '/data/bug-13093c.php'], []); + } + + public function testBug13093d(): void + { + $this->analyse([__DIR__ . '/data/bug-13093d.php'], []); + } + + public function testBug8825(): void + { + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-8825.php'], []); + } + + public function testBug7844(): void + { + $this->analyse([__DIR__ . '/data/bug-7844.php'], []); + } + + public function testBug7844b(): void + { + $this->analyse([__DIR__ . '/data/bug-7844b.php'], []); + } + + public function testBug12675(): void + { + $this->analyse([__DIR__ . '/data/bug-12675.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12675.php b/tests/PHPStan/Rules/Properties/data/bug-12675.php new file mode 100644 index 00000000000..ea4674dd3f3 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12675.php @@ -0,0 +1,37 @@ +username = array_shift($pieces); + $this->domain = array_shift($pieces); + + echo "{$this->username}@{$this->domain}"; + } + + public function with_pop(string $email): void + { + $pieces = explode("@", $email); + if (2 !== count($pieces)) { + + throw new \Exception("Bad, very bad..."); + } + + $this->domain = array_pop($pieces); + $this->username = array_pop($pieces); + + echo "{$this->username}@{$this->domain}"; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-13093c.php b/tests/PHPStan/Rules/Properties/data/bug-13093c.php new file mode 100644 index 00000000000..3fecc5f91db --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-13093c.php @@ -0,0 +1,49 @@ + + */ + private array $nextMutantProcessKillerContainer = []; + + private string $prop; + + public function fillBucketOnce(array &$killer): int + { + if ($this->nextMutantProcessKillerContainer !== []) { + $this->prop = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + +} + +final class ParallelProcessRunner2 +{ + /** + * @var array + */ + private array $nextMutantProcessKillerContainer = []; + + private string $prop; + + public function fillBucketOnce(array &$killer): int + { + $name = 'prop'; + if ($this->nextMutantProcessKillerContainer !== []) { + $this->{$name} = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + +} + diff --git a/tests/PHPStan/Rules/Properties/data/bug-13093d.php b/tests/PHPStan/Rules/Properties/data/bug-13093d.php new file mode 100644 index 00000000000..1682c7c22ab --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-13093d.php @@ -0,0 +1,50 @@ + + */ + private array $nextMutantProcessKillerContainer = []; + + static private string $prop; + + public function fillBucketOnce(array &$killer): int + { + if ($this->nextMutantProcessKillerContainer !== []) { + self::$prop = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + +} + +final class ParallelProcessRunner2 +{ + /** + * @var array + */ + private array $nextMutantProcessKillerContainer = []; + + static private string $prop; + + public function fillBucketOnce(array &$killer): int + { + $name = 'prop'; + if ($this->nextMutantProcessKillerContainer !== []) { + self::${$name} = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + +} + + diff --git a/tests/PHPStan/Rules/Properties/data/bug-7844.php b/tests/PHPStan/Rules/Properties/data/bug-7844.php new file mode 100644 index 00000000000..b202edd3f29 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-7844.php @@ -0,0 +1,20 @@ + */ + public $data = array(); + + public function foo(): void + { + if (count($this->data) > 0) { + $this->val = array_shift($this->data); + } + } +} + diff --git a/tests/PHPStan/Rules/Properties/data/bug-7844b.php b/tests/PHPStan/Rules/Properties/data/bug-7844b.php new file mode 100644 index 00000000000..c423628f8c0 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-7844b.php @@ -0,0 +1,39 @@ + $objs */ + public function __construct(array $objs) + { + \assert($objs !== []); + $this->p1 = $objs[0]; + + \assert($objs !== []); + $this->p2 = $objs[array_key_last($objs)]; + + \assert($objs !== []); + $this->p3 = \array_pop($objs); + + \assert($objs !== []); + $this->p4 = \array_shift($objs); + + \assert($objs !== []); + $p = \array_shift($objs); + $this->p5 = $p; + + \assert($objs !== []); + $this->doSomething(\array_pop($objs)); + } + + private function doSomething(Obj $obj): void {} +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-8825.php b/tests/PHPStan/Rules/Properties/data/bug-8825.php new file mode 100644 index 00000000000..e1aa5c372d8 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-8825.php @@ -0,0 +1,24 @@ +isBool = $actionParameters['my_key'] ?? false; + } + + public function use(): void + { + $this->isBool->someMethod(); + } +} diff --git a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php index f8268f8fcd4..1e651b6b486 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php @@ -64,4 +64,14 @@ public function testBenevolentArrayKey(): void $this->analyse([__DIR__ . '/data/benevolent-array-key.php'], []); } + public function testBug13093(): void + { + $this->analyse([__DIR__ . '/data/bug-13093.php'], []); + } + + public function testBug13093b(): void + { + $this->analyse([__DIR__ . '/data/bug-13093b.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-13093.php b/tests/PHPStan/Rules/Variables/data/bug-13093.php new file mode 100644 index 00000000000..0d780ce1a0c --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13093.php @@ -0,0 +1,40 @@ + + */ + private array $nextMutantProcessKillerContainer = []; + + /** + * @param MutantProcessContainer[] $bucket + * @param Generator $input + */ + public function fillBucketOnce(array &$bucket, Generator $input, int $threadCount): int + { + if (count($bucket) >= $threadCount || !$input->valid()) { + if ($this->nextMutantProcessKillerContainer !== []) { + $bucket[] = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + + return 1; + } + +} + diff --git a/tests/PHPStan/Rules/Variables/data/bug-13093b.php b/tests/PHPStan/Rules/Variables/data/bug-13093b.php new file mode 100644 index 00000000000..5e462a1cbee --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13093b.php @@ -0,0 +1,26 @@ + + */ + private array $nextMutantProcessKillerContainer = []; + + public function fillBucketOnce(string &$killer): int + { + if ($this->nextMutantProcessKillerContainer !== []) { + $killer = array_shift($this->nextMutantProcessKillerContainer); + } + + return 0; + } + +} + From 1c33d217865a826d1bfd42a116cf419b1718ac0a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 01:13:08 +0000 Subject: [PATCH 1497/3097] Update dependency composer/ca-bundle to v1.5.7 --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 767a57642b1..889108c2eb5 100644 --- a/composer.lock +++ b/composer.lock @@ -72,16 +72,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.6", + "version": "1.5.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "f65c239c970e7f072f067ab78646e9f0b2935175" + "reference": "d665d22c417056996c59019579f1967dfe5c1e82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f65c239c970e7f072f067ab78646e9f0b2935175", - "reference": "f65c239c970e7f072f067ab78646e9f0b2935175", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82", "shasum": "" }, "require": { @@ -128,7 +128,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.6" + "source": "https://github.com/composer/ca-bundle/tree/1.5.7" }, "funding": [ { @@ -144,7 +144,7 @@ "type": "tidelift" } ], - "time": "2025-03-06T14:30:56+00:00" + "time": "2025-05-26T15:08:54+00:00" }, { "name": "composer/pcre", @@ -6606,7 +6606,7 @@ "php": "^8.1", "composer-runtime-api": "^2.0" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1.99" }, From 0ce2ee33db36c1aa23357b78c556d923778ce071 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 1 Jun 2025 22:13:32 +0200 Subject: [PATCH 1498/3097] NamedArgumentsRule - do not skip arguments with objects as default values --- build/PHPStan/Build/NamedArgumentsRule.php | 8 ++++++ .../PHPStan/Build/NamedArgumentsRuleTest.php | 23 +++++++++++++++++ .../Build/data/named-arguments-new.php | 25 +++++++++++++++++++ .../Build/data/named-arguments-new.php.fixed | 25 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 tests/PHPStan/Build/data/named-arguments-new.php create mode 100644 tests/PHPStan/Build/data/named-arguments-new.php.fixed diff --git a/build/PHPStan/Build/NamedArgumentsRule.php b/build/PHPStan/Build/NamedArgumentsRule.php index 14895bed7ec..b44a10ddeae 100644 --- a/build/PHPStan/Build/NamedArgumentsRule.php +++ b/build/PHPStan/Build/NamedArgumentsRule.php @@ -142,6 +142,10 @@ private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, } } + if (count($parameter->getDefaultValue()->getFiniteTypes()) === 0) { + continue; + } + if (!$argValue->equals($parameter->getDefaultValue())) { if (count($defaultValueWasPassed) > 0) { $errorBuilders[] = RuleErrorBuilder::message(sprintf( @@ -199,6 +203,10 @@ private function processArgs(ExtendedParametersAcceptor $acceptor, Scope $scope, $newArgs[] = $originalArg; continue; } + if (count($parameter->getDefaultValue()->getFiniteTypes()) === 0) { + $newArgs[] = $originalArg; + continue; + } $argValue = $scope->getType($normalizedArg->value); if ($argValue->equals($parameter->getDefaultValue())) { $skippedOptional = true; diff --git a/tests/PHPStan/Build/NamedArgumentsRuleTest.php b/tests/PHPStan/Build/NamedArgumentsRuleTest.php index 596023259c8..45647bf7d89 100644 --- a/tests/PHPStan/Build/NamedArgumentsRuleTest.php +++ b/tests/PHPStan/Build/NamedArgumentsRuleTest.php @@ -84,4 +84,27 @@ public function testFixFileWithMatch(): void ); } + public function testNewInInitializer(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/named-arguments-new.php'], [ + [ + 'You\'re passing a non-default value \'bar\' to parameter $d but previous argument is passing default value to its parameter ($c). You can skip it and use named argument for $d instead.', + 24, + ], + ]); + } + + public function testFixNewInInitializer(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->fix(__DIR__ . '/data/named-arguments-new.php', __DIR__ . '/data/named-arguments-new.php.fixed'); + } + } diff --git a/tests/PHPStan/Build/data/named-arguments-new.php b/tests/PHPStan/Build/data/named-arguments-new.php new file mode 100644 index 00000000000..61f1c151d25 --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments-new.php @@ -0,0 +1,25 @@ += 8.1 + +namespace NamedArgumentsRuleNew; + +class Bar +{ + +} + +class Foo +{ + + public static function doFoo(int $a, Bar $bar = new Bar(), string $c = 'bar', string $d = 'baz'): void + { + + } + +} + +function (): void { + Foo::doFoo(1, new Bar(), 'bar'); + Foo::doFoo(1, new Bar(), 'baz'); + + Foo::doFoo(1, new Bar(), 'bar', 'bar'); +}; diff --git a/tests/PHPStan/Build/data/named-arguments-new.php.fixed b/tests/PHPStan/Build/data/named-arguments-new.php.fixed new file mode 100644 index 00000000000..2cf2934112f --- /dev/null +++ b/tests/PHPStan/Build/data/named-arguments-new.php.fixed @@ -0,0 +1,25 @@ += 8.1 + +namespace NamedArgumentsRuleNew; + +class Bar +{ + +} + +class Foo +{ + + public static function doFoo(int $a, Bar $bar = new Bar(), string $c = 'bar', string $d = 'baz'): void + { + + } + +} + +function (): void { + Foo::doFoo(1, new Bar(), 'bar'); + Foo::doFoo(1, new Bar(), 'baz'); + + Foo::doFoo(1, new Bar(), d: 'bar'); +}; From 7534ee1a3755ac6c1a9f563159dbc9a0756993f5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 2 Jun 2025 11:03:58 +0200 Subject: [PATCH 1499/3097] These DateTimeInterface methods are pure --- bin/functionMetadata_original.php | 6 ++++ resources/functionMetadata.php | 5 ++++ tests/PHPStan/Rules/Pure/data/pure-method.php | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/bin/functionMetadata_original.php b/bin/functionMetadata_original.php index f4c9cd19fc1..a57224a0bbe 100644 --- a/bin/functionMetadata_original.php +++ b/bin/functionMetadata_original.php @@ -130,6 +130,12 @@ 'random_int' => ['hasSideEffects' => true], // methods + 'DateTimeInterface::diff' => ['hasSideEffects' => false], + 'DateTimeInterface::format' => ['hasSideEffects' => false], + 'DateTimeInterface::getOffset' => ['hasSideEffects' => false], + 'DateTimeInterface::getTimestamp' => ['hasSideEffects' => false], + 'DateTimeInterface::getTimezone' => ['hasSideEffects' => false], + 'DateTime::createFromFormat' => ['hasSideEffects' => false], 'DateTime::createFromImmutable' => ['hasSideEffects' => false], 'DateTime::getLastErrors' => ['hasSideEffects' => false], diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index 6136b1c068f..fd7076124f4 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -86,6 +86,11 @@ 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'DateTimeInterface::diff' => ['hasSideEffects' => false], + 'DateTimeInterface::format' => ['hasSideEffects' => false], + 'DateTimeInterface::getOffset' => ['hasSideEffects' => false], + 'DateTimeInterface::getTimestamp' => ['hasSideEffects' => false], + 'DateTimeInterface::getTimezone' => ['hasSideEffects' => false], 'Error::__construct' => ['hasSideEffects' => false], 'ErrorException::__construct' => ['hasSideEffects' => false], 'Event::__construct' => ['hasSideEffects' => false], diff --git a/tests/PHPStan/Rules/Pure/data/pure-method.php b/tests/PHPStan/Rules/Pure/data/pure-method.php index ba2ce7e3be4..eca2976cda5 100644 --- a/tests/PHPStan/Rules/Pure/data/pure-method.php +++ b/tests/PHPStan/Rules/Pure/data/pure-method.php @@ -421,3 +421,32 @@ public static function getB(): int return 1; } } + +class CallDateTime +{ + + /** + * @phpstan-pure + */ + public function doFoo(\DateTimeInterface $date): string + { + return $date->format('j. n. Y'); + } + + /** + * @phpstan-pure + */ + public function doFoo2(\DateTime $date): string + { + return $date->format('j. n. Y'); + } + + /** + * @phpstan-pure + */ + public function doFoo3(\DateTimeImmutable $date): string + { + return $date->format('j. n. Y'); + } + +} From bd5566e785b0b9ce0d3bf6c81da384f36fce2c5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 2 Jun 2025 11:28:33 +0200 Subject: [PATCH 1500/3097] Make `#[Override]` attribute errors fixable --- src/Rules/Methods/OverridingMethodRule.php | 40 ++++++++++++++++++- .../Methods/OverridingMethodRuleTest.php | 11 +++++ .../Methods/data/fix-override-attribute.php | 34 ++++++++++++++++ .../data/fix-override-attribute.php.fixed | 34 ++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/fix-override-attribute.php create mode 100644 tests/PHPStan/Rules/Methods/data/fix-override-attribute.php.fixed diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 2dcb9d3cc3b..50b72cbaeba 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Methods; use PhpParser\Node; +use PhpParser\Node\Attribute; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassMethodNode; use PHPStan\Php\PhpVersion; @@ -94,6 +95,10 @@ public function processNode(Node $node, Scope $scope): array )) ->nonIgnorable() ->identifier('method.override') + ->fixNode($node->getOriginalNode(), function (Node\Stmt\ClassMethod $method) { + $method->attrGroups = $this->filterOverrideAttribute($method->attrGroups); + return $method; + }) ->build(), ]; } @@ -116,7 +121,16 @@ public function processNode(Node $node, Scope $scope): array $method->getName(), $prototypeDeclaringClass->getDisplayName(true), $prototype->getName(), - ))->identifier('method.missingOverride')->build(); + )) + ->identifier('method.missingOverride') + ->fixNode($node->getOriginalNode(), static function (Node\Stmt\ClassMethod $method) { + $method->attrGroups[] = new Node\AttributeGroup([ + new Attribute(new Node\Name\FullyQualified('Override')), + ]); + + return $method; + }) + ->build(); } if ($prototype->isFinalByKeyword()->yes()) { $messages[] = RuleErrorBuilder::message(sprintf( @@ -278,6 +292,30 @@ public function processNode(Node $node, Scope $scope): array return $this->addErrors($messages, $node, $scope); } + /** + * @param Node\AttributeGroup[] $attrGroups + * @return Node\AttributeGroup[] + */ + private function filterOverrideAttribute(array $attrGroups): array + { + foreach ($attrGroups as $i => $attrGroup) { + foreach ($attrGroup->attrs as $j => $attr) { + if ($attr->name->toLowerString() !== 'override') { + continue; + } + + unset($attrGroup->attrs[$j]); + if (count($attrGroup->attrs) !== 0) { + continue; + } + + unset($attrGroups[$i]); + } + } + + return $attrGroups; + } + /** * @param list $errors * @return list diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index c63d2ea3eb8..23df75cf0aa 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -834,4 +834,15 @@ public function testSimpleXmlElementChildClass(): void $this->analyse([__DIR__ . '/data/simple-xml-element-child.php'], []); } + public function testFixOverride(): void + { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + + $this->phpVersionId = PHP_VERSION_ID; + $this->checkMissingOverrideMethodAttribute = true; + $this->fix(__DIR__ . '/data/fix-override-attribute.php', __DIR__ . '/data/fix-override-attribute.php.fixed'); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php b/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php new file mode 100644 index 00000000000..974d6598ee0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php @@ -0,0 +1,34 @@ += 8.3 + +namespace FixOverrideAttribute; + +class Foo +{ + + public function doFoo(): void + { + + } + +} + +class Bar extends Foo +{ + + public function doFoo(): void + { + + } + + + public function doBar(): void + { + + } + + #[\Override] + public function doBaz(): void + { + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php.fixed b/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php.fixed new file mode 100644 index 00000000000..749dad7abe1 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/fix-override-attribute.php.fixed @@ -0,0 +1,34 @@ += 8.3 + +namespace FixOverrideAttribute; + +class Foo +{ + + public function doFoo(): void + { + + } + +} + +class Bar extends Foo +{ + + #[\Override] + public function doFoo(): void + { + + } + + + public function doBar(): void + { + + } + + public function doBaz(): void + { + } + +} From 504914345cb21a336c195288184db5dd252f5bfb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 2 Jun 2025 11:37:47 +0200 Subject: [PATCH 1501/3097] PromoteParameterRule - promote fixable --- src/Rules/Playground/PromoteParameterRule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Rules/Playground/PromoteParameterRule.php b/src/Rules/Playground/PromoteParameterRule.php index cee351e3ffc..133aef40995 100644 --- a/src/Rules/Playground/PromoteParameterRule.php +++ b/src/Rules/Playground/PromoteParameterRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Rules\FixableNodeRuleError; use PHPStan\Rules\LineRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -52,6 +53,9 @@ public function processNode(Node $node, Scope $scope): array if ($error instanceof LineRuleError) { $builder->line($error->getLine()); } + if ($error instanceof FixableNodeRuleError) { + $builder->fixNode($error->getOriginalNode(), $error->getNewNodeCallable()); + } $errors[] = $builder->build(); } From 3f70082cd3a22c1501fa06fa756c410dba686654 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 2 Jun 2025 11:38:21 +0200 Subject: [PATCH 1502/3097] Fix for BenevolentUnion --- src/Type/UnionType.php | 2 +- .../ConstantLooseComparisonRuleTest.php | 6 ++++++ tests/PHPStan/Rules/Comparison/data/bug-13098.php | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-13098.php diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 08d678152aa..03255de9d97 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -676,7 +676,7 @@ public function isScalar(): TrinaryLogic public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType { - return $this->unionResults( + return $this->notBenevolentUnionResults( static fn (Type $innerType): TrinaryLogic => $innerType->looseCompare($type, $phpVersion)->toTrinaryLogic() )->toBooleanType(); } diff --git a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php index e49a6100f73..f981f926213 100644 --- a/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ConstantLooseComparisonRuleTest.php @@ -242,4 +242,10 @@ public function testBug8800(): void ]); } + public function testBug13098(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-13098.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-13098.php b/tests/PHPStan/Rules/Comparison/data/bug-13098.php new file mode 100644 index 00000000000..b549d338fe1 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-13098.php @@ -0,0 +1,15 @@ + Date: Mon, 2 Jun 2025 16:39:50 +0200 Subject: [PATCH 1503/3097] DI Container - throw unprefixed exception --- build/phpstan.neon | 1 + src/DependencyInjection/Container.php | 2 + .../MissingServiceException.php | 10 +++++ .../Nette/NetteContainer.php | 13 ++++++- .../Nette/NetteContainerTest.php | 37 +++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/DependencyInjection/MissingServiceException.php create mode 100644 tests/PHPStan/DependencyInjection/Nette/NetteContainerTest.php diff --git a/build/phpstan.neon b/build/phpstan.neon index ffbbcfecac3..ce7a3a0b372 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -58,6 +58,7 @@ parameters: - 'PHPStan\Broker\ClassNotFoundException' - 'PHPStan\Broker\FunctionNotFoundException' - 'PHPStan\Broker\ConstantNotFoundException' + - 'PHPStan\DependencyInjection\MissingServiceException' - 'PHPStan\Reflection\MissingMethodFromReflectionException' - 'PHPStan\Reflection\MissingPropertyFromReflectionException' - 'PHPStan\Reflection\MissingConstantFromReflectionException' diff --git a/src/DependencyInjection/Container.php b/src/DependencyInjection/Container.php index cd785677b1b..005f72b3eb9 100644 --- a/src/DependencyInjection/Container.php +++ b/src/DependencyInjection/Container.php @@ -10,6 +10,7 @@ public function hasService(string $serviceName): bool; /** * @return mixed + * @throws MissingServiceException */ public function getService(string $serviceName); @@ -17,6 +18,7 @@ public function getService(string $serviceName); * @template T of object * @param class-string $className * @return T + * @throws MissingServiceException */ public function getByType(string $className); diff --git a/src/DependencyInjection/MissingServiceException.php b/src/DependencyInjection/MissingServiceException.php new file mode 100644 index 00000000000..bb562dfb19a --- /dev/null +++ b/src/DependencyInjection/MissingServiceException.php @@ -0,0 +1,10 @@ +container->getService($serviceName); + try { + return $this->container->getService($serviceName); + } catch (\Nette\DI\MissingServiceException $e) { + throw new MissingServiceException($e->getMessage(), previous: $e); + } } /** @@ -38,7 +43,11 @@ public function getService(string $serviceName) */ public function getByType(string $className) { - return $this->container->getByType($className); + try { + return $this->container->getByType($className); + } catch (\Nette\DI\MissingServiceException $e) { + throw new MissingServiceException($e->getMessage(), previous: $e); + } } /** diff --git a/tests/PHPStan/DependencyInjection/Nette/NetteContainerTest.php b/tests/PHPStan/DependencyInjection/Nette/NetteContainerTest.php new file mode 100644 index 00000000000..e7629e4f83c --- /dev/null +++ b/tests/PHPStan/DependencyInjection/Nette/NetteContainerTest.php @@ -0,0 +1,37 @@ +expectException(MissingServiceException::class); + $container->getService('nonexistent'); + } + + public function testGetByTypeNotFoundThrows(): void + { + $container = self::getContainer(); + + $this->expectException(MissingServiceException::class); + $container->getByType(TrinaryLogic::class); + } + + public function testGetByTypeNotUniqueThrows(): void + { + $container = self::getContainer(); + + $this->expectException(MissingServiceException::class); + $container->getByType(ReflectionGetAttributesMethodReturnTypeExtension::class); + } + +} From 5c22b9d6a65aba28da96b0780b94d58630eb1b1f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 2 Jun 2025 16:43:25 +0200 Subject: [PATCH 1504/3097] PromoteParameterRule - deduplicate already reported errors --- src/Rules/Playground/PromoteParameterRule.php | 60 +++++++++++++++++++ .../Playground/PromoteParameterRuleTest.php | 1 + ...omoteParameterRuleWithOriginalRuleTest.php | 51 ++++++++++++++++ .../data/promote-missing-override.php | 34 +++++++++++ 4 files changed, 146 insertions(+) create mode 100644 tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php create mode 100644 tests/PHPStan/Rules/Playground/data/promote-missing-override.php diff --git a/src/Rules/Playground/PromoteParameterRule.php b/src/Rules/Playground/PromoteParameterRule.php index 133aef40995..9381a369227 100644 --- a/src/Rules/Playground/PromoteParameterRule.php +++ b/src/Rules/Playground/PromoteParameterRule.php @@ -4,10 +4,15 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\Container; +use PHPStan\DependencyInjection\MissingServiceException; use PHPStan\Rules\FixableNodeRuleError; +use PHPStan\Rules\LazyRegistry; use PHPStan\Rules\LineRuleError; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use function count; +use function get_class; use function sprintf; /** @@ -17,12 +22,16 @@ final class PromoteParameterRule implements Rule { + /** @var Rule|false|null */ + private Rule|false|null $originalRule = null; + /** * @param Rule $rule * @param class-string $nodeType */ public function __construct( private Rule $rule, + private Container $container, private string $nodeType, private bool $parameterValue, private string $parameterName, @@ -35,6 +44,49 @@ public function getNodeType(): string return $this->nodeType; } + /** + * @return Rule|null + */ + private function getOriginalRule(): ?Rule + { + if ($this->originalRule === false) { + return null; + } + + if ($this->originalRule !== null) { + return $this->originalRule; + } + + $originalRule = null; + try { + /** @var Rule $originalRule */ + $originalRule = $this->container->getByType(get_class($this->rule)); + $taggedRules = $this->container->getServicesByTag(LazyRegistry::RULE_TAG); + $found = false; + foreach ($taggedRules as $rule) { + if ($originalRule !== $rule) { + continue; + } + + $found = true; + break; + } + + if (!$found) { + $originalRule = null; + } + } catch (MissingServiceException) { + // pass + } + + if ($originalRule === null) { + $this->originalRule = false; + return null; + } + + return $this->originalRule = $originalRule; + } + public function processNode(Node $node, Scope $scope): array { if ($this->parameterValue) { @@ -45,6 +97,14 @@ public function processNode(Node $node, Scope $scope): array return []; } + $originalRule = $this->getOriginalRule(); + if ($originalRule !== null) { + $originalRuleErrors = $originalRule->processNode($node, $scope); + if (count($originalRuleErrors) > 0) { + return []; + } + } + $errors = []; foreach ($this->rule->processNode($node, $scope) as $error) { $builder = RuleErrorBuilder::message($error->getMessage()) diff --git a/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php index e97673ff5f6..40f16771aa9 100644 --- a/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php +++ b/tests/PHPStan/Rules/Playground/PromoteParameterRuleTest.php @@ -21,6 +21,7 @@ protected function getRule(): Rule self::getContainer(), [], )), + self::getContainer(), ClassPropertiesNode::class, false, 'checkUninitializedProperties', diff --git a/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php b/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php new file mode 100644 index 00000000000..c6061a6ba95 --- /dev/null +++ b/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php @@ -0,0 +1,51 @@ +> + */ +class PromoteParameterRuleWithOriginalRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new PromoteParameterRule( + new OverridingMethodRule( + self::getContainer()->getByType(PhpVersion::class), + self::getContainer()->getByType(MethodSignatureRule::class), + true, + self::getContainer()->getByType(MethodParameterComparisonHelper::class), + self::getContainer()->getByType(MethodVisibilityComparisonHelper::class), + self::getContainer()->getByType(PhpClassReflectionExtension::class), + true, + ), + self::getContainer(), + InClassMethodNode::class, + false, + 'checkMissingOverrideMethodAttribute', + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/promote-missing-override.php'], [ + [ + 'Method PromoteMissingOverride\Bar::doFoo() overrides method PromoteMissingOverride\Foo::doFoo() but is missing the #[\Override] attribute.', + 18, + 'This error would be reported if the checkMissingOverrideMethodAttribute: true parameter was enabled in your %configurationFile%.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Playground/data/promote-missing-override.php b/tests/PHPStan/Rules/Playground/data/promote-missing-override.php new file mode 100644 index 00000000000..9403342cea8 --- /dev/null +++ b/tests/PHPStan/Rules/Playground/data/promote-missing-override.php @@ -0,0 +1,34 @@ + Date: Mon, 2 Jun 2025 17:06:41 +0200 Subject: [PATCH 1505/3097] Fix build --- .../Playground/PromoteParameterRuleWithOriginalRuleTest.php | 5 +++++ .../Rules/Playground/data/promote-missing-override.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php b/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php index c6061a6ba95..d96c10debfc 100644 --- a/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php +++ b/tests/PHPStan/Rules/Playground/PromoteParameterRuleWithOriginalRuleTest.php @@ -11,6 +11,7 @@ use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase> @@ -39,6 +40,10 @@ protected function getRule(): Rule public function testRule(): void { + if (PHP_VERSION_ID < 80300) { + $this->markTestSkipped('Test requires PHP 8.3.'); + } + $this->analyse([__DIR__ . '/data/promote-missing-override.php'], [ [ 'Method PromoteMissingOverride\Bar::doFoo() overrides method PromoteMissingOverride\Foo::doFoo() but is missing the #[\Override] attribute.', diff --git a/tests/PHPStan/Rules/Playground/data/promote-missing-override.php b/tests/PHPStan/Rules/Playground/data/promote-missing-override.php index 9403342cea8..353d3f9cd20 100644 --- a/tests/PHPStan/Rules/Playground/data/promote-missing-override.php +++ b/tests/PHPStan/Rules/Playground/data/promote-missing-override.php @@ -1,4 +1,4 @@ -= 8.3 namespace PromoteMissingOverride; From 17e1bcd459c98fa82199baae2952f0129a526c6f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 2 Jun 2025 17:16:49 +0200 Subject: [PATCH 1506/3097] Fix build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9a00fc8586f..a780c4306fd 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/property-in-interface-explicit-abstract.php \ --exclude tests/PHPStan/Rules/Constants/data/final-private-const.php \ --exclude tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php \ + --exclude tests/PHPStan/Rules/Playground/data/promote-missing-override.php \ src tests cs: From 21583a7d72c010fd704dedbdff60c3cb7fee4451 Mon Sep 17 00:00:00 2001 From: ondrejmirtes <104888+ondrejmirtes@users.noreply.github.com> Date: Tue, 3 Jun 2025 00:04:08 +0000 Subject: [PATCH 1507/3097] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index bb72a6ab2f7..15d646841b7 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "hoa/compiler": "3.17.08.08", "hoa/exception": "^1.0", "hoa/file": "1.17.07.11", - "jetbrains/phpstorm-stubs": "dev-master#56e49161f6f411647350b769efe7c640bd9010d1", + "jetbrains/phpstorm-stubs": "dev-master#ff5b25296e401bc3bd512354f9161d2adf64b8a2", "nette/bootstrap": "^3.0", "nette/di": "^3.1.4", "nette/neon": "3.3.4", diff --git a/composer.lock b/composer.lock index 889108c2eb5..23b8f47b2ba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c786f1dc6ae74db5aa83e606ca74f28", + "content-hash": "68107fb5f84e93c0fe2cd357dd1c0552", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20" + "reference": "ff5b25296e401bc3bd512354f9161d2adf64b8a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", - "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/ff5b25296e401bc3bd512354f9161d2adf64b8a2", + "reference": "ff5b25296e401bc3bd512354f9161d2adf64b8a2", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-05-21T19:01:16+00:00" + "time": "2025-06-02T07:38:28+00:00" }, { "name": "nette/bootstrap", From 99f4969c129810b5ecbe7f9f4e2b137612e3fc9f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Jun 2025 11:33:55 +0200 Subject: [PATCH 1508/3097] Update composer-attribute-collector --- composer.json | 2 +- composer.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 15d646841b7..116c24c5d74 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "nikic/php-parser": "^5.4.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "6.57.0.0", - "ondrejmirtes/composer-attribute-collector": "^1.0.0", + "ondrejmirtes/composer-attribute-collector": "^1.1.0", "ondrejmirtes/php-merge": "^4.1", "phpstan/php-8-stubs": "0.4.12", "phpstan/phpdoc-parser": "2.1.0", diff --git a/composer.lock b/composer.lock index 23b8f47b2ba..f2982c077f8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "68107fb5f84e93c0fe2cd357dd1c0552", + "content-hash": "c7980f8aa4ebf51f5e1af96e49410b09", "packages": [ { "name": "clue/ndjson-react", @@ -1442,12 +1442,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "ff5b25296e401bc3bd512354f9161d2adf64b8a2" + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/ff5b25296e401bc3bd512354f9161d2adf64b8a2", - "reference": "ff5b25296e401bc3bd512354f9161d2adf64b8a2", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", + "reference": "0dd20984fb84cfc157f0ef46f6e9e5b5477b8c20", "shasum": "" }, "require-dev": { @@ -1482,7 +1482,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2025-06-02T07:38:28+00:00" + "time": "2025-05-21T19:01:16+00:00" }, { "name": "nette/bootstrap", @@ -2057,16 +2057,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -2109,9 +2109,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "ondram/ci-detector", @@ -2258,16 +2258,16 @@ }, { "name": "ondrejmirtes/composer-attribute-collector", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/composer-attribute-collector.git", - "reference": "d285f54d139d1f14b5d4ae928c9fb45bff22fda1" + "reference": "bc4ca66239df03c35912807aa92435fe1e4e3323" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/composer-attribute-collector/zipball/d285f54d139d1f14b5d4ae928c9fb45bff22fda1", - "reference": "d285f54d139d1f14b5d4ae928c9fb45bff22fda1", + "url": "https://api.github.com/repos/ondrejmirtes/composer-attribute-collector/zipball/bc4ca66239df03c35912807aa92435fe1e4e3323", + "reference": "bc4ca66239df03c35912807aa92435fe1e4e3323", "shasum": "" }, "require": { @@ -2316,9 +2316,9 @@ ], "description": "A convenient and near zero-cost way to retrieve targets of PHP 8 attributes", "support": { - "source": "https://github.com/ondrejmirtes/composer-attribute-collector/tree/1.0.0" + "source": "https://github.com/ondrejmirtes/composer-attribute-collector/tree/1.1.0" }, - "time": "2025-05-24T23:38:56+00:00" + "time": "2025-06-03T09:32:38+00:00" }, { "name": "ondrejmirtes/php-merge", From 17ba0eb8714932b6eb8a77461606c0487804b8ba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Jun 2025 12:05:23 +0200 Subject: [PATCH 1509/3097] Update nikic/php-parser in compiler and simple-downgrader --- compiler/composer.json | 2 +- compiler/composer.lock | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/compiler/composer.json b/compiler/composer.json index e91dbb6c6a1..b75d11494fb 100644 --- a/compiler/composer.json +++ b/compiler/composer.json @@ -6,7 +6,7 @@ "require": { "php": "^8.0", "nette/neon": "^3.0.0", - "ondrejmirtes/simple-downgrader": "^2.1", + "ondrejmirtes/simple-downgrader": "^2.1.7", "seld/phar-utils": "^1.2", "symfony/console": "^5.4.43", "symfony/filesystem": "^5.4.43", diff --git a/compiler/composer.lock b/compiler/composer.lock index 73238849d28..940847e80a3 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1186b96250453fd96d9977f1136e6a2c", + "content-hash": "2ba10cf4667614eb8fc8e210c6d791ff", "packages": [ { "name": "jetbrains/phpstorm-stubs", @@ -210,16 +210,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -262,9 +262,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "ondrejmirtes/better-reflection", @@ -339,26 +339,27 @@ }, { "name": "ondrejmirtes/simple-downgrader", - "version": "2.1.5", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/simple-downgrader.git", - "reference": "6e40de0b168686ce500f29a49536a3c8fd25b982" + "reference": "c73a88c7d26ea9e5f0662b9587e7282a9c34c96b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/6e40de0b168686ce500f29a49536a3c8fd25b982", - "reference": "6e40de0b168686ce500f29a49536a3c8fd25b982", + "url": "https://api.github.com/repos/ondrejmirtes/simple-downgrader/zipball/c73a88c7d26ea9e5f0662b9587e7282a9c34c96b", + "reference": "c73a88c7d26ea9e5f0662b9587e7282a9c34c96b", "shasum": "" }, "require": { "nette/utils": "^3.2.5", - "nikic/php-parser": "^5.3.0", + "nikic/php-parser": "^5.5.0", "ondrejmirtes/better-reflection": "^6.57", "php": "^7.4|^8.0", "phpstan/phpdoc-parser": "^2.0", - "symfony/console": "^5.4", - "symfony/finder": "^5.4" + "symfony/console": "^5.4.47", + "symfony/finder": "^5.4.45", + "symfony/polyfill-php80": "^1.32" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.3", @@ -383,9 +384,9 @@ "description": "Simple Downgrader", "support": { "issues": "https://github.com/ondrejmirtes/simple-downgrader/issues", - "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.5" + "source": "https://github.com/ondrejmirtes/simple-downgrader/tree/2.1.7" }, - "time": "2025-05-28T09:11:05+00:00" + "time": "2025-06-03T13:58:28+00:00" }, { "name": "phpstan/phpdoc-parser", From 803aacde1efe9f76f95013174412d5b794476c15 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Jun 2025 21:26:03 +0200 Subject: [PATCH 1510/3097] Fix --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index f2982c077f8..3e1cf79ba68 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c7980f8aa4ebf51f5e1af96e49410b09", + "content-hash": "749053a0ff72c9389eab76807faadf37", "packages": [ { "name": "clue/ndjson-react", From a82e010db4f9e8c3df7986ad91dcad9b9c00cec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Wed, 4 Jun 2025 16:18:55 +0200 Subject: [PATCH 1511/3097] PHAR prefix diff job --- .github/scripts/.gitignore | 2 + .github/scripts/find-artifact.ts | 63 ++++++ .github/scripts/listPrefix.php | 32 +++ .github/scripts/package-lock.json | 323 ++++++++++++++++++++++++++++ .github/scripts/package.json | 20 ++ .github/scripts/tsconfig.json | 14 ++ .github/workflows/checksum-phar.yml | 124 ----------- .github/workflows/phar.yml | 139 ++++++++++++ 8 files changed, 593 insertions(+), 124 deletions(-) create mode 100644 .github/scripts/.gitignore create mode 100644 .github/scripts/find-artifact.ts create mode 100644 .github/scripts/listPrefix.php create mode 100644 .github/scripts/package-lock.json create mode 100644 .github/scripts/package.json create mode 100644 .github/scripts/tsconfig.json delete mode 100644 .github/workflows/checksum-phar.yml diff --git a/.github/scripts/.gitignore b/.github/scripts/.gitignore new file mode 100644 index 00000000000..f06235c460c --- /dev/null +++ b/.github/scripts/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.github/scripts/find-artifact.ts b/.github/scripts/find-artifact.ts new file mode 100644 index 00000000000..3f3524c9cce --- /dev/null +++ b/.github/scripts/find-artifact.ts @@ -0,0 +1,63 @@ +import * as core from "@actions/core"; +import * as github from "@actions/github"; + +interface Inputs { + github: ReturnType; + context: typeof github.context; + core: typeof core; +} + +module.exports = async ({github, context, core}: Inputs) => { + const commitSha = process.env.BASE_SHA; + const artifactName = process.env.ARTIFACT_NAME; + const workflowName = process.env.WORKFLOW_NAME; + + // Get all workflow runs for this commit + const runs = await github.rest.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 20, + event: "push", + head_sha: commitSha + }); + + if (runs.data.workflow_runs.length === 0) { + core.setFailed(`No workflow runs found for commit ${commitSha}`); + return; + } + + const workflowRuns = runs.data.workflow_runs; + if (workflowRuns.length === 0) { + core.setFailed(`No workflow runs found for commit ${commitSha}`); + return; + } + + let found = false; + for (const run of workflowRuns) { + if (run.status !== "completed" || run.conclusion !== "success") { + continue; + } + + if (run.name !== workflowName) { + continue; + } + + const artifactsResp = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id, + }); + + const artifact = artifactsResp.data.artifacts.find(a => a.name === artifactName); + if (artifact) { + core.setOutput("artifact_id", artifact.id.toString()); + core.setOutput("run_id", run.id.toString()); + found = true; + break; + } + } + + if (!found) { + core.setFailed(`No artifact named '${artifactName}' found for commit ${commitSha}`); + } +} diff --git a/.github/scripts/listPrefix.php b/.github/scripts/listPrefix.php new file mode 100644 index 00000000000..d7f902e8555 --- /dev/null +++ b/.github/scripts/listPrefix.php @@ -0,0 +1,32 @@ +setFlags(RecursiveDirectoryIterator::SKIP_DOTS); +$files = new RecursiveIteratorIterator($iterator); + +$locations = []; +foreach ($files as $file) { + $path = $file->getPathname(); + if ($file->getExtension() !== 'php') { + continue; + } + $contents = file_get_contents($path); + $lines = explode("\n", $contents); + foreach ($lines as $i => $line) { + if (!str_contains($line, '_PHPStan_checksum')) { + continue; + } + + $trimmedPath = substr($path, strlen($dir) + 1); + if (str_starts_with($trimmedPath, 'vendor/composer/autoload_')) { + continue; + } + $locations[] = $trimmedPath . ':' . ($i + 1); + } +} +sort($locations); +echo implode("\n", $locations); +echo "\n"; diff --git a/.github/scripts/package-lock.json b/.github/scripts/package-lock.json new file mode 100644 index 00000000000..8f4f624afc0 --- /dev/null +++ b/.github/scripts/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scripts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@actions/core": "^1.11.1", + "@actions/github": "^6.0.1" + }, + "devDependencies": { + "@types/node": "^22.15.29", + "typescript": "^5.8.3" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.1.tgz", + "integrity": "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==", + "license": "MIT", + "dependencies": { + "@actions/http-client": "^2.2.0", + "@octokit/core": "^5.0.1", + "@octokit/plugin-paginate-rest": "^9.2.2", + "@octokit/plugin-rest-endpoint-methods": "^10.4.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "undici": "^5.28.5" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", + "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 00000000000..e809b2ee739 --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,20 @@ +{ + "name": "scripts", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@actions/core": "^1.11.1", + "@actions/github": "^6.0.1" + }, + "devDependencies": { + "@types/node": "^22.15.29", + "typescript": "^5.8.3" + } +} diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json new file mode 100644 index 00000000000..62ac6dae381 --- /dev/null +++ b/.github/scripts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "Node", + "esModuleInterop": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "dist" + }, + "include": ["find-artifact.ts"] +} diff --git a/.github/workflows/checksum-phar.yml b/.github/workflows/checksum-phar.yml deleted file mode 100644 index 185fc779b42..00000000000 --- a/.github/workflows/checksum-phar.yml +++ /dev/null @@ -1,124 +0,0 @@ -# https://help.github.com/en/categories/automating-your-workflow-with-github-actions - -# This workflow checks that PHAR checksum changes only when it's supposed to -# It should stay the same when the PHAR contents do not change - -name: "Check PHAR checksum" - -on: - pull_request: - paths: - - 'compiler/**' - - '.github/workflows/checksum-phar.yml' - push: - branches: - - "2.1.x" - paths: - - 'compiler/**' - - '.github/workflows/checksum-phar.yml' - -concurrency: - group: checksum-phar-${{ github.head_ref || github.run_id }} # will be canceled on subsequent pushes in pull requests but not branches - cancel-in-progress: true - -jobs: - check-phar-checksum: - name: "Check PHAR checksum" - - runs-on: "ubuntu-latest" - timeout-minutes: 60 - - steps: - - name: "Checkout phpstan-dist" - uses: actions/checkout@v4 - with: - repository: phpstan/phpstan - path: phpstan-dist - ref: 2.1.x - - - name: "Get info" - id: info - working-directory: phpstan-dist - run: | - echo "checksum=$(head -n 1 .phar-checksum)" >> $GITHUB_OUTPUT - echo "commit=$(tail -n 1 .phar-checksum)" >> $GITHUB_OUTPUT - - - name: "Delete phpstan-dist" - run: "rm -r phpstan-dist" - - - name: "Checkout" - uses: actions/checkout@v4 - with: - ref: ${{ steps.info.outputs.commit }} - - - name: "Checkout latest PHAR compiler" - uses: actions/checkout@v4 - with: - path: phpstan-src - ref: ${{ github.sha }} - - - name: "Delete old compiler" - run: "rm -r compiler" - - - name: "Move new compiler" - run: "mv phpstan-src/compiler/ ." - - - name: "Delete phpstan-src" - run: "rm -r phpstan-src" - - - name: "Change and commit README.md" - run: | - echo Testing > README.md - git config --global user.name "phpstan-bot" - git config --global user.email "ondrej+phpstanbot@mirtes.cz" - git commit -a -m 'Changed README' - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "8.1" - extensions: mbstring, intl - - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - - name: "Install compiler dependencies" - run: "composer install --no-interaction --no-progress --working-dir=compiler" - - # same steps as in phar.yml - - - name: "Prepare for PHAR compilation" - working-directory: "compiler" - run: "php bin/prepare" - - - name: "Set autoloader suffix" - run: "composer config autoloader-suffix PHPStanChecksum" - - - name: "Composer dump" - run: "composer install --no-interaction --no-progress" - env: - COMPOSER_ROOT_VERSION: "2.1.x-dev" - - - name: "Compile PHAR for checksum" - working-directory: "compiler/build" - run: "php box.phar compile --no-parallel" - env: - PHAR_CHECKSUM: "1" - COMPOSER_ROOT_VERSION: "2.1.x-dev" - - - name: "Re-sign PHAR" - run: "php compiler/build/resign.php tmp/phpstan.phar" - - - name: "Unset autoloader suffix" - run: "composer config autoloader-suffix --unset" - - - name: "Save checksum" - id: "new_checksum" - run: echo "md5=$(md5sum tmp/phpstan.phar | cut -d' ' -f1)" >> $GITHUB_OUTPUT - - - name: "Assert checksum" - run: | - checksum=${{ steps.info.outputs.checksum }} - new_checksum=${{ steps.new_checksum.outputs.md5 }} - [[ "$checksum" == "$new_checksum" ]]; diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index a468bd523a2..2c51d6bbd34 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -23,6 +23,7 @@ jobs: outputs: checksum: ${{ steps.checksum.outputs.md5 }} + compiler_changed: ${{ steps.changes.outputs.compiler }} steps: - name: "Checkout" @@ -107,6 +108,15 @@ jobs: - name: "Delete checksum PHAR" run: "rm tmp/phpstan.phar" + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + compiler: + - 'compiler/**' + - '.github/workflows/phar.yml' + - '.github/scripts/**' + integration-tests: if: github.event_name == 'pull_request' needs: compiler-tests @@ -131,6 +141,135 @@ jobs: ref: 2.1.x phar-checksum: ${{needs.compiler-tests.outputs.checksum}} + checksum-phar: + name: "Checksum PHAR" + if: github.event_name == 'pull_request' && needs.compiler-tests.outputs.compiler_changed == 'true' + needs: compiler-tests + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v4 + + - name: Get base commit SHA + id: base + run: echo "base_sha=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + working-directory: .github/scripts + run: npm ci + + - name: "Compile TS scripts" + working-directory: .github/scripts + run: npx tsc + + - name: Find phar-file-checksum from base commit + id: find-artifact + uses: actions/github-script@v7 + env: + BASE_SHA: ${{ steps.base.outputs.base_sha }} + ARTIFACT_NAME: phar-file-checksum + WORKFLOW_NAME: Compile PHAR + with: + script: | + const script = require('./.github/scripts/dist/find-artifact.js'); + await script({github, context, core}) + + - name: Download old artifact by ID + uses: actions/download-artifact@v4 + with: + artifact-ids: ${{ steps.find-artifact.outputs.artifact_id }} + run-id: ${{ steps.find-artifact.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: "Save old checksum" + id: "old_checksum" + run: echo "md5=$(md5sum phar-file-checksum/phpstan.phar | cut -d' ' -f1)" >> $GITHUB_OUTPUT + + - name: "Assert checksum" + run: | + old_checksum=${{ steps.old_checksum.outputs.md5 }} + new_checksum=${{needs.compiler-tests.outputs.checksum}} + [[ "$old_checksum" == "$new_checksum" ]]; + + phar-prefix-diff: + name: "PHAR Prefix Diff" + if: github.event_name == 'pull_request' && needs.compiler-tests.outputs.compiler_changed == 'true' + needs: compiler-tests + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v4 + + - name: Get base commit SHA + id: base + run: echo "base_sha=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + working-directory: .github/scripts + run: npm ci + + - name: "Compile TS scripts" + working-directory: .github/scripts + run: npx tsc + + - name: Find phar-file-checksum from base commit + id: find-artifact + uses: actions/github-script@v7 + env: + BASE_SHA: ${{ steps.base.outputs.base_sha }} + ARTIFACT_NAME: phar-file-checksum + WORKFLOW_NAME: Compile PHAR + with: + script: | + const script = require('./.github/scripts/dist/find-artifact.js'); + await script({github, context, core}) + + # saved to phar-file-checksum/phpstan.phar + - name: Download old artifact by ID + uses: actions/download-artifact@v4 + with: + artifact-ids: ${{ steps.find-artifact.outputs.artifact_id }} + run-id: ${{ steps.find-artifact.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + # saved to phpstan.phar + - name: "Download phpstan.phar" + uses: actions/download-artifact@v4 + with: + name: phar-file-checksum + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "8.1" + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + + - name: "Extract old phpstan.phar" + run: "php compiler/build/box.phar extract phar-file-checksum/phpstan.phar phar-old" + + - name: "Extract new phpstan.phar" + run: "php compiler/build/box.phar extract phpstan.phar phar-new" + + - name: "List prefix locations in old PHAR" + run: "php .github/scripts/listPrefix.php ${{ github.workspace }}/phar-old > phar-old.txt" + + - name: "List prefix locations in new PHAR" + run: "php .github/scripts/listPrefix.php ${{ github.workspace }}/phar-new > phar-new.txt" + + - name: "Diff locations" + run: "diff -u phar-old.txt phar-new.txt" + commit: name: "Commit PHAR" if: "github.repository_owner == 'phpstan' && (github.ref == 'refs/heads/2.1.x' || startsWith(github.ref, 'refs/tags/'))" From e5b4e7bb35166a7c3d4af5721271395e47b42b47 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 4 Jun 2025 17:02:16 +0200 Subject: [PATCH 1512/3097] Nice PHAR prefix diff --- .github/scripts/diffPrefixes.php | 85 ++++++++++++++++++++++++++++++++ .github/workflows/phar.yml | 5 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/diffPrefixes.php diff --git a/.github/scripts/diffPrefixes.php b/.github/scripts/diffPrefixes.php new file mode 100644 index 00000000000..0f3c22cbfc6 --- /dev/null +++ b/.github/scripts/diffPrefixes.php @@ -0,0 +1,85 @@ +diff(file_get_contents($oldFilePath), file_get_contents($newFilePath)); + if ($stringDiff === '') { + continue; + } + + $isDifferent = true; + + echo "$path:\n"; + $startLine = 1; + $startContext = 1; + foreach (explode("\n", $stringDiff) as $i => $line) { + $matches = Strings::match($line, '/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/'); + if ($matches !== null) { + $startLine = (int) $matches[1]; + $startContext = (int) $matches[2]; + continue; + } + + if ($lineNumber < $startLine || $lineNumber > ($startLine + $startContext)) { + continue; + } + + if (str_starts_with($line, '+')) { + echo "\033[32m$line\033[0m\n"; + } elseif (str_starts_with($line, '-')) { + echo "\033[31m$line\033[0m\n"; + } else { + echo "$line\n"; + } + } + + echo "\n"; +} + +if ($isDifferent) { + exit(1); +} diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 2c51d6bbd34..a33076e52c4 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -268,7 +268,10 @@ jobs: run: "php .github/scripts/listPrefix.php ${{ github.workspace }}/phar-new > phar-new.txt" - name: "Diff locations" - run: "diff -u phar-old.txt phar-new.txt" + run: "diff -u phar-old.txt phar-new.txt > diff.txt || true" + + - name: "Diff files where prefix changed" + run: "php .github/scripts/diffPrefixes.php ${{ github.workspace }}/diff.txt ${{ github.workspace }}/phar-old ${{ github.workspace }}/phar-new" commit: name: "Commit PHAR" From d34fee110c79f154e981a13bd2c0f8d78318fec3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Oct 2023 09:41:51 +0200 Subject: [PATCH 1513/3097] Remove hack for prefixed global functions --- bin/phpstan | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index 119af4377c0..23e5725d38e 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -29,42 +29,6 @@ use Symfony\Component\Console\Helper\ProgressBar; } $devOrPharLoader->unregister(); - $composerAutoloadFiles = $GLOBALS['__composer_autoload_files']; - if ( - !array_key_exists('e88992873b7765f9b5710cab95ba5dd7', $composerAutoloadFiles) - || !array_key_exists('3e76f7f02b41af8cea96018933f6b7e3', $composerAutoloadFiles) - || !array_key_exists('a4a119a56e50fbb293281d9a48007e0e', $composerAutoloadFiles) - || !array_key_exists('0e6d7bf4a5811bfa5cf40c5ccd6fae6a', $composerAutoloadFiles) - || !array_key_exists('e69f7f6ee287b969198c3c9d6777bd38', $composerAutoloadFiles) - || !array_key_exists('8825ede83f2f289127722d4e842cf7e8', $composerAutoloadFiles) - || !array_key_exists('23c18046f52bef3eea034657bafda50f', $composerAutoloadFiles) - ) { - echo "Composer autoloader changed\n"; - exit(1); - } - - // empty the global variable so that unprefixed functions from user-space can be loaded - $GLOBALS['__composer_autoload_files'] = [ - // fix unprefixed Hoa namespace - files already loaded - 'e88992873b7765f9b5710cab95ba5dd7' => true, - '3e76f7f02b41af8cea96018933f6b7e3' => true, - - // vendor/symfony/polyfill-php80/bootstrap.php - 'a4a119a56e50fbb293281d9a48007e0e' => true, - - // vendor/symfony/polyfill-mbstring/bootstrap.php - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => true, - - // vendor/symfony/polyfill-intl-normalizer/bootstrap.php - 'e69f7f6ee287b969198c3c9d6777bd38' => true, - - // vendor/symfony/polyfill-intl-grapheme/bootstrap.php - '8825ede83f2f289127722d4e842cf7e8' => true, - - // vendor/symfony/polyfill-php81/bootstrap.php - '23c18046f52bef3eea034657bafda50f' => true, - ]; - $autoloaderInWorkingDirectory = $vendorDirectory . '/autoload.php'; $composerAutoloaderProjectPaths = []; From 9c5bc66bcaa54204c683cba2203f1c863b274f79 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 5 Mar 2024 09:24:16 +0100 Subject: [PATCH 1514/3097] Require Box with Composer --- .github/workflows/phar.yml | 16 +- compiler/box/.gitignore | 1 + compiler/box/composer.json | 23 + compiler/box/composer.lock | 4085 +++++++++++++++++ .../patches/ScoperAutoloaderGenerator.patch | 20 + compiler/build/box.phar | Bin 1972194 -> 0 bytes 6 files changed, 4141 insertions(+), 4 deletions(-) create mode 100644 compiler/box/.gitignore create mode 100644 compiler/box/composer.json create mode 100644 compiler/box/composer.lock create mode 100644 compiler/box/patches/ScoperAutoloaderGenerator.patch delete mode 100755 compiler/build/box.phar diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index a33076e52c4..a9ca6a28163 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -59,9 +59,13 @@ jobs: - name: "Dump autoloader one more time for attributes" run: "composer dump" + - name: "Install Box dependencies" + working-directory: "compiler/box" + run: "composer install" + - name: "Compile PHAR" working-directory: "compiler/build" - run: "php box.phar compile --no-parallel" + run: "php ../box/vendor/bin/box compile --no-parallel" - uses: actions/upload-artifact@v4 with: @@ -85,7 +89,7 @@ jobs: - name: "Compile PHAR for checksum" working-directory: "compiler/build" - run: "php box.phar compile --no-parallel" + run: "php ../box/vendor/bin/box compile --no-parallel" env: PHAR_CHECKSUM: "1" COMPOSER_ROOT_VERSION: "2.1.x-dev" @@ -255,11 +259,15 @@ jobs: - name: "Install dependencies" run: "composer install --no-interaction --no-progress" + - name: "Install Box dependencies" + working-directory: "compiler/box" + run: "composer install" + - name: "Extract old phpstan.phar" - run: "php compiler/build/box.phar extract phar-file-checksum/phpstan.phar phar-old" + run: "php ../box/vendor/bin/box extract phar-file-checksum/phpstan.phar phar-old" - name: "Extract new phpstan.phar" - run: "php compiler/build/box.phar extract phpstan.phar phar-new" + run: "php ../box/vendor/bin/box extract phpstan.phar phar-new" - name: "List prefix locations in old PHAR" run: "php .github/scripts/listPrefix.php ${{ github.workspace }}/phar-old > phar-old.txt" diff --git a/compiler/box/.gitignore b/compiler/box/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/compiler/box/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/compiler/box/composer.json b/compiler/box/composer.json new file mode 100644 index 00000000000..4ea8c3e23ce --- /dev/null +++ b/compiler/box/composer.json @@ -0,0 +1,23 @@ +{ + "require": { + "humbug/box": "^4.6", + "cweagans/composer-patches": "^1.7" + }, + "config": { + "platform": { + "php": "8.2.99" + }, + "allow-plugins": { + "vaimo/composer-patches": true, + "cweagans/composer-patches": true + } + }, + "extra": { + "composer-exit-on-patch-failure": true, + "patches": { + "humbug/php-scoper": [ + "patches/ScoperAutoloaderGenerator.patch" + ] + } + } +} diff --git a/compiler/box/composer.lock b/compiler/box/composer.lock new file mode 100644 index 00000000000..f83b3a8b2d2 --- /dev/null +++ b/compiler/box/composer.lock @@ -0,0 +1,4085 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fb82d3fddd187abc2f321b38688fb441", + "packages": [ + { + "name": "amphp/amp", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-26T16:07:39+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" + }, + { + "name": "amphp/cache", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Cache\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", + "support": { + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" + }, + { + "name": "amphp/dns", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "addr@daverandom.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], + "support": { + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" + }, + { + "name": "amphp/parallel", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "5113111de02796a782f5d90767455e7391cca190" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/5113111de02796a782f5d90767455e7391cca190", + "reference": "5113111de02796a782f5d90767455e7391cca190", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "support": { + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-12-21T01:56:09+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", + "parser", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T19:16:53+00:00" + }, + { + "name": "amphp/pipeline", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/pipeline.git", + "reference": "7b52598c2e9105ebcddf247fc523161581930367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.18" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" + ], + "support": { + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T16:33:53+00:00" + }, + { + "name": "amphp/process", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/process.git", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", + "support": { + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:13:44+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/master" + }, + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/socket", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/socket.git", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^6.5 | ^7", + "league/uri-interfaces": "^2.3 | ^7", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" + ], + "psr-4": { + "Amp\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", + "keywords": [ + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" + ], + "support": { + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-21T14:33:03+00:00" + }, + { + "name": "amphp/sync", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", + "shasum": "" + }, + "require": { + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "support": { + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-08-03T19:31:26+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "cweagans/composer-patches", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/cweagans/composer-patches.git", + "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweagans/composer-patches/zipball/e190d4466fe2b103a55467dfa83fc2fecfcaf2db", + "reference": "e190d4466fe2b103a55467dfa83fc2fecfcaf2db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3.0" + }, + "require-dev": { + "composer/composer": "~1.0 || ~2.0", + "phpunit/phpunit": "~4.6" + }, + "type": "composer-plugin", + "extra": { + "class": "cweagans\\Composer\\Patches" + }, + "autoload": { + "psr-4": { + "cweagans\\Composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Cameron Eagans", + "email": "me@cweagans.net" + } + ], + "description": "Provides a way to patch Composer packages.", + "support": { + "issues": "https://github.com/cweagans/composer-patches/issues", + "source": "https://github.com/cweagans/composer-patches/tree/1.7.3" + }, + "time": "2022-12-20T22:53:13+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "fidry/console", + "version": "0.6.11", + "source": { + "type": "git", + "url": "https://github.com/theofidry/console.git", + "reference": "bea8316beae874fc5b8be679d67dd3169c7e205f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/console/zipball/bea8316beae874fc5b8be679d67dd3169c7e205f", + "reference": "bea8316beae874fc5b8be679d67dd3169c7e205f", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/console": "^6.4 || ^7.2", + "symfony/deprecation-contracts": "^3.4", + "symfony/event-dispatcher-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php84": "^1.31", + "symfony/service-contracts": "^2.5 || ^3.0", + "thecodingmachine/safe": "^2.0 || ^3.0", + "webmozart/assert": "^1.11" + }, + "conflict": { + "symfony/dependency-injection": "<6.4.0 || >=7.0.0 <7.2.0", + "symfony/framework-bundle": "<6.4.0 || >=7.0.0 <7.2.0", + "symfony/http-kernel": "<6.4.0 || >=7.0.0 <7.2.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "composer/semver": "^3.3.2", + "ergebnis/composer-normalize": "^2.33", + "fidry/makefile": "^0.2.1 || ^1.0.0", + "infection/infection": "^0.28", + "phpunit/phpunit": "^10.2", + "symfony/dependency-injection": "^6.4 || ^7.2", + "symfony/flex": "^2.4.0", + "symfony/framework-bundle": "^6.4 || ^7.2", + "symfony/http-kernel": "^6.4 || ^7.2", + "symfony/yaml": "^6.4 || ^7.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fidry\\Console\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Library to create CLI applications", + "keywords": [ + "cli", + "console", + "symfony" + ], + "support": { + "issues": "https://github.com/theofidry/console/issues", + "source": "https://github.com/theofidry/console/tree/0.6.11" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-02-14T11:06:15+00:00" + }, + { + "name": "fidry/filesystem", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theofidry/filesystem.git", + "reference": "3e1f9cac40f807b7c4196013ab77cc1b9416e3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/filesystem/zipball/3e1f9cac40f807b7c4196013ab77cc1b9416e3e5", + "reference": "3e1f9cac40f807b7c4196013ab77cc1b9416e3e5", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/filesystem": "^6.4 || ^7.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4", + "ergebnis/composer-normalize": "^2.28", + "infection/infection": ">=0.26", + "phpunit/phpunit": "^10.3", + "symfony/finder": "^6.4 || ^7.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fidry\\FileSystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Symfony Filesystem with a few more utilities.", + "keywords": [ + "filesystem" + ], + "support": { + "issues": "https://github.com/theofidry/filesystem/issues", + "source": "https://github.com/theofidry/filesystem/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-02-13T22:58:51+00:00" + }, + { + "name": "humbug/box", + "version": "4.6.6", + "source": { + "type": "git", + "url": "https://github.com/box-project/box.git", + "reference": "09646041cb2e0963ab6ca109e1b366337617a0f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/box-project/box/zipball/09646041cb2e0963ab6ca109e1b366337617a0f2", + "reference": "09646041cb2e0963ab6ca109e1b366337617a0f2", + "shasum": "" + }, + "require": { + "amphp/parallel": "^2.0", + "composer-plugin-api": "^2.2", + "composer/semver": "^3.3.2", + "composer/xdebug-handler": "^3.0.3", + "ext-iconv": "*", + "ext-mbstring": "*", + "ext-phar": "*", + "fidry/console": "^0.6.0", + "fidry/filesystem": "^1.2.1", + "humbug/php-scoper": "^0.18.14", + "justinrainbow/json-schema": "^5.2.12", + "nikic/iter": "^2.2", + "php": "^8.2", + "phpdocumentor/reflection-docblock": "^5.4", + "phpdocumentor/type-resolver": "^1.7", + "psr/log": "^3.0", + "sebastian/diff": "^5.0", + "seld/jsonlint": "^1.10.2", + "seld/phar-utils": "^1.2", + "symfony/finder": "^6.4.0 || ^7.0.0", + "symfony/polyfill-iconv": "^1.28", + "symfony/polyfill-mbstring": "^1.28", + "symfony/process": "^6.4.0 || ^7.0.0", + "symfony/var-dumper": "^6.4.0 || ^7.0.0", + "thecodingmachine/safe": "^2.5 || ^3.0", + "webmozart/assert": "^1.11" + }, + "replace": { + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ergebnis/composer-normalize": "^2.29", + "ext-xml": "*", + "fidry/makefile": "^1.0.1", + "mikey179/vfsstream": "^1.6.11", + "phpspec/prophecy": "^1.18", + "phpspec/prophecy-phpunit": "^2.1.0", + "phpunit/phpunit": "^10.5.2", + "symfony/yaml": "^6.4.0 || ^7.0.0" + }, + "suggest": { + "ext-openssl": "To accelerate private key generation." + }, + "bin": [ + "bin/box" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "4.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "KevinGH\\Box\\": "src" + }, + "exclude-from-classmap": [ + "/Test/", + "vendor/humbug/php-scoper/vendor-hotfix" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Herrera", + "email": "kevin@herrera.io", + "homepage": "http://kevin.herrera.io" + }, + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Fast, zero config application bundler with PHARs.", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/box-project/box/issues", + "source": "https://github.com/box-project/box/tree/4.6.6" + }, + "time": "2025-03-02T18:20:45+00:00" + }, + { + "name": "humbug/php-scoper", + "version": "0.18.17", + "source": { + "type": "git", + "url": "https://github.com/humbug/php-scoper.git", + "reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/humbug/php-scoper/zipball/0a2556c7c23776a61cf22689e2f24298ba00e33a", + "reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a", + "shasum": "" + }, + "require": { + "fidry/console": "^0.6.10", + "fidry/filesystem": "^1.1", + "jetbrains/phpstorm-stubs": "^2024.1", + "nikic/php-parser": "^5.0", + "php": "^8.2", + "symfony/console": "^6.4 || ^7.0", + "symfony/filesystem": "^6.4 || ^7.0", + "symfony/finder": "^6.4 || ^7.0", + "symfony/var-dumper": "^7.1", + "thecodingmachine/safe": "^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.1", + "ergebnis/composer-normalize": "^2.28", + "fidry/makefile": "^1.0", + "humbug/box": "^4.6.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^10.0 || ^11.0", + "symfony/yaml": "^6.4 || ^7.0" + }, + "bin": [ + "bin/php-scoper" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Humbug\\PhpScoper\\": "src/" + }, + "classmap": [ + "vendor-hotfix/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Théo Fidry", + "email": "theo.fidry@gmail.com" + }, + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com" + } + ], + "description": "Prefixes all PHP namespaces in a file or directory.", + "support": { + "issues": "https://github.com/humbug/php-scoper/issues", + "source": "https://github.com/humbug/php-scoper/tree/0.18.17" + }, + "time": "2025-02-19T22:50:39+00:00" + }, + { + "name": "jetbrains/phpstorm-stubs", + "version": "v2024.3", + "source": { + "type": "git", + "url": "https://github.com/JetBrains/phpstorm-stubs.git", + "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", + "reference": "0e82bdfe850c71857ee4ee3501ed82a9fc5d043c", + "shasum": "" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "v3.64.0", + "nikic/php-parser": "v5.3.1", + "phpdocumentor/reflection-docblock": "5.6.0", + "phpunit/phpunit": "11.4.3" + }, + "type": "library", + "autoload": { + "files": [ + "PhpStormStubsMap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "PHP runtime & extensions header files for PhpStorm", + "homepage": "https://www.jetbrains.com/phpstorm", + "keywords": [ + "autocomplete", + "code", + "inference", + "inspection", + "jetbrains", + "phpstorm", + "stubs", + "type" + ], + "support": { + "source": "https://github.com/JetBrains/phpstorm-stubs/tree/v2024.3" + }, + "time": "2024-12-14T08:03:12+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" + }, + "time": "2024-07-06T21:00:26+00:00" + }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, + { + "name": "nikic/iter", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/iter.git", + "reference": "3f031ae08d82c4394410e76b88b441331a6fa15f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/iter/zipball/3f031ae08d82c4394410e76b88b441331a6fa15f", + "reference": "3f031ae08d82c4394410e76b88b441331a6fa15f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.18 || ^5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/iter.func.php", + "src/iter.php", + "src/iter.rewindable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Iteration primitives using generators", + "keywords": [ + "functional", + "generator", + "iterator" + ], + "support": { + "issues": "https://github.com/nikic/iter/issues", + "source": "https://github.com/nikic/iter/tree/v2.4.1" + }, + "time": "2024-03-19T20:45:05+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + }, + "time": "2025-02-19T13:28:12+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + }, + "time": "2025-01-25T19:27:39+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-24T10:34:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:26+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/5f3b930437ae03ae5dff61269024d8ea1b3774aa", + "reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-iconv": "*" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-17T14:58:18+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "000df7860439609837bbe28670b0be15783b7fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-20T12:04:08+00:00" + }, + { + "name": "symfony/process", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-17T09:11:12+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:19:01+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-27T18:39:23+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2025-05-14T06:15:44+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "platform-overrides": { + "php": "8.2.99" + }, + "plugin-api-version": "2.6.0" +} diff --git a/compiler/box/patches/ScoperAutoloaderGenerator.patch b/compiler/box/patches/ScoperAutoloaderGenerator.patch new file mode 100644 index 00000000000..5f993cc80e8 --- /dev/null +++ b/compiler/box/patches/ScoperAutoloaderGenerator.patch @@ -0,0 +1,20 @@ +--- src/Autoload/ScoperAutoloadGenerator.php 2025-06-04 20:36:51 ++++ src/Autoload/ScoperAutoloadGenerator.php 2025-06-04 20:37:31 +@@ -108,7 +108,7 @@ + \$loader = require_once __DIR__.'/autoload.php'; + // Ensure InstalledVersions is available + \$installedVersionsPath = __DIR__.'/composer/InstalledVersions.php'; +- if (file_exists(\$installedVersionsPath)) require_once \$installedVersionsPath; ++ if (!class_exists(\Composer\InstalledVersions::class, false) && file_exists(\$installedVersionsPath)) require_once \$installedVersionsPath; + + // Restore the backup and ensure the excluded files are properly marked as loaded + \$GLOBALS['__composer_autoload_files'] = \\array_merge( +@@ -140,7 +140,7 @@ + \$loader = require_once __DIR__.'/autoload.php'; + // Ensure InstalledVersions is available + \$installedVersionsPath = __DIR__.'/composer/InstalledVersions.php'; +- if (file_exists(\$installedVersionsPath)) require_once \$installedVersionsPath; ++ if (!class_exists(\Composer\InstalledVersions::class, false) && file_exists(\$installedVersionsPath)) require_once \$installedVersionsPath; + + // Restore the backup and ensure the excluded files are properly marked as loaded + \$GLOBALS['__composer_autoload_files'] = \\array_merge( diff --git a/compiler/build/box.phar b/compiler/build/box.phar deleted file mode 100755 index 402ceabdc4b28aa4e3862e8dee2091e5f6770529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1972194 zcmcFs2Ygh;^N%2cbVMm45)q{t9)u!Akt7fZ5D0`W7%!Jga&V0+AwiHL76e35K}8Ui zVi)WUL{TY9QGOx{7C=F}tG>|OZ#`H;PfdEY5JJ3Bi&yYJFVj8MRD zKgi4(`Z*Sv0dVbLT4Z`-|2EBqV?JQ!9;IFQwjy6LaPAA)GW8}i#7iibcbmuMyiy(Sx(86y&1PP@YsaNxTwNNnbY zJb5lh9wAI|1`F}~FpS-s2T?rcv!+;sGt&AE$?iuo7&U8V725ngd%A5t`p~8|giNx9 zg5IP8hX?Wm9eGJMm(vzVO7584)z;l%@6*;25)yts>1e@!(g=TI%Gsf`c;0iw6WJg=BcpvAexe0GLC9rm z<|rN^etJ{8bHz%2^n89a;I|uTq*RBWH2F1TqQCD1{x6>AT+V;r()#zqMMskQ)4VPh z?9J)*m`~#=diq&V`)cmG_xb4ytWS?{_}xyA%|#EilH_{f!5=0UY~%+b*vs-eJwd_V zn!18OH~js(n^tG=-#4)mu&_roR#g?xbOBRO935N2&s<`CCL_?VIEY~aLDEAmm_9}b zJ^V1~4HE6e9~VE8n#2!B=p!SLRp=Y#@SpZ{GjuUMeGyPr%$@x`KOMntMj(RXG*F^v zLTJw)Khnp~kg=KOK|Pi+l$e`TU&2Eh@B0sI<3CmKMGjA%*Kg1mb$M;3Ku=16TA>|t z@4j~Y%oPkTpMjt8c>@l=5pcMRs3EG<4Mn2ocF3;24LDo(4H?2XNipDI%xn&ooCtn;FL7dsh0q2Ry!eKmhb1c8LYPkSen-p2>NZXH6t_R3Od8z z33*IYfjkeR_ldcM6B!xYi*AXcw=j|s{z#xdTrNOYV}Ku@|HV`WO^FRO(-yQBrnp>@ z;Ju{XcR~+!?y~+y22YIxe2CZMh(sSN(UXDxX-?Z08NEwv=&4@JMx+Xl^l>dQWNNQ2 zQjRl5Zo^EP#+q-lI|!W|5)G;kz}>pmdpk3?9kjT0V`jGny{3iTEA74o5cgiT_*+J5 zr-77h4khAeb`8PL07#ozpDnH6=1FGL9Tf4JA_;GNlh+?c{Dh*Ig>dkSCb0CPOKobB01{R6nwl1lxJ>e z`ZuFg%!e=F??MReq`|)d&X3*L>jFk7*Uc+FJT|u@;KR5v`*a?jCyn^f-flDqGR{Ti zX@mntsRuuLBJtxIRrxuCp0mU^NoLrB`6c!c#_ZIxpfp38zY zCK4rd&5G*AQVC|4>NF3nd**%sJEu(Uj!bEB4lozrMs~nNoLb%~)fS7(r z*SB)iDh%-|DNVS4v_d{}DNvRZh%yS(uo$X11ne1yo+=Irn^`ciLgb`RBZS}sikgq&(3cW8^jxORmyV1v zO(whLBKiURX|;d0tJOk#CgJgTX>sHA7=69=T$k5ADMkG%ob~upuARpq*e=@H<3FZI zL5s<$dIrQ@`Ch-W@~3d8hQNx=9{BO6Oq!)Wjr34I7A%w|lIegK zkZU7=Ug&}?z7G=^fYM;Z`*2IDRAgDLzY6pXxqV{kSxv6s*MOtf_itDE6bi}90ZQ@t zEbxJJn;lG*kX_vbr4v`TU?n~*XZbkhs(}Q{4|&X-t*QP#QiS&?NBvhq5&tnOk93t? z5(qlnM))I9ncrAYc^gy`8r18}4|0!(|Juk0Moz#4p(hU}iYI5F=dYc-KxwU+auRDg9-HuT^|zQHWjTbQSnPsQZIM zR7ZR9zZ-616y(TF6gwp}%`W~9zoEZef%c~wsk!`@Z01>ozHGY}mS|)<{7&rQOts~@ z99fiB6byHhgZl=M>bl|0g$$4gKw>xyARn?AV?Ft78lLqxd#4{i+v>Dut;7XCWGwY$ zGUN_u{?C=y%;o1#XC4mC70QEOBF_>AxI!{;Ef}bFcAJkFfYRiV%!i+O5r0b>`x1ZS ze)(=UKf}&HEbx$!%N2g^CutsS@cawQPAKP(?6IU}^JjSSz0jEWKoa&swwj1-1fyTL z58lSa!VFp-vUv;XnX9A;-3vlb?`fm*?`0C|>&(xmhhCSe41my@QF8||A=;l2c855Z zya?Dc6Hzm;DMg$;iXmQmpYs)dfCno1NA*Bh3vWv;^Z;{pR-dX>{$mP&*rQxBQF|5a z*mvIhGe5?jNd2*qbQhP({*MyNPsrCoPdyAK~^Zvx2PQCB59sFo5`Dm)QnEq@a0tiy%HTZqT#|;!`;ePf6yepO&3p7XA^ zNsY9JMt@vUawR`b+kiqN;_<=W0x!uo2xN%ekMaMrmTq{K|30ii@%NdIK)_bu7>;c) z2X@@)(IhG9kAV2A{?+&MqwGysQjmhPHhT^R;1^#B;Ftus0lXfZ(=wF-5=-&Q1Kdjd zSvmgx!n4T_)a%2~mgXCM?zVaIj4Zzwd05kE9=}kCY{x7;^ytU;@uMwEk>y8+a2hv< zn9i2{b33Fywm3DJpAMTU_m3bG|eCHf;^Aje%pb}~wNe6yo#4F^Tfy#j5G zZ}Nx=tV?&2;u~{b9ts$U<~X^9|2#Mk%{`zzEA16ojgg;CE3|nE9HYJdN!XL=>+}y* zG4*-URJxS6hnp}3$0SYyJP$YDZS0XBDtZ*mU{lEC|H+Z!(QT09 z$(kMh;zv&(%R=*FOozGtO4`aY*h>BF$B**!9MYEVbMd^4#`_ifyneYNm?Fh5fcP)m zKTK_MU0zC&S%p3W|FRvyAT$^l0L#aSBQ5NZ0pAX=l)K|T6*}!zrEGDcU0X2$c)T-i38IrF6|1Flk@dZDIu-7z%A>N>w#P9^2 z6fP0N_v3$2x7~*Jy6bWe^CKL1)AVET9ORU%mfB#iZhBL-X~!L<{&PGtjDva$WW5`AA^#)XoE%rO4SDH+$qVyRUAoy5_hiG zWuR!voba=OVtNv^?+)vy{L!FFp*xCwsD44+6O$Py$y|%}yyJCMpLW+KlibBns9w{q z5gnSWQ(XV;A1|(B4b#$4Ps6}u2AjfINDrIvaJ6kus`>9qZLp3VjXbA6*<915k(HDCkSv(g-G8YR3)gxQ)4uRRNvMPD&}Q0N*Reeq+dv zv9Kz|hTk&TLo}1n?vvgj#=^QK{^<^T*)Bikbit~sETQ0V6dxVaFt~6 zH-H`3^psk8$G~8>(hO2Yj5J8?mYeSz!Mzd#gWVK^?@9*$1=#oXe#>JpeHWD6%`C&S zQ(ng=OJ8ZCAr>c~ZS#J|6LF(PY-t*h<+ z9*oyt8?RG0H7fI+w45v5S2O}i5#)p(8~s**N%W}%2_Bti!1$9(^Go|~#BWQq^FI7~ zDf3H%K~u%aX4OoA-{COV3^Sw;c?E_x?(nyD7_o0Gh_u4NB1Y8o+WaM!MgF-mo^OXx z@0C3-o@D%9vE!TBXP}eEz7vm=BIcm&I)2G#3>(A1O*Y3xe^P{4IEMOV7X>Pl`IZ)qnfl2jV%5v*V+1Eq%XBPC`svlI2=V z;Az=QccHENRP|qDm%LM_(mh}KF$>HH$*p6y&z7aCe|m#!Jud)6pVw7_aLQ$5!2CHS zM-w#-tHo%a%G;?X27PY;WDRf77T(~7WmhYE)%OMpzdUcSZD~VQGOuq664qi0R&TI1 z@B3HS6k_lO-Ffyge4$(<2@INp%v|du(ktEyJ#2dI!@3OKN4p1;!{DV>eywF6Qd@oL zir*PqKiafln-!KKkDFu3%>0M4e#X@gk}bFhN3hRkh+{=&uJT67b|Kmaf8M1MCzXNs zV=avB;u4$NOgWHc4+Z?~u8t}GLfSunGCeQIIh^jGWDwj zAsoOc!a+09LwsPVF(f)#XW=X+Kr4}$l_nQs9jN-@9z^2Ad36l70oz-SXW(n%0>*?f z$l>=m%%ey>km4rj7Xf|f&h8H|^Z;$>PCeL3BvFV?8!V;9b9pnTz9TAw1_uy(rTNev zPD`kIf_M2$CQ9E z#o!S%cOopN_QXj}rj}JXYA$Rr;{3VcgDw#*a@g+2={LJAUBTH-->NF@;2kTttbv@vJ{66Jh?$FAx&X&1Cs^vqd z;lP$qU&cwiXP*N1z;^y zELG&h#z4SG!E}OJQd*DlFNxx(P!dT)8P@#=UQoNW*W!E3t}T*cDtpl~$T}ri^4gm= zPFTW999?;`!zn3J2FVm@*n>g(lKQWz0eJ>U_JgK^Wsux2r6eCS_nQr>#CT-orA+am zHt4k8q-`|n%_V4moLu)1t2HjCM!h*881z~40lw-2zb=MYYSK4qXpM+hAQL~$>k0bl zvJ{F$tCpH$!$;7_Q1^f8vF;Ed6uVPT+1s=yn}l96O23sWTvAA#i2Zaum)3;Ac3 zh2ajYw#w#UzU1H_C|>Kmdn|KcJJTE#IB}rCA1VGWIcn|!6K`dVQrpqjomt{2C3Sit zDf5b)JX22LbJwxh*H?;irZW$PbW?182hNa_}LFqMDi}QS0?Xn$>NSVQ{sv;)U<#s=zzeF_eNBc|4+m|!VxcyUUF-Q1Afgq-vVku--saIOl?6|OsvZl)U4~t$$_?_+!sx=H4mXgd`GUE6XWIngQHj~N3klGEenuyHE zfLPq{G`R!7&8}~x{9k{3k85_u5HoYI;S?{sSmJ%{BhS8BDc^$zQ=4ZyqrOmt0OO9 z^p&fdYi^mW+j|DiYum=Qy* zniQEbEPBq^1g zuhV$o8OH2`?|McQ#&mkjl(~7%gRtoY0%6xj`_j`#Rj{BRqlK}}%`Z%CsHBm1$(f5 z>V|EbJ&FJ}p-2CZFJNnl!2&ulrc?JM zk8_gKo`k!B$dJ|iAdc=aV zm0(K)ohN&T_5d0mcq)-is&z#`aD(z_T$}2Gmn#8rqSGpsE{j^7N4%JK9+od7A6NVs zGIpMS^-5-~ZAGj(J^Au3=0IUHX(N<=c&FW*rx>JNG!Pmhl$pYrIkD=w182`j-H#)} z&wgUXt=cFNK|>D&HiY5j^ecN-R*OwKUxXq*FNoT$jtYN zjrjkU&ZL4FkXper8Ajs^1mYU7c-N#O0~k@8 zMFADQM&d$gY4yP3?75RJVZaI%8kSic-%rWQ%kn#LN*fM?E{z~=?`~TH;;siu#fdXg^1iKCq+{-f zk=On8Rcjc(!ug2tvBhb%UPNRcWPO~A@-1L@U&2jt4p zpQ@7a3jJgRq!sZk?3?dv& zlPYKoe$Rb$lPWTZnj9blI33-BN^f)_o}U4a1OJ<+cKR#SLKqdBSlK~$kZgkd?;)wz z`@wIwmHQ{K7Ao{N7DNv(C*|q` z-;bC*go3HmnSdQ2aI{}XRX9?iUk@`rf({VSde~yRN2Nde9r_smU*HYqx4*ts5T~P@ z0ViEfMrtN?TRDHMjo^GbRKGkFe2qzD#z!I}k2cBkoen<{gL^iwkfOdFggJy%bQ4r` zY}eN}GNH8i2=zlk#wv$T_P|KM=ZqMv(zftc(H&>F2dt`s@jwvzja3G&Z9%1eLfx*< z9A_EgR95(PDby>&9X`69&Ah;;i&WN9NV9d6&Ci4e#3v0+m%-9BHcOw|0-BrUIn|MA z#3_tq|cz;Eus|pnt?|e3M+dM3jC|( zy{g`H$Qhw!HMj?*$Ld~11s`o##SyD&Lkw7?Ewa z;ozWEw!d2m|3x{{_gDY)3d4^ZMgQ$jZfbi#9=tLQ3*xRCU0 zLR&cQFJb!v_UQatY7Lee+Xyhib2j;xG`yLLn7%+NJQL`no}d2_>!af3qPY1E{TWUG zWk@H#9xVH3uinF8DHX9QAdt|M*Pn9+%sN_rKVS6RT^O9}Cr(uRz!mmfX>htSFdZil z!hGK+bMM!K@3jNwE0H%B&ujE)|MdIycoc%nyy^LfE_fuFw9ylutWkshbOxa z@70EpD$Z`mTeih^ficEyxjMS34EfwJf?+lCRfUYBu8Qp?v!Wd9*#tXwjGthwcl!9;XBMZsMc2&@30B3xw%2wf+2l5!KqI#%ySx`tWHLsaUpv zDn^r6h_h?B4V(>r;=%8kGmC$$7>jS*^)(V*xw%?G!UHG2na5yl!YV2oOo~hn`#srj zPeIM!H6L)4F%qL;NHq_WYF+?gs#Tl+h1Ro@_6*>Y8T!#az5R)C{H$e+gn75q8Wgv$ zAi1H>IZr{v5) zwY4keuQ|zZmQoEZ7DbP-4#=dxk;2nW_{mRy{t_d{V+)ljEg*wm1#NPYkW$!Yg zCHEHtuOt1gY+Jq=pI#$JUlv$hyu8a^2DapvwSb4Gr*-Mpr8`OA8P0&Zo?e!E_rrHA zV@S(9sRc=T?VKz->p96My)9t*xG5@`7_&-IAtti+6zLP2HXtT-`E?VsX>rjLak#_j zp|=C)dZ*x`2(Jf4AoSL6vrtJMEO#W)|KOY2Z-9+n`BppC<=NrET0R;XB?WQW0$lB> zn7{0cqysq{^;^<$9qITe#Wv;ntJJdMx=N87=|PbzwM|9) z_8-rG$7-{jaEU|E3{j$nR3ElnG8wHY?-f(=E>*YA;jr2QtMHxYj>gh?k ziD+#+sS%jr+5F*^+3OO}Ev4{xuTmjni#{JNW)i|nB#AMH<_zObx z;`<1Sk4TEs(0=?u>I|k>Ia?vK4EgO2+%s>!^CyBNks=|zXzQ;?RhQFPlF@PWogxtt zB3v(N(Lmd9>z^lByb1UofA8^7Fg;x+4k+5F3_x)}zHPQ7>_c1gzbaQVVM{rVwvfX9 zvymOfJ#P`>)r99HRW1dU^BdQi!&EFYsx}pAk|J2olWXrn1Od$(FLE+)N*sENV4rkz zP6NSp7X1G`d!ACsbj#XFo9p5E_RgI<_aN9NaxQ!VN|}>((Y4I8C2^+>%hypFLvtt6 z;Zw`)l<&ygXAh|6)VEo)naqGVRAcer5&b$JSk$i+-e+VS%w`%H@zTIyO9%4i5k&5= z5Xrjyp7)tZT6{zdi!jt3n`Gt6zv!0F9&bCD;rmtwUdloEmo^nTDuAhOykPugj33XG z1J-fSSc2mghFnxd;0%}g7128>)%6_ONe^!Pn(6hAQyvA5gU+EaEsy`4 z=1d}-KJx1pV|^B;BHaXcmh&5=B_V47EJiJWFqZl z{=EbyKeFmZbys*iGg}xrAe~V7H$>|`S=RS8oao}^LyxdN$mZ1!Qcj{$UFP5> zKq9Ae(f^oAJaLwl3eA_{??ma>hN7>o#2DDU;;=edS6NE^JRx@^r5B+wvWl-@i8KG3 zQJrO~Y%htI_(akgd6A%X3R10q{Md&~D;}p2Mh@WZt?G@L_38@})6hkQzAEaP*YR-- zEGr4Ki>O4`P@f{E58Ur)s__lbnHj9Pg+&{C#^}-xojs_ila-`GNb0*wKs@Co;}0ep zj~6Lh*uh@B>NSGq4C$2@gXTEfZ{3+@JgZ_W&5^i=+4Pa&Uy_Ksl7xs3WOnMczeN?| zRn|O7o1TJ5S}mn$UqiHGAN5vg%gPQq%6gQdBRzdG*ZxYE^o91pq0y4b^YHQR3aXLX00U zfwirjtTvAVzlFYru;;Z6QmEj7;B~7wp-NY%Q}$7ZkPF*Hou`h z5#$5d0wd+Wh1q*p!(r;sV-F2+?Ou0Io;T=m1dZ@V0{v75Y=6O19NRrXP0uMBpdp_N z_fPu#Uc3te7sW_9;S0R*?5>?boyDs zv}FIiE-P?inGa(S~r3B|iXpA?^l*0rj>8vHsM3wUilgMJWK6XApj=no95)J8uj z)pynq(B|-a13u>@hml%BuOk55b~$lT1lOrktKBT5&L|DTsZRu19XV&>Fc)q&H2FXa zhSV9QgG07#)XnF5i;eI{0!@Ut1WEz>(eJ)gwf;JWn>!V+8k%ZuKfNU5{zm|F!@f>` zGjBS53RmR!*~ufJA)ell#!GkEtm)B5Eh2RKw5U6->!u$B^qgFUr(k%uZ~ygA%$1IC z#Q~Rre*}{$Be}Q0PxTHrDEFw-m$WHN#}y3zkYnc4rnM4k-wWvX@?Vb0JQyhq*^Rh- zxQT##6Oc>JTJt;$8y}Y)3Gzk>c^e@AU2%?Dp~eS!B|%m@UvT*c`nbuOzZ{k0(%c3= zER$7lfXn(B)2^r-q|LHdmFkE~c)0H^+k9PyEItnu;MnIK=an7VD(doAPQRk12oibAg)MZfpNY-b$3L`|FV z@oH{%-z`mP0Zi$pjQ^?4iufQ+XFIEr;Bqz~zn${EicXU?rYe@&Kw#-Ky=^b)Brkw1 ztvfVHg)T9fW?FXE@FD%FZ+M$66U^N8!u(?H8=aZJZ_9Ir0)}V^EP(KzW~W;)OJ&}W zC5$`=or_Z)vDc1Zk6#3%&ni@1V1PU`U-g!4T~O%xo~K>xmV_N~g6 zb$pmVZ;~y4k_{2PsA@6?$FfTW;G^h_2WsDO7mJ(%etEyIJP*Nu!}A82z~)a{^JCZ4 zJQ76Zjik@f=$4@muG+}pX>ovOI)XO3xzE7;%aa8B7QhfZ6(zjm{56~5(<=j?MX$r* z_Xe~A<3!d7JmW8-Tw<)$2JOtPDcCTQEhHXm=FTK_?3N6_58RXst6f+bTprhiOV>P5?~$5Y z9er#+Pbu@mbo$FE~Bd?&-Vgw^wuXYVnCg_yVS@g z70j1G3Y}4T|D^#pFq}@dWh$ZzgMNriP$voX2RO^UX6@$qpiD>plY}B+S698RAwwkr zx+1rzxFYfT%C!a^WBnt2t;zuDs2^v_Ae+r^@CO3CL#~I$!5JRRct91B=>(lNT-jXc zz&)@0{*S@ZqaK1-qh>v+;xL^E(T|lr{t-`25S^15Qy8&f^byLJ>C71ckIm;RF(MjbZ3QGF)+o~a<>@(tnKhk`^x^(tgMJXu7f9g|&|6U1 z&#um@@Ia>{OKkZD{^fXZ5Y9Z3NPyEZ$O&+2t%QUH-_=#AA04GhK!RzBO*Vq*f_2KAM5Q77Yc zLJ7M!WG|%6ikzZkBj^P@-WN%rQ{`Gch~Z$b{l5#C&z?|0MNT6ERDB`Tq1l4f^D#QE zbKhT!QFSc1AkXNZmyTi`0!oxULiTFr4RaDu`>KCxBK?_45g!Z1w}l)bdBKYIyhSc=lB>%hbMVc7v2jM=zs+Kd(vspJ58pX<5D}f zI_`C{H_zs=nXe>Hlv=tFUa|GSF};{09hd^Q&%i$t>{*!${u)dw~*j zXH+yfc5^IutS%HalV;prt3u3{&M=(f^q4OIGx!5B__?%G+K6cJ;i9(~P{;chID$b( zanO)0!CeD&k#W(coZH^=>{#I3fdT|5lMLAsTuZ2p;Hm>IsZXJDT{;F9^7^J4^n-v_ zV+tXl=>WR&>)TZ7S4T{obi*4df_M+5*%I7F8H`;GkF#__%|0xq&geGx;+=+Q2yB*D z@aArS)45aomvyA8+$D1WuZE@FAAiFCBQ_t65ngJ`v=#Fk2FrUP5`Is7fJ=t7C73;zFIT!5;{)Q1)#j=yBnD_q@Y^ zIy_Rbi`+oxPXtLtF2p0X)?yc(?F3v&gA6NQTHK+;?JAHR{1DJ`)_n3NGpI8+_(Pr% zjsV^Q9w@wC`byfD-Zy+|Cx+4KSd`1S43v?%a4(}dt3S6$NZ1V3IImsuT?&KgSg#32 zKS)*YH4(K_N82SNeCYnV0RyE#*ouN##TDKn!)o`%eFs{jd=2O>`+gfxNDMh+h=$}Oqvo*+MzdYg&1X3gDR=1s?QD&9o0$rLZ{hK<}Z`2dDNYTH~VA)(K%617jOGqmtdXTrl_Hms`54VW=l zUJ%+jm{@Op^uWC=Xbjv%B(1hfyqfSSx6b-R75r+Y76f))tI0Kk9yT7hZ zj_9l*rNj~wsYW|;uhhdCGO)W0g4a&}@qQNEauB=xARvNKdgEOHZ@1xhC-XKQ?NQwM z;ty)frNWjf8qksKj&|f`oAHJP{WG9ahkXUKeye}~g!$HS7v-U+`{l(!cS`V&0lqM4 zx{8c2BgV~onhOQmio)AKBKGe?`$5$w_cHrBV>Ocf5&W8};WE^G9n5c7{;Arz#}X$- zrs+j{$v9Yq*Chwc=>HGN?Mk$5=PsJV-1dnZI^e^y$#gMvNdal%DPSF+dBJwZj-Bm( z#m>k>qW=JrpjV;o`{a9b89H_gOmjIYj}VdQo+;6TXp?4?sB=iM5Al?s*DdxUDJel5 z?*rzwOK0UX$FZA*`PNt(;A3S}^B*8zPkiEfhSb@xiL|sdFTHIJQ4_(=k?Bb~^Yzf^ zP3nS-I5_2-Rwhb?7NWiBqxY15)7hno#{Tzn% zDC~YofZk^~d;Q}(m_Y3IAZ*M~gKZO7B>gT#+wH}d$1rl=m|2&|Sx%qBvQ7HB#J>k^ z=KkaNF@Eg!#`s8lI7}b^orGS3_E6K~DuWXH=#$VRYyq=7dr6z4&DR(HsXB;xjvF}3 zE6z&L!jF{Q4Fn#mzw2WLP6MIXEYHF*t^f$ROoedliv>=?p|gf7{~kAdcF-0K1v0R2 zK~~QCz8CP7!>k3rsMtLo|08Ei zyo&=@>xb|XauR@&s3gF$e-H6sRYE!#2CXXOSDjQrwkZ1(C&RRcFmBm+TXo&YZ6a0BnU7taM|C&`_ z$`Vw{n9QXqp&ygXcLSBC(_Zey=#?=hb5%{?S0J%O%AbT~-jR2wU&-JDE78+wls<|W zZMVrTree;{yB<=tx0O*)xOXR36EaXtJ9OWZx3LVdhXP{BWDNF-vw(DW3MpqJ*dDxL zkX@kG5@zp7xEu2fsm`fF7x-3OFn^U-D? z9ueNsOi#)Pp{+9Sx)ZF7*cYbZo_k349G%nY-EfL3E7D0lXZ0Ow;2(+IH$|{^3zBw? zuWsjN)^xTg0wsZ>K%v`^Ey3|M?R3F&QhM**vA`9$Z3Ry3`pK5y4k9KZ#qGepzW38t zMlzg^kW-yL1OEu-2&5sHI&cSzp6Jq(VGNDFEpT`NWd0H?qO+l91a~I=PWsN+()>(IKhr>dGi9e0Xan86 z;cN9!S9QxZCu31sb{4|Vtid^U^AOqoF#_rRT39^!CjQ1qI!a?Cv+goi`n5GM-n>d&C=->t-ey8xQ!Jg%Se(4QY~=CIo*w+QrT6s;aghzE?QQ zo(@0Khodj_HUyQt-XICgL2G;C-CWj&)<(hU1k624r`<~7O$&ve`YurW99oOr(<$H> zg1^Lyzh2_sg!ZdbkEuMaRz~S`d@D4SFp>7#U~K()@`dTFoGg&i(!PqtMDJ$cF8l(U ztIYc#ACO|qLi_ccu@5sXt)&);QHqwx2&rF^G2JCe&_eW3>;Da962tJl65zM~L^HV0_>OjW~OO zjWh%t5mY&to(L*4zBo(e)8g$S;o%}kSi8vi^taR67TcS{;<*hXC?77JibtniIB?elK+Rm z|4$Fy@E7B2`&9;u?oSUgl+Mbf z*cIS=gZL9co@gQ<8)3-4{LZ%uQYXTl7|JU#=m!BUkh3to@n_#%(^oKWI$~0|W6%## z!t;t8@86(5mp4A7+-q!5=HZWBSizFU0$AW)WULP{S32S;u1LI_TL_@h&{NQ{e{(EA z0S^kM=?4M*(NF-L1AV=DqRKrCq|=88+yijtAvdjf#3zD0tGR#-fw!y1B|OcLI`;3y z`!55yCq=ddcY8kpcYRG%rR<%fVjLX~aZtvr!Oo*2GvX6L4sR;2*(EfI(1iVilB7EAdaYBhwY%D+-NpK)_+1<>3zmI0F2b<^hWvlH7d;^QE)A z_R~w+4ZI7(yd|tqn*TpwW!lM&vl&T8Lf6!M1OH6jO9@lZIwlQNzDLKMyS;(QA!jh? zHP^%u-w66Y>CPXAtS9RjF&$CMM@sctJPI<`>1h`!S-ikKwrGHcQB5KahGowJ$u)%{&bQg<%=ca`qH$B2GJtFaU|*Tu1sqHzQ-<= zS@aaZVF0Xr!oG>RhiK%Wy|k#?w@hRB8KQwi4c_YnA|g`RY5; zSy?&>nA1&7cE8z}S8Wps+GimWccZnn=<+&~sFZ~X5^DBi3nMFDI;UkTrco&+DfC<^ z!C18KCjwv=d1e*m`;3?0e1$K(i*fQj6z-Nj~gTvnhg3> z{c-?P(MkN2(dAsJOM{uZnVeLfk=dMK_V(v@Y;qHl~#&RHCA*3cilUj z1*VG+N*VIeZqBcL7t0V|M@LXev%x)aP*Wu76rjC(_;afMIs@NhH>e0Y;m9cwtRyi_27cKE73nuo%LndTPR52;4hA$nY|^@vTav8p3vB2Q0ypUv?bN*v9bj z*+~R^r0e8Jkc>TVP{)%@C_euXN$B)An#Ah(@>QLgN_>G?B$d)p#cZiMn%aKYF}Nnv z(#h=DZMa0sljp~cg8Yd(d+P0?H&Pqm=GOIN)v02gRlFNjRk&M)eiBp&hDROS4XgOp zo_*>hLK3*GXj}%JNJY{Q0{SOW0JW-)lg^1gb*i4$^A!Qf{8~7~^ zQT6INV8!A-0@0X5qnZG_3`6AZy(blzP6xQLNo4mseL)=SmY)dnPM?5md_J^xs@h@h zQ=P<9dU}R|e*|+&Jpps1Dr$ureW@~ZI>~-Yp@>^6%`A%eNT5)esXJ<|$C9tn=m_)v zfSlqyN0H4ZZxi5e1pN;5Pu()C2CgzW_K;e`ch-QW$}U{OU{-jcCAbS@PU}N-jJN$& zjaiF2W}Id7-=xrs(Jpj-rjk)QniNp@87Q@>Su8e>l_ z>ic0!RgBhATh=gJbtGGKTin^7tW))mI%XndG~*LKOmV$b8O=4tEAtGNQlGWqO3WT- z3o$2ywbX}hRv8U_jg7$7sAi0LpJa<7nw2$QcQ9KzJ|Ix!G4PKRKj~7z(MkBtl=eMU zK%k?nJcr%swz&-165O$x0`59QFE_6pzl^!k@#i+5J%sz*>>&bbT~`3LgJFDi?bo(7BI&!*4<-YR4IBLdxsxFg`OcA;>le{fUO2klkCUZ#UflFP?&7c z4+2^vfl>gJ`tpBw!~z73`waR)K+ks*9MKzUUVi2qRpX@7dnQo$6dS=&vYSRBl}E*KuP#fab7gsH zI9^XAc1WHVqFuc6e=jnL)ObmN7&drh5@;m`Drw|bplz+^-@@qq;zcI`MtYndt4ldXv|$6R?8wuqy+8uRlh1**9i^FB!Wwt3%%yf z#5OtakZxwCKdW}z;?{Ud5SK>fhJvOU(xDsbPug?yH#qNK)=u1pLSBIEo2~>Q;A#Ng zRr6d`K%o;imZ=c}eFb0yO6Fbv-s@Dr61SSel{_U9`UHA#;mB}ldT)HMXo4t{U(=ru z(?^;Lrh|x(=KlQZ6|Ca8I}nkaC=?|0=2%{mO3uNA`*1??uZ$kIAEcW72v7TvjO?aB zLo?rMnZguO;?<0GBW$4r-w5!Uod@lyG&qh7p)@pTuAGO;U}Ok%Q@#Huzc6^*nvW=( zq--#0o~kQy9$Tg|>Tz34IxgNcPrA}82MGB^AphH7-xthx+%AhX&aXNq!)Ym%vljz( z*Xny!@qMM{|16}WdHx?H^+T@Ywx+`$WGS>lF3O_h<;t6DQhZM0_%*4Pg8=lL<;-P3 zosiH>%K%We1h*9Vc+&C!jG%=HO&2hnP8?o>o2c-QVD=zDC76lO8 zx=Iai{gWRE&|OOa)`8VN*QkRkV%Ev31ZfTBoQk72>Jve(X&@l4#rpAoWD^mDRugq zE67wS83A4_`}!dO_PQnUD`r(E;31h4hh~YSoNNh`k^?0EDv1B^gZtHnfX>1&*Y38V zL{l^bmMDYZn*sXvQ&nQ>=P7x4#RmN#AbQUfakUg&y}q&Ty)3DY!N3#P4f;Vq=UgCw zs=}5xojMp`AgseGwB=%yj^lns`bkjlOW|lK)NcKFwGp8Mg+u-hyc*o^5I0<=UoN;B z2*^2oHeA45>4>MGh^Jc{382mp@9baSPh_Cjcrqi$jQRp<6Xw`S`Bg@`@^BC~zByu?JupLwDP zh{LZvqB|*w?(?8C`HlBgDr?M{r>kD?a=xUw4m6IubD65LnsDZ6M!q76FRy(WgxcNL zYZEJZ^cf?Jj7Yw-AS4c3kufzvSVH=(3YllUc6y?BvNIF$f zvDqyNW|y7#90XbX)YQ9JuwiFRFw2!xG@p_dKLL2vA6u*2a?YHUnp%t)8Ka~+kAUWl z=N7BGld{j4fD!AX>3}5>r=b1*?k@XTpJ%T3u+}qQ^14+K~OZpqGQJ$~8uPpUI% zI$@6aQV=W)2Rra`kiMw|ce{-7i=gS>o=j7Ls?M?ycW%>Lg9)ZjL%|ij_j=Y-Te`8B zI@5Q7C*&(I%%22xw~TndM>x@D*n8^mxK3<2l@7_%4+2^&1Hx~?)9j;NXEIMZn!=un zdGUvA32xRwA>y=lD3h!E=fPOu{66t28rc$DS}Or}Gdkep*SpkJ$2#0)`&^WYHDpV0 z^W{j{53^W3{?9L8`as&Nb=G|c_*79e*@RY7%9K5Tks;AYNgUgjo)Nzww z@N}pj7q75rE`7pw2vpp8;zSlOyD|a}ws{Idc!!-i?1*B@6q|T|Q`{%5>j7*8TSZy}L z?G6RGD*tE6GHniJ?^xqx;CK{0#2Y?WP(|Wy1%tB}Jg)p!JgP9S;K?quiCs9l0FF)L z57!YDKkKX^;Z(`ri-zpsZ<$V55B>gGt7cyoUuW4DDl$fnB9M-9{eM4}efLdmI*>th zydXeAML7{u`s9{Ez%f`X7tPsvGsEc!=rC>45DoE0r?N>~tq9L$z>IlA)0Jh-7ZWWE`=qv(EX@?q#YqK4I8bf(~<>%)L)OVr8n*DEA#d11KGOBDz=Bw%nIC`66 z_Nr_Zt7-K`iP;zQlb}A6>52)6FD~hMi^`YklmVgi%*Y; z7zJUZw_LQXo1C?mxzaHUhtq4|AHf`yS-OkB+trIEs7sM`c*5g(xdn!32yBov)h*zu zp!T@Q%+r!I{FnJ*n(x$;mchD(#=ozv9)q;_&x-#t+;rdlx* z3IVUY@tj1Yl_c!Tb1Djih`a~=~RYyQlq4cc87V5GEoq;*kJ=wrNf(e`_ zV5r7n+~MB;W6pF2Cay?!;=&Bk5LiYt0roN`ffehAtD`f?8fweOC4rH>Ny;Xrk-gXd z_ED8F(UFkQ@Q;|mr!?dGF|r)Q1?hqhEqI)PbjG98 z;|)6Vk^T@Z@wEs1ka&|YjIOA@SY2?d6Lj_~!s&Ygc@X_XAjxn#>vL&BmzFm?7LDz&d; zW%f|6AMd0S4KX)NMm;Ui51}Rv{mh&WSbo0S=O{2lLty{Py{Jv-g{;mArx?t#Yz?{m zPJ1Eh^zr^huTR|c=ywFXAwOO!WVpO`yjN+ed8L_=Zx$BZDIFwT1ozkl+f-^HK1@+s z&E;Nn6W4F~H;=CzpJHymU-EekFn@pM7ZrV5EU+xu1Ae>F-w_<@Gpm`0wiXsR1<9Z6 z8tr~&#w@czc_2VQ#EnchVk_#4jWW>A#K;(#Rq_i%S;E=!pawWxKC`?p-zH8<&y&t$ z2HLt~K2q5a4Yo!*gN5cTbsn*ww@QYzUqVeMe|k(E^0kcn^4LoAy7895ya>*!0fvSw zC7v?R`m;7miCi2H+4A5dSEQ?E(rziOeBMnPjx)*?6{GNlf~Hfed%oaiB}V$A*9O!D z*c#kO;fR~%GWFUP-2C|cq`q4mwSg%bAEtc!IR!Kzqo!U)i|^lIS-w zDIuBUX#jW{TUb|B3Bg_~;)$$@#Im;$%#e7`m0Y1}11vL8d1A4W3Fc8*>DLo>y?x%{ z7g;-&X|X&Qk|~q&w-|8pMq|$FZGs#(J)NRlzQs3}hsI1r?Dlx2#wViP@_CK#m^;h- zSRUR8zs(bH(H)2+<~MSjTnvTpYqvqoj~ebocme|PHWVtK4SW3LtDELBHm`=?2 z-^%huOIoTEbuFW_JUAn*-;nHngP}kgNVlRlUaFs}a;KJo6ew}$dp#vak{w{#|XG% zweY&IqHQXy*5ZkF4@mK^$u2wyg?+Jdxw2V{`zfC%yz3w!#wES3okY6?FwUJjKjZ$h z#7kwtAUGt02n9vrRq}M7?7S0DcEO~Qy%@$4IF!#)Uu5dT z(jAr@t$}0QT(YhOLs>j)6sSNLC_#EwdhNrg=4Xv#Aj@Q5J`j444Jum(4F)XY(36|g zno&!(k$5R|n58GB9Z)pWdhg~Bm?bTv%A^FQGYs~+1f%VO++UhM&tO`7rJx=)40Vr; zX9hrJ6Vt=5&#Az<2rE$YVi<2P5`<)-Ki5r2_$D9J|}n;<5e){h%Q7D z&0K+o;SNYRs?&J&`DCSiEiaMbE?_Z(QelUnFyG#U8O)ZJ?g=E!(mB#d>D^h*%-5CU z($YOj0>VfuWJcm8xUgn{+O?P|t$Am7o+x=#PX5_?Lh}uws^dowdKpb?-pMM+o^0W4 zot!Y1z~gk-({UMNS#o>jJ7*L^6_?;5acrsk!@~X|d!RbnF3ve0Go+TV8C37#u}ni$ zqD})~6ws#qU8g0ZX?22m7>X^UT^)h+Gt`#-@JVH1TFz^*(=&;I&Xp^oCjc~H_1|i# zU^&)MzRt{%RNDCK$<^LG2TH>uap~kr;MDXG>x>e(a=B1G zT=-O3xJ!3KH0p$V;MndS@wqCW*6M`5Lo>rj?_DI2Xfm(+MrZ`Hre(Y4Nu@B>AJQ}i zL(KN>Ct~YRIxxr`S^xkNZzuF~|F*9)~V?=)Ud>N=sAy-IiG3 zF3A+dDE1x?syoLlYrZl%Q3V0Uh(f;?+kFK>U+VW1np4V*+V=LHOaYFPr7)b;$sWHRF+Qwl$#kDmn~_3;9Iu{ZAUhkKdk z2Lhv;Q`Q!}@D+32BL=QRlvQ`+g+XUa&{Tkakp8Zw6$;i$?JRvECyUSF{)TqEbr|y( z-Jm5DaU?#H9lR}9uxr8H!2jMJ%TUpMj7*&xBKhe<0?jbTiJ1$fuLM0t&bIf%2JXzy zdXJ$8MAc#$(8O_=Qbf1&;pzVY^QjNFs}-T8K&uRQQ3efiLJ{20l+kDlDCWUsn^hHO z1!0j$n9Jj%#JDBPd|zE*Rx_cH%$~#4j@czq6(aLtZRJP`q0bi^o6FjiDMM0@vIEA% z0N!B+R~8m>Vjvf$NOn#RvhcU<)3}GC%NWK)O|(kBB?Zhu^pZ49vZws9w|~o!(IcZU zq^cn^@}N92qvmql&|nPxSfe*1T8g5g3T_XaCOLlfwp^gi0(##fTRlc?9knlzSEN1_ z+dWCLJHG%#&ntf)%@FNRhwyn_CAcr&MK{WK?_}g!FG~JZb^=|5aNF)(;~D0%3Sp#W zw33$Lg=HN4@=hNEq*n+KR(pU7eo@#!7b;U(>}W7ccih$H3qXuGl&TIZ_dT6SJC@{L+ysW&Q5-bItuZATo{93-nv}de%mVbk z<+#ei-6@2>xuIb30aC^vIm+3Tv9@J=aTBun)2srHa4Zi?k#9!Zcl-zHj9(d1Y8iNa z9r;dNNQ12$;%)XNg0~ikc%Qkn$zoPt8Sc6atO&Mllzr`hIv33@zLcTLn3*D>vV%D9 z?WSESQ{V{D52dV&(7rVLP8HjiVaSo7W@c60SKL#E6yw0-O$V;1&%9j@*ix}kB#c5> zA`^}qFrr31@r6pKwyOvMiKQZivKk|Yto3ZTqShDRd?WKwMjj!O59kQdvchRBUD>-B zSGC;VDqUBIVUcpJ!Eq!%CJyi3kRi2}%g%AoPwzhMtFmO4 zB0zS9jHIG|gb?Ol?htP<-5a8tHtY^_|C)fCLPS#8WiYEgyDm`K&@z?-rvnf6Vt+3J zZkaSF+Ppq#YqF8~EMxU~I-I-+HUf}GBLQYEed1aM)Dm27NwFd$fY(V*Z$aB2ZMn)V zlo9@%&M94d6!0OzR;EtxkvBZ?1_nMRi-$Zn1^FW%vc&0 zZxcgB4~m%63caXR<*&r#dG&;ao(t#l?9$6dF_UGi3t4o2oPy_kryutV0GfYEn42(+ z$m(x_)o;7Pd4w^e3~+wj;17f|1Z!2osS4hV9XU#YQH)3q#=%~jxe&zq%!d4pKjPN} z?UVj~K3J@JclUM7Vhqscs27AHjA_5 zPi8Nj&v;QhUKqeY3NQSP;P$i<%3O{qzU|blNepM%LoaRYVE`k==W{u6N(T1Hzm=gw zUAcxD1f3pe=K6tg^~4^5Yy$&|HQ5~4Hg)>HnA==$fAu!wMG0%d0G7TXnX}~zsXr!< zv(MhAt|5vtBrRB(HvgnN?-V7l;IC&>!QV#AmCajBRV7_fh9nicAel0PVw>z>iOY;w z2R9KoS?J!(^Opoz&M3}T;$(|GULN1#wQBM4>>WMz#Y8Waky zg6HFYm~YNi>-I8|6r~_=5kCAEcAdlgd-Cnrw$z|HhYW z9l!wFarK@z^kGbmr{O>hiFOe>jJWxU2L1f+OBg0fj4Qc8It+D)fv{kW3jDyz@Z`~O5+?P448O*L!&_b(t1?4TOkTpBZUY7IyCh{cSZw{%qi$v` z%A6*#L@Y;#4a0&yEq&T{__VW^y}p^z+LS*-O0+P>L>UsgVU^9BtW&9~icLyjL^#<$ zL72o_igsGyyWg3iG8XTp?9+VldKpIgkW4QfgOR?O^}5!)TYM_947n`S!PJH=ck7D|=x1N{!qxtp_u6?b6Rb%mAB#kcJsT_ER08>3Zh zTEbg7e7I0gn(0BQdz#=<@9?c=tcr~xoRQclT>_njviBP}k})*-p|Pwy=tmN31+Wgc ztEN^_QF_jN){qWV0eNAH)N}vB6lTrt^eZzJ#WP4Ca^?~C8G=V3C^vfX(+z_@VW=px zgMb zK_E)03yBqUn7<|Oc>zW*33depd;giQu4cq2%8o!(KszPSl>n+Xz1d_2ielf9K=c+7 zg+}G`#1HAai#i6hXm@$MFzOTlf2x(Tik;C9xksV3zp!Au04j5HNqU5Q8K z8W$b@TRE2~3u|=+sgZ&!cg@h4^xsn=(doL}gRPZ!iDIKz^-VP>bMA`#!q{g$uf2!c-|}*&ty`) z1fc+k)K?dQ)5K0r4$@t4POC? zSbqZT!RIbkWwMF*Ue5X-S4@eR?YLY~e~N*y`0<8nd-{?n&#HRS?J`h53=t2mn6H-O zQM^_*@_s6%Y*~#_=h|gtOV+)*UiD4f1yQ_=^eo0;+f-g26H*I={iKl&kZRknU#wxI zD9)LY(E3~s6laEgR-5Jm?JbyyzwKtVes32=M{;+{g5ttJBAx~zj;!9e*DmHM%9v%I zj-q$mT4DqD>#LWhetjiMbefu-86`U{R#!G}mHNrKx=b zQ+s4U!efjTr85L`!yN@s9Ihvd{7Oc5mz+yp1?KlnhCIxemzQrQr3OIY9wFfeDBL+x zWry0fytsUXGWINOZ_H4EQj|O}bR!Ll64vsgxp*RB);AWcyb4AAG5xQTEDRP0<@*#y zu>fX=FnOD)MsvR9H84*1CA2%#?;eJY-iOR*7>rn)4Ggjenq8~@aO^?GisD$qg39UD z!m04E5f(Q~QdkQLgZH&p&M`$x2?EeacLoZ>bj2@;{i-s6q)4cL$?`tTeoWx0xDuc& za4M50-Sh(ZIV*oxE-Om>F4+xFWyItdIZo~ePS*Wbyu(aJaWE1`Y=ecx-3Av$96bvM zNP?w?^63Q`d11zO1A`c=hW4t~`*yI1 zZOZTD2*yZFL75x)9l~8#JUg8+qC~Y4Lq?=wb_U2f^?dZ;SH7z zLP!_}d_lUUTHtN%hMge>jIxG`9<`RAJJCx6daQ5FJWEUJDfbmpN#!Ab8_?tmAACA1QF3DDlX3sw~|UaY+3!hw#F zsWZwl{FCdh$ym{%#SEiA4qS$EQ_OdS40rgvei$Qlz&hChtswEtmwKownkeJVT#pVm z-`)&^Hs9aH-w5#n$=_!{EZ*sy$o!SzI`iZ$U?p>C2|CZUr7#^pdI@`mobc3hIf-PD;y)8 zUSt)?;2&~!q`b|LD>uH*aAgct3774n2-I)LHiEgyR9qmiN5cr6_+`rj`UPW9dW08D#r&py(zw7u?N)l(F|Fc{pu2&W0-? zh$dJFe)DJYD2Bi)x*T1Uaw$}JdG-E(Rc^41DOJ)APZ8|m%vDAv0EmO{K=FgscWq=2 z${)aCpgdHz8&m9oDbBoLVNQ@%jX8MPhIu#D*z*N0?MA$6dsxM0@#2Dpxrcg%w4Qb7 z_;;3t4Cc3tJycco2g`9Kd1$kes-OMgO2_UU$soUzru`;VaP>F-7Z|dPFtTDu0{mVA zQdwcv$w&J#U>UBVT)-iUT|#_0QE1Z#J9)Hc-4sSFV+JS}v6STm(eW+r90q|h z@GBQ8YyzT0Gm_a3QvA6<92RJ`gqa;47ln|=Y1*0H=J8@Sv$-gG#Wh@%-XYaIhGYpD z@FU3Yym4r!+A1DgIci=vA}P$j>b+HW!_yI+f3Zz7tnUvVmTF98>G)7$k?V9hgC%`& zL0p7vuY4h9ECZr$Cz)ZN|PsJqLXIcH~cLJ~+0?~m`D%l&SCJGQg4v$M0K#PL+% zi}U8wmGT*h6uE6g1~CW2>xaczb+WvO^#Yb3=Q;WLv6vALWr*d?KvBUX6e7?-nE739 zCypk;8(Db_fjCi!w43$IYmO^or*77SR4om0a`w`hKKABd=;jSsbzBUTiNIAT!M6o= z7CE(nB-Z6#S@<5dkbKoTt~uJ85G3R2uA8Qjbaak(y}+?)DTm5=8eW@H&DhRz;;s?YK2-6qX zyY}Wu7J{jfE(}n@-wo^syCXyi24gFJU)O1}a=Iu?Aku4AUOqNFLNv1Irs75yFNiU` z&}v`Er=gTQfdC{7?O@ENSEY$P%W?gMJv;6V5jTk8LZp-F(1kh83D?M^O^06YzYvz5 z(|ZE2*^_Vc17jEXJ8h+=7DKxzxL^iN%0s+y(r|fT#EtUsf$x}0KA(whBa3A^bW7Dg zoS`PbxBqcBH$qK!=NSgI4&m?+M(V9>M_4zJOq|v$GTSU5KqKCPlc~bpu^}Le^&EtW z2$u|SznkpuM{sGy@cMA6=56XtCoK0S^`j5u#yCc@u0wo`WOatb%gdK1_~~(4WCh3K zgD89oi@qQ{y9o3h$nZ=~+(G=MYm^UV7#BaM;m0A#O7iNUxK`!Smdh+$Jis`|H zG9Cz0;36_AXA5)0cqG|P^K=~v!pPw|+@{8Ra(oupIS7Z|&T#aa%7ig>fimd>-!TCL z_^D(|L$VA&oniYVTxm0!!2S30twO#TD{>}1QnD;`37s0hQP~Nh=c9L)fI>AC14{puHKbCurvL=1Cj~5N$<2|T1Z8^G(66E9ORbE8JIjV#pJ$-i;5+P9n@R*~ zWKm5`U;ALKM;M&=C_@Zk)dfXb{lQmkwfVBm0$6DN5F2hnni$%m2poXg!Exid5Ui14 zB{j8}{W7Gp9|OetKVN0#ngr-za%yv#f=g1zh3F=yA@~Jm_A6nKSq;bYWUZMB!)_dg zpwtt$Gf+IHQ~sPpv5`$Rmyrm7;X;=~M1=Ek z;V?3pEep7hsi3L!k_fA4Qj}1!0@p;qfY6`4X1glv0okkhP9EczG5Pa!Ry5N#frne- z^((tJagjm7KO}~^8-(}K5g7i zXa;g6vV^!49|iT*sCt8WNO{Ec7Lt}1NLz3JWG#W2H6+KL2CN~W9z5M;+$^;`h=QIJsn1kI#q)I%rOJHK(5Bi&3Ze{>s zr7tK!O<=L-NWrhEMPT47P#J3vauKhM5SB$;H- zzyib$eP~siSbhV`b<0M*&=8g+qSmL#Ndcw=QIWSILQE;#W|o98d23Bu9#KoS7WxQS zR&78L(t`aa(2TBuH2fB3=G*rQrlg~%^qjD^;jedtH}CwZC(jX@{IyfGCK}WeC1|E= ze(~2nU^1PPBl$^O8k!N_9O$T0szbPg8O0n7)`&k{(U~_QGl^nU4!M~emI2@Ehra$o zI;n0-4!B_lR`lG(b@-1_9E;$)H1rL3yC&_Hff%KzFtgjAK-s9=f5M2O@}?+q0msXV zKf47`wD|hqA{NM11fkxGyU)`@CI`g~3ws0?FG3>si7@Dc zyWTunt7M8`jCdupJ_M!%{{Aog_a%BP3?-sXP75R&o|ke4=O|8#ahrv+Sq9bHwaS_H3^f2z@DQ-<^!-&6U-BxgN zo@Z*D3lbgDe>a~XChbX}hWlrCb4F6o(N!VUj_(HI=pmE3Rd&Uzkle3UG`+g zj^`{>{tM+b1}5hW^O~vhLhpe`6x>GmMz{KxXJE>i;vMo-apFVdUqFHQ(+VJhIf_W0 zV<~UyrxNFN0R9{N?L7Ftez2M+`AZo4EsJq`F^=cKc$%=XBS~74t&C_mK~N)Ws}=HT zLaa*%Y~n>0U81dfd65WjFz2{Ef&AXRWGiDF&?5?7fPfZHIw(7423bHGCw_VtA=(^L z^OKr3xDuvpSl>?kU^QF_c*ax-!6Iy@jO9IJ7B8+X58NfnD0o{Y)m{KT_f?&e1ny`N zslhtIzET2+vwS~OJ@%5Sc;WV8vk612 z)LbrNX9o%Z>w9w;-*ZTcKaAVEuOd+m9odkex-68&Fe`b)w9-V7nIadjyCKjNCZ(wW zRHo!KzC$qZU#X=8?+rjXA@+wum;go(Zav4@sCZ(;V3|%0qZgQWDj@3r3Ycrq_6qz$ zK)Nb}m!Y(&JZdjyOX2YCSZggmr%&Iju++BBtN1YH{so0~ua&Jg2|Hb~L)gKk^#ZbWPkBdJ=+9LXq=Gq$5Zh=gWHlTlU4tmk3MOlDy$c9=wTxnybvVGuQ&@ zOxqm;38Ow>>TNVrYmVG`s;P3_N~Gxv7}klTUi|d0R(g1;a1O$MkGxHQ@8yeIs}f!1 z)52j(R6}Oe1AwP4Eei$GZw+|Dgqm8dR151PiYa176p926^r0U>XHnZ zIAcxYL7qw&2}O?2I3jBeL4759Ri8}c8RTcufD@0q5Dl#o(VPKo$e@~5gl1r!#?T-P zQoyN*Z*;V9KQoRU{`~;~27jH!(_RLVr7 zu&&HG)CD!PSYP`Nacd9`83PMYXd~EOluv3Wwwk^MDp$XoIg|h;xpULT#&=Qif-&31 z=_Z5Nw|bHPuad=IeeUo)f;CIqoLq?@5Oh5gYB3P% z==;Op$On~#>eM>`&(6lbsq%q9C1+=52mq~I86t`SNxj2DD@Q>b%eON3o9BjIA-S7Db&@9rBKkmeAn9=0{(yHawZHvuXNJvt{ zm@ctcVtmf%6e7ngkL5^@7#Nty2c}Kz?QrsR1Adxp5b_#RfNtXB@h<9dHBjfbqfa}c z&g?0mgK{h?LY)SMW^K3aGC`RY5D0RYc1x%50?$~gt%@p6XlXB%{)th+(AZi5(^Bor z57k;SSz#}`w{7Fs5tgKY!^q?mu<(E2g{LU?csn z(*U~iZO%pnYOt5on-82@-Llmi01zk|zE#f_R}hdv=x+$5=hy17-S!SJx(DyAsu7ME z1ofPf2rb)4FCU`HeB;NN&I|Gqm|1v&Ru2VN7ElD>xqKL3F#yqW;c7 zYG~eR|6)W<+N==}v}25o({ig1VN@yLs~Do0LSW`m#Xyn00FVt@)mi}Rvv;&D0j2G2 zh$@}K5&3i%U)}CmNCd^_MmpJ#{Yk(wDi#g14pm{InWqih_9dQv9+q<(rB|*6rgS`WkcE8() z@4V9X!vqVHmZwe@RTZW89wy4P)faeQ(6n6?AsJG3kI8NVe6w$hz$zz1%xetWWwzzim=As7J(@OtJI!$i~-zWJOQ1`w!$hzvpXY%3r0CKzTc zte~w&I0mbMA&z*QOC*ZCs+r)90PNY_Av`B%5ce6v;iFu!5Sqq?h3jLkVIz?5qwGS8w?B*moyO(XT5S{2KQGXkJ)0=^Y2FIxYqI*w3-tuwU}EFZA&RvTQ7?^+D@eVjR84Lk&wpbQa&#Hm7kKVyP^4OpcU zSMg+_X>-w=>$lm@zroKh+1qn#Gn`1ojbJW@bi8I#)&gLWvN1fn2*nE~#DZOGk%O~f z=%{L}8PD$O(1-M6a5loFVL1947zHs#z&G=7I~T&RF*!Os2cc1k4l0_>*KI(+^a?q7 zaA*)=asqTR;0Ei)_-_Lsy*FIs25V3kz(M#=S25y;!ng6uUc5!3q2DGQ=$Qk@IR-U(O7yaBI-r*d;>I!#Cpp989RgUsrd_uIrZM=>xnPzv3u_4&7e>$WPoEB7=vI`# zfzV15STu|^Q-@8=-M4^m{!F_L@~s@AJ=5_efcV_E z9S>iO&(~m~HxaZE(1<-9Y@w$BOuC*BkE~!ozX2$zZqQ(oNlExyf006?OD6_0SX@{% zEf#+3c295q5e>ovggCv4ssRmZAVlf!Q*D|$1A%De9pPKSZt@1gFpz*0!&jt1&kKi0 zfF54@F`R%5BUJInVi`E>o;6r9&2Hn_xeHg%(L&}kE5qJ2pUd7ILU<0TB`PJ1FExb6 zPLIX389XM;PQoZXwGF_Me0pXk@D>(A?>`uT2kyV=F5U-bFH$lYA%rJKX=AoHWc>2{cmFZ42GFgkv z=Y)hsh2w8W5se0+fl(TnQR(0cGYfbPX+@RUA=| zWhb!sczB^H+|3%MJD5peGvIjfrZ454gjX_@nuhgjV`pGE6GusUCT;^yJ83L3&-!%c~uR73Cq(Q|aqBO2Ikpx61@*k}SXnJWSJ| z0^gq{vb`o)gB9HfjZBgqU@b(5sX*Dskp&hLgpE#;TDXq@@2*Ejsa4Ri8x=}_RLCd( zcp!i@7?*<=wMrbK{*Q6L=#OxilYq*wSSS<@!AGXW>Rd0zPaF8I7`dEh&n2@cJv=Wk zQekB&RaB(y4uscplY-_FV-k_4hXn~G++OP~+jM{a36PlF=?Y(0?BQ?bD$;{>juW5B zR6=m3hfNhaMZn}LmaJk!Bui#%de|seBwLh$wTCE!w*{c);LZHr1Spwa`hd(RDlnsn z0;5n2)h5#KB%yqa7VgzWhuQhU7y5AE|9L?-CZRfr3VRNMOeW_V#7pWV?$pT7p^q@{ zQ?{QGfX^kZ`_XMS5l~qt<sM_X5QXQJ8(T zU=qQ)yO@pRdHPrqoZ+~^^q#QbvoV<}ye`b=j2;z3VKk&762A9O9^yp+sl;LeQxEDe zgTm}jz?C!H`XqeX(VaMOw@;Dl1gNf!dT#>&lH}kkp$lO zyVQNq1c6bPdk8anLoZ~F9zn$8Vj!s8zYax+Ac=c42@L5d@!c#J@Ob(y_BJv;r^EN- zoed8OwmJOG+@ncY0nJ2DILfe~#X8s?`%bv2Jd%MNm>8=uF=DTyGyS_xpr5zWk)?)6 z0sNO8&x*p0MB8Af7lyklf;cHuz|tnHBWCx)){N z(91K$`WT5uV#ev(iv%4S1BlBK?w?UC6X1LIdJ4by-4gz0(<5D6qE)HU3h!izq0?Pv z;j>_onf1oy8&ND5Sv*QXS!@RniZo9IL%({^kv9zVwSZ>svbFG%fpA$)*jXFg3-bc% zajXY`T|4tX1&E9G^2YKFk7*BL0?PsK>`xccI5EgwaW*1Oh8=a>8qV%&jRH)E~2kCkM9t$;zcNd=q z?Bt143+PHzL<-?#NOlpbDM0OSFmOJpNOCHi9$j%70jW4c(g0Fh$0}K6AZcR+r29EU zn)(1z@PX|rLb8B8+(e zRfiy9_`Ke9e$Zh7Rbi1rXY;y@szX3kDVw#PBqEp1q%_F zWb2k5i_vkh7@TUv8y2xFte6B;?oH|Zo&YV7Sy)tDjPM%JL68+%7-IE&(Uxa#EwC_H z)}ypq!Srjk7SSSs!m?`zTqVjZkeezf(mALe8ri{uB3L(&+tsr3#>xPLn{x@Gqfj%S zlNI>v{p`60!Kkp zyH#Nk3XkSLt{hp0pezu)RvV&J3f7NT=g=k!1BaIjwysNH7T8xph$=J`l~|Ky88KD7 zws6hcgk^!W)O`e``b>kjU`}c~f8(y+0!s~3LYs&L;r>ovpa*%p4QO5sj_}Qh1+tHe zjuD(;3r5&mfXe&*^iE>Y0&&FzL)%;w*KB57_~=XF2K&^6Wr-envExA-kw-6x|C+?P zI}(@$gu#PfQN59{-pnR&&2F$}!yv-4K#!2DM5N!$d))&hzsPEn3CRMgwBeCr(~gEr z^zT9R2fOxuNmv$8)kh`RJicp<_JD`vg`PZZMMxId0~|ODI-HqbgenHa1-JF(8w3k< z7#tU*Q3>8=ijxR-AHtIrUAK=Xx+Dpr^cD}Op@Dm!;&QtQ*h4lSgTS&CN#=(m76?Bs zIwVL4FWsw)Sl(b>&&zb+2M893tPeP7w27-FM5vD-vc5})bMI`y+>TPl#>0IzM7v&4 zM8oa^7p+S2T&X28Q$u}MbWl&h^qI^afXw_tM)8fABs5P?1w7%8c^AZN=_8Vu3ld># zD#T79wOU{!u&`-j1V#8T0xtuB1?H!O5ts!^jZD<`5{!3U1(C2GFyEDhv+yw30;NJP zD}k&LaA1$Nb{S0fd7A=1i7X4CAhZ)^XD&#uA=Es8iuCT+j-Vum3F)aosnJAhP!A~n z4PY6Fhbx?urg0$QSs;FGJYM2MutMwrXET7EEBKDrUszynco7s%Pz1QgZ2RC3aMo7E zdJt_E%!TNfVA#$HXbQ$DmN40D8vBdTEbx#@mC*KZGzbS}b`@6w_eIZTAcd%e&15p$ zYT+v;&U2D85t0S=s)FZ31^${;6RFAyPUuIx121i`z{p}?KaP3~Vq~=ivSwa6`9%g< zP`RZ@M64A|>{zfmV@6qi#^3@|lqGZ0GgzLAfK-lPYy4l}05s=uXL$BrRqn`PxsWk{ z2h1})cyFK^mHhza4~PdhkU5GoXKD4*Quz zzq2gyw8JG&4ln>%BsqizB@ZDwMdqXQRIEK~gH$w7bW26A)z@|4I&=~SWrF{@6{*NC_1`_IZ4%c(oLRd@c9NkcoGfjwe!p~2Qo%HG?>;yC()qb z_jd^3=|_2jZ?Di-E1{!k+@F*!NVLmy{NA{8(4(H#M)W!(o1d*l_%fY>@Zytkr+{u} z+}*nCX#{y3IAG9blmnDd|A-zpGluVhITWM#NdK7sF6PT7o?K!DqroR!=}u`nMEbbc6&XZvwz|C_QwV062Ni{VB); zD|sugE9@zu<$?ZNJGAnmhF5gIm7f@pXQ95B9@Jxj<`}YI6|Uy7i3j{`dGv$Vw8R%w z^#;KmD25LhfI7S#G>4dQkTr;}{v7sX;Ew>T+~}1p2wa|*a1%4TdgN2+fI8UKuzz(L zL8h8PMwo5a8$zn)!{>VOovgeeurC{C0gMiT-t4Ws5b^}vvDzN-eSlt5|X^d%wIS~(;I*m zGaO*Cx_!+I4j>%wm>gh3b$>1;d5ZyDIbdhadV|nnfQlYHbMQRkLY}E&Pg6a3si_WV z-~A!M%hW%GXPC!c%NIZXaIR8@z`APXx+cmzD|9&X*1kSsV;*9zSt1-drD%^ajFA}gf7S}zzzXcpKR zcy0(#QCe9GiC|@cv`*d2aqF<)Fgyr%t%y|-B#wmD0m~>dyn-DOW`Sj(DygWD8WuVM zTzvm=vk1-t`LpWFb*u_-G$6hGGn1!cERerS-25Y(*t`$f{0IhRb!DxB=(0rqLe3Py zx)l+@N&={R(V2e<%mSH325E&PHNv@Bi*QZAHvauN&D}N1X`O4j_Ak(Q8!Jxmabdc`*d z;U||Ultg4%U>Bn3g?Smk%)bZ-EMVo@;e=)ZlW;>=C=fz9A(I{eRaCj1pYK>gm>7a1 zo26Mr;CVnBH@+*+by`4JH0%cvsy^IOMZ!XWSDW93w#nd84V3^0mBOHo0aUN2=MxCZ z0?Wr_3g*5Mt{XE~?D+V4%ax%7X91I1*m5D%eH)QU1yEM8S8;Bw>8>D*tL>mW;u6THjuOyNyoW{$95RJz^TH(q({}ai?DS&3O9~kXtRpT5n?r09< z90@OxMZ#m(;KljBLv%`+;YMughW&R4-QGfUAMxgZ9(FE9`2oQCF|r4*JhD_IPEd`j z2aNZ{Q6$F!xX<_HvVrmCZ$04h%<%ic0Ezw9@ytfLfo&=!`oq;L4y<<}R|6y?=?eHx zNZc4i@=kYZiI2V8MykT_Iv1)gqN>2HZ39y`I&?tdIZ-iGtwNzzG4@sim z9ohCC4?EJ0yd|J9PzVf##e$2&s(jSLdmw6Yr%_5GGTo(5f=AV{_6+MJGT5zLQ^K-D z5xm@RC@Vo-3ZM(u_sm6LE*40So1WIr8x_p|M)gQHo&~Cv;Mw1FZCt`R2ZRBzXh6mP z1&%9bEoYRSz`lfyVm&a zRe6y+IBoj9A}%(Xo`YM$5*~>l*e@TS^7u_ce5fi!lZdT1;99WK4m!+6$H6-QTKqSm zsxWMi&|3w6*M@k3UW};(Obongb&-g#tQ%Y(4SNTL#6`x~3YWAr+6Rn#tP|+hb7XD; z@G%8|{ezs95K)9c6N5yLsGee906VDuh=EP5#?B!m#7QI_{#7JItvJC@&}*3Yw1$Oz z;#xo6(?jCi^l3FUlq<@Ijp33I3)doHHNp{Qu@C`YS=qDDo!^s}ER=>LNYNbQqC&*m z1_u~aI6y649Kq+OWK(Vkg`G3tozphann>*bEH)hY1d2xc3IU|oSHj&%!jgL}hDbsa zF}kQP{Lx;aj#CCCioG2)jJnQ%xps*CKZI#g?I=oy>4TCA%_S056o_-aZ1ao&B%!}4 zqL79V0Skea2Z}(EFei&lIl|YIG%3NCH>jiERWLLZ&r19rYQn=h`?L&D2-~m;qnmhz zK3W6+gl|)8`M*HohOiVK-cZ`mt>Pi#QqqyZ7?-TUBZsm>mfI*ImHzmeu5h=4LY_OI zfc;3>C<;C3(`-W@2*!2mw%=HSwp7Z{jti&; z)zSQ+T=5MM!TH`t_|8fa2Af5*P^*CwK|$c%TeRey@I5i1i!)Jco}7_1JM5t4qiF*t zijgs~_*gFVJ|mh>Ge3gYd7f2m#yz!Ulbr_50zCeYq}TrkMz9rp*Jm1Cn+TSqq|-z6 zhg**dF4m&{Swwqy1becCO1kO>g z*foeC&oIc|0Qq-rtrG-kfiU6KDz#D(MToFd*Mu;W65eyHL z%vlz`uS^tLq8vT@!S;fvoLT@_@1xIY0yG(Th-(IOhJqCR6!An}1Vh6ZtlZH#hY3v* z(xt~PZXXG*DGTYat7xs-+4!-fd7fGz4r?LsDS2EQu#p|uI?l@OOw%I)Ilk6no~*Y3 zxgE5GP(?-x58Oy5DvX=@5GGn&s(io3JtA21oTwmL;|^&Llk8*we)xQA6#}+k4l#O30wD}>1R#2L zs#A;*Es+58ol@i4%p`z$g?ue^j{>J@`R+_U7?9@;USOj1b6KkFL)zGQ5# z0zz9Y?BGJEX-ZRqWapn485$jxDEU{&|DOV)nQB2<7iY%u`kXY)UV>pK`DetlX7YRj z%vYGcqdJk9W_&Ec5MF|p{Ee7_%#b<)>pmS1@^gi1Fa}F=846UGj1F4ZUI}-{ClnU> zoDEou4kSJ%y3*7$+wwrH4SrJrX|mo*_OFnvmrT#g;G6k(DV~f z^pYsVy~1o>k|Rm!87hQey%c?5yC>`=kwi=@p`Tm=U!U?`ytjbiWXPGXka3V`RXcUVA#r8zTF0s1M!U|G}%x0_EgyfT1y zC*dqlrAu-!=`t!1h*G0{)&k&m>(shD&5`DUO~e^0G;3~Ac>RFJaUH(!Q!O~NH$KBy zwgkIapwOqm?{cp6=DT5c-S2?dkm%qzd;%Kwb@1*9YdvTS|29Ys^#MXJNS>~X`u%j@bO9DLfrB=E^TW)cirpzm{krV8?vva*^q-tkN+2BJ2 zD8eM6c`l}UYfyc;;=B1?!bLW|vr-$ZQN^U5=i_032Za@{i2?Zo00WS5R>p=$3rYNl zlA?sV!5+Nu-0uI;08X+w5C(vb)e&gU+1b{<1_)~A$gQ^s-Xml1tTiGGg@X)WTE+^L zEk!Qzif_M+4Z>T1+VOwF(RKhTPL0{{%`;|YC1R<~|7$eYURtH#-@sH(MSpoFIa3I5&1sl!hFwXXE?@+^$XfZ=`WDXk_pvN_4!CQVeBQfK@$RuGiX6 z17v6#YVCx40Wz?%kSHk3@%Gt$<2vDIkO6-op^F2&V!4@nz`!ft?t5r}49*g`i`pIG zr!WJ?z&gP;w++Et>}{oPN^9qe2~JG6;TWGlxoAua;k)$qDZamGoiSqqiwac5hSSzS z%hnA%W878T`2ukw-=_;E3yql}%8wP6+Zjw)DiGF)z>&*n2+xc$LPOxSGnCf?HcS)@ zleOvSxim}$Cm#(Hs8NYW(2LkOyP`+a_%pbgzjNm8 zr!uGlAxEkb&hwu!6-ic82P_M&Fo{Y{V5DI{h}3*9|Heh z13Rdp&92^%)X&xphOXDYP(e~w2BpbjJ7nr&LD7}92WRsm(2M7aGU)D|qH$Y<1H5Dg zf$l!f&w>9E3;r3D8tgQq;m8n=nN`8M7%UlJ(KRoIZ=Lqzchhhg43gmZkJYFkPe&O~ z`2Jmz)p;JaWKb=RP`t&$un#zWfDOzw#{3!Cp4iBsS{&jaRuz^oIwu+o166%~Gcyg~ zm@&2ZD}@Z?WOnXZ0t8fdcqx7=Dc|8sVtL7)!GYMvI_#j4bT(^$vMAFqbyR=wXs zh?1<4d5EM}lzAT(9iUU~=Y3!%f!fP8d5N6cln~|o92Td5_-JGGTj>+b@&%2-m~rl7 zp5Z~((I<&c$&tQ3RxITG;k833odBmRWC|#R+zhNC)Li&xdQxB@K}qsG=|TBwm2n}_ z1h|0#PKR&r_h&{Cpd>?>9w0*bF=A0Iv%sezjZo4v;;- z7jA{nvId()$AIs=@$-1{)#NNPMJqnJAs}LRew6GHK%7vxDEE$%G_!eK zI_nRB#RYLY0e-;h3p^iXF|9s9Sn?#$N)OcS0L-SBR<0mgEjIi%m^e2ud@q-%8U$&v z;kS=giBpLT9|gKA$KPl|wAi4WqSY)Di@J|w^2D-_s#&LQC0L8SiQuWUVig$z)BW%3 zK<}rGPe&54#X=PtP6sPLYv%VJE(3&Ww5fJw^~POJW9D<{aN6Wt$sF|i_} z^AKQ`Sar1(VLHLzW-fiY096bD0a+*pL1mqitbjwWcH2SlCbw~h4oeE}1$joOHPNlG zcFZaK268{Ncl${AuJXuFRnLPQ&x6a z*hu+61=EI2;0{oxVHz3XUpB%YkkL91@AMPB>F|PHR#(cF+Vo5BOUXpilBoqOj}q)Y ziP=3?m0lZbb&zn~>7z%2j7!8X&;phmrG?FM}50HUQYd(B0kG{>%j!Z@&#(}+};U*(1EUG4v z;vJ7e<%nZR{fU0eWE>KEXySZ$!YG^p-^5PaqX^Dq{xq^s?7~(YedaV10o28^4$SjuW*gl!12L6JXuNknD7e3Vne53J!A>9KAR*}GFjG?GgI(l6V_UIbD$Z4mh7 z!dl`li^9=?eN7eLK2%Kmmw53M2d>Ey>J4Dvf}K^TY2cDZf6iFG$Yxx2aCm-SD|MhB zNs6gNmSF%};gtY*T^p`{`-aCr*at;{c*BV_yO~`~myzxd&&kta-K^PHOvGyohEQ+%Tz+CHah7J_wO2uvd9)tqdtf>up+`VNkHgz8B|`1Z z))*2Biw>R=rg|f~7{gu*SoS{O8xyu<%9}wn>Zd=55veQ*h`Wc+=4lnlDwYnCW1LD2 zx{)jsJBx~#{lT4-_FLAHs54*ZY);w1(F77X9oR%7d%?F=wJ{L{C`neNL!^*R!kb~v z(9}ybqz`Peeg+BjjJ2vupk8L{hl2`skN&TXGTtEyUqgoDanv3Xf1Wwjn;_vG8xQk7 zJ}oUDCm=dP83hrDQX#aqI?PNLbHib$YVg}C%`8VovEUMg1kYv0g%!kG%IF>-w>KMB z&l}+JvvBH$*#G<|YBC6%^6DG@JdG=fAkxt!^SXj4giKZrR*cmLq~L0;>j`4MaWF{l zICZQF$~lqF3T#<81hS~oe+7?!%{O5N>I<$L0UKA%BAD<|P)@P3tDljz$pLJHfG=VD zf^)*rF;k{WuL>6+K#M1?q&U+jx2T;cSPx!>Tk)mad|$|%0bB(R#s`xr%A;FQcruc= zZy~-Jm26YeQ9A((Qw#EA;zMq)ARUH6_zAdPUE=sPQ_FGjww@N+&xxDRw`@Ud2Dl|U zyI&&VI-5NOh+bjS0EIse(Dnz*FvN2DVQdj=0KVrzF7+$Iw_JIGJjB!!3xD1L?Rj>% za&KV%)FPU}bVUbKo#pZHM&Og4zr%@4^BoL9y0YdV0;Sf>BPIgun8uCx0jcF$6Fe{; zX9~Cz8M(F~ftV&woQPaW`q@k?qpCTaXhYbfa01kW`#!%TRLkY+5)~II5bD8dW3W7{ z=@q9Cf;4|gNG(?{RfOQTYBJHT0nrBLy`vyx%MD8~T!GrP%oqN~$*{Q3cEXhGfXoyt z%mV=tgKeC)jRCPu!+B>2(Q-x6^g>0oVZ2WS)c84NIGvWGqN1E2SA?ir8R`I}^9aY#HN?-71m}G?v*F zcK2yi;0aI3Nb2a)u~cKa!b1oxbcZs~S$?+qU>|{cyZ@U`I?LE_SP&c?rA{;$&XIAK z0Nh2ME%p2VGaS?ds8zwLSfAing<2}i-fY?j0CzWcb>Ld_$&jHq#KNW7ptxA2bf_+D zsM;`8n=g?si8J2}4dtzf4GuSu;$@H?PBe^emy~P45BxL67J3YWp-uohI288edwPH7 zWMV60cJCTxFwiN+Qws33x5T@vG*HIov{N*+d532Od&Mc#21CX(w|F0h9)IJ63k}&d zLn>pZ40ML1jKLT(S|hcU8f@Fv5C|AtH9YjBRk?))uA z+?c;?is8OA^HDld#I%Fma8vu8@q}q-wuFR?4U*^2s`Ac(K^?xd#g?GVUx9HmDkW|> z3l`OYIJPIn`%m> z#a!eqz-v=GcWolr{4h?6kN*tBM`3xA-Z1aj8jam3zkKcxOv9M(@}Ub&U@)8RVS@LpKX|%CuHTv8vVccMean&mq_{9f#N-@NDW%!POZ{4$4Pm-3S zEGE#Rq=#wcY($gF1Ww(vV8;@KZh?*JObTrX&+5@emjQWi7uNA7K=W4#Hq*tf5kmH= zbSV*YB-s7>EA>_rrun-|G#H*GMl#oz6Cuk3WUU{Ed01*br09?(8C|-bJq0B2rmG#F zJ(5$pbi6212|aUTgo_oML4YX8f$&|O=cBQbV{_nmEa|3+juVtxt}aW` zv`ppLii4el_h*3EzURuyz;y&@{>g~IxR9q{#f$;0NGczL&I!;9{~c{Z(B>ys1ZXO} zC0Od-Q~+R8!ds6ZSj$!r-Cb8J@bIXs*?_mZlygDCGvAs7GKFB=K_euXdos?kfx+~3 z3wStVeh4QD6MXm+9`Zns)|n^YzT z)e+8tA_Vz<12mLx4n&>Zh39GIpz3_zFNXRTY=2zq2o2FHN4EOub!uXUtyWII&OMHh z%|B<5*aO^=P(`ugu|JIDT<~q({OfrFmSeq`02HK4bxMT*wDPZVJSKMqxtJfC!Z8?3 z3CdUM9|t^SUS=MAcLXKBC9-Ad56>P2y8mT6<>KHTis#zTx9D<&;X-wbKbVBkD88+A znMb6WzoF2n4gx}jTAe5mn~6=gEP%iFYhQQ5m17cmhYcMe`op>L9)RZ_o5J%wa;#YY z?hMx3ATrjkb$ZmYCXsC6$|%@Hn`j|^X~`gyLAvEy>$>|{7!wGPs4@@nl9MYFZYhAD z@BEry_?2S`UWy>4(8a@>ftCQ!M~nVDMC{9<)jwVZxjMlR92hJXB>aBAp(w%1(U<=5 zP-eFJ3hB5Xt@)J)Ii@Q_oN%0hD+Tpq!d*gGhMs{(C6#7B}A?qhrnj;M+S+u z6&g3xlOW|-fuuzTxh@PCwg;Ty#wEuJpc#V_9-|aQ+m$IS0&u&AOyZ)IgBt+NyfK51 zl3l~ZfK_nEnvD-4F~~6l0aT#&5JPPWsIl6ie4{K!xNhp`Ab48_iKQuq*AX0NEa8q( z4zapZ*d>fuO!#bhmck1a<%m|S&Jd%UP7D{_c=;bCd6vY&O@tWdvWRIb!^GDN95z25 zNb0h1*DPQP@xG*Y1xtlvTZ!4dlD-qP9CE>O1%*9lR}|+yFITeurOt+y9L{a@!lK0HDXU6Al@yU z9JtrA!y`ArjNdXUF>GCRH`f~iwb0`UpdL}d>bMYbdTeAyfE%x?l^nE$YvJBrz=a;c z(3{F9#?Da{8?VwTeT1N}G^2bAP`>MAZ=T?HgTF2CsYXX|5U-q5l)-PU7n4uy3 z!&RZNeyXr=;m(S5h~vxqa^uU;5KK-T#3Mx-Vk-#d+JXQ0;$xo?K{>00XI?RNgGRmy zu(m7(D7Jf;I!|h2;Ctgl>4eh@P8QiMgV&p*OXhneMZ2& z#b&Hhu@pMnXgm>~A#C61cyzB)>`No2^1WMzu%Sd-ij6WEGH}#4qT3}SG7-yN#MlG_ zE3_A!w4obs@q_XUTwZ$`KlhRWhZ(r6Tk(33$p6G~ygxMjF9x@mI)ws3V5fZ>XG zECdorFRdGxZ%?QZV@1g&!~pT=u+G2`c}vXrL(0w25Nv&BLtqIJ4sjNS z_!QhA|NmtO#sv!jhzlH|=g8rWXow7{!XIySXv0-Pf}NN+4kL_78Njp48IqHJs1z+x zMWb&{n}`X^Dvg9%35X<_J-I=S4Q^KYXoPA^oEY(Jm~+61zwIsVE99(!e3&-Tv3vk@ zLa1-F+9kW1rw|_*Qiixq0=q>PNExSLg!>JB8`B6GQigs|Nkibg*z@5&-`!+v2=R$0 zHZMru$VF>KRetzt4}V)4;B!T=xU~n<0dCG-UfIPjyIQyh7M2>0&1?xZfH@J;alpPm zhe!jKRgtB#RWKqwHOI0+&I1$EJ+oAyLF5GAoCzL$Yq!$E101@3ZGU`{lWGqp^ScX|MhE?EKc-iNJ6YhroMzu!3fFGw|N0v zhMXHywbZ%c^t#{R4c~!ghy%S{7U>vebmd?-UaoFo%2sgC9`M4HevJ^8Safh$DEF{S zKoxGL3%eGqe?UKp88q92y~~q6<vFomY4F$*$e51Qb$_J+5YP{Q~)&+yEl&xv9Okd38*0CZK1 zj;B;Dg7?fJg5%+0&XKZ@!UdxcYB2@%X&K2_iMeiX)!am~tXwU;-YbB@O?N>Kc;w(v zc~VURm9v!(P--v&MhVcG$I`8KKs&%mPy{ zT^khSS!p@>1~Iknrwm2mHe}@D>Dy-OjmL>xCmHihS53@HK3uPp`(0vC1nQ@Eyta)RTScE6vq#SjO7M}>p$Yls2r<+!z>l`aUyF&ie3FW z%p!^%WQbX466>sr(z>a^_AuT(kKOog5VAhwCQKP!h77cz{K6==Y|whzKti99Jsh3095^ z0YA-FZmFNv^l{<^`f?5o&_0mT6+j{C?=_f4x6E~#W`t59^DY>iBU2cLS26- zhLnGJG`6Kf^wW&y_h8idqKY~a%@*oWEOD{(f+ht)BJf`3f;@oy|6(IY6R?GPWWe4E zIMESIThfw?lBbwr_^h?sN<&zf4>KG>|3=OLCZZEC%+oRTE7LF;=0pgv1%IVsL*UWN z=t{qM)1%h#w*}i{Lr!R$ER52Zje^G_$C`ebNsL&Sb~79WUYHfi7U@I*lb?yrmrkJp zEX-ON4&ba3jFr-QnDYO+VevV_&oBejB9|CA3Ii0+G=;C58CJEfR+&_dnY{;d5<3tN z7W#T7BNs?X#=!I8IF;#wFFq~PIcsx%Lg)@7S#CsxEQX$N>seT?sjLyB&=v*i4J7L2 zxeI5VyU3YcT&NxaaNZiPHWIMCEaG+U0tK^#+5HQ^I;E}FlCb3wEs4dep+i$*TWC{*w}AyanaUQF zAAnY^)8oU0ZsAQ^tUyQUVxbO(7z5$kZ(F6m1Xno?VuMss_$ZgyG`LR*5x_TRrc6Z# zH<(KR<JiY!xdu7Z3{P^03BW*?_g5 z_K$c0aV_)cN%T^P{fmRFM5?e;T`6U~14LCdGjN!Q5(noIG4MHz1x^F_af+F;X;eG0 z{aq2^woRr?JEvOYv^rW5 z(mfPvz6G(KwH1GT1*UG#E)}cNuWK9mbwUVeCCoNlA-0@mecz#ALO`Pany+9wU2hu7 zkIyB&eK;M)kiugio(WY6m*3}u1Hn186+G3uXX<343Wz1wofa0xxnf1+$bur!4gh>t zrA;>il5{vo3#fy@G3&97le;ODIwN5<3qbj1_K|$Gl4#x(9-E@o8EcqMuXwo`r^wp~ zYfz06p$JpjVpnIV?$+9d@MlxSuKVKuzdieZ!;5{83&7J$B=u=|k<>>paoUC#Do3n) z{T~LR5pkS>;HFR{y@8=?Z$I#!mjVBWao`OUbr3Kq9dB4(alAqx$QbwOJRK-;*IWf@ z36SZ-hUp^)$p7@I2)|ll6fRh!6a+7vis0MgKB1v-6sLt&GSCnhr7@hXt@nWY>&c$2 zNa{6=NrT}0l;CoMu^|mZ8P8CT!>_t0wXZ}d71BltQzjq{ADDZp1T1y<-QS9S4Zg-; z_QVhkkk$~UURi5VyjWj&&1NTeCpFXhvDmRgt=4fci*axUe&+G_G`~hH>CUCo645{~ zMkRZVA-)7eXB$WEbR~}41fq^AH-?c67#~s+y-6}@YR4DI)9d2Zh?&eyKpQ@3Kkw~R z+LVZxsg-8G!_%qou*-bvS2glGtuhj3yk;*yNC1Vd5|ajNFal<9lQv-gA}Nh%q`5q7N_ zg(@o64(d2!Rl2_s6q|h`=nPn%J8%Ayg+PrtYzc8X5V0YTfRA`zz%wJ>rW~0vWxMv| z2;oUW()5VLi=_@~wQdN+b%E)DxXnQk<>&exC0t1iZ4NFxM1#%2w6UDvU1NFyFLhimKm5|sHg>PQoe5W6QM^E=rzx(0as7kkewh5 z5t}q3$Vz6)7-(8&J)y@g3L;knkIAeq)tpwtFLOACzouUrgR3)_|sfsFk^;0lJe?Mow9H1b0p_d$FB&%2}a)VZ<>(wp2GQo;{>xjU8Y zGPwA~i>eSeiqbmVQXDxu#EeH9cqY=wHffCvMX7=Zqxb&s&9N{47N|!*HYPAmb}%+7 ziBt$>xo#uHX7-;T6q_3Vk>5v8bMzRkrap50351066&zVH>Iyd-Xt(v&N;+y|w1_ zytKf@`uC1*r8c;YEf(_ekkj&;^Z$LBug18Xt0rl_T>H01^}Lb8YOdD4va7t|&Fs@| zjW%?#4s}_0JZfaN$0Lpu&2}KN%P!vn`F>Pwe5pu|?nO>6|NP-!r(XMO_Ra24{q>#* z*O>O@0@qXxTDh+0e@!wm76-01u5otf*L7(Q-hDJPeMxzE=vpV#9|*ODU_Mz~&6cOQ~D)P7M|{Daf= z-b`3Cy;=8FfeR}6Y`0qCdEn!SY!6B_++Fmux5J!s8;V_u47xwxwM6-HE}K62pS6FV zG)#SWTG3m1tKY~mw$M3`oOYu|?^)Wx=2V*!Yxmsie^>8gQxFz_Twq zjXCf6aC9r*y%#$+FWVv4v<=fW)~X*9%k_QqBv)+Z#(o>^hD_}Fsz``a_Z=%AUmY=| zo^ScOGq1T+UH4Du#Luk{$CfH|_~*kd17<2(G;W&|FlBJ5DVL6oE3B;AIC5M{ft?2{ zxs|*$@t>(}-@9)r(qqTZVs!@oJy&PuNvogRevY5^deW;pE4tpVVteS)lf+j^)0+F* z+!*CP+pAsHSxvfi{BNkw`#D8>u4?)q>5|_^_j!LD@(&B&bFM+zz=z${MUs0|i5Tns z^LVLM&m1cbIu_8dWF^(9lzT(nOE<5ut#ne-jjauwJ`J#%^>u#ZQ|kV=hi@wIvuMbF z6&eqH;u_esfwzCHp?3o2htUU z?TT49?engf}pyU0^Ec*QSR*#ttFXjKZIv`J+ zddW1uw?9{2j2}9E$Gof7(|){8S$=Be&f)F{e|DUBR5R7H#MZ*i`dps0FGSBso z4!&Qstjv#ZUe~jp-O%pdhL@A)yer!I`uMyl9g0^Bi7CB&_3?pu-jm>N>zuN_P~M#F#%^62+J%9O**%K!(s;vyZm}6d#hf}7IqG^ z@(wK&aJ8DMVl%sqFZPDV?LE6J=bV^XEA#kGtN1S2>S&gwJ^mdwGt{@#j}aF~lztwR z^~R8bjoX#V5uUG9=DvYZeJ0v2Tezry@n`>?diAW}kRsD&HhERI?C_P=<-GDN{NU@i zF*#dgu7CjR(Gg|amD#!zK#J|J(!>WUI~ z9ygr0{Ltb*%g64!bm9G$Y8z@~Kjt!Y*Ql2#n$3>f>$UyS>v?PEHCo?og7xUPIfJ*w zw^+aKPpRuGtjkSWSMAt)MaOy`ilQmIAARf;QUA`sVOSn> zb~j%9=a%ALqTZszjzTst|d?cDrl7yWtu zXWqiUvKDTp? zEm7oeg!R#@m#_Cc5PvSt=H$5crORiVU)0{_seRM=POoN%v<{qU-*ZK#1KGwpJ|8y6 zJJ`BP$b(tuRI5)GP(+3YG-wcdGRGVHI|==cCp38S`f4`Y>%*(B3^{R3wR`iV10z=0 zKB@H2a|f$31-~j`R&E!5)uHvN zJ#Fe#IP-d6?W;wy zd9LZbk}Ds!h#fg_YuOD4U5osPTHY!1!2E4GRG97lxOG5>-=UAJlf0(pjz3l6*$v;r zo^?vUe$#sB!H0F1Ejqha+d@;Uai1oK1{E$nUR68WQBA$FU91kYh&aFNOya(Ce=DE1 z+dH+p=b<`QNACTsUc3LwtlM`siBtZ`?C|bLqxof2@!Ll%^Vs8|ytTa1!ruKSj+wo2 zMzJ@MXPYGZAAdcxcC3|0`-K;3xpioHtzG-*vR8V~UiR|)=T}v{pIvgXDtvQxr8*V1 z4c(mUewH_H4(#2X+de41RFP!&xlx+C`LvY-Z)+;gIdiLDeO2Jus~gT-n{+M9V3lpc z*ouGH{-J|j3K|F7T=|5k{yZEbfk>#pL> ze>QvWP&CM{(1iOQo4m@$y!6+oY8A;+BD`|%h}wr+<^A^bQZJuFJK_#h8+9x1+#$Q) zr|hshv?Hn0`(1e|L|1Ba;BM2nPn+xXS>1YSzX`vS?{=-T)Y`sQ;)#zNciW7ZJ;`BA zU;nbsPBn9XeA9YYo|-4JZ%tMmf6{s7o^`&RT!Ig7-G6&^zB~1c)Uf**+^Cq_%a^%4 zZhpxz)+ed$h1Q4c3-vn}SgXR`Y^{49s2W_jb3y-A+m^M9~iYm|%XYngLDT3R)YtTZNz=g+xkRPS03jjuO%-^M{LlgDNK;Xc2Z`thX; z!*V=6H2C+vNX_{hldjIn`uCsSO=Bj1`#E~`>K&P@+SPdVC~17&znRts-if-^&}!dT z{|)wSPYiw8d;hw*M~+d z4!-AKMUlTta(9P!*2l-TuNgG=Xr)mr{X&v&{#fkt&Tf+0sX?KoD?>9UR`+@MV(kZy`BU{Si zG1q=qe!jkZamTuQKi?`d*>+vEheh%x&Zsha;?3we`ERv4Rer>**4;)`xp#Zsp>0YHgXdSLzbF)4J@bzEZl%wZ8tOHv{^oUo_7y@#e2W^OD3?;9aiyTTpI*$Zce=5C zhxr$#xHvYy*kZ_^sppzy8KL#Q{O8B1$GsE!-l@2KNVblitt+3&74@s?pO&*5ju^9~ ztJ?dq-JZ!=JJ&hTH!8Hr>HnORrAAGSNDk=M|HHQ2Uccu$R>?o2%eL`119r@E>G}O~ ziQD@QY&k!xVm{kfS8KFX6uSA z4)0uVt2(}n=bimc+t=xm;+FHwkt!b)F`u6_jUJN!=glS|f4~1+zo%?1w|f1*F8Fl7 zb5Fg}Ez5VVJgi&wzdrBwFI?Wn!A4u~->fV5229gjTzPlC!_F^fT|V{;t5NfMk@}BR zDX-Q!F8N#W&YOV0DQ!OIKYXG^^A|sw2i^>7u`2)OE^}6|IvUl)r%iP4@ekKKeLPaK z{+ask=a+hRsYUgd#k^e)_tB2+aDT^>q?QXCyq)7-xozjAipVIxSE>io!__mZpQ{}( zyHexr%D}eSJ*VZ!?Dwr?;N82QdekfFUHa{U|!M_s&r`aYx9T59|OZBAQ>z1GIRUp2VU(DRzZSIW! zK6`nuxpA|9J$R51JgxA`7NdUmYWcU$+|$o;C4F6XtN*2~1><@Z`!M%fTuh;_U!R?8 z9PB-PY2cj1QcV_b8~MCQ{x7cHf8P76JUV_JZ`ZC?ey3AI-p(5J#b?UmF*$vri@Xbc z-1W~c|Hfy6SGRqB{8;^?E9;CnZ#VbY+leLJ_Rc!drFMga%lobK#EtoKtx3860Xaa% zzr9&$eT6Om^I^}~qz-REJL&@qcddT`m6P9V!Y~lW-}_hOL9~Lx278*D!wH)*1~PoJ zA~8MNu$XrF(K&|y`;u1I)CFaIYA=`X=k9ygyN5i@gMi~L$Q%c^?5uTC5eCpt3!A$ zZ~;Qrc^l1%q8UfT-am;YpjKp)tO{%(8_F?5gkdR>TUf>zROxh^voa?C;(K+0!FPbn zlM7my1Jn|=m_+wzu^d$Add(G!4Zegrcq%_rBwEZF0fm{ z_FiP^NeQD|`5{%xe}!=zhmMC04=k?8Xnex+AQnqwmwWXB(jkUZ`J~x~n%TvuMeK4p zksgvtP@WhlNc9R*;soko3M`+)?n0%uM>_(S>LE1|wf0&6%q}>xvK$e%qxKKUX_xyOGcgy9fJVz$NppO}q1orGhw zP9k3r8X^3I{c&|^?FW=&bCJe4GS|`{E`B0haAS9a1&(&cBH=E>oF>dS9mEW8+|U#L zwzffiw<#pG(GV8*V&XoMXhk2nyA#8~z~xCCQzlG7!LS#)5UA_Z2%@d!AQ%bt5(sdC z-rw9`&A!~-|L)GOK40En!q0KX4?ND%7tBHuc|qsz4uU6UWakMCK0HBu)O{o?0hu*z zZ86zwMe`mP;#<0tECX06!~2`GY4^|wkw_>Z?OQ(DA)2IRu&vqM$FHAm=T>i=%aAp) zowXN0dPkEjr*egzeZ}Gt;T#K_<6u1+rLv|`y-6Y>-V-1v zhlIG@XfbtPwuYu7^md?y_ifl*D^T0O(U6zW`FL}C^&&P};{b3{!Nbr=h5svf(zF1d zADZw08M0lGw}*+)n+V)m{Ax&ZAZzf z0X55UEM>o8aG2Rj`Cv0L}etoZ&0BCPoH5um6QPHMcc&Rp{K@!TA|HtNKkjk0v((#$E)w1_K zk&~+M@~^to?4%dS)YjMq`ztAo(|FHhF8NEE%l|HiSMk~GV<5xSW6b+z9u!Elra1}V zw7B}2(eN`IJTV7ehoFqqq^%wt)DTgh6{Y|DjE*YEzDb`G$6-^)iOx=@*+6f6mlIx@ zdI9CM_oV9=4hkwsy9WPh9o~bw%WMmYBUgNAo(wHGs!#D74?Uv|!!b%JW*roQX-vCE zU7v=b7p>YuYCehNmx|WoNE^c473Mx8v7n5{*ay}uhG_B*Ev%TtSPd-;fIhFSm6+U( zbrztU2V!TboSH6RL7L&GDyHo3!jiKSZdu&LjDe&jJvxFV_`%@mQRzBnXTD#Zch)2I z+mx;2E7&9-eZ4$j>%6>G+yaY0YbQGuN=BM}=R2)e?{3>R5dW^HAPTRN0Htu202}Js z#+rC>(WK5CcN-8Jf-IkHqO_!ul%1gP8|^9fWFz&*A}J+t(aK+}es?@l@A}C) zv8*WA084@p&@kQ1)7AU<;rz5c{RmqeT@9yW_$S2_HW2ZPHH2S)b}TDpfRD3BSXRxiX*->rJVXnuxOA{NFXo+5DNrM z}Pex{Wn3J#-`5H|zPS(Xo8B1g(mx7#f=U1eE5Em4(hsspcDmjV2jq$>8a0DUEZn743L~Gz^B~av> zhs+k~m+RTCDjT9X61^`ngIZsdFvSLXro!3C*^*<4YCEE?TSB#DgQJja}tMN z%%&}0H@Uy>>*7{IVTm7PkBSpSM3sAyan6v^uq}ckxQ1v|%r7m!vU74QS4d7fdLR-A0Jw7UD<^@35fgF5)ICgr#!ZaT@G2Vi|hjgnnU!!Q(v@AE4n?4kt+3SK##pr94;Ms_(UZ9HucOOu+9 zGLZgv*REUBuVJoA^5#6}>B+-Wkrjl9CkC(t+Sp+|pWf40N`AdPZN{k9#a!J87fHvB`xJJtX5A6D!=~2w$Hr*r|%<&OiB#eeP zTRdpT_utCHaaZIIb&x%4!$1s%_xlwZGT1|*flhTlAfatSw$1@_KFhI)th?Ta36%Wz zIx$J1skKPxmEK3_>D9GPDbXDUrwzg?byeCe?BQX4zujO@^0C@};#VS%9ZFobX!hs} zrBXm>l9)(V01pu*3{}Zi*H{;-A<+zmbqKHyZx{VNaYM{l19gtimM8t%R^JMSaGrV{ z5oUfw*H?pL)KkS|e)(l-tc2*3iP?}ycK@vA=hYE#idk$a+`Zh)!Sf{dI6)`r?H;on z9-*?9|A0Kkb&%U?!axv)@AoP8!eRopw9qTl16I;%4;mtP6$o*i zn8nTRc4s#fitlbrs)E*GVBqJQ`Df_LL8pZLse>pV3HY>)u99pm^;ZVSCXPOT-rP!IhUEBOk{|3j8UQI zV6Y}$oyfTmu%x`yqVe1T2bug5T1P#m$}G^1X?I&5Cj>3v7J{>*ahftE(Hh6BR~If< z|MmJ!Z)>rOjx9J|Kb?d0@v`;i!U9}YY7L%G`*fj{II%KdosqQtTECB(<(P9o@9$+^ z_iB05WVc6?XcVPigZF8i&ck>%o=*1Kcygx2;b%CQg?rWST|Ud-U$7xt70a;-!Y~j3 z(EYyRM!P9ESwT?Hid*MGY2w8mO)fFb6+!%W_3dweNMiz!A<&!#OW0^I`Z&hdcC+qo zw6I*7uBE4Pra)3oBiV^^4Zu<;yZ^b)XZ4P~r#rj>f07!{7M+SM90mKHWr-85D%_!o z(bw>SFP&FyZ`w!@{?4!1N?dH~ih`lnJB8zVgjfw~8hSt?okC?T&Kj&5+u2<^MA!WH z8^1uj>z5Gw3ub3_=9$@4)10k#Ob47aOreeUl*=-uTP5;@-I%jJwb zgbpQJ;_>l@SU(6Iv|1q}$PPV=JHCf(#xWmAYuSrQb`IlAlCB9SUL-9#+rahjiL@H{ z%-NX^!<;|KB1+cg7ISM;CU^*aE*oPwaLh-?yZ0ZN8F9VaHCY%0K9%9KfI1$x>|s>jG@W2^>*FHOwL4zU$NZ{)_~XAH=C=0>;CEm}UZ(h89<~ zL?cak{IvPTVV%FS{eUE^JXJx;d(<}P#tcy1DVVM!<-0a`*~m7;b#HSqWTwEE?W{ve z4l*^rEmg{8CATwfsmVbS@07e04drtpSsLd=dkD{^33AwrSE6d@46S_Ml`=&M52;rY zK%Hf1?pzwcFzlW20aVjAF6@FhP zT;>Esr;4WKiY0y=8>u=P4u`o}sb)^!OMRfp;Bx6)LL(8;^mD8Dw%4|?%fu4NdiId; zlCp>J3(7V6$Yidd%i6k|hU`;7hRpyt*)s;E9UKq2{crm#(l(*w5o^sW(mCU)) z24{~BPdcD5G2?UAB=iRkC}OuT#M{x&3Y96(juJvicyv(Mxq|NeF(`Uz<|O0G_<7dCpHdUUgD*2?%h`nihWYmi*OkwJZZs)k``=M6-BfiJ%LOV2Z)4=gfYxh9 zrlkEk=N)s0sVjf%F{g%lM4mpbqk!FBv06``7n7MWUQD41=Ek?tYy__fL&04|sjyfe z!RimQloG4u2YUSZb5D=&a`ZU5o=?WpYbB8@@FI}s*!PTW80%yTJ4nh_)B$4ss%TZw z7bD4k3088kptTR7;gzAnMcEyq%tJf=dWSMkzT%6DheD6FKA~=}3r~_DHCek;w(#ks zd`Bm!tN$Jg~fX}V}0ZmX{Pr@)1eebWh;bo9WH|91KNR$YPONbCKzBXmO8%t(e zyVgjI|J?$P(8tsBd8dzhuL%i5;LfT>aX~!*PZdfu+@gTp0iM-|RxKJr>Js|vmow7^SuMYMs`~SSXdb)){#zHT&nF$z^Rb6fu^`eVjyGaFK8xWuruhU3`}oY4R{0S7?_fdf&JB1Fclc zYJ)%&-TM`@2&j;R6uL@kN?(vHS_0{!6hS8DnhcDi%)=%v`S*?ws_~U9I)doAXU@6j zigR}#?_D8~)8bzbr`Aq(AlK=3t8T(UD$dTlF6g|l?FE(gUbMEX zN(DR$KY>RLv4S}G*2OX|36RAUrO0udyk=MSWCKY-Jqre3mC;(}+%bp9oQ$(;w1vd9 zOW9K+LuuRFzG9NlRV0Wc2`pJa7?H=$EOCw4m8;HcWULcf^4{sV?rO2}mT&Lg>%#{c zBQp=*AH2QC-XUPtj9kd2T?wFpmENY)@@o%Kj&uyrzRva;k}hY=IX+ruc2JnenHPXI zHsRn?wS^3I^?wQ1xUuLT%mPeB>2@-H0o7VdbK5o$zWZ0eGoDb2r9^IevQ4E;nzlJK z?u^qMYI!mg1zBullHo(Kr}2L;0D>eQ3lOB7X{(DQ5?FluUMz6_GTo#H2XSzV^E3!C z`n9}WmN&1GyYEkCi`U6*n&dcJT;kgu&dv@F%G^GL{Ezb@3!=CXex41S_cktYwkELp z5%?`A!p)D{?N9)0{<|c`g9((|A`&Nu+aS*oV`mp^^Mk(+5cxcR`jmbDKy;87K|I|B z8GG>bIr~bpXcrV1k#|KDqNAvu|2haa82Kp3vfzF+J{v?WNiUPExX4yGYe;!>-$VW! zO7lk zJlN<)`DMAxadEL80ak*J!XREnE5hXj!S9=gDz<2iM#5_p#Z-nNPS!{_r?D`RellE? zS&SA%R^qef&4WU`#-`DhO$~khifl;a(IzNsB3}?oCH@>Ufi@ONv`}g11{b3ke?f`~ zk18FB0v3fisdyic+ZEfP@FMMdVGe2dY;?Ukw zS|=GM)`_}dpdhC*UEw?G7mabND^ku5qddo5Sg$H#I%W%Q8GF6lh-fE@Z^n=+fCZ;> zqL#}d$cj-V+ID`;lsM#Hf%hg}*}<^^gP|!NYf$|%Bks7tIl<=PyKq~sFfjuP3giSF z-rFD@JVRWFC3F`bfhIk^w*adsenX!^KEqzP36~RY9B6+l(Brx_J8gnpg^dd!hm%xb zNKQ*ICk6IY#InWm8>B0JT_8>A3xDhE6yr>^09&piawamZQ&atYrfJtX-^|gBaosB} zkO@QlmaOGqaUrQegRl^IA5iujnyL+1tOi{Qlg$e4P@)J>tRR}FxmqQ%_nRPpS81oV zc`{73EiG_rYV}EQIYCdJ069fUL^C->`eGKUc{x2@XUXlKI7`R~XOdM~GY}-mqwViW z9u*M{FY73dihDiDiPE~dCW3HbLJoljfhu7FVIp63@iKn%xeSDu18R(ZNUorHg1nxw z8j`Swd}P(o8Q}$;^TG7b@i7$Ko5@DzkD}`{ZCab^r6Pj;x=5*|CK=Zb-4YR{5;Vue zVroPX7KSIrcg5RoOq3Xas|(u)5=%%7;LxPSL=`JzDSYctmF2l-;;#l+>rS_Uio^_d z1arL!Ig<^9!2)G3Fq^&jc`&{)EkF~H5P!Wx-1sjLSJ1med<6qgttp##*PbSZ`8 z7Lz#T!d&OVq$tNAUs;Q8x|3H^ngBhhcXd&_Y}qD$$T)kg$1JY9qay1=YfLtg zSMIvWM`_x*U^SUNtB(fZ?h-ww>Df;@`h@S15Biau;P_bxcqDj5spx6VGPZYbNfV`o zcwb@4=d6M>RcIZuB_^F0NqQ0g9A$a&A=pMMp_{<14r&?id5p=Ag6kFqZO^xHx<)X9 zg5boV{3C70HM}N22WXTs6ViteI_%6j^fmg%|OZ?_8#3{Auk@tRskw85Sh*z)3A}HS^ z<#vUZ7zHt4dg2iXN-`Bo19O6Ik^*_?8CsBN^pV}8keDWKyk{QByOKJDzpR!Qa)s1Y z=Em_$mXs;ncWw#2;wA$PYwK4L2Q5}Zt#_MY^>AEm%}S`M{SlcYG!m8ZI!0kqTl+cK z?Q|aHKSlXpERO9k(0N|AQfQqyPM9xi&0kF>jSN{GZ!F)J4yAfEu;#aWyoA2pP&8#m zR3F%k^ii}HZ?|AW+dcqTz2)8q@+w-dM*^}5Fxt6JwBmnkVRPi)TF%WK!QDGJ4(Xm*@RV`%xr9v4aKB8- zsP-KhP&O(Fd3LQ9kPQyFHd!Y0;$=tSAeSQ4$#3XS6=getz2-M-;CpD|;;bUzyzQz^ zwkKU;Gxxg*w+U~vcT8!!@nlXO@w(Z|=@1Vvjeg*OvjjUd=$JqPhU1kA3$#S}`Yhi* z3J+Tni!vtS)Uc1u{_Q9)%+3|E+}l~@U1XTe4Bk=^{r$gJS15f68}=C8sAi&vZw zJ(jgxkP@+6(Ipy*USFfB@_H$~wl#Aa>au_y#Ns*#s|{m&Yh)(8jfCfVVa~6UNi!I| zfQtM5h7`Y>gzQR`%|Misy+B*vqL;`JBTmtYK$)2^ZPb?AE#$qmi?igMKx(Zr2zzx^ znPAmvti1#BT}3{}$BMvBAzhs~o!T^2?Hel&=~0`imAMZ}7DU|0Np5I2jJL)!LCr&x z5w~Yj7If#%;8;S?e2P(4qlylV#0`41Nc+aZ6#W7P!>DuHoKCV!WflT{2L;dAeJ*2m z;ka%4*LNfPo=<2LxIUY9P=$XoRLO=Kp;}C}aZ!237MZOITM`%jH2i*r!Sm(8T89xT zA{w!FRS%EWeKfa{gzk&2!JDK&FQqkErPd;Y(j*_L(AhEtL~-m?+H7OG#* z4oZ$`UAD*1}&IZS!v%=`F z>1LVUy$`mN;b8G0*n~lhqs3>udBD-d$w?a5Pl0DYP9n$k6YlFzeuASlfWJ5jz(*%p z-M{m^a_GqQsh7CVUCggrc}^UoFF1ea+y^JYC{CQjU7^+{xOLYq zjy~Z#yhT%F%=^Feu5HskH(|=s!@DI;oZWKw>BG+P!h2`u>_WokF7$SxWx~XH9~LYY zw;ik9v24pW>A)?1ux#T(iH0UC;s;Q}BTh*#}4{r4?wKTP#|5l+&` zr(^9`3`@H70D6XGo%*W;xJMyj|8s`%$!t9^saSLk6AJm{<3I~=9=ZOVg%)9i?|zAJ z=z%Jj#@9deJFUIZMF#vWCKUiL3RvzMnG1LiTUFqLRlrTpu+VfmMFX_kA)`n5qtA;t zUBUuxc5h-?9C_r!Sf+4{64}1^#>m}}-2q|^)JHM)*5~KaCijs6yS7+#`fbZ@?OkAo z5Ss*E@Q6zitS)E=fh93^iZ>yVBYig?Ums1T3S_7PA(ITE2~0l4L@KwXG$=3Tsh}_} zgx7l&6IC}IDtWLD6$5e*SF}0;=BE#p0LyRrWM)Izf_Ilt*H7V&kbW1&vRiQ#|T5OqJC5SW1~6qJk? zO4bbhTks5=<4_zm_zsJ?jAMvUz1x8K91r`Yzd^lnj8Q!RzfeiH>;K|Jk@LhsWzWXO zi6MLwKb=IW|8Il3-n9e4)c~gks*vJ3YMGw~RuAqRQKuRoU*i}ld{r!yYIe0+(jhtl zo_v18^y8(~fzc5-QX6rP z;9rdX0Cqz&c`QyI*G`^@lPASw(KWYR|CS~*uQ_QV?8UfRl9N5npw3we^>wtjK)BzF zgTwB44~f-pElxSRSuB2}$xYwr@}jO{a#W=~1>52))IlV^fA<~ajObJ)pS}m( zT6i=->E4nZAua_}PF6TIxzLLi;EeR*6BrcUJ^8n9ZB@0Beuu` zZizQwdsB?Pj~pKmmRXg$81FKgEU&-`oVb zF>=t7Dl8(c4D=MFD83I;?-q(>ymq%_)}x!;BdVD?OH1yf;1T)w5iS1VgNMSa1mAvf zyc8FDW*R^F4?x&$jvuiEIAes*cnKqPkmuW~OUh|o{kL?3BX{+VFn>h}H(QJ^IT_a_ zG4|DEM%c`xIRoSlR-0uHv_$Q1ExT8^jWT^_C~Cr7Zl<{(G;%#7nFB;Poj|iS%>Emd zBe}~VTc{KtbYIoVgU+q9<18~EA^-tUF~GxazWuokKHP3TnE6_QuvtQ2NX~y9L3-N;h)Hc2>sCTY2`<>h4JL7PqG=RVLsk0wg!3e0IMp!^R0ux+T$IGZrKrc3kYh}JZU?)t~9VN}XtBwIy8sgOK`N#^Z! z$KI7+Nw!XM4~w3XJx?4*@6m&0({z_4z%1`=H^;3#h$3m1I9Sm-$&7|Y58^C1bvI^H zl7tTW^go)BS+$-*0j3@$oBCdOp57J*sb%>@yb zw_>Nh&1}AhaVSJla_1wv)QKkHW7TF%$$Bo*$E+^r7T66 z9fUY?5-6L_zRk|Mw5kuIU1EI8cRlWMAozN*5R#N&pDM|#Ag3dEy>h(}G+0&dOl*aqw$EBdZE z%uc5^fhZjK=X6cFLTZstkV-gTGf7FKKWIm*=#vpQ>JPWP;E_A{lp3^Vao#1^kc_Ge zsk?mUK#!Z>Lsod3C1#4R33cIdp4k{kmYLn4 zkdAFebhJE3Z*H%$?G=tf!93Dp9#>#K?4q9IDP*_yyIsG8T|dW0!~ZAyJSv-G8E}3L zpt0rt9^sotc&#BEp>mb__JX=hOGch^s|zNv8Qdffl3V=s@-h#U>&D#%gpn}}E? zQwB--1!zSl$!u+F6tuN1uhVAVNj6|-%`tSI3s6S1SGVP~B?)_&IVAPPSkosUby-6i~;PHl^xoSFT$e8^Md$Mkn^ z^rKW74-FJ`q}#mmW=vIEMsra{;6G11OEmQ2Jo5wWc0JgyB&PYG_r~e{`mXoSUjNl~ zmMn3E)*idmFst6{$}u^>Z|58=wIJ`;M?XNCqBsMk}k`cUUU-g7mLCQ;z- z@vu|rTV0Bg0i%Xllwic$6m%a1-kEZ)A@bmWQNPYi;n{z>u z<2#b8fupzTE6Y_7Krbated1f(vQH9a~T!0%A&VK-{OG z!NW4mf!KaLfp45b04ETLHHDn6ikRe@OQCeXd^k<028jR0kATBhL`3TkajZQfS0}jq z1pZ13*q}q_iGldAw`=u>>tX3Bnw#MpBR6OHHIfdgv;?lq#Ghw zMB@1IBp6L=!M63Up;$A1e4(3v?ujvTspdB~e74?jSW(f~!b98B7`H{9c-VAdGJe@? z-Vga4nvDO-cpv1!ewY#F3>MRHm}21MzvHnXBF$~=VMcN#zB(+S<}8d6O3>k<$5({w z&a%%0PA#bEPx-i)&eV8Pp1kVm^K*=NJe+}zzFX&!!?#ouu*91>OSZp6U-dJ4kk*&4m@zLF2lT=v`k=*Q>h z@=8LQVQ_dDjA2LMOq+OmY9_j3dm~dC<)P72vIcnRa5T{w`B>o$dTd z7F33XrzZ%;V3wbl-min6==f5X&W`lee)ItWBcaxCT`1`q=?FbLHkEis;rk_Bu}CX- zjU9lm(;}9YBHEXw8J4fIke9`511^hWtO&%Tzgjf)w7Y{=Exy&g!mDV}v>yM177h4b zTG;K7p#|o4VN`@=CI2IB(Ww%|*BrGRUnz>)fP`Mt9sdQ?0S|3n#O6o zJUiQs=*>a9B358%cx}lmW?Xau%A8-LxHYi1`;@{=I_|YrB}_G(P?9c^s`Lx6^}jSI zam@Dy><&ZCi9nz1ec|W}!vj-HD{aSgDXezV(POxw`HC(syYCJsdVIBmY^K8@!`TTk zkHH_lQ@|xNoR>e|yz%*Qn6uT|VLoIQ^_>W%omvSeov7+IIbmKba1!GU&!Q6I)VM$$Q1sBKCVz_mm5tX$pbbu{!SkSjo%Fy?G}uWEJF5^BZa?ZF8$eZE|XO(g!`!p>2M1Ufkl!%FwZV`=qktZn)fe3_rDY5@zuF`w9KbsB~?1 z0_6eqgaMKrx7jqVt`JZ6($>@ZPo=86)ZX31OCI|*OJ*+jp2)rURQa^h%XxHMWslMG zGc(>S#=Typ)jFSA^@}G{sv_p#IR#z0=j(TB?Ioowa-UyhDStQ+)>|g40(O1mY~w-D z>qZv=7Ymly2gwsz9lXldVI-%2rlDigct_HD(I~rv3)LOW0`GzUF?ISa&~@=x&}iCa z!Ax0j(P_a8y#O(i z?VE$w{)c###1yCgIbLnx&xe>$mIe`FqfRGW29%;L2}P@9iC35~nR-kZP8XoL^LGcq zFXSue?Bi_m8}iWWkxD$d0OEm8oB;7qBThNzPdbrvzR`%MocOa$JiqvTRv>UZ&#|t} z-U%>{bvau)vMx=;l2uHAcE;8vAvU&(2@jlZ#aaRMsL=cB#S8KG7f3B-0eQIzQbBm} zO8iOEXcI7uV2wo~>OrCiJxk763TlEh4eng<8hK+x@p3U7if{}>q)g(GN;|n22vXj9HO))=Rp@krx5<;1S16Z?;H7Rs`8R&t2j;C>Edj|A~Zp-nQH!Z^(s#|kZD z94m~c84+A+>_g0O$nYiV#R*OW7N;Y90os#BF)`>#iW$3u+(zWeSX{<8IDH?l$)&Nh zP7-Cwz?cMSuu@R2=Szt2VogE@NYHF13N>Aa%q?*bN~C=ulnn*`ix$Z3MwDWfCq$~t zPo@0leqWN|KX#qQH;CXHrB`b{h9z%e zSJ4KO0m8RQ=6y^siWft)M9~acSQ}b>5D2xjc9vre^%!ngW3KXU65~;fAtUr$kdL>oO9aNz`Q=%oPEsvZp!= zUHC~0$mcqLRsjpIF?B7U7hdV{Cc4C_t+2{P6cO%Bh~tI8qF0j|7GdukiXvJ>;Z}(? zv4v#8luH9zp{!r$7Zm+RsZU*{YjuBeY%&Wk2eZ*DYeKPQ4VP)6K2WA{dvG%D*^I@7 z^>!tfPjlHRTMyc6&=+HW_66TjbJWxW+77)|7z=2kegh)S#YXL7n+*>Ft?1RfJ*#L)?oXLS<)f)3RQG}lDmzRb zGWDCMi&ToK2rW`_RLaij%|*60LWgJ!A2ygX=ozSd!xHVW~0!mIAvAZ*zLM0bdNBM zo;pC&2LVl=6i|I#*V?Gav$TH=VgH-$_aiE<5Sq=qM>er`yq79mI%z8VODWm5YSEIR zNK7vp^={LqWG8V+)q zmc3o^Mea7cHxS$S#?@v0z~{UN{5p7>oVWVh!4R4HM~0R|LyIBAHCf&dgcj+6qe@H3 zGOQjuQf~AjD7@W1DIMBR5ykMdltX38nvLU%Lg3Z%@OL7+q9d}9`Au$8>7M7Pmj9BSL@5thD$?`LOv& zi~0*`ZZUsvE=_9MEZ2%;yLYVI30p<%_};qWh<#;$fEHGJQ)gXxGx z8+=R_5sBEHE?Nzq-bAp8GlsAZH4NaeCJp7G@pZ8|!`~OOblbPD=9QMs$KAEeQyhF5 z)i`i+(aeFGY_vd|?R(q6o%{&6ZG$^)7~B?-dPEB}gWfREPgB@WiTw2Yl}LM-_xW(X z@B9Oel1)nkF%X9D`4u@7x)lTkPpu!=UMwgI#ap3l)7jmDO(rChYL)(Xv#q+@Wm}y~ zAT#s4^GwtMPV}~Xm?UP|yqLNuKijj=e zl?eEx16Q@v8du;3yVgCuibxI4$T_AW-{rJ7&J;6-a0BrzjfR8CKCV6+O?gUaKJ2}91i1uMaDHuO@;<}lMMbKKhoR($RFUSrftO+t&+=X z!!Qs<_xcJg0(PJ&DRh@UAlbCg7lf{w5)^qH3oMP0#!gG|zgMx-#5GR3sk9JCch1!v z>FU;ImN6qVcq=6EyF#yu^%OrYFUE@*YKtD+Vh*|t%M6VtCzPQHV=@=s^G884dqM+; zGp@AFL4zUB54-n0eK8(}bx0ry5y$7@}O!?7GTf(pVdeZN;?s#?aD&=vH#jqu5h3ipH_PVOuirDX1b=j#~E|-KyF8I^d zxA&7h4LM(Jc$p~{COHN%u;Gm$8lTs5lt3As?f$^<4}%BX{F6b&z5$h#QE%EX6oudO zD=reP9VsA?_Ot?3+f6DDo6uTKRhKDpg8@syj_ex>oA~c@flUBRl&=U}UN)Fg|FrEy8G*KLEN4Or+&$A_$_;nrgG^e8Ei{b{PLNHmHDviM81`0GN4c!zMOu`84 ze9$HSTW+^C;-W3KdG3CPOs?VKj3tIQX|!FResGf|XkS3+5*xV<+VEoq&aJ*$Va&;T z(8{rD`bY4nZEF#LebeC@`tTV#{arBXDoyurN=-r~Yb`ofyw$jIDpOH}>H)Qo^JB>l zjr~`XIHTmkz^@DQ+h(S@femrCtVEPkVhIUfm$N1=gAA*9e@?vmJeI{nptW~iX88>D z^vuWo=5*va2QH3#)2;Upg^xjt0x=AQ@BI}$c$gg!6}-8i=w%lK1@R_Gt#PJcn~UFlqu4rgceDFrJIgZ4&j4Y7FP&^=sS=i^{UmBW+sp@0Da(mLJUo+D*NRt#i(OovXZkR~K~< zP*@^f!xVv!vV4{=+v>-g<>GK(m374tJM2(-LTnubl3$0Ycgh8WmSM6`^yTsC{q7zlV7VTCfB0Yy=Jup!_yh$4TtIEBYBSu9Gtm`ApQ2 zMdXsELLmTGuROw3!Fi≦wPSJPp z)&CaF){b{^zec<%xsDjttUBS$Md%tG-6hFgJkXnOXvIoM@K5B*pUr$Z4a+(eu5^bI z2IE1ZUi&&RhJ<8vLUX<;p-2lof;N3iyWBFlYHTD@v5Z{XuT+00ImNKb*^)R~HjVM~Thuu&R2NyG4}Z$7sh zcuP$7PlM|9?nmt}xC<+%!m2iBm?4As&-T~tW2UgCEMGUT7=mfhd6EOjLHG@wL9zjz zQTr8mqBi4+EuOdt)_-grHo+ zz`ij2QV6@%onHpc(|$3u{o-gm)6mr05a}6w(XZ|@Qq=#AkU>$YYl0xo9}7rbnAnSP zCCI!>`dYa5+MNLVw@#?L_nPZGcWU2Zfy$BD8XYr5=hD4_?w7~o)a2R>8)m!pm*6*@ zRc&wDFcAKpUtuI9CkWO~+9%R<(-30Qx^`Rn1{E^l0Bea8*-qA~;=k__LNIyh3;vS$ za`)Wb^PHWl&+#_4EWtuhG4p|ZOTuNc`5HZb`gl1XMPVE%kn?*8Q;@D@CF*4t+zX{8 z<3by+?wN7lg$B9CvRum#m{KH*;sF<%pG^B(kk+$Ba9#9z`SS}lqcjtPl*!!CyT~80 zH0YgtVUb#|AHJb#3CMHQnDx8anHh(pprm0LRDQsuq9?cFAp*!)Lo3! zlxdEH4TpEX7USQuR~cqw zZ8sc%Y-SI}KIuj=NTwr+uXuPAhE_W>d-?%SSZ5hTKA32@qb|*C1=6u^z9u$T3bbA4 zX;w+_;5bJ1Y)S0h)0Spi6DHtF+qP}nwv9^Lwr$%+rEQy)nU%I}v-jTzeGPr^9jrT; zYs?riBi?5|5)}@0MK~VIouVUpe!m@Y?jb@ls35E2T%6mP9^G4uvV^w4mpOclGeX^9 zk^kD}5|05Xyny7lO}$X(J~hm5+!DnlM@HEx(45wH?()nz3PP2Wit$};0uaU40RPcH z1h1UiznYX6A&il+FRtF}8WBq5e25Qa=W&o2M`=qbs>XEXk9>Nv(}J$3$>=xHGvWOkH#V4O4iSh63g7Ba5h zviYZ%g|zERU066T^M)VOnFPYKjFX5Ev8V|yq! z=uSKKTfx(eORLBqk~2I#dl25?*}0! ziwbm}9@a0{m)Fps`N7}^%ybrCLm>u(R?TI-LF*bphrEM-r$uUdEMw!~@{pQj(& zzp1nj!k3kbZgZJ}YuVxAgp3DN_9p1eEdDFPCc?qn33@Wa`1(3N#gQVDT zheU+Cw|_(vnDf0d9HX4qfvH-Vy6M6sprvT{jLVwT@1pf$KwXw-Ro4LJ%y%<-E7I(a z;QNge%Lsnbwp36nW=+Ut$^^1$mAtVhkFd}pX;6Z6Y)S@~hyy`wSC&*n*pRq%j#B)d z)Xnl8R9`?H?oh_&Owq;3v~#X4+&ouS13qFYp)+Y%s32x9VpOPzVnbwj4X~Pd$SJ|x zDe{*4CR97ppE_v?Y?)*U94Qqv_sNXTMh%m8rsse%@fpe=iBTRe!iwG69I&IS5cR_C zTar{$j8R1sKVTV1S??BcZ$yT2prg?qm#&r@w8)K6WP#d;b~G+j8w!u!h3sid^`Hom zy~XXI238qv+oO1f_^&glupx=+H&>UT4#`6)nD}fchOSNcw{^iPdSOZ?=Fb4X)SvfO zmIJ}&qrRKWyJ5FWN5)XPn)7hZ=joh{ms}Q%pF3{7k{*3ol3l8&fbN;13md<@&2P8I z$qvaP*1i6)9{o|B^_(VIQbS?i6XKGbpU|Jffah-lLhb!47Djy1J2F%0KnjRtcP0#0 z*nyRr?ic&`UDx)u@@so#`5_B`sZ6g;9@VxKFd6EAT}>np6ADckEbMGP>>sFomSMla z*ITChIgJifCt*rs7t@#6eawiM5gg>E3q&%@z$V&fP%oKd!YD{xkhTviQG5u` ztCG&rS~dNq5TGaY;bX+vxcF+|EB?Z6BcDEEXOZhiXfht@<_|FSP5Lp}no{u5&MJ!@ znr|=`BZ9cxkb#YMNxxts1NNZvmL#B{yEyX|Yx&jj+EnBt}Pk z#0Jjw;3Y;Ot?$T5f`_V*ZI4cL32PP&g4W;x(vAY7CDTT7N z7jQzdF0Yf`WkQ{8HMVY~90x^(u;D2#oGbIo`4Niw6 z(Nf$hpfg4gqBGaw4m5&C#RpU%YmYz7`%&Fuh$5#|MMD~r zer_HiIikc6K(|-Q86b)_rm5iiQ+^l!7sC+v@!WEGhNjO^& zOGCXb+hJJch%gsYnQBxpGWidWwnXJRaWa8-YV9>VzzoB|&&1t6)scG*GNE#;)1O`858S8El#G5@%whab zCl>?jxjqsUYHoj0WD7=yO3lwp)_Dis5XjEt?4l2+=+91)jO>GP$5`qem}Q9^U)dy4 zi>;Z(N?|s0)!mVzo_v3rb6Gd?ZQxxQ%^u{mhDD@+(!mC@6p1-u%KcJ z`to_zfc5;II$_pXf~lHQOOymH2aelt!L6koBrAi+A=q{Li`A9SA473%;lqXXJI5C{ zo*F4_jbyDf&dQ0gwLEL6$$GPEy_Uy|!*L5}?Z;Oz?)?xfu8p2n#qHk+@ntK2F`;%$ z$${bDJwm+_bze(a!eI;7B)d&@JFdElXb)m5HN%<)YmtV?FYzNPQ`Yl>>_C&y9ug>( z6&uL(a}JxUziuI0Z2Z|02cswp;y$v?o17_i57SE$*9*jm(ZdYS0XRE2lL+uf7*XdG z(9?LSPmE&&oL%p4)^=F#`Wsl3c<1{POi1dNim;ZNg(oAWNi5JA++8!(1JA`9>^|kC zv=6i$XQkZIXT(zuDb-F|YRLgzO@za2MrYx)V0q$Y`Y3txv|6E$Z{%kcJ!eul8Zz{k z*T@hTWa`uCWJ#uRB>o5uUR5{5oIbswW5&E?<*uJ1V##r0mP$_sy)XQk%|W| z7u?SPaX##O{`&3qslqlD+amA7pv9hN)$Rsio-Ze_yYJ^i;K$Y9zpJ-rSd=s>P03=QaFSsF}KQv@Ji%4IiLsB{9%V|V%wFGFq+>t zRtLG=t9%FO%3Twp;b_a@CsSNRV@rJ@V@-o51vaWKVB^`iQZz(O!YHXTNistqoR;KM z1XdYoNFV17gK+Mf|A4Zdo$z_@H0(xM267Aw<%koQxRALaEL&XI!;8eE4HPM6;jmyQ zCuqU3%L+Mp1)HkQXW*aJ3R}BEf^_z^5d?FJigAk&bRi+v&FPHkcY~cGCM|v|QX>61 z>8|%;4i60Q#ND3sayP3;4D*bNGh!S`zYKOejasHALu+ZJPjZD9b+cepdR~+{IC;pI z-s{hOSrt&?*kqbYt3PNvFxt4HS5c}%amK{6$JCOvgI`m;4zC1*1n<5}XqiDTiJs&v zUx1~)xzNf$mRM{Z9h1qqv~GYcF3!nOUJf}#jYo~#)A>S(G8QB-kOSX@0*mS(~ zadPFe!V_AWzVinr&gQ+W&K?zM$(`0ORP$uGzmSNVzR~I98+nY}cv2ndF zpXTnX!tM=W?{_xhe7RWWNdxF(-*npLCiD#sX_$??Jx#&Cpz9sb<$gQQ(r5+9wshr? zxh);7H_@*+O$jOowF}x%FMsqO4Mih>{e~4pY)@&X0OLlLU3|&QN0U96R}N~eHs~9$bv;5~t?Mz46U}+} zEVD1m)4er8H9=kX2;}D)OI{&04G~T(qZp}(SmS+F3=D0-B8 zuev%Mv=>WBe&+1LN10^R8Hu-8j|une)g!NMd+~nfq3IYK+SxmWio&bU25Nd>OqsCnoU4^nh7<0!B#?VOxC0SRzPb z!7x$!o!$rvU*}!f6(iO+A7hASzWnsHw%B+f&7<;;2|BO`>F*>(>oY%RzK2>#($FIi zFSQYUyl$$KoEdCQ1edi~^IobrhUlZFlh9GBjG+W zJwMM$?tBrrLXJ^!FDoX_RC(!`Gy{jErtC}-17sw?cgWLV3mi#cirs2V+2iRVTP`cQ z(Lb7IoH9gjIWg`Q&gx%24Wfuh?LFG9RoxRQ%un<)2%P$4)24TxIuxt5ug(nF&)YHZ zFWV|En;AqQIkY_PF_<}ylbBA_+@?Ts4q&Pdm9&3F{1cecCtBB4x~iruQ+r2~VaIBD zdE6R)dl*0R>XbS%S?t$h5k~i`4V%HCQi*YX@h@}XIae;7350A#J&$dKqe;j$8O%LEY_ZR&IH)O`LZ6AN1yzhpI#*A(m zl+B z3$C$4ire(`{-;JFT$!?Ttxs$qS6zys1okWLGMDJU)iHArdC_9qcJMk-yU1+uUwm`j zcl+_e#Ojr@=d)WSy8$V}alqG{l?V8kMf73n9Jo{t-eFBBswHz|VUQ*1w~VF6e@o-_&OFUCiSiqJKrs9EQ1y{2>{YPq%|*9{FS^YGVlO!6B@OZEFsIwuhniJ)A4f>F*x z-pP~H{xE@n*y4C2E`%YO2==}KC288Q{m8-T11P2gEstuF)nxms7C8lBfSAL09>5bm%^MB@!9G zv~59f#83wB7I5}>B_`x`k#h*w*jz>7P_o(1>ggx&(a(=S{%FOTq+IPF#Y@GN=Z>NL zY8%d+cYr{YLk3WC57lemL5Bx-_6GlE3SoZ73uKdL?RgG*mD#%OD4P~fgZ1Fb+~oY- z=v!NI#NKV^4s% z^f49a0fyb65ZP6oo$l&QB*zb{>J9>3IJ zw2qP$AloVcs&bFvQJn76wLPjswtE_IZmcr(+i3^4WFOX(4;y-mRuQF z(YTPFDme|Vud8C6JJWuubL$`9>KeI$rHo2&>v$u$+B(oBsMcwdOnUQ*$S`VYc!f)7 zryky_tX{Wv-Lk+Hj8q(bSunPpX@kq&5cHI3lk;XKk_&eo#_ zlzH2q4?c7J=heIkkcF#s9H>N2=RD^;77F`q@+K4ex*x0fZeot6fMh8|X5b%`EHl!) zgeH%?;ha)F&A{k&{cs;>VR>+VnG@Ei@$87nwwuT7yg7MCy58>?6#+v3;YCZq;*9P$ z1^YkSegFOU-o_268vYR~Ni&$4hlY~foSxFbXS5`&_fnW1wEO)UtdW6&=)+3sq(sHH zHc0iQ0bQH|IDVb@#MDHh?8R7G%JQE9=9Y}JEXT>F2{XGmhzVn!=S+l$EGad8Z5V&K z4xmEAwNJZ*@ckgLSa()j}Qb8T}59NqqQKabV|Hi|08|fn%Xbz`2VNJA` zq%>&8zH4D1NJK#ngmi~1atE(*J6R35e36DAyxTiU$+rPeF`Dg}L*?)W}m_tzZ`M|;9u#It@n^C5p&qC z1c|RglijrP`JIEp!T+rjhazRpGvWqMDvIufd9i$uVOkMKg}NkTMkFwS493U?gV{;u zSv;T@idI6(5nOWn5F6iW5U3iS$M{4Bfyr>e!C9|ZYzGdrBwT{vLGuKh1qXOALY4J!=02n3?e7d2`e*->@I_K6 zc@0zb{@FrV{)ASfvyk34(gbc9(DunISrB{1puInM^i2?6QaWZRiV8#ji4+!IOyS?! z#+XB%Km06sJ&rB;TjL8|bor<{uv9@ol?@t?L7Qd9;y1BK5}^RB`Fa@5^=nd(n4sqP z*WnJ8r`V#rGEQG?MF&JU&nC#*&$C|(Hs-zaD@%&Qs%lW{=6?aJaJSva%0QU6j_N_` zn9GBGF~)mF|0NA<#i_utBMuOc7ZhmLv%!)S!L5#%!ke7{ozx%89$8aN)z%B5F!Z6a z?=4WlP=H0N8N@MrnfyM{IG#!{F)c<-5{9RIr8-bf73B}RhplQIAL@ji1VTu{Bvoaw zPAAkn6x3TmB4mc&wB5#;^tdz>mHO=d?91lid}XS@5|2 z81*lFw33q(_4y$lv~wBp88(qizbi8XiQIrU)+!hftyDuPim;%B8j2vW&Km<0RYqqg zy5nvgD)M@su%u9ts3i;qTi^vz$#Y9wFSFfJU>7(Qi7q0zmKELhSJiCk_SJfyvJ_C4 zrbX<$nP`p5(icz(jSV9u`nzJdjQ!Jaq4Ot|4n}qXL#G2<-0#H0~_9 z3v7t@QSG7DJJXgMjK+&!Ef0FBwsc+Isqs!tH50zSTQ4Zq{p=BL;;TO(!HB19js|N& zdivA~USiixPzVY2Qm;JlZphu~W!4k!C;7C^<+JB2e%WF&{2qQJ=gcblQ(rsVB=VHV zqKXl&*cG!3wr~P***lQIn=EIENUVBsUE5;IDS#{?^CQbSXJ*-bczDelbkBbaamG4E zvb$G2h8%Oi0QpmdyJ-o1`T~<|nE^RT)o9?$1J}J#9$!q5%^&BM!_RKC0@RuPsARgC z3c)Jp4zAhqPj!=#^Vw4X)!erL(t*LFj;?-`$|MwBzUd)whn z=3@mYa{pBDac?qPtvll+f#L7HSV+NWB)9qL3ZdbRM!9fou62&(b{bbZ@@*>bm>p?s zY`w|9bs!+;6wANF6?)L$u;W@ z8aRY!BxQsL{MdrRF_)GDajOgdDw|?lPxp&U3SfP<2ixS6&7J|Poy`*3W(jDD?JW(v zIp&a)WeXnqzsu?t0^PO1HLK@U={hTamzSS@L_dm=YlhD3`h_`>#CLJA1>+MnT0Dlw z@ve!nBi7MYU}ziJ{S-wm+sQ0->;LuZmRFl6Xph03p!-}pcVj)^&re#BfN^b0vn>?^ zu{H|pp{r4W71XLz8>}n#pC25|46A1IWiGkttZ~BgqO!n06;zWhR1BZa$IxTS^8{QI z;^MGHW)Uq6p*v^&Va2|%J%F(}nGUP7r8uZ3T>-1WiBef9$zkm} zZr^9<0{TEeXR!`nWB|%fyZo3EQ6r!3!`au4r61c1Pd~eZU>8;_!0+FS3D563uA{uG&M-ip8yKt9%^#U#Lyv zDGFO=-YIcPv|D*Z56!_?Rxve6P;rz4_l4)N%Jg;(kHfwc=YpyPSW0&@devDHx*x?R z>J@PL#ia9}ILFJFmble_D1Th7hJHPNxL>TjSK;G~5$pHqGriB_3Uj7*yNvhc%v)Ra z@D9d}g8ArBc>boTWrrY@w!erjWKeq7b>3VGxv#+8)iq@uR~!OTRr+r^7wU?07iM5NU2GwolS zoS}57v;w(sZOKTXcj1O2{sH0rix`U|rWHCt2D#p#PXzU?0oVQ~>W3fzqS9K=Kgv?| z&>6~zrT88g=b7kJ+#30kyvqF$2IFvcG*#e5plyBiyf#HcAysZLN6H zvYy`9j;!{WslMm@?^JHOclG(8{f=a4_&MPQxBQY~Zm!){Wo%aIHZP3~-BgkrNEwAz zpehRtvy28EzD|~T4GZZWZ(g15;d;)oy#-8I=c~1~x;7{ACi55Q^M^JoWzoZH#8t#8 zZ{i3Us4?RmYdoBa5&`{zTs`a_%tPp^4X zny<8NS1bw+xf#ypMyYUQu)3Vl#mHFB%$U(39(LOwT>cm1vPqFYRyF41Zr0#zH84hD z9jnxRvdzWf7bxoRts-h6{p9r$ue^SPEs8!KOn;80XzPqkvzR~B%tNg+zf%Gw;rz4M zuGRSHI;FS%9mP8*ReN=}|CV!FCYxuki5gJrSW>kn8C;jca?}Z`mFB4P!KlAhS8R;s&r5@@J`;ak9# zbkow3SljZ_xO_t89ZQ9w_m>JqR_6r;p`MZv2bXe-ZN)%0W>!M+ZDcPf7PU%=*e-mT$=1AGs{y-a^`+-#L?NrL zhjU=ZXN+hhUy^41og+iBn&D6(hcA9Y%ZEsgKt_aF>YyX+HX4WZ?O?_#m!f^cI){9Z zTWdCwKrXHPGqxD~=|QT-libes(zrk?wcDWMO3_J#7_?dnN=4;R zu9#y79oeZOVJXQ`OSP6C4*2kUeMZ+HPmHk%Ee@^{t{D_*1ckUHUIJHz_@;xy93Mo+ z>J%F@V3E=aBdkkyp!brFB@(-sYr8*UWtv3&@0;sO2rbU-Iu8+6k|_V^*~1uL_t`&* zIUWOm{_|QKED$Wyhc#wMg|oJ|1WKuJ1rfa!bdE)+J;STXS0(<+)#88;_~ zJjXUz6%@HzvR$BOxGpi7(C!`UPO4t@ROHM?##ZL>tCPyNQDrqtWsRuGav5aa+uR_$ z5&t~VMLY{i#m?Zn#@$6+5@`t22r)NXsAZ)n&+~y-4tx9{2gidZYU6?uB zB5Y5)T3b*@=47|BF5J#^6Wv6JAE-1oVfo$TMkr|4|@i3#v= z#SP6K+G$riQ{{{!wy1XNkKHG}@;Vk=T`@zejW*fyqB8WByKgzvXn$WBV-u>mAO)+4 z2%FHbrYO^(=NrLc5CeyC!VYslVZmYz<{s>FFy=M{TEJPV4EQw7OB`W@C@3MtN~-yy zOXljC-Ss3W<+#Fk@f4Y;mJ)J)CxG84z}ohuZ1^u}?F|JFJ`zQmP56+eQ;bQ}>X7IY zX`9%x3opJ&ZH_B;Y^tAbxQc817a2O8U-2w*Dr$Y~YFtJWoC0;#Ns<|1;BJv>apGAv z-)S#c+x2x9Hy=Q-6t3_3*`9%(aARe7Fevf^V#PPGE`&&w@R1Yr zhs9qbNZ!p~$c zkZrsWOpqXA=*Y*(@$U-UH88OLq!o$gGADd!1I z%pjN44(!axg#}iNw)#mWk_l)QN$gcjHSFl&42|MhSX-DN$i|>%RZc`B?}$*$*^G3_ z=pCbc3s-SXt_8wK6BdK{xITAk$G7#(@?@qHi9aws8_xI#GiV{ut}R@~UPAku;TC^Q z1=4|UMPw=kaKDCnvYq{e)qa@Y&GS;4AHtqZeN{Hr5!9xb;0GCD#rL+#Z|4hXAAg|+ ziefD5_dDUhQ2*t8PbPkP4uNXuRjBJLF(*FfUaonaVv}6@=d`eP-+=v_03A-7_O|(9 zX60~AaH*RPe5^iT4j0|0P2HqDo;VzZE!pg2Y)2{Az*NKp%*DPk4dK739Q*zMrgBa+ zV!W(ME4+8IrDX5y>Pf~@jx3Nxx}lk(W{SQ)a-o{HYEh}~d3#q(#5nupzE>$cZ1M%) ziGVLQT^bKEhT<7r@zM7@9^GKI)QyM6PKO}*g>T5){DTin#tu7i;gg*a;pd;-xI1x` zfARDVtjQr^4DxFUyx&2hYm?46Fh4<}3=!#F9Xhmw;22x?T2(&}S zSGwQMZ*t)0nI+Ax5j~J7-t>TJs63K)iRYEx$Dq{8ts9@(SHBKt(4;)!Ol5p#mAyMf`Om+q&&cyWTWm|-gA{b&0rv7O$yyWGI+G(0Ln7SS! z;PCFi^MZvc-6ov}n$6efX*0492YSB7$HWIYX!%*qjNm8d?QR3f8Df3-vVXvO)4;`U zVbv+)iU7B&(HHygdB^~+m%wQix=9%agtIV{l}Ay=zzISil-7B-&uS(cJY{ycvletL zD?zM{`mS0NL6Qafy;}kSApqF0iJ9Uj5YsMhU8Es;QJQi7{OoBsj~GBuZ|C zbqPnWI*i0OvR-+-R2;qcc!8IMW;vj5A0!K{n!0Fm75ty|0z_z)yLR^<*gvXjPPFu# z)W*?KI~DV)N=yy2g(lX|gC}&Ml-$J)+1#tOoT(A9}0aBRW5d50XRQP{n@pc1D&mahz zV*qgsAdUgVF@QJ*5XS)G7(g5Yh+_b83?PmH#4&(41`x*p;ut_21Bhb)aSR}i0mLzY zI0g{M0OA-x90Q1B0C5Z;jse6mfH(#a#{l9OKpX>zV*qgsAdUgVF@QJ*5XS)G7(g5Y zh+_b83?PmH#4&(41`x*p;ut_21Bhb)aSR}i0mLzYI0g{M0OA-x90Q1B0C5Z;jse6m zfH(#a#{l9OKpX>zV*qgsAdUgVF@QJ*5XS)G7(g5Yh+_b83?PmH#4&(41`x*p;ut_2 z1Bhb)aSR}i0mLzYI0g{M0OA-x90Q1B0C5Z;jse6mfH(#a#{l9OKpX>zV*qgsAdUgV zF@QJ*5XS)G7(g5Yh-3dhi(|fu1M>eD+oW+?Eld=|hJ_MeLnJhSj0M@(w3H6s5R*<$ z;H-zb+L6-HMq|1q^J~q||4mPSwT^rbE$j%vdUsmVk7h^rx`reZd5q>U1vdYWSk;rV zZpO?k69 z!s+DIP0g{Z4Zqn3CgHoBh4*}7H~4v+O**gIetT*?GaemX*;%aE|KtJz*O54wRf#L&q5y^$B_@t)E%(PlF90c~d{H)|Bvt)5e z>LxU%iaH9Gheqp-Q_M zbfG~FExnul@V-rooYn0RRvq-{Yo0FbsQmk7r0d&IHzk}h^lYDp= z@?1#ckJlFg+KBnTg`ssOSFvyfRqVsE<|X24G*f3TV@s`XTrYl4!--~m7L=BS)u8+_ zuGcVf7es&1SH__twSPdrwVu`$cvWd*)o4tD>VNfwSOmuPdS{@P;WVSKAi7#4DcM$c zyoEO|RDVYvl42Z7?~-iVBEW+Ri!Kfqk{sSTp*g8a677q3aI~T1{s<+DZ|MwH0lnqy za{=LrPTsI{owCirwHa%NUn1OZ^fS}nx08od35_xxdVcFVP*a7F;s4pT9KaerY4D!~ zpV3dTlXs*2oRuE4SWE3gJ#be`i&;`ftwi#!1B2U`@ZXy86} zGj0N%zK`v4Al(R-!h~q-kbBu+?NlFI+qiY|_GIgNzrF79M+n#S>v?kA{w-5U+tiQ* zGbRO>z?jNQfLz8l#coJTdpe?ciqr^)YivAY;4zmp@CJfHrK)pH8n|vL)pDh?(s*(a- zEoJ4&&hWJni(c$sa+w8a@$lQpLHXb#vJS-&trVn8(9{I_%=7|Z>h@m`YBBBLv<>LN ztz8&+*P5)?jD&-4xN+*mf*mta|F-+AE(k5_E3b2uu%)8}#lX{PHAd!KN`=4+EJMA? zJ1$aPW}hnFmmn8ttRyqJS5d~!|FE1p)Q+oeSGeLVU5Iiv`AD3u!Ucy>k+4Cvr7vDL zdl4h&jdPF-|G@)Jsa#WFz<2XN)-Ht7sDy-!n?RT50j*=;Wq$aU{kO+OSU7>ivwZH> z6-F6C`~B_qOMKJaEbyMYY-=NreRoj})YQnvYo_`nUE^ydpmha?ZYG3q=T51}&Mp@y z&(63s`2oi@b^v;i5fSX>1C8svMq5Y*O$iZZUycuLl6`GiunS6#80l_@4syG@7wfpD z?b!|pbpjp&8RZ0m&i5!kZPSc>Ca{6RTq&s{YJR0~D?4L4yIH=8REI9@C=x#_UX1Jd zC~nQ;aoqtP5jT=XrJ0nk62~nNUMcBoU!29w$j4Par@?x9KDpE2Lj_b!;m_s{c=s+G zL5s4U%+PwmJ8nLu06c!a60x=>44&0hQR zzbS)j*`YH|s4QL32JD7jHTgzZp#H>Hu5l12ZA^yl6c38*&7Kj)-c3B1c9MK-?Iymre^Cx7n`KcQSptcTI(Fl^~U-&&&I40YU^|%nF&r_A*EZ)#C+&KpnVnv8<=WIGfISR$z-_+^nUCoVcQwWBBt7J|>z$bsU206t&wXU>|+!iw~P>{WX4ME~%G*&P*O? z4;LNH<ykmJzR z)XKe!HTZ*fV}dtp6g+gYb%lm{h+lbqgUJa=k}l`VQaTfe=LI@1A~;2hB~XfooETPnt4f!I^Xosp zZos61kdXdSKIVNV?l18C&q?LOjlzVmr%F-!y>QLj?I4ez@aYE!7zwQYdImgZN zhwtBQT~IbR^>_R{Ui|Ly&{O<9Gfuyxeh)_`BzUEzN(m~BMivE;F?|PXq7TO}Qu9j&(<*`e{HRWxt`Yo|9E{)>5>W`Y43Ne><5@W2LvV>8 zWSDk6DJ@KO0sT4Z!m$a=q}g9cE?#3;*RCybpU2M_cob{SjA5ye%rq8-ngiwi>{7N# z<+xE5`>rpeD=@P9ip9)8>Kmp7sq!MDCLg(+E#MA8IpxlNXn1KY5I@ZcXK2sPxUs5# z+P@IQxC^Mxi-ySEpi|lTUd)p(S??S-$o$*>2+~mDzc0!{$F_RAQiu1SPTpDVa6;^<&8GHphnT6hUUGHsW8Ufk zJ5%fB=}@8G+T2cE+`4Kzo={T1bFs%_gJ!a_(PujPhIO+^if8Rw6TL<{Sr4sp%k@_v z#xSoF&dL)cu$Po)*lUpILNL%ZW#cpF#AO*h(ifoG>?}T1gM|4Q_USS?RQKP;f#csU zWws^R`0T51)B861f(T92QyBaoU;aVkH87H95M)%uNTKve$fR`BZD4XXiN(6MUdAW# z_jc!sKS1b{lfzbCA^+uSqdHOFO8B^+#Vo)JJ$4uKKsy*t;VVEgE%;vyNGyd|9%{7C z(_bE$4=Mk79NoO%q6+oM_m0RNeyr*p>gck9l7{|0ci>XB{Fwu%qn&$K$O)>FX-X%< z+MQy25wPPJg|*E{G+}}~S$oW!HjZRSr0ZpRq?x-*#3F4ap@)o1}eEIN| zp|mx})_z*x^^8f<9(D43S|(cQ^5%YU+s$`8cDOvA>AC}daZ;fs#cUg8GMQ|8+D-^2 z{xJ~1%Vc6a{HbTx(6b|*LZ+(8nP-HxZrllK7FzaA=g4){N%EdHYrEK0UQND85*>0Y zs2QRwz~zj2IYC7asEbn}+bFqpZl}BR_i*q3hkJY=-Xp9L`3=R--bJ+2qiJiu!H)QS z{d)iyT(r$ie;r6Cms{(AGq&Lm{65_UVp$liQtPis){69D%Ks`eR5Gn1(lM#1x9II_ z{B69OJf8Q(39AmP78@(ivI0nmLWdd$^jZy8OkB4(?deC$dS<#PGmJ!mc=Ynmz1^M% z!|%O=fw3LGx71G|<8IB@R935;u2#)pT9|f;iHw?}gcu5m4b>$b zrxsl~y56E7pE%)b$3^mUF7HoaEHtz6k%##*Yya7vM`Yc_P#8x|8d#W1Sa-*7mwVgi zw|l4ixyy~U)5jYI9XUz~IkEjD26TfM*rE%iLVUC+6eb!Mt_`qj+?4VQ!V>-l5`Q-5 z-8VH#WCL(RNGh1%+@ANzAtupTlS1oR78q5=I{I3(;)znTCF%JQ+4)q6i86jYiGs_; zzrkV}%AzU(eAH7MnqV8}4z22I6aA6A`^l6k6Xo>A4vL&}5M9WdMM8p3EKM9IE+k+E6s*+U z)ciEXoab5$1f~(`xn6_9I8;F$qIoJ9+NMmSMTPbzes4&xY_vy$Qx@bc-oQX(vz84- zL0;pgBKjn+6}agHFN=xSVbHL|bOo)CcCxbm>ObX^&UXmgSSwrPK}Q;*z3;ZOVvWdA z8jI<67~aQG0c}?ibifOVH1r=Q~iUz?E!S`f0 z;uU2NRejbI7s@-nWnVHu% zGXCkZBN%Mkfr7f;c6}aLaDi)=LghZi#5+|Al#K+HA+tlcIk)-ZN|fRA+)R5(a%~C= zX~_#(7kU%psDW7uE8x5q!IiB1%xB`9QT3Trmy_XQZ^p6rsEG12JO$a&yJwqP)Dn%FAifI4#d;u+qQPrt%}RWF#;Bw5civ+y&aVno7D6L0-r zpV<3A&bvRKax*xP;P7WgGmUet&_S5pTOsiYKz`@Lb^ObzZfYxMc-gIdvA71@HvP+)gY&0~?4c#|>^@=O zu7ZpgYpdfXF|#lfuu#F%a>1Ll&E`vS&{}#rZ%B7%9@e=e#kt&r>T=1B7C3q}HAg1f z^DHdwdn(Q)tP~7HBv`b`nQI)$D>C%DXM(cP&SuOvHk~>~hNU`|~}MVrmsA7Vm!l%r0$JSP0RbUc@1`BnTWK0`*Uz+8mR$4^0$~=DatF7)A_* zI*Y2THYJO)MN)IQGm!SMgHv_U1AhPP%S} z9!_R3XGiBpkOJ&CD*EGL0|0FfnKHzfJ)w5R}v&p=th?T%bRFr~7aXf$;Jsoj$ zzL6&~*lN+vwgagOl@ge`Tpc;fng*iyukdcsUuN=2WvfA}a}FANkSbm>NsWb6LaR@X~jooAlB@(^UeNH3@n6&*=gWnO84 z{LTt)@CQ`IlDVJ-g^c*xYqSUdJ0gC3oI;#DzyIf<-Vp1)1|CA6x9@AF7&7v1Cm+x= z=mJxWED#jStPAPu4rxb>5$1GC*f5NvOy({&3bi>43UMj&0%jo^MO_L0bdut2<_fwL zH7EHXFz+ud`k$8@!dT}FJ^Uz)`m)}V+Z^c17qi}QC1SrfWpBDtgBDJ4Oh*CoiZJC8 z;quP$B-E0-4Z_zF=B#&YNabSIv#uR~myhcjZw}n~=eDk?Yc#yhww~%q!ghS+x8n067xvej4-g!HKEfU$`eLfK|p7k6If(~aMz!$^;RO!_{%Y&f&QWp zrus>(G5igZ{iI4j68uX5YBr;l$~&%Cca8K9nN#^qF=eK>y0$-p5nJaYb8T+IjvJrG z<8ZlVwkN-s+Ai3Z1e{DTodU;rM0>2(xaw<8qw8MV27@l>1gFh7J<(lKV?9kRrAu@2 zX>k2d(T@6bpAJs2GpM8n&6c3s8ADEQlB+=bSG^mFlfLr1fjUL7)ouav#XltbOnp;q zPx8E4iW;?6Ws1Bstb#$EJ`XzRki7wlV(hn*Nn zDrUfn(-=qADSU2=7VSGc#M+<^E-^TLE2`1=V8Te&) z-wxgH-376cLe<9 z==ZeUji2j_tCv#@#4PLV;_K_`$^P#1wn4GT`u;c1;8+pHF%axiDpe;Ypv3JIjJvTr z^~Q0~Z!hm7PIEhFdX85^5~j1_b~e>~D`guih~h6zi~~Hh`0SvuDy;_IQZ#AEvZ&uU z0tvAJBIy2V4?d^KqFDd<8y;lJ$AgfC1j7Kl_yk>t{<}Z2x|b#Im7QbBZ0M{nyE~0; zJafI0lZNatdfonR(`J*hcu7yonRM=-aj+Jo5Ck}IkT3qnORSuBn*i9&h_xMF}d+6 z84MoKb8*>rNyOolr$2M7LgV8xm5GlcU$aU<6iADw-bX-+-U)z22v4Y<=7^vgSVnGP zp~@&HX3*Q9LC@v?dBe-<4pPTj>Lr$Pit@oGDyMC&(DK{GL|lT4ptdY-xpfFo>`CSW ztyhAhxr9xnE8h@;@y;)PjX1rOCb9O66QM2S@WZI85g=FVKbK27mD8Btl~czTBLR%>agC3a3GsQyOeCe*kW@@rSJU-)=v@#b~W*yVaZ zQv?xh+&DtmIlRz;@t)7zR%MKfy_2ht$F~PbX8*NsdoID&i#>vKe()MSl+&Xy>?52g z&227#wY9QiQtnJDvuZR9R~=Q`Qr!Y3{oUlXMBwhnH2z8tvkUs=J+u zHbd`a&11EQIgcfubZ1r8EYVe$6?eF(bR54(cc=*EP(pjr(ZY_+xh)QbKTw{(1;X$0 zdOzn+-;-0&fc)HPt;Eo-fWNLb+2%iwGmifUD?rr0u9l|d!c+s9N!T7tHK5sl;|yS3 zRs3{&x2znhXSq_@?SQSaK-P;^g*0P}io#)CsiM?9t2ZU3kth|^Xbx%|np(Q44N&7w zyJDi*tiiM%{bTU-h z_++*kK)sO!qsG`K!%Jk=(&xkinI|iYt$ne#S<&`G*<_$61^x2cXI~y~f(E|?s@mlOr^}t_LmB8nK%#|O)AC18>D;|u>4ntPT6~%o? zYhP;G#fW-}QY|O}H^F1`&NFdD1IWYmprMa@U@tLlVq-Z(OACuXA}MF7N)=U>gx1t` zxD}R^$JJU%;rOaUKy#=cnw;xy{GycV@B4qV3FC2MIniKCK1l}BYrp%gB){Wap>vtm!K>Bk zw@>Zn^_zGZw^}})haShs7Lt>>bmc0>Vz}QKw5S(h5qoot+|wkQC5t2Wu-6NIoc;Lg z-mkyB{CWBw-w}FpItBWm)k*|LK|<$}Fp50Ry_I{5S1x`aLW)6kc}8e&i63fI03w9z zg~Y4#o0YPtw;>Y=$L3`NzPBujnZSJNP$9iA#QqgyKe`Z>S^A z86Q2&aje$<6U;I3`3fLW!9om(;zUlJaZPv-GREt)^p9ps65+xouMQo^B-7{H-pgTK zEMxV5f~6#MAx=H&hqw;t5%P{nh$kx{aWtK?cvTY+vPF$TK_cdEz<89YqJ4rc4zEs~ z^W(8Q`s-?RK5@q9X7ys})nS3F$%DJVl7}QBRVm|%^Ot*h=6rCjToC7+J69h!GMtZ( zowHG+bWxR_uW~RHi`>~t;<|%G%)t{uohrG@aU4Q+t*?Zbt`j8-*?s1V4KA3hqJYtr z34DsF%pH4*!x$35e0`^kGqzZOjP%vKeW$2#B31mGFNaFZgWM7ug#C$U5&P_MIXx7R z)7jUcl^Y3M@6OnRduNi|?x4Oh90YRfpB91-gctVaOJrZW?mOqwbDPd*H-at#~iYFm103dv<9!=2a8>lxom5xZNCddV03@W-5VxNE#;zeSrodsp3Bt(IKQwjGBe!fG)&tNNP*5{DS zAg*)VnmGI1%V`R>A}R-_@J-(J`;sTvuHW-^0jOI(xzo)Gp>6$6Zz;Q1w{ET<%69?P z$7t)BzXcdd+I6b7Duet&RSQ%fyjW#(eEv1yl#fiBrLA%e`)-Avn9T4ihi}at++L6c$l`g%tVpwsb zX%}q0xr4rZ$=iyQR7&OTeUr$BbuERh?R~49xb1`zX{>n!9<|=<=Lh{f(a$UWywJ~) zety!=k4IK&h<2=QK9nAc4$vNYgH-r;Ov7YDLt&?i@9*_qc9gPv6(i=s_e?w!AidIX zn7wFMZ$>X(qz85j3qtrBMXt@#O|`H@IAVO|N@h9$+ZYw)c9H$C{*0* zwqd?D*T0=@-A4O2Ss~W4VUy9^aF``=Po%onwAiv!<{n(A*23ynZjDsZi=kF{P~(oC z#C`h+?rI57o22zfmLy6e861u0Vl8AdUlzTSZ6OsZ@ZWSc{l1ZPr9>GaRZnrefHDQ1 z*HZ>ImEevMKRv?S%iwXWqr5zK*i#337f)ipp|Lq8)Q8^4eD(W!U*$G0%{KP*(fv}B z*e-f9kjxDwL-<5^GiX0zkE{DUbYsrusu!2<$1yWsTQMiXzC<(VR(ue_n@w*TP#1ybdt4$+A@mO%mvk9 zDpo>GL3+RI6As2w{i^Dd3gDKdsM8tbfEx4D_Oqz+mZj}7jXk*}G+>6I+c1w+{GH?2 z86s;(;jUZ;Y8K2SWvta8$SjsFR_(IH^II@Rti~`spW4nCwF#@mt4;AeTAyMyxiY-i zuy^|aY-Q#Uy91x?1AUBLbqQrG)`*UAx_ijE^8`vQ^R>AIVfEvIyG+s5l$e5! zHIB7o^d+zQr7p^%BZ5Tn79~PpqC%BO%^pHp!29-o4P&0@=1i!sj7w7to|{AJvx!`C zc8`=%tG|NS1qf)tQ+%1z)$yIRfu(BfSptE}VoY5Sj{vV^uqw&PK`sltJ9sPb%^4lW=KMnrE(`~^5d8V*H46Z@H zw$!p#T5^7=02@6ve(!!#%wn%1Z5QTA@$TUNd7P*unU_MhTk6mS+H zd+V9l>LT^*AjvB4+Ovhz3Jtp@J9YB}u@b87gng^?XcyPw#%}m|tnmah85G`n)BMXD z7`I@m7XAiLBm%2Ky-C##b?n>Oik?4*HUkLu2Ep}$_c*JRWc@#uc}zvQ!~&w{X`%LA ziT|~@(EbI@SZ#CKI1v8MuTVO)m?@W+>&@g!$~82h7pA#{0eYF6((wo*Kv&!HNpcc; z$$vk|vMpOSV3>Y5e`txM-F@~|yL$INSO&G)3^^{O2w03$>~Nc}0>nBe&9j;hJ;Va& zAYfmG-ZWf%AdkmC&)-bnygh#V>o2F1Pv{=|zh6zn`s5>a(P+gu^3H0tkRdP+eTQS> z14t=c*%aL)%FtPUZ-(aBM>DnR5T9{y*&p^U#<#=2>``yf9d^Y}g9uSdsJ%ekb|K^D zCUc-rV2NCz)xz9-M2@jE7r1Toh#6;PJ5q|k#gU)vOGR0vXrChrIiUs}W_F}+=oGd5DB>aYfmFZU&XFs+v*_e= zsWhKZZ(mWGcDo$mgiB13BNAq{ho~HRC23d0Lz<+r6oYJwkRc&_KW!q)N{YyUkwtz# zIVtr{vhFZnl92ag=rXaVs9T_pk`>Sae(1V8n2Oyr#70}AfO;~fXib7gh#v4I9OXx4q zI^4-$+C9~(kim2}FJm@DF68(gNp6L}UvHX&I$vV;({Gq{r;NBER}8ktz6Zadgk+5c zroxbe%Bh!Qm}sA}*ajt0|A#?I8SVrHvuh%5k{ukNBqnr~YR)wgk;1sR(LLgSh+$b# zN2Me(De`@#>@DYYcz75y+63iqf_)|SDXTB5X5@&vf)vbBSf)3%f607M+0rSy44;I- zCLpQ>FU_w_+GcuBt8vt5l}!RIQ2DaCh`aU_gY4(mVo;q{yDbk{>gZWvQ?%DJ+1V8i z;5o2VeFdFP2UzuFWDs@tyf8<^#=8ZMHzd6Xlqh4 zr&FU2x~Orz5$kKspE2M;WI!=$2;0R&how2RKw~m#6m3^$OG5cY)FF4`u_Q+paeNI< zvMK{41#V)bIh0wduDb0piY%b;>JSz<1zH)%$yjEYGlL}*(TvkHlY;ZC4<#e@Ad1{00x6hSUfCcZe=q% zk^Smb#(?iS;N&<1G)Ww#$ELKQ$Rqc08HsLzdxB>rDWMJk>@yC12a$PpzAU7GI@~Ef zAH;lGNe^hIfE0C*JX<8F*0TSietj*?h~!Z2(I;W4DMaUspmbMEAg1-WKIer>Wbqr3 z)fik2?7RNW1(N51;$jmyM=$jCx>6iff7QA9t*{`eetdWyN2OmF z-@F2fxg@_Gt{WMxfUP{KOWB>HBxi4$5}D8IZMoTRag~UZMglIr!C5hq;!eAL@p(Ax z-Hh$-$R2dZS2x{1df=(_U|-&jM`iexN3;I3s_C#duoP4Qfl)V~YtOa+0M(h#Z`(E$fbae*c<{jiJRn^MWR2?uYtk&k zf;4CY^e_(sEmJlZiBBLYJKom+ef%L&q%0*p6f>7YQPZOQeeXR{kJ8(Va#M~*E0X4z z6EqQ$(^MvMUlK8Ybut?l%V9MDwfsy9Xy?WpS@nbeslWf#~;oXzmY9vzuYX; z_~M%8*Wfe>HiBXCfFq-0={zXh&*&2;YHR%MMBl~&u_Xw#! zA+e+>8fRYk^f=AXgjIQt=JUD#?Fq8;^q)73=7^CUYLhTIvFG_>{^dkgocR;J`zc_w zaI?{u)`0;pq84ZYd+1k`lT-rV$4b8@^2#yx2yCDrNox@o`Hg~W=|gZ1AR`X?M~&AL zZ+)0v@FGUPZV;crPj*ypppprBl7tEMl|6cj#%@Iax?+t}?z828Ka3xWx1N6Drf3O} zs~-WLl3Umjeu>p;Jx7ZS=Yqtge&`xvcNPXLk|F6fNTN=169~Fi%OyDog8uU8Ay7uo zq+3jV`Y;yh2D3F83`g^+EuDDsZqdKG%&AE5Qb1lwlISB3mMJfj_0Q=h<@}tApX+&j z%Z55FC%KBj_T|$aFIH51tRb~ipGScot$>TtKq)L$4c*7JvTOD*M^le;K(oo(|4Xw; z;|rRqDsxbsX{^RY|qk=w2o5bpzRbT&30~q=juL!#+r)xExr-BT0@$ z99qKlk?O)>>h>pvQ_!OkDb$VXWrJz?!kz@mcGC%*t|`+~+8UFX*QaTkGuRTo<5ZG6 zqFO%loFVAawm?_^se0I{XraI)Q0SxY+_fInK6auG;QBF%TRYUC>g_0#Xk5Tb_ob(2 zXJ^OvwYW8&LF;UANuiVveKKQybo?5x#_8;ps8B^aOdiFeS5x*0NT|}y0RI$Sm|Bvw zAa3(xhp;zx`~+Ev2HNmWYsT*GMit+nt-EmQ|5gPSkNP4yZI%^I(uxb!ZpE~i7$+xh z`7Qg@Bkl$Qc5E;Z&Bsa*9=E!-aOxI!Sb5FM7nRo>sxoe8t>K(&-XUooK7q5hobHYD z;HDXoZY|)*9db(>8jF3AQ})Zj6 z0%1FhaLdv59ueGZ9rsXwB)S!jLr?!8d@VRw?2X0Lf%iPhkwvNB@whO#H?D1%(i{tMr^X}N z1!?w&qP)^=vWM)khFu#eN_CiNsnt;>CmDT;NT;7Ke4SXSOzAnn##Fmco2yf=Z)R5* zO{1xy^??%)wt)=wa9I+zN+b+9b`+2AW{Dr*S-*b)tyWua;zksH_pg{*RJJ8Zb6F|7 z5O&38NrJKg1Z>+CAZxIXlTnSwof+rSRsMUAZLkMyR^7IKz-G>w@7%wcix2TbY#2Ux zAr(MusRZ+st6pM|-4mx{M05=@rXCRYCRwkN`;Ywj_%t|MoxMAL_x8>C;uaoRbbYhH z^^RdA5=f9lo?<*AR0#UwdOS?lQKwE6uuy^66RoI()M~zb!0IJm$6SI~Okw?4y!MdT zIu5xHd#eW!r!;_tj0KBSu(tz~;tGgE%t9{t62Wr}o&x_@8Ya+x_8?9*zw7dEuv+uK zs8EZZM9;UcjuEhkh6D>BCHcg?KR{fu5UwEtK@}IqH-q4Vo;^|p#N&}vAT zCQN|eFJuVjW?uAvpY?~+!Du+$y3#3a%NgI8FO2&v2<#fbCaH1R>9By<^CkJI+a-0d z4*3S&11H-`+oqpot%Z4Lp?-^P%>OJg%uNH?CNZ1!a8Bnn!!vVKbI3`lwlK|(@tp zpX5z2#q7?lw?`*O#}?_nC+TO)3t5YXF^j-zrYpy#@OV#ne)MK5#LtTGtQ4MAgr}wO zv?4qyh5HVjmkyolqUCPDDS<`zq`}toelHhUKj~%Mh?7;wJlseXzKWsu1mLDCE+%>F ztszp<-e3rAty$QVJP{s@AE@9j8Or{KkG%*Rm_e1M9f9+xc9^+SuUMWZwsnZ0vFG*Rnk9nsk5KEm7EzHMyD2+XQuBdW z^QDzWy5UA^ZXyjXt&#{WW3up>+E4}ks zOU#2+n76b{1RMw7dm#3K;U!d(MtYk-6ZsfAT1YzxdVJ}w;_v>B^hpo&MRu{se9kX= z?Y6{NWxxR04~;f;kUEs~No&ZW1juQpB7Zdw|J(M!9=DWA>Ot_QjMWca4DDj1E_o9A zB;tzTwa+J%$b>1n3So2g%UYD=e1-`_Y)Fsq{2&(;L2PWCbS{rb)?JvwN6Nw!qMfbR zjcb{3rz$+q$H_($+o2`O;vGG>B;_P4`Er|NOYE5JX1be}v}51jeo=To3jLsDPwqK)&Phx) zfC5mcDijJ;g+IMnFV@eV&7;{e%%Y&37g;J2AHy}__|*d6zdtR~U@dYR5`@MNJP9P77HF}vga zdZ>kcy&f#qqglF+GLvM?zk)CiWJQa#8g@~H1S2+a#Vc%jaWbKc=X@^6LbH$kg#3)> z-RyR=BJ0$Yr+Nn~$9q{ich1hQqCr^`(dP1a$tcoeW@n%qWt zK}NP(rO9L(=1%~06@Fw;2&9NC|U=)IqOin81l*EIpG7hwjNT4zxX zP$uE>HqGKd7AMpZ9s5RIy|Da68TNHUn0PU(akcN390@Tr1~)xUG{!N7VVa3f5ptE zZCa2;>m|{2og$cL;n6h7hPza}GTbBuQOYchz%z$Yw4Bq0J0;4+HEceF0({DZ!VS~i zMi;~JWHcW3&)>1nqxBLNwV&LicGET4`3tUf=f(VR8Rqshh;~3UgfJ`e$zAMlJ}E8I z&3aw2IgTDyH)-;~ByU2NhA?n4CuGC^*{-cNFW8=VJ!iJH!49%?MmYCcB3gVo4hg_{ z&872#<^MKp{XWc&HY=*E5@AgKAs^VUtpQ1RG5f^;jTc#ZhbV|dIlR4%vPqO>sm;@* zY*jjpzeLkj`agttuDdzeaK%4AKO^Uma2d>i-U34xc=qeFfc!@Y^AkD8aFRai9d|EJ z$CHc8@!)biIqsgGzU?0V61)mp1=*hVK~T1SE@^ny?VnFZhr|A042^((?n$e4X`>@@ z@EP~ddXwXe;aQh9{M+7YZyuZ+#0PmWE^R#Q4NkjS)bsC zeE-?AKf~c6SQN!NKY01_mN52ax=;45mkV0)CD{z0F+fIAJD%A&0gSnI*bfUL#EfE+LnFV2tqbjCT|GxpK z#jrmRcSjR)rakWe6*?ue+zU5Fx_2vZwMX`_FyAZfpLBmQ z>>W)AdM?J@asT3+p@3*JMMgFkn146}UZ=n0A%gz3j_E%IVVx<_e2hO>{Y|k*Gx#+l zU6Vckf&M1PQSt9AC2C4|8xh4I%{*VDWR4e5QO=6i4}`TB^pAN&wz--51*m|(2pG|G zbRUvaG5^UWAzNvpM5mi<7V&itm+Z&=ihj9Wrc?GwjhF0Z8QC@1&jryOig`L?e~}{_ zhp>!iQIbdOXYvvLU4^$Xt+$E@lIRG*asmIsB(sWN`R$_P{;&ncK;J;`s-j|pY%4Cu`CcpIl2DC2qN0hc1XUM9A z2k>f&nl;1xlhnYH(TtUXOey~&_EK2Z(wXb%)UygJzhemU)r#`CVU96w!6O zRR~(ojGBPTRds{W37ol{W_vl=$^DJT-FJGMBE0ohB|=c=Jg6?DKPq$2$GzcUZy?7+ z_f^=m(dwj#STpJ)R|l^5-}RRDzXULN1Z%c|xhq$Q3*eijh* z^F>7VC7eD+$!56>b~Lfw6*__0m1Up%Hw;LP;d9=KtVAy#O{@Yoma1tf+ zOrksU)Obou+Pgoi%CIfNs7ZBzb#Op*IUK6Ff20+*`525I(J^uHd{{cP9#X08ou;|S4zM$$Sb!1JwFkZTnkcuTW3XVg zcj>322+k36K_TtkilK;#CBu=htQlwkuXc*n`htoAKG;gw=r1M8dR0;cF3g-I292T~Wl^7d3TeD~^A1t2bm+!?a(VldVmyx4k6 zb#3ojkDMyob>LkTRg5f6jXvU=plxDKLEP0Omw&>>y-(W)p?dmK3vmgkw{RDQVa{z= zb0)#>e#hZcM!pF*%Yv1*cY{}EeF@r{J!>D%=feneqbQkExqX~w9N>Gn^KEk!cX)m4 zc!uL6&ig{U$w3Ak^PR?}Cr`a5L`6?wL+ude?hAGehr?ziLVZ=q?tHg7iargPQNz2}kSfMDqi=mE=(zzCy%yH?N>#c2BwJ4{Tw)Df zj2PPuFP_8rU;~$j{bTCG66$!2>9n$uWcd2f7mrd8bV>(mC9-2j7Scbx_CjEF1|)I-gQM^ zA>6<6V-df>Z=2+{Hag+QXtyU`;O~w*Bls~jvMTsNtuunuf-u~G9VmZ^Ty`9|b)>#) zb>!(oG(%Oufl-W*OXE)Re7(rR++F$sQmN5Ybc=xleW(=*s9X@15kI@#pkxE^r0}H2 zg=UAm8hdKTmX;k{!eD|c`24v{bXnR4c1T9|?v5@=I=C?CD2B!)w|ivm=v>)R>{hjf zV|3K9X}E(jPRFE=jy$O0)a9z{9b4x&Pn2F28`YW}9a+*IrHa4s%P`FbK);(O15(T> z^^0M4QvO5}YW~VLv!dZ%UafH>JZUp3lS zW#Ip7(tk8ZtC%Ejmf`e|))9KUIWNW#pZP#_7I_mVg^y3X7DvTD9?K$HplDM7=6jpT zU!rgo-;=YA#%=a8Tg+z{r%0I`70#Qb)pR%vjW9bo#@&v?))EURRdWy_~8dKQgD?@6Y{^C;Q0^vbFylA5ry;pR!77fv|Gq+t`k!^HLKpnA=KCmx!J4l6CfAMxFnGbk=5m^(vqlI;*BeR2pN+yV4loLX zl6jcSB0~1s5HZlKKJQyvw7|(nzua;A;zM$M!Py9O!-PhTD1x~w5QgO6?FdI9j!|P- z0Ii==A^lCLpt&(9kJe$#>Wg4+SeSYtXeuy zhZFF{YuLnZpsdl@P~94~O}A}mDsv4RdoOb8Hny8#Yl`fgDlE2!)^Jr1>YF#*z4rX# zy!WAGOo(*|8;={&f{w+8R@Sp&4^)D!1$|N{^OlD#SR+DVEJ(4OWi`WI>&I5GA6$cX zEjfRBYZ{hD>b1{VU3&4i-6~}2QE?XD_lt<2X749|jS=0$tt$C?3A^iINL0&A+cT23 z9xy>AIo*X1xZeEgb#P@19a-rEGKO$IXPxBjD-HC)0dwmo@XWDvkPr=Te#9ko|!w@*|EYnqa!=kYOd&5{M^mI7zD z?OwgnghwqE6#%6lOHG)jR#}1s-L6-2{+?a4qAR%#sm5wcf}Sa1;vFk3wR8l*8&>nc zThG}GYvP9)H5=l`+N_)52O2*bKh_Am`0?us6@zB#8w{^xbQ;@KlDT%lR1d1ADVD)r zgoYiN+u3LzV1g*;Pq$%NSGuvTUI%)M^5ZDt>Q$=;tYwX!`LIu4l=eG*cGKAK?`cuA+nH(9^o0kLA4q1aY`3aag|0pP24EBj<#)raehT6+>s)7pJCqF>WoQ=^Z@(5v~vy)vsu zu~^IUo=gYwnw=PpDlnw3_-nC)xXG(zG|NGt7I5n}5c4Upt?H(Oyb+qx;n$NJq8|W8 zE_1lLL}F2-yNz!P<6d;I`}S{eCur6ot0)6>t&2X*(KjX%rJ(LVd#``BT7B8IZ(BH) zWX_c~Yk`Hz?*{upOHZ<(Pze9Y@B&B&;q70w)Re0pk+lzP;h53t27{CCFktXzOt+dw zK?JpeyG2Ch`lonqX-)K^Nna7i_g=V}g!4J4&9=O@9TT06Ud9sa6>ud+6m8Ng>5O~` zrkjEwHejL3^MJ;G2VpSVxJHb3L13{Jry)5tv+P|RNGOc zdHF|e{5Z|9Y<_KYV%0Qh1WUrRq0zMa;$jrF2O96YW|)WzkCL*iFDf2*#M z$e*o9{96-uLt$X#Ht7UcAJBX5`CEr$zG2)^9To$}97AK!=S9WTm3IGmLlnVqwg|KZ zhxBvkK7?W6N=1Pox2?FRQYkDw327VgfMO~U9i9%oY){uyTeq=}Yi(Xe6jUWCdwqj> zhHNC7h&y~*F-80<8d=24jQAomnBI<}ay_z*0&h@4#a4{&Ey6YH%8eTmaUHsvLAlNw zrQr>(;)Bpgnr;Y4%jiLLG81LtT&s`p&xuC^n(eB;*^ z&Y(#vfl|{zKqop$4dOUUS1jkJ8~Su?Rj}AZBowL|O+~<=DfgX03GuIeM4ej|O6}@p zx=0;L^ip}5lt(Zrqp;ebH0Vy6wM4ysSzoQYe090rTmoyp130N6Vwk6)>X>?&wjM;K zqDgO?DRyt=Sp9jO;%` zun(rcm+2lBAMmeR&~V_hQt`@+&0E|=V8)TvS(VWQjVtbh_hHx@a7`9U{Q zAPUBIMX<=O9)qR-fc_-gHrafj0;FlB?xGNfg- zro-^)P~0;`DsHT811+t&#-ubg!K8sWd-UN0cy?A>Oi%-z0YjeB#1|Wjcss9cPxXJr z_r_iJw>;nZ_`+juW6xf-8=ShV)spE3=%0*gA4~mS%o}~nU{~F$D;SdbI_@e-;;IBAYsXEslPRtDOo=*R2|H{w#P>aK_d;8HEl8WwV4q&P!Q+XjrG z{>IpY+7(q5D$aaQN|D2Xs@YM4w61tQp!Yf&DPVUyCeyipd9I6*T^7CIkks$uU8c}r zM8nopEVAS+|E+-ptqVAFNf>Op8thpF8cY@PCkP$2gLWV)a(4;#=dO!11xCLjmml2* zLQ!TAds1cPyNJIdd&*b5E)}Mp4$VbmuSB&SNVs0@RMXd8ovUW105L-GC|{mSFqW0Z zcYKoGBejF_RTJN7NmgilWKyF+&P~*;Z62M1BBdX=a4J1WDcGK|XpT3kF1}i_96iXG z!Q&U%k&ZBXAq0|H=)6|hJ=EIr6_2QQ-BApnzFX)@ldm-o-!WOMw$+JQAMDc;f!%{= zTp(QAP}ex-dWJ?V6m%eWMCwKN>j<9wgi%ewH;>vCz%@&>-4FUjup(lX7UvHuI+OO@ zA|?V9hP_GTpm*zx+R@M_$}nIwLlz$)e!H1P8Cc#GV@=V%n>cJ(blyg72*$*`%CJea zWhp{5{#Bcqv-8#6)?iAlIBD^uH*BB523c>TG4o@Ti29B`rYf9^*EOm~=?;Wn#%sat zuy?nN{;LPT*txQQ^+(i*4E9yFn<5${>0N|=xOBC4LfEB^Pbj9kme%%GdrkU!hTXgyEt{Pa`J}YO+N0Kl;}_;NuRU0;Ad?yUY<3 z{%dr))*2uhfSIih$E)U8Z54V=L1I!$6~~0q=g)d>d{;in=eKG~Cpvs#h1`Z3B++a3 z9YzK}x`?8^nRgbC>{D3ug;?{^q-QXuA1u3+)YtANHgfOsU8-X-rgYkL0ho?a4j7X* z%GC*)367@xGh0>d2%?$CydJ6-e(;@0lGF#=l@xVsZmsid@876m#~V6Crzu9f$`x<* zflBhV?;b~p;Q^nLv~#RcS*RPC^oC(>g7S)AiaIKGTP><6Fng2P_KgV)a*?Oc9-!(DTY@zU9eWwHc5!tEM6%We|{mP7NU{|dj)5KypQD`J#>N@56 zO-_0$0s);*!erABjzEAxIScUcgzE9T2nWx+%I)$M^T-0_US zvaT698YQy@0)Z6G^8NeO()yf-%a!|?J?FX~Co}W=G`+`$M}W`M*$WpAsPnV*)Bb~9 za6}LsWdd_ziCfc6yqp_f=J3?F{vo;#S8J;o|CSqFyx3pOtq&Q4CBMA<-(VjgzuIrU zyS@g)&u*LiyZ_?%ySs08UdC4C&6=$Dyl!pqX3XO02K*Q6Hd%zpZM015)+gG`BF*wQ z^+z;^_de>?F4B2gR#h!gI9GgLC<_29Cobwhg#ZR;8hl5~knJ z(sHdtuWlSa+Zrd7I%(|@CBzGvngj{l@5uKFsg)J^oyI<_%!Fj-Kj>QtBU3<*35l)nyi$e->|*W?ueVIj1yr`qx%v3)lY& zrx(}t(OqBj094EG(OfJ$Ig80QRrwi}+!6*BUod)|k-6bA`o(BPHn1{z%kMn(D-iie*>ClhG291h zmop7wyUZ7h-G=f8)a~>0=j~olmR))p*64EUSvK#Iv=m3~dhN;{0HGIy)|%rfr5;0M zwmYpiQe8ZeBaLNcaDBU`Js*4zHHq9v3F7p`vKN^S!?s(M%{XM4j82|7O`ZH8lGRP4 zSqLwV!M14F3_5{nC;*dRKn~>ryBJfq^``p)3u0p%Y6Q;lA=%`i=b7BvrV-HruJ+P{6m&9w)GIcyTtk81;raZQC0HmPF3q-WJF8kfJ>}@R&?n zrH>UfL=jA_3yB*>SW+$d)&U)B`$DLK#m(SuOVW_xbWrdw%KD@yN_vtf}d0gV88J z^>C5V*f&W6YM2NQc%HcM zqD3^~U{7I-$Rzp)&-N;(AFwy6e#pzRyXqk0ynAsYfyn{zzBH==?S=wp&%cV4vR2pB zFu-tt-L@u^kiS{Wz+dUSDLZZb5`Bu3cPH1^Z_|6K;$2_MTG!VDa1s?_w^HWOeix0#03rIW5F#_`rBcEKMQNT#RoRduJX*WP^=R&n6RB)wBOfIBax$klxCTfvokka)D@Dmg8Plp#1H#$+#&daDyA8!-lv)*p2PP$tbi&jOk=+)28 zO`PSB2og~&wu~8@#!2m9F|GI6Cd)`A70i!%=L7H$2+8QCMFG^~*(iJT#Avf}|81ww z;TmC(vu@wB=}>nD+x{l2qduEC&jkl9aj#7d6P*{7RR?Wx5LH;Dz4^5&zu|5cCSY_< zS8-9u_gRSU1a>^4F2#hVI4=cMl1s~+Kn)hv;`N6|<3l)-FoFR$eEA#q95~od83!h+viTETMUe&KFGFmp_jZ@ZNk*bVco9!h;|p8*T(H$T>~L6c+(_h6Mjj zf&4dkC*Q`%ONTV*9_WfnXzQ${zg?ApLhq7 zkO_7C7Ju7LNbb~$GITr1*fVk9)b{;%;I@^&cnP7O)Jz~mVN=7WZU%O*^^F>~h}Tt} zZX&M{tFuNDT*58F?9yLJ?aj9@z0)5A;D>*?h~k z`y#Vb``FYV_olzo_o&$pb*yEXw9VqQ6C(8rIG+!~3{G;rdwBc}&?byvVv(hH z6uVr_@AANS2|x+jo(wV|j82(bWd5K@G}I|UUMjL$aCbsPa+GEI$(P;<9t1z+BDqFK z8!Ek#K7;GT+lU-(I>C>6Xg~dg* za^r%;8=s}gyPzq{aY8v7Qk-yMkB4&!pjXD~^DJHfVt3bh5`;gA#=%zGXcmbFb+&TP#KgY3eFjo5bHw*%)E1uFQVQPEw?HBVrfN_%@*UM^buk+&$`!yNw@r z)EhQ8LWu{6og;$|)sdBC*G#8>Cp$x10)RkC$s-c#f%@G9{o3Qtk9aUq>sM--^;Vv)$+<#R@He$|qYnp zPCtV12`1=u+r>Ic0ECz>;3I)M5?Y_434OS@#tduAVKHS~kd#LD3sp(Ul;-@M=j%mA z`n9>7@^tt}W^Sla0V)RFQN?rFRnkF1XneiL@L?-fI?ONTs1QO7%OsEP11Wkl+|K#O zhBC;ovPyP`WB(&X5xvN^*d9mqBZO3w(Q`pnS47nsYZDq#+O)92%N2X0mh?btUymb) z?2t!OCEA?mJhZGr0>8z9e%KqFb`N`Jz4I}5Z3MfE7^4$`Q0skXZ}tF%g#bM|*@O9F z9AZjq5^UHhoTM_@-noqn*K?BmC?4L$#Q;XNNSD;}k2xF(Z$Ts_pqj%KfOQ|@lW@o3 zts~RFG#Md*aT4Z>CmtV%`sksJ7HRg?$5$1RQawzv3Qs@4r&wB^7xL)`#{riN@M?!_ zEm0A#-~n@TWbJT&icwU&eYhkl3G%$8(ZCw1m+zn(FR1hMj!yn$o5=%Y|0oIQwlzm5 z!zexcn$s=|R(P^aS&vvS8uAT}eAB{oy5rusf7Y8EUkuN>{6%N0$lCQZiAy%oc)t4ER1DJ+&xm&OYW0Wi)~Y zsF|va>;VlOgvugqt)0Qg>S9UX^>sr@y(u7@#BV!+!;7=C?dQ=p6(APomBUse@A9j{ zoB5bG2?2RXcs5Mtid{h%lQ9Q-duzJ(uPE#7B|ilxz3vg%{#a*kg4Pd<7U`+=!wF3r zrwP;A6GFGhe~Q%^om>pJS|e*a{cR^R?!IdPs6bc0JcsQ@ZzQUCf)gFqwT8CyiD@u(H7{mVw9Ncu(69G@ zzq@yRJ^%gQE%`w9<;Cl(aPQ{Fy+3~V^$%ZmcfeUg^i^Wx7nl+v*|bKt=CIun{Fy8|;AKMnz7Tia7~F8l)=5NDG-oj+h8wRIL{eu2 zC735tq&K;Q;BeK%J`UDO4e$+zY>%(rdRmK8bu%r2mR+W)dvPIJP$&c=N6g~lku6^7 zA^tOcll2P?vxm>0ojz41EpW;3R}==BfX~_VT4Wy&bXy2@OJu{uujj60k95mL?arcV zsg&vPvt^@;bjOg=Oq@R9?xmT-C!_wm{wcdeCr1c#)=s@9(;Y3MX$U-tWn#&T&9tAt zixPOAH>ys?@+3Ekqm$m-?)f`x7vV*XJhwD(UdJu87u@*9&}EAYy%X=?MB}PkMhokY zxma{g7x5ee?4`HQ9zNM$0XRrVDP1&_L}6T(Vd)^Vii z&H-W(n5f}F6d`B5PQXw})Mn_S!~CHGkR-Z0fVPs%XHzVWdyh|M&HT@`u}}jshmbFH zm7Mxj8lDG+AB2+yRKfiIyOy47QSV|IClSklNZYb7k>}rk_k2Hi2iK=sY$&sKvAOse zrHaVvZQI_Dp`8%aRR4W*&LZyrk8J$-jE z?2k{*D#A&PdQTKmYWD5ZOv)vyHLiLxwg2wCPZm(NMWw6q3xMdBtVlQOHQyb5y}N8C zgR?bH7%bkKJ0qB}-afXSM>m88=j|4AAE6I8D7O)tYaKTS<0?qxMh&J9!9%*qMDS!V zIOz@pZbG7INmLKIx&5I0Hl>BOGNbk4-#=K>Z>`FBQ^+;e zW0;>5qQ|J&KZzPcrHAHwB=APgTJRPUlT`d8*DLs{%y_Ql8JOs({sNb@ArGx}%o!Fe zXG523EfPmOzfPWaRf(RVO??T~pGsfpuu& zjcIy6;Ud1>EHq4sx_TbozrEmtjDd|KZLGGj_l3SFH#1J)(i<(^|v7gLI}~lA@z`8RaF;x_uG+@!mG4InAT_O zdJO$dj4$KMxAm;$qUINRg3)$3ypqEJIiGh8CY4ZXJYqmORKWJ+`WM9|G~e`+zE8+0 zgN-@|HaX_SPo#`KJ{fHPeB2uz_6GdEguC1{5z7VyuTb`<)!m5&hHQ1yglvvUYh|S) z{wJ5O#-z%ntDIsp|BH0BF38zS#f6~JAdMru5L)vH03*hN3IQ#-Ur9yx#IQw7m~V|+ zU5GVVL6Vd-3)Tc1cWFkiA?On7!9-fBz<+F_oF2#>6P6yeKu-+n@J;PnR{R2*=g-3% zCN=04%@m9d#pr-)N~aul8Obc(Xayk{zkx&>obuTJ8OEr+`N-DvbSVQ&Jg7zOxBZ z)oOyoLzvo-&`O-)K_5)G6SC5u2eYZT%$SfXHUF7t1)4;-HK4u6>pd;fXaAL`*D z_8K$@-@-w9v)o(gp?8iJZqV&xW@F#pM(}a?o{m7^SsRQ!P=7Iuxz7GW_nl02$+FMV zyrJ6t0TnCW*Cc3qQ6q5)O9i7qaJz~GoFDh?dsQsh*Mx*+ zj-;aoCD^ard%E{)_byZ;AC+0k-(xo#99SnrZS^@S?PS$-Ep5oSWRi7kkz^suwI_%p zlLBErKv0cJtx_G+g?B!pno85SWTlAsK;`B41i977=3z#rt?|mV1gmAV)3l z{D~+H1h1@H%e@||Cj7nE{F!1kP~As}dx?(0?USbZ+bP6P$3eA0;xP5h9qT;^Ax-t3 z%yA6^WS*f$W-&cE7*B;FhvT@3T1AIo?Jzz&Tu~u6%@7W052AmYp|XBoa-NNdtm=UM zA_PvRIa)-^B?k>x=4}Pn_8k@=_Fj_#wJph~3gdjVnZeJS&9d!NYM29oY(ucbQtPzw zZRguL|LQ~Pfw!O>7j^%IA#nU1hlCd9Y9OL%GZS^iyCv4v=zR%eo18)fJ$mmncQL;b zL0@b@y0w+yhMR2n{QP3v9rrKJJxcXs;aP75Hqg;6Q1!0t`|r*#hP|VS*0|0kk+l$C z+c1-)z83&yoQgE7jDrW<3&D7q$8h$_^d zrQxP7cZ4XBJ#yvALgN+aL8lWeA|MChXi?Dpa2|v?^ClwmV%|um51fxOD&|&$o+>dd zo^-KN7NX8W4X{p)@$%&t)rSs4nO<=qUpm1R^PRi;;C=69OLq{nCs>>BVTH;kg`!oa zkl>RnwXy0|YU-it!5Z(&OvV#P_Kt+U^Tn=~=oNlm|B-UFcDwSP2O_nvsgu+)y(8_J z5~9`I#b0gOMNDm=2j~-~e&*?H3ibun7Bb+Hj!3~~1?m5b0xF&`p1S@yC>;(J! z`wmWO(u;4E@WZ3C-1X0yYYLa<2}Cf)d$mm~jvFw8(g|7<_12cxh-!6Ic8_T;fHn<$ zf!QO+oq&vc7PAQ?Ru9Kr(WSiAz zvddmlHs)r4XwuG;TI5?jM!`_p%v@2a;55lEfISiS8_fO7voFv7FO8C2Pr@)1hVT0; z&IO5Eg7X6tWpf%u6#Tf+Tau-8XHZkRr5`9U{CC?T2#Fd`mZj&c?{nVM_58A#HiVSO z1XT!;Hi|P-m_>s+KZ-LV=?ry48A36zv$36A*YAhNRcGAk9(K=8PDc+o=kjhaa`jQK zmKC2^MGdcIMu^q_lHKcChF2C8+LS#(wa!SzB^AJY0z2%-nwF@@2LTt2;&W;cSS_`I zm)@ZNbXD9AAN!C)QQQolivkkZJ4h1$DAWgst(SJ!ecTQTfda0Zo?%=4Wt(3E8+ zTJsgsvX){2-YT@b+I5il779@u2hz&Z@<*z0wr{z>=+d9FoItB`Sq$m!bXy!Byn;wt zAt2B5wc`7$4~51mxeG~C<_sGQ(sr=9K3Hcp+vt~@)q0|qnR2~<;j{e7qrFUV{MH(`1t=JF zEHyl*5zq0x31rc^9o=VwV3FkYenE|f+ZpE-M49X;pj^!9+vq*j|+n}L=FtB%j3C>ZKC z3gvFXZ&@Csm#B^0e-Q3*1KpqWEc$WvgZ?%1`jPz__AdM%DA$q=-cRxcWspHj#4r$r z@A(yTD69)bsw?PjT^1B|LGU1ZER-~zw1IRInnWwI|J}4jU2%?YnD@Rnw>#Tf#%dIr z_Xuas_e%KEe^_+ei)GG;cjzn^2<1L>RcP+akX_1EwOVKEo2%>M8Q&B=?Te@v_lBfu zLeIT2lrt6_!fPO}I(!%e+F2geL))8oUTMskRKzvJeCOcB{IPg6foj0PBh~Jo6Z>Qk z$A(HP0Wy#nRVYhgDE0`#Pj#q)BzADTLD<6L&?1P_5RgibQqVP!@lYYiG536tf+7!z zF!gwurLg90wA^mue+NA?A6i`xo< zFc^UE_Y}LU?ks~6lh6{CrV}kP5Po#667qn~sJG98LZaL6uvk`?3ZWxs*QDln*oZn~ z0>q=@jW!;ss7P|&R0-o4>^4u^vr*oq$w^LSMN;O|S-j!5tg!63<0?X|EE<6g8i8xN zG&lfpTxW|lJose;wHNDz&^<(J)7V*`xX->Zcocj+=$uKw59rDaL&tkR@S6VXZhr!O zQp--mFc7@^EB26sBSBRt5|q#)!GTI}sMKqu$aOc3h2vPZ6Uw9iPTmQq;EUJe-PxV7 z@9uS?3E3f+%pg$fjNmSE?;7pm!VidIDO$}qLbS@$ZN7iV4%5qcww=wV^P8*d@EHrC zo>pOrhwDUdc&5<=gya_CQD!zb7}SS2SKNtA)j_mJ7csN9&D|f|4LZ<@Yhj$wHUp7ri76^)p@qmNfpY15D+LGZm@Btl zCpX9(QqH8TXu#7=l@GUIQO0#`!%!^|1MN~ksFF=_%;K(>KT54upStK9E1HS}?qD*R zc$K$4ES7y9?a`p;Irfo3mm38N-|Eq56`U}sAJA`E8U}4dQA!=8M-k6T1t#Z?c9$*1 z*9qEoNPpzWaqR4fSEwR;#rmJBJ$SLG-zPbP4$%fP6vYu8n*doeEwNq!&6&8^7{V{gPIT7tlNE4W|yjUhKa4gb8W~*jOFf6NYy=T$20UiJLKp;;Aw_J9c5@c ze%1a-IgiQ z?FWV}KpB>NZdJn`HN$OF^<0N97(!pK@b%ni@ z_F*ppqbTQC%#n>Gx5-BT`=nToEk#NjznGjZ_uYNpr%S$kZOSG|mOv{a0AZ;FqbgV1 z2ITC0nkAg9K{kW}%J16sqFsHfHy0m@%f;o@#ntDJpXR^diSeIza~+@Gm(3%s8xUEN zv=UI!a;jL(5v^OUvRy1vri%_0=HF{xuvIIFDd2uWM52A7b8<3-B8-FZ3wF`dia9T* zU{A?O)wb(JUD=m>fEClqY_p_boK#4QB_(D40dRVaXb@d!B+{ur1kaH_4kzC;5YpvxyKy=fH+m?Gl!^tb%)@N@=s& zMaAggwLGUa*F{>Y@LT4cB4c{KLg(k_hZyM&qhrxL6IC*Kb~7%3W{AUcgj?)jo!zWJ znYuWgqQHrOf^EWQrX%^AsnYOn6b`5T7!7ccu90a8;+9ZVi|zF_6`=VY`^aXm!L0pEn}{$9}zk|*L z<+>R!c9gbYFHyAqLcC3q=zVff>{4-tvO;PYZ+&@qtU#E_L(<@q!D18svNP7T;@8X4@kbZ*11syFcm z_P|D$+O9}*w;hmx)20z?j|em}U!d7+*8LH}v0WJTQQSc`!>I49{=VOyUi0G?L+v?! zN&W+UlHY5CFc8Pz^H=1-2ZgP4>tO3#J2u!>=u2U*LP+DOVH!hzl*;J;zF4ibwq0H# z_sxCpb9cVH%1j~@1Iwub>}#VqGsIL9^z1GOk)Q==Nf}_Wu*KS@@8x!UpG?-1>3I6| z@E9#&!^PJka`9-D$!}IlP$5EA15T`9hL-|UrD#QxoXhZ%@Bg- zfj#JXtfYbHO-frg(IWKd-bb%iYgapfJ;_FR!YoA3 zGVWsMC_LRxb1Z9$gM>B6&4Dej0>#z<#+@H7rpjBO_abVYvLNZZu95H&lpN z-{3cqw)dDArucYf`9+Vch0!!>k&Vtc34g(0Jy_mfc4~@a;Sl)u)hpS;lj39cyZHxv z0)>#>OT#b}#ozN++=szhP?%28S*PHO13`v+EtE97Z3Af<@-a7L|GVkBG08rfm%!oN z`#bl%ys1hNvP34RMu?2noY~B76q@N>oDfNCG>S5WZ073RmG8}ZbYG0~@nkf4dU#Af zamD4wEcJRiuhfDy3Uxw=GZ#lX_2^C)n>ugA{xjcLa-U<9kb+Apz(+F%zMK%99eAw- z)+nukP9eVp{8KI$oI&9vv%HazWvr2YlVdhgny;uu2pBLhfiMM$KcOv(;~k;nY|qk6 z8)a9ZEMZV^fpcoB8#pLFi)}N<@VwXlt!$r0>$E%?lPfS*QCVV`vhTPSy=wR}01s@% z&F$-H6XE=|L?zmj(mXEEE}VQeKo4!MvEb{dug>A{uxCsD0F_qTZreBzeb-m8fKf?k z+N9e8+e+&g-E7+gP2D2ByhMS(NF$qAT2x6Yo9rh4zC+z_wp%?o3^|<3nKLu|W09>h z%Zef5R0467mMqetzRN((pS!-5(haDLMnJ;Td~=HE_F_k+)CD$mkH_?DF`AW5D^nu(N9Dd}C9aF+S~gP+j30v%?kH;lw4lb{sd3n^a- z$@F@oeqCXnvL88wEH1AapWD@6RMYmqcZ@?BkJ-6^D3^-ez?lX~wUG7S^7vZ{w;4nl z;_sB_@b)%>%=qM62}>y_h_F(mhRFKQBKT#J-B1lgX{uRN0!3zT&fkB!IQx3~@r&P+ zsy)0n3o^?J*Si9XN-xLZT^gV~Z~1BY_6k+o zOOb-z-g#(NTHXy@o9Q)EPmhgxgAM#spzpMCchr!G>S#@zZ4?-WJ+0ehc3QvH{ti*-mb|}3Ebb;M+M&>^}I9qpW?PNqUbhtts%;zoMS(}7#v_jl%LyhR-sjt7Wq5V(9r!uhR5Hkq;0F61wi6-s2%T0HVu)?I+brr>m*39M z?Xn;mt&5_^qxoj5=Y2I7h6F0Pxd+>!i(l9(rFa64dubcW+gD_jW1oE6z)ZbL_DJtDdHz7GZJXm{-Qu7i^dQO;0)K zkgr}hHTz)w4~>&;OT#b_$KU5uoUj)yDEI`r)ovj8Fa!~NI~ZAaT?1_znq+RceRtC~ z*|O@?zG<8L-|zl+*WQgpCJ32hlG6$ykg8&dnyR%x8DE4E;j}<0Xo4_(*2PRO?#tEr zWqLKc>Yw*-uWu$VxMck4d1B5dZyBw|JS`(av_wcXPZTRT%&5c~6dfM_gZRjDd|yk2 z#iYWbTw27D%r{!mMajnxjT2gC?haIf4KlQ65lI=RIhf9+iCQ796!Q6ZHTfi9E>W>1 zRS5N*_yT&V@%P$v=0`_v5YQ9uDF&|dyrXj%K~;9B(6gK+-l(QY$&FekD)4Kc?in;^ zrpp!*HjvhGgEPzJYB2CIHowgaqn);=GA7T4t}{b)>uXc2bbG|-94)`=L34?wYD;%x z@#^k9?h*TFyM33g3h}Fr1*%8uKL}x9*VQppq0$xa_WK9cZMMO>mkyYv5Y$_MIF6yy zFl;uvqk8Elhmx~lUBd)|F!YGtUhJpxb58VQhW82L$If)%0lmcSk@<+-zul^~Z(hHAwfIDSQvTuN0`?d44VE`N6=TmU6hUdh6HNt2SW3K&S2Rt>eFquN?S3M- z(&*~u_WJVcm)r00-SvESJA-9Mjb=F$E7IN7L}D#utVL{q?fC(}=UC#k;5XOCYb-YA zdj&ostWb4INxf;Y?sE2?GGb$F83*EGNpc2E9W{Hm%~HX)i%XH^f)lP6?o~(<01IvlK#Y+AW85_aJJo#5uMl+^-Eip&`A!9*;9m~ zN~l;enlwzsu}QNm5^c(juR$E^%`8Oj-zYHJ*7x#fFcq3KMFZ+sm(aWg6rjlzaq_G7 z#<#m6y07h60}sKt0oNO@G7qahHh3w?#v6tI5(WSweAD~ekeuqX$bRl^w;3ptP&q9ojB2sM#DFNTv zy!8Vr!qU+Q95CXp!(q!8Io#4fPccOIQxK&1#zTA-Hhbl6) zt69v}0+KsW8+`Hl<)Czla($F!TQ_YugY!@GyQs}A>bqjIvcr}4%!Zx_#z>;gW-ETT zN(}{1(~BIvDyaKrKKPCcp^FRGw<{I7QdT8+XvJT(r@BozHEn2Ux3sX+y_$BEh%Cc= zb+}e6z}GCV8^dfk)mh#p$PtM81WXK{Vc_cHFvM~CH0+F)*&KtQ~jp#A_2 z%Dz}d7&eiFjn5BVoNC$tOtl(~QYo^#4PorkA~DK|-!ggtWH^-t-}4lpp%c=~Z;L{3 zUkaTU+7BEJnj(KNK$A(;AOj~ePL=V3u}0}}@1i#Y+`sRo!3+>LO<^YuAYmPNN)fj1 zN2~G<-p(afWDb+7PPeHcblU%5>V(%fG@aKBI!UWlFskbwrl?EG&OEd_D4LyuNy{z4 ztUQCng!>OM-#%`jDZooXLtzf5sfimFlsCxb-!&JKT#1U9MT(gsfLSee)INHD0j*eV zZ`(Ey{_bDFW=16^PU5r!w%T#B)XkP+Xd7$>`oS^`S*BwlG9`#qnzYM*-;sLZizr*& zDj=~<@{Y&no;%*@{4&`joz4mek)LAZQkI4RTd=1D)A3nv)FJ*B)5H%jTHIvYWp?*A zejL17zh1t6GkEjs&%bzo;)js@apS?bH;K2fl*Q?&)5$1C>x={}j0y77)PGteaWb-v z(yJ}?@ZG10ed6=Y`|0)Hw^x(vooIc=%WeGCPnmZ`0gduw)LDnbj}Xv>QjP?|j|`I) zMZbp;oD{xOu(l#X=UWHClVnS9q&&=GAfWziAH* zQ9fpD+l@1pWDM+boRJ)5oH*Rtl0?Iy7z)2)S1L6HCfw;DB%JRSN|aUWI7MzZBrE&~ zs|F)fd_Py^N9gD%AMYu4%1uDB&^*hR%}171Q-H2od3?d(r@ky)Be$FlsXMFq+1FV) z@0n(l(jMnIWxzeU_!6>>D|hZA=ck!tl@unXxI}X$jNF9u_(Bd|F>uu~W4OeyUm(>R zS@m&iNESut#dxeKZ!&;CYUVQc>7%p0uE@Tz#CxCX)#up`ieH|362&~8#)#r*Jsd(L zudejD@4zL`xUQ+a(Ko`BA}CJA!jBE7KsbWD)Jls?q3(f44*FV_)Of?Ed#!QHZK%k| z1Je%))tUHPGZ9(()b0QUMT;yWQ=ht3lhr8jS+GH_dCj!+fZX8wP-%+2AL$i+Z|Mys zv8Xqh)eqe-n~)W?%kI;5;p%<}ifbxA#& zNHs5CD!mD0sA1Y1E`iauJdh%Jde$fo*f7T1pk!97Ju$k2a+&bM~k$9ar;*=Fz#`7rJx-d~rMVxL1tWHq36@yuD9$zHXv||;QD#!2z@U~IrO}E` z30=1U1h2~Sm{*jihlk4L3DqC=`9+`|#;2-1wUV!{7o5sXLMN)6)h3DaaaEY8N7uno zmS3PjJ41HB#Iv%&>&C+zo*pO%u(8@M>Y&3@$9D9R$u%h(sVT=pX?kq+R?*0mRTorL z5GDyGP)GsIFX6XI@>W$Lz~u+`n2ElqP^e_VZctW&`b|~z5j+rb8GfVjZo<|{*t-a= zKj0VRys1fWW=if;x>{q?1aI2jyS(NJ=2yKN2 zsL(!Lm_JR zuqr9)gsi%VxAE$!O5d*9X7K&HNkvM~vwJ;bRA!V($R;6=YLWhTvyW~|X{B?S%rM_Kk1zL6rZR+lA(zY|B+gsG zeeTx=-NR*?5yfhBhH->^5$Yl=pY_l5s+<+G`E-7JeM8@HCDiMJ#yow|bu9JTW`qQH z7UaD$A6y%>v~yVt{H(10W`)FzltM8HaXjZ>C&!d(wDuu)eY@^8Un^97@>vMaAjd{D? ziyE`&gHa3GM&4<1K5(#ye3S2sFessI63mO zUONZFEAoyD!eZT2WwTq@W4AA}G8=ctqrs4E=%CDM&7#hhwu;tZ>l{FtK()LLzEwB_ z#&Aukl|b1T5tKD39{u}mv%{8DnmYKZgpG?#@NJwtwHz+MBCq!a*NXcz`GNLkyTg6x zzS5+F$zLuex%Lzvas_pfO;5ux42JLd6+ZOPN<$h93AQp2a9|P_Bo16ERcYp~5or?T zW1^t_chZ%u>r`@yV!!tLIKF$(l_q43Oi+UmTW2_Pxob4q%woI z)YtT?oE5YAbbfn%lfB}W%a>Ia;@PvRgVZUL65=gF=_PYqNub8iCjZ37ro%+=%MEG~ z47$ueE+1Ib>YEyuJz9&$Dac@? zNcUBNZZGhK;H-ztbEae<^~}X1MajOX>)oCXZLy$2yfYg9H6F^-qcU<8w-@)PSy+w{ z?MAs+H`sn@asyGx1y11b3e2vZKY_(x?2OZ(^JcJuxZhJe!j??Nh$kmj2ihI^1%*=4 zO2aS|ea}~1VbB)#FrA>=9D-vyCah2eAFPBdy`9xADM>05*}t2%TU*M+`_v})o^x`} zy}8ZaGeRQdDN_ipHY(wUn(qwt^xT^e!M3O~#u4Zu->&n`o!kvB;^BHY8jP+lufj+C zO2oq=wCCYWZf&cPYC=e^5#n5MlSly{S_*1OWQ3CwHz=ALE`>I*eDYUx9t3_sSN>{# zYA9veP*kd5>kK|G0{?{`5hf%~1WUm_QEOP*S-_n^5ox$jQk)mx+QQcA$qxZrS-wsa z-mp?yQmC98mnp@*p@+v=4O4h^3SCYgoK*5p=glioDy$OEIxNh1+{S4kShI_+eZ{S$ zL2fW==y@e?CWW0aWl~}TJzo8555`cPb!dB)@6lnfP}+*atpWNF#w^u%&{0$pgWE|0 z&Zg6@|E}k?_=-|!awQsZ4dkA5dRhzMcBS@D#aa8bCt(>WWog2+>-@2x^@f%!+U#f; zE)Q}2*HHWN3w4p-O9DX<$KUs_m7XI(8 zcX?M?^Rn=rnVrwf_wGTh6(LJxIn@Y(v6?fR*u6s2y9^^D=mw3V3?Ui2P3l$yxocfz z?X=x#b#AY3;%D4)@idOT9zV*BFSSxfggAqcIl(NK0=P)1)^s2JlohpWE;3olObTP+ zrN0RyqMaR#soHu6W*sLFq@4J??CO{@B1dpv&c&11~xLtWHCa70=Xi4ZC+8P$nC*Qj=P z>nPx)LRAxrkUX0zHTf6u>-JrCpWZ**K74%tA^wIZ#=kztHXc8T%BE@|qaZK}Av2t6 zCO8l&$vKgDG!&!p5fz5(HumI}BrUs?HwfEk{Jp4OsHjoKFXt*1WjCoGm@`|&`6`Al z+5>8)D99KmC0Iw4g1#gLtg1__kZUix8AJ!a1F)l(>_jxSCBusL_oLDAp{IqFYUUmj zcEC+pcKIeP86E6O5-PZ@&rlr>H@4fY$Fy#4wsPE9>4K@7{g7Z8X2RUaghWCM2)$Dv z%1d5*ux_x-PX=bC5l&HL(APJ;;whRJK43+&OQ{w2a~!?|arp`T zMInn+NzbqDm(_ALUp?MG6z}+<&D*Ys^ovF@Z=Xf=x*{wL!P8!4?e@Ohxf6AchN7qZQP_DAv}1O8tH_bA(x~8@z!r zQ`T^Y$>C|J^^k<>Lf4SZ_@wD}O~ElUrOlqzok&Kg5R-&&!)G!Pfeab^I$ELwH9`a!>dw=d{y1D9e78@Yd7n zj{5td+;DGS0sEA4GpdhY*t+1q(ONFggkasD)I^oovvlTr@(GH6yHqTn-N#4ggK#RQ3l$)(1qz}?xXgN) zZ3y@uIXO9T55feTnWd7!VOc?GI)|NCtf=H}iZV>k$i;`V&m^H`$+bb?7!h1WY?`Km zASs3Kh~%Iu(SXJzs8ub5A?=bWs3*(w`SiMH`muWg^yYDhil@nzL_t=;ODAee;BnFd z6kkaEbT}wd^J8oFu`~J6`G)Gm^CN#ig}Qb&+{Bp>IZ^E88#r+fr()b2PP(YsCVPlx zqkNgO2NUw9Ili@SvOIkcg+`ae$>91 z{|eP6xN`p@=gBR+2+2d{WcsR|_$mVwfc0gnXuU-iFH;r9E&<9|;@{R!q-UjHOR!QmS;gFBoTcs># zfD)eE=5fCZJlnoeT{ROTQYfVT1|w(r4X-n~Ezq0|9C@xqpX>PCQdQwtkozs3T{mxT z+iXiRE6)(1MnR5ZqG|~cb!82&Xge;Pa5d?A0H4{v8VEfm;f)YjbxXnwcp0t_$-}L_ z{@R2hl?45EL^ND6FcZ10`MrW{ei;;X2J9=kOLX_ZuKH$)K{`3fS1-g@3pL*Hq26JF ztXmP*4dAM(Vu0}>m70XB`3Gm|dqC0Cr*aik=XD&=+$rOf;^7~P$MxqhWlcuDDyaokKM>e*o@v_ zc$_w+!lZC+8t)JZNQu`J)QHYFZOzuKRJt(B0WO|`3-cyVNzk|!WD2Qa@8U%tUq^}f z)@kexrf?4Uv(%iDl!>IyDpeeUIADU$fpUWnP8oNrljOLn}eiBJs{l4nH*H9ORV zCS|FeAf{#ymbC*Z7y<`F;D}!UFBJ%w6$}AXw#jY~IznxcHg%hLEq?uckzkKtfR{#` zcyMm9uhVm$GL?V&vk&=(Dei`@G>&f&{aF>Xh{D~BJ*dW-p&~Xpv0uG>Be@RbdjWvG z79l_eXy?HIXfx_tI=Xy6s*kr!7~hbtu{GlDT^-+(&~Vr(=j{(-^Q^RMMpyAq@YMGr zLR>YaU~-^}E3Jp?12VtUQy&nD$GRoMI(e)EGoR0W>Q6vtZCaKBL}dC-7B_n>tLVen zyN3!Y25|N}nm0_RC>;bS5#Xwjnn39qq^N+WTkP{`$m;C9J(H{UpA{Z(*SmL!1c6-( zWC|6--?ezKUj^`JwUYc&@r*&lh#Rm!hvU{V5d3O^XU>G-@-+y4J7&8ruUeBHmjS+p zsPnK+oO|j6+Y5xEfmI%4aF};L0%Ae%+x1a5<|W%#pAA}r){;YZy_C(mD+@QOfY}!@ zb*WEI#`M=o-L?pvTMzwmHdLZ103e(pJAPXsECEe*|DttH1=HiUkqi}iz5BvGmcga4 zfx-UZMbasAx8Q&{#q_qEVn8zjYU-{NqbVpDVgUM!8uXBO$L+qZ&Bp1|jARL;lUDf% z@nhXN9PNbY92{~LzjIX8JOR%-cMcR{A@FwPH-)YbX_VU?@YaM6LoHXQVKjAWv9TED zTg%_o(r{0(#dvDUm``^#4eW&v-l?>7`k*n|%g=7ge+W2>F*~WK5cj-Jt&58NQ5Gpi zp`(a{)fVO_K+TC+e0 zL)rVF-k+K)(do=HVz+H%sCs7H^;=Y6(vz$%i!ShtqCkKJc>$p=$BVcf(WD72Q>^Wk z$0=8EyveeDsBWN^N-z)ZyTy&P-Ypd39-PfGYBn}7307HW>`pi8+lxfC zNutU-xusH%wYG+hd|uZI>si|dZm=V3%ZZ`w+?(hd?2x=>sHZTNcEK(w_!jF*ebb%z z`QqKD%gdjFn_Jfb?Wwl>TuspwuU%j8FK7K+V&`%60`+|WuX~{51URpO+*JdEh;_&< zlHts37B)snexvG?|2dB9I0TJ*M}c*8mD_@Hm9w68MEArzrV zXn%sWGU9`bE2cUzpq&utcowIuu6e!Mf-C34dewy^hbKNYyG?nm`opt~P%J&Sfb4S< z8GcL>y6WZ)D90}0u1X(VUD?-n9wcx+We;0d!JAD@{uhW()*;$cCqk*%XgFj3xCLjm3<|f7 z9i&5WH-xkUj!?WYn%ACP`W&nE-nJt}tZisrP3PH}_kFR@Nf$3Q`+Xz(7n9L|ty$m) z8AIX6?`%|y5yD|s$~|!V={p&qp>k;nkq3MLJi_4b(z-s_va(0w(*Wav)@RH5qUv(1)jVo)Yo}F^?2bPorWnPB2SB_pkkch-d*ijbfmafmuw=>_x8HH%VvO>9)J~ zw|C(OESY#8g!X&*DutFD!r>?&#ORYX<7|;gk=gu3Dg_AKSBlhN_^f72w+rf^xOpfL zRAT$D1vR#^h*_PMMh0`B&9OMugZea_{|8R7)YWN>Sw0tWcL+$rM8v6mSz6QKh{6g% zd}&%vB0q%Mkt()~5NOph+F*s^<&=YVX+v%_a7kv$SxhxXXqUu@7?~6_R$xotAc+h| z&!;`AfhVq_;LnEwG)hnb-!AIqo83e7{FaMU{PTRjpj}@k&)xb9bLqu-&E|%EZ~1ZR z9p_{c+eT5D7`>yKwod8jAM}>m--5tiuZ*~b*p>vl>;}%6zIt?V+OPOm3KwO0yzJfa zo6G<=23YN?S2?a?xrKAUOgo^+o|fRyr&99En0BdFUg&=7mnArm{47^hnKk(Zjgs9? z0x=MU@B0*EV#1Obf?}ew8siTr5sCL=NVhwMnzh}uQ-m1a-BtOU`x{?co$)o?s{Pfl=+9$G;buQW$=ci}mC!8z& z@Hh_n@tx7m6qqvR9fI^)kTM#iDsX}e<;bN!mHniontIEl!gyqA|9#_izPHrK&_Fxl zniS8XyP7$RyeK)%QYMw=1%yF32eo!XBRCn$z8Hjvr*|Y{p|onw2|{w6m!IfAVOw4l+-!(JP2=|*j}`I06K zSIl6yJ#bgK@3xm{yz*v}3b~EOut?tiEwYNIHF))pN19pl2^v4)zc-ew6;^D;z5%UM zU2obj6n)RHxT=^4679Nm(pEx1`=Vu==pLp$MXHSB5KG36`~gK1|9y56S|@~Qo#uxm zc6`sdKIeEmE%G7=Qi2(h1VW`H#rj_F3ZmjygDBu=O;mv}f%{vtUYgYh@o@P%yINjd zUtYg^^EUZPHk9AoCU!mfD7X@gL_uKG$$}Y8RN{Ixdpjxix|A@R0jrmL%p8hq|7iAIL z1bDt$3!&#+X~Z=}OXr)b%M8a31yms^*I5)~lp_WasVc*5bDY~UP7e)i>aQMj9I*Hsc)^_uVp}zG` zOu9QoXXCnJ@l|pjZmm-_(lIgO+V4D^0SS5BU2pyfO;uZsnieNqZx$Q*{xvU4oFV7D*XbT~osy6R zBZY%pk(X__djBk1Y?)+06+Jh8MdZM8NHWo}WUXw0(CsMT0)M4b+iuh_5PkPojH<1Y zNLy$rQfac;3Xq^H)K)4FJP@>U5+_(n?8tUnmTJENsgJxs|AOz~2g)blTsIfGNZ=PM zXU1pFoSDo{vX2=;Aqf&J2$)g{8mOgOWkk9MmV+2hiOg_7V0oISejdNzpZ1?bhyLO5 z{_(S?N8TH9McM09PvhQ8&LmHWgHSFBL^%r-0cn~zE)%uQoeJ}uXIDiu{2DqUo zIKT|6)5l15`#YKakxa)XcJ271>zg(Di`{kN|M~p$>-YN~U$oOT+rZ)m-FVx|8zYUI z4N%%xfBdt5E&Is0HCm(Jl~l`a+CUWD`zvk{mFyiZMC= zU|Jd@b4+65A-LUiK25`~e0lsaIGLWD9-n^s^m%fP3(CHACn`R<i27|2rgLPnv-B{Ql(XXBAZ1>5gCrJ|$-sIp&2Tjj zILi{vW6qS=E~S&oENAg!Gl?QlZCYAD8Hqs6BS|1*(FjG+kan+uI&nIUs0W_Xk??)o z8IEk{*1c&DuHF8ib7lX8bI>nz>oIsZY7a`7H&vK^Z#*nv-d165?Ars-Q5ER2+xan2 zh3#RjL)dPPuA4JN>PbV5bU`G7K;m;2t-yAmPPv&Ai~iS@A|XoKF-&MGMhTo9fie{g z>T*VtqZZ}XGcpkma}%-<>aoCpE*r4DD<>u-#0IFXLw$i_$`dMAs2=2-F$}@C2iv z**sWvMy4#_Wo~}2q*hxh)A0B_dk%qZ$f7G5XH4*F#-4w%Fm*vK~BvMT?5*AT#vv_X9uFQ9AgxnJklzPd?PiQ@#+kBFq;DlM6> z=X#w`H9GZ%4ks~H3Gpe+Z_{{@E??g_{LAeO?X$7qNZx)ht5iuXotnpQ)pSh1k zoJgt^THFE{I?48{50g9SFyX-^JPHA-Jb78jW)H6+d`ZG8rDjpuZ*4G~({Id2a8@7jaCvzP56?ygDhY z&{e&@nQw_|1vZbQI+eV#ZJfRF4#nx&g&VznjFtj@SMd~8Ue;fzakmM1x9+L*5soa4 z%jsZHt)pBh@mH|*1frlr>M^dN!x!={{A{?`?4D`zzQnNWb)2SSmRw@z)l9YC&<)TC z*?I^h6PRly3~x-qaANbO4`+>W?QPZZ$Odnu(}F_)$H)_KyMwo9VYllcW9mKW+xRwv za`eq%DL$|zC&)DV3L&b(*Yv)oKZE6Z*=H4?p{!X*%sjF|mq;&atKxbI3MohW1!mvb z&w2B-l+vzr%P>2_tup@6TM@O&PS4)Y+BVY?8*b76V1Np{#ZbRdjXlxi6*G;ut~(>0 znqx7-lO!n78w2HO6y@VAKw3_-0JNJh6`b00+^gR9cuh*wJgal^1Etypbu+cpsX?q9(bDI{Z#*RBJ$I&q4`aZ+Qco!U;@Ar1sZrfeZHDTq`O zW5^%bxBa#Kk{wCCkdo}Yv<4h26nS@i?(VrG_di6-sL_~XFLYy!Y{uij;|^a(n04Pa z_Zq}qVHUX_M$X4%b)77Z=>3~t{dd>z-oJVO$8W#S{=~NdIsG_;@mY@&Mneq0tCdUU zdyPiIF!B@P@qiNK#<9D0R&KOcH_n!U&uiZ#r0yNQk3u@fYIK8fC&4^km(427d%;TL z2a6gdtd~AN+cYOzBz|yXQQvqx8wfN#c4_C6C`*`?A5Vtp(t$CLEsw{Yenw$(nJzn)&ePfY&)D zoPC$1m3xDo$mL7Ny@o(eIQ~K6VjRLgZ_DB!OE9@D4YDW>2=~<}XZ7>UJz zdU~;-Twb7__8NXb+z@#JeRLjE55un@8Z1y0tSafY@pl8k2Yhzx@{D{=24{WeaD004 zsXv^eF0w?3T6;A!ha+d)pG*(OQ)hHO9gK!zHle#QX55YWrZv6M@MLgmF7aeSWh;{F zFz^iOH4@HlqE2mLkgL_6#3#l)i3uY3u6DLy zcn+3lM1=q}coPW%5lEt2H%wrGs8vYf1fr&}dOsjOJ$UUYoZF(vzux~v4x)4r-34KY zTu`}wkwURXd>4?KL@IBi=`sL6GX!zuCn4e#%`wE1oX3DJf#nhP!B!SwFYpD(unze& zhHCGM_-MlpE(Kyn-q{R?ey3xEM$4Si>5TiIF9zfOF+gw*ydks;y;1`_XJ9w4bPXyW zQ~8yEY9W$@VN0Ld>3lgnyXZT^(XbDijwvFPqcr5C!t;;knX)#5G|FBozou`{^%}78 z-6Z4^rhvKEVCTKW%WAgJMhlJbecZsog3wrbN(H$B1PiC#E#VI&vv67J5iH_DfCuw; z)3iWT3KmwW0L~OaVyH){-WKndE?MBOG`;~>9|!TKnO9AfY7eyuhwrfm>~0T6XeXc| z+kn(d$rbp>hL^hsx#&$!Z0;Vo?1)18xsbX|O@`7f%7|U=XHXki-M3b1W?`!MR67@8 zi8gf;3SMa(iyT{*7Ryb6_s;@?ZGAj-Mi*_`wh@-rMsx5$>Yieb9NM%PZ zlcQj}0a#9BU8KwaN{$1F{i64?Q#s}B^wORPmKr^hy_2;!C&i|#y^n6GYTd!MvX}4T zfa8$ZmucH#t1^%lOhJ}u+A2$cIdeiw37(~-4y;+R3)0cDt~8wrf{-q>1~lJ)3z=b< zPd1^-p3BqKc9r3Hu7Rhm**!>~ZRz2b3A83G&+UMHP6EuUS!=*oue*gxeG(93e|$8W z45oiMP$~LTJ-HDMq4y~%xmyYaP!%ij^EG>;W1`8cy4cDM%pKYh)mnV_MM;K{gIBL! z?f;VfW3@J*x_QM_$pitU$Z+&ix2tWZjAo6s!mNCgj~lAcffGB8xrR-yQH9IX4S%Si z-L##`j?|sbPbNksNt-<=xLmI&HF<9y^)nWb)R)aEl^l~$bpFxA0=IE`D6Tbh zN;B>`%sdj@t0knZ%N-Y;7c=?8=q+>Q?RL8wkK2(c(?KmP&|u`H_?8Cqnp*q@bs@%P z9!&_}x`%C%qZ zfr8s>qP{?}vRHr@kU74W@a1yK5XwrBzlAmsc1Ao5l~4=XIy!??gSt`Cd#RK@g>HoE z%PSex6)}$oabfe1kKF=wBW8CDFN=~@ih{!N2I6p7vo*h$x0cV8-Ql@B$aJ&IS=*Y; ztd=$j8cQ*)rVh;*j~#Uoa>NnCu9JNE22N*IaRp&j_(FWN`{Y_F8@*eB_3^BP-K96A zmAz^TjYKWe{>qJP(>bl0klKC%cv^WYN^gCR3m<icT!%Ud-&J}p|~*5&c##o2lKfbS&l`)R<_u98*>40ly!GH)~HEkfZX zccdh6ZwyM;-bHbyh01Y-{xal_ax>p=za8@rrYxxRCaV?vf!~{v>;6sbKx*&O!E>7x zBr^fQXlvnVW9ta0(gLgCi-om3_Q4<=?Z<-n)~FKQj_$Y#3G5(C+KOBsgU%ZX5~sUm zarCv-=m4S@L-lYs2}>bk9q7zBP{k7AD@pL3(dl%ee(&)mVy_djUnSwGy+JXDLK(<0 zSW}WLYfvkTVS)mbHk1Ily4rH3F%SF@3+}}Hh}B(LNL{Ud@uLYiL+%~MgJ)=vsvQX8 z_x|13hW2U`x_BM4I7-v-1&g9clI1R%>v>Bisl)53A%{Z7+cw`S)}C&HkR-#mZnrla zZZt30C#_lCQtLPpzRy!=s4#YF63$^}s&*$3PUS#=)bh6pOw|^gN-4ISsMfJvvK+{8 zc!asz`+dcIgzc8(-#7uzoLqn-w_4q;?yo;7ho7Q()M&WO@u^_Mj-}v^oXAzg;@*Di zpb^joizDhVGC5Bc(`5EJeERsa_sjH`-#-5K_h0|^@DH}+!P)r(j6WQQK^*!F{x23Z za1R=dBxb}*0!Q*NAXEstnt0roOdQm{@_D3RoN&P$8H&{qWGOu8;SDyNa#yUXtLIjH z!_2_rvji3f+ukvM@WkbD=(2ZA83s`zABNYJc<@E!XNvOCc%lNeRe#-v+{~#M23|N~ zfy=~N^xvS#5sXSUk!%r#f{N870+sJqbIN@-Ud5p1L9j)*ta|tc6<#P7R8C_4$2Ux# zF{vz{IN>4^ERJE`g!;2ka5-PZZw_o4(Ck6ayvc%sPbXB;{2RKSOJ+txT>Xg3d1W|` z1P`QF9hG7tSOi;9|H6kt-Ri@5weZ3K(+~DBV1cYABL3}*S+an4e_hjc^lAld)^nXm zRHXk60{{Z={qAAZW1Vm}S(I<*B7CG%AC!4KpgsZgi(`VjqbiS~_c1YW(0FbT_`rrN zp#+En=1FpM-XBf+qchSY7ABN+P$P6P9N$i^Z*Eb@=dr96x*Xj0Px`n0RB*v0bs=-= zg|ANrnIHf}tb7ki8SN|oEKR_h>J;iZxPt@;6qO1;2tkuE8XRGbq`CL$mKdn}9D5MwXI2>OcILINc4i86m=8aN>K-W6_kiLb623Zy^xy#&k(YQt>6xs@jiJ%>tvec(_i#i>w zwXYpNH7krv_aspKun>wueo9s3p;uW3^*2C2Dgou>^NP64qlqu|OdLmD@jQHlD8__> zYE?kYB~mc9NB$KiWIhcfA3%14JKFDR|9x^ff1_>I;``_YPCuI0!3s5nZ){E6rkWZtEIha=mVoqlt_R&CIJoXFv!XU^54hE& z*XwE3)plSoR%^#9JoCdT^|5=~XnYmgi#8$$=yn|%gaLP`4;<3gYgF7B0V_jUn358- zH9@rW!J|G@c4lijuWGZ34HF_1b}6BhS(r9Z8;S3dAe4x0UYG!$!`A6F3yT&4ke)WW z-P8W?;%+pkbzCl@Q|)^#g}2LQ=PIOFy%u*JakD_X9Dp{dYz0wF%U@TDr+`e$_P%VG zNX~jwBVv_C+JZJozd^g7;iB5St*O1BPbaE|dd5G<<4ZGENvb9me<9s#kwqoDPVVD* z$Q@=EL10^yn~^zDz!C@#=>cOc{c5))mrm8R>kUo8TN(N}1inpy-L&&aNVCY|9W8&WCj0tF+Dfe7e&SF{S;!x4Q1cKV5Q_F=KY_!WgI)XyU{ zcmgV?xJuSIfKveIP!fePmwd_4STC()D)aPQU%fN(ukWu%vx)lL;z~&)p1Vw3*r#mOr#)DnLFmQ0K>)N4O4Rg zJ8P&}Cf2n^95?*Tw$7M@AS9!Q|< z{0cQL9!E-B|MFdvKy6H266AZk47poJ03YED&HNz8lk7B7DAaD@jJF$G)sSYD+j>I{ zRqrnIw8+bNV~)L7X;$FhPvxaKk9X=;v{$`2i(+Cg#QI=;dwv_^N-@-Vu`!0*QvYq{zdh{A#!~394t48) zR>r@W#{XA}yl|C`*ZPOvtS6XzbdR@RUk*Q*uQgH5@^!;CIwr8Aj7wKP&nsvDj0pxqd#4xqW=JNgc!;CMSX;Ctj}Jwr~x7jF*MenYowOC@E_Pv@!QOfifDiQp+MS*S1cGDX$t<@r&jUHj z5<0u5B@=)u0ywVFy`4+32qy9EReq0?M0^UQ9v>)$)z_XAF&@M~98n71p=8k#SdzwQ z;3+6O(D~H?`cU|nR7nUaA`Qn!2PYQ?CoiA}uBoWIS6s>_F$?RY%}}rPuh#+})+D)F z;lX?)>P&LrxLCjDxtBRUw+-$;fBy0N-+%oE1!RT_^c-_UpxBHDWC7`3x7&4(2Ct4? z=Pey~8IMGNCoE#>82;~ccXJ_>LRhdkhQd#7va$vus(f@Dr~5HOWl;6s&zGIoXUcSImvFFb{s39O2Mk^9=EEbH< zOwy;!+*G~;k01}opr$(YMT%UhxVyU&0A6Vl8COgJ7aACqjEv&cDE~rkIN}tvSOpu@ z*&}(TjLV>^_y=4%4)qkMEF}_CXB2AYmUU;PZcHi4rJjpS7sec8?3dVT{WGVk8bO{RZ!r1D-rMt z!%U+$d;<@eFf5<|8omF}5XsQo@SBKLKp_epTXN5+@6|d?JnB;vuNX(KDPKsYG3H)5 zy1WK+@&Z=Wc~CXfj;ed<0TF2pSL+VW&mIu^L)2)&o>X;bGu|^Dpt#Z?~hIo4+o>sTJM&e zrkSA|!+fn1>s~hN6|sCvJD06&zI>nfC^%&(W}nc@!zPnAFt7EfAE?)Y50X?bN4b5^ z4y1U(sGNkxt6bS>HL??W%S#BWXMOACGA(vDt*P%@(fI3UcgdX9brQF(Qa7@u0Gv%;(q!&6W=jX$4oxT;+V0+_w@Sx$zg5LB)7lO_%V(=eLbJBn z7uq-4-_F1=#(xP z)To8_Z$9$DJ>?&`Smgf|D4Of;f6eW3t*Ms-iqY&^joritGt7&-W}25#v>MOe4I$;O znRs1+JjPmP#;BP1P!=s z{`|}2J-uf9?Yjv)pPWQoMgfJ-Fm${u` zwVVnXK(el%1}A73)gT~j0d7%wEMvjAn(qwYcoohgj=6@IbIMgU>-c|PERqo3JsoMt z6%})+(d*T%p)EgTaokrhXLsk z%TIBwY=vOguvfO^;$8o8+P{2@%dq$w7rhysydPXnPlsoNX^yFV#cHuiJDZwNk}SJT z98!9cn&|`KpP(ucoN#(mUS{Y1cZl`hKY#yAHt~|gC50bk9tonBlq{g;czQB4fq!2R zvqWrL(qQg5`RP05w~jNq_%s}y_s_uo6CmQcRQ{~?SAf&*jbil9ap0i(Yl>mFzqWNi z){SAIx714%wTC0J$0Cdglx!sudzcBN0n!VCy{DE;?jONM7sd-4?GHCbY))*R1Oe&w zdS!&R0c`|2Zw!ue+8>^Mz8n-->%tWl?H@t-&KRe*v0M85MleMKZfCk? zn$KQ)Kwt>=(U0==LH_+2WUK7Exu*(a3_uo@#^zy{tqo%@pf3L$gP~O59Hay(**hXy zVQmxLv=j7O!i28H6c^Uy)#4l+SfpEatV^te?ku`J=$e*Ym}jg4Rf$z|QZ}j9495-2 zN9r_t)FEXytVt>`L3Nuv3}!CZCS9|_H_Nz0x`MaIGF9cfxQj#qkS&$h-am5XIndUf z1@t`PgD_SrE%)}L$i1z0KwwCkMP#AmQ9KrPL2F0Ua_?BB7Ne z8#AVz!KCyVB?`nf-M!u3l8kNZ8xsVI6Qd|)40YvIJ`LEL4pbQv?6em|DY6zZFG>QR z@JNvv%3H$y4)G(hiV||ev}93eScy&5<$` z$H57y(T%+CTxO3Q2!6qsZ{{Z7R! zlJs6+*Lp4a=?=ivV?bZ*0)0ba%zhBu%NpEWw$?7m$X6kujf;t0dS9A6jOY7DlIpa& z9^MVJdT!)*VRZ+ojafnKu~v3HF62+lsX8*WuPD~|ip4SYYt1eT8@OL1OCw>%pHEH(<8gWAx`95MUz4^( zywk*$>J3XKo(Gec4cBc>t!?+OFPjV4pAb!$GNIw$KvBz>dI}>fNaJlwM*!*%E6djl zp{*(xE_)&^GzZeKg)ne6-Ya0UkZPcAv4!=uE^S+axo)jn{{yvI{c_sI7XP28*qd<% zX$<*jI@1;m8H{nv)Ui{r+dphZvxpV!R9aE(Dh_S^82$S`#eJ_nLeE)!Agu)0x%YY! zf>-DJ<9E&~C-2v*b)&Ihu1_OIEU6;yDo1VBO!nT|-G-n6lWXcS;#|bRJYJrLU*3G< zy_>)L;mr@_SHp0@p20E}>sZ~7 zuMMBMbTB_+X`DYHGwzDHzdDd>M>3^AgxqURT~a@-XfzgHIALPJqDlszAju1(fGS7w z-`Vq-mrQ~EG;-W9SVv4s;B%-E9%t;#|7{SQZG~RMyXl7)qv4MQV%^HMlxL&!!R_UZqdBIxADzMF zhihb!EBfyHZw(%)0Lk10V960WuIVS^n~RUrlr*0EK(cL9M=OeAS6Q~ENHT;!+#}Y% zI3!nDlKEbzV~Vux0yX~BlYzb4VPF+UhIIe}EZerbxrWEa_S5tO1eMU-vPp-OP7LQF zrs7Bt!5+$FR#T^~MVf#A`>+2HC}g?z=|&Q@Vgw%nPzskxGH2?6F&JmQPeM;W_OPi4 z7eqaT1WQz+9Qb>OYD#UNMzmb|tujrgGa3DKJD!ZrK+27lA3;qZb<=KITG`gy~lvqf+?ct*}OdAP^kk|oji{rdNCjJpDYVj^MplF z81;u?>@SE2RT3wiC7rYmc_~j`Cq$5=t*Uk(BJ>e(kuF&o3=0^Rt)l=ZgYo6hvwd5X5uY?jz*F4Pg+uE2tDm0fx9mjC>_*-&EL!X%*J9rIzJNk8?q#3M1*Yw1o$9k}+QtKIHXc@>ILuvVL?-nuQjMRxESC|b)r+d7==K%EXu z16-}FN?oQA$uB=v=CrVq8$jZYO?rKT&UZR46`|lR^h*hX8l6a zRPAeE9SsnmBhftJumSgnS{|C1Aq`DMLjwW=hDJ$Cz7!O^Wzs%*ttB)|2$1gD1FR{u zNQWPd99VY#9Vx6jw$lpR=^i%S;km|kpZyk7Uv7_1D;=*RAY&J1ytMR$EP71vPR$n@ zld0c4)R5EL;czsa)-_~OW|6*nrKz&&!+`~L1tUjSYwk;>Q_*gK$4f3)F#?D6obJaOB!jq?43SXAuq{`et8C9DJ4~029Z!3G8p3sx zit1$47ECm#7dleJdNP*=GV>+dt+TxOeT`18>UBPa83S11E=3BLMAGZ^O8k(qyE|N# zXEXEJVLZtfxc?cJpq=@vB&%pltu@(xQY8E@HjCPor`>K7{gQ#;y$*cDd(n%R@&%d4 zN)L?mcieXl!`=bFvP~eY*mHb*{FV09!s7vpEA|`!=49Pzws!_&JKI{tV!L}YGx51b z?D0;Yc8Psk5v#q>8|pTuFBkegWJ$YlGX`DyO|Dfo(}o?edj57+3e|IFi@7uwJ-5)VnJYJY$7>mM z!-X*8%6Wu$+Pce+25$5VFG<7H47RW|XYF;V#^4udu)Cq6fA@#h^q3f}k6v_ZMa;qY zU#pMG*R98D{HL%kgUZ62>CArpjwo{g$t?3iRSz96B}x9Y;R92Ym6gs_tK@8JYsAi1 zS9=RMThX!-_EJo#FXFvryW=jJu2TI}Q-_?q&W^G)bwfR(We?n3^zm-0ucetSMbFIt zL&%_KxQ&TbFt`jVyO=*RMHf`j=W}KoCM-b#jW+cH9vhF1e*m>tZFAa25dN-TapSRr zm;sVDooORud#HCm%}3b4V+=M%*!Xr(l}+hqOPhzodJo7bFFUX&OG*;<(*? z_U+kK`)n~=I8Fe5L?sYcX~}$juU89DC*ReN9Zu(<7SsoFe-+Os@$_f$@Z$UM<>ck7 z7q5Q$;m7f>@PYBmt1+IB&jnW^0{lOpQyv^UPON~0G50kSocJQ<`q-XrQFg%sxvHvJ ztNRa^YQfr?ol3pmC@1eK*!1TEYU=NWVU&MOc8|)OR1aXaR{B)$76H$=97Bm3) zwK{yN#r{g(fKqe{L*NKu4-l^v#=!h}OCuIo2;B!C7VBu_)uk4aaUJedJzC8}!A)}J z3&DZwLS|E~;~^F)n?uFS-U`#ao4*}7jpy)%(tnbsK>^Y2Oo}C)M7xXe9ZcrpPb&4e zqZH)-j-8Nk8WC)JrAQiFAZ)&fz(6&fVq3`h=n(v1Y(JO_K4Q}3qIccB?+h>R-gNu7 z2h7D4TJ!_D4he=zLczv+%foy+d%esFW!8}y9`n{pwcDZxiWSQa+7s);6>ODZ|z zQ89WXYn4WTv(#cFaWy3n4Y3 zd__P?Ar%RcK!XNhp*d_h?Id*i6kWWXF_oK;5HSR8C~t#uR3k}*WPmY7$(yUrkRZdq zr&By23bZ!CZ;@WOSsV>@zDAMS)qaA*>fvkN~cDg_VYY8COb}p z{ATiAYDZT=6nb8%m>cCQo|hDC=Ne1mc4Mkq#0?;%aiqN*N}~*_UaS9J1dw!bYJPKv zpgC4+6t8>U+s^e}_r5>qck$PECP0{mtg0!o7*>cd0J5BcIVv|7#*n0~WpZeX36a&4 zE_>YAYw!SmtikOK$RQJ^(U4;+eWm59@XdSm&cq7kV|qJ%vxP^UuzzHtc}%Jmk0Fmn1PaRL|Uc!+6jj)YE#s5`i=;i97xb{P&s1iGT%D1d%@_{OoyJ#*i6V z!zqZAOw6$DBeZ8?et+~UKp(jJgd24z9hLU04a!M_=>WFvN~8tdfQv+{)v222emwQMi$v{$W8BfAqoRUpYQX3wW_GAlQe zA^y*M#F?L#OwTgvQLS3Ri=+Voz4#1M?(??*Q*n3;rf+YU*o|OoWSpINEEW~} zK9=i#SW1>2tCX_4c0YZ3LbIQd>{ZG0MtA4u*xg7Q>$8ha@A_`oeF`y^scjQU?`9@b zST<5jy#+ZD3f#|1%Br%PVRid=)7Ofv@eL6e#&;cCj~po+-D`N3W>QK))1UohBI1^g zcP1ylg}!2}xDO&ETV9bc55@UVzO`*yU=)i&m%u?X|7&K;F6-NnA42x{rNM)#T}q}Q zDkQ6IH|Eo)=j`A}9gskP-EGO-ZUm-h-~pFhjhA1K@V%u*TBN3MRL-f_exBGcxz4fL zgl4Y|!3CPZj2Fj35pAv5g}9UhHET#kS=X-qxam-ZcFdP*@s@NRf!vYq|_^?%1nsx%8Q5dIKbr@MFWJ^S+R zeYVORCq|kOh2UzVI5l&#%TV`Ud4VHHiaH|{VSbmVi+uS(ZqHt`^Tqk)+2z}dH_<(= zxwyTHY&;rBp=E-0Pg5e|z;SYoz;ZzimjdW2<|-&-e*nATvD(!zp~!L*g_GbZ%n}XV z#I2yt6c>g)i~g`nnG^+nnn{7e)SX6KidlgZO><#*ign;W0T0xR@8s_|GhC$dJ5eUO z)*93s6( z3-qD0sp@+620B4ik}ncYK^tQ1)^xW*F3Gzd_|TA1R#q*GrO@O`fWS>N$<>%J-*2(# zLMQHYn|Z$f_4?!EXg(c%ehjChq0I=Hy-B2;ag4iASY}oTOTI<{mO4@+)eeY;gPvpX zrNtI`l6aN6{c{>=%gPGD4JUBQWubU2dEAnEx~bExpEj>|Ra^CY!Cvo*=&_|l7731} z7^RunJ=g&gOZULCSe$`d5Ql!h-|XTwnjgJB*6r(4^;&$t_QGwzSW^cwPht?#9E6vi z{$d~)2`4Y~iQfpt5_MUKTds`;sethWfmcz|w7(@OVkEKGSBNU-zRAgJYcne)H{~;G z&!2l3Y`1emZ0ERO`7bJfDVdh1;$b}Tqxyfip69jA+&g>cC#6^IZX!1n{m)alD=IuG zn`FC{+J%yBHX$3-v`P8+6(VE?4_KP9N4AH6Dqdv&>?`gg^x8ASd@zvEMM{`>?CW#y zIp^B>{bRTYYqc45eIhBsN=xSIiC%|Pb>2Dc8Ycl&A#o|1+(yAPntu|j^Pj!<)At|F zKm7K~uj4=IlJU>CV;CP_39f`s;X4QjpS5eXNKxcP+|^8QGBL@2;4>5;J6oeBIQaInZ z)BttllNQ#0EDvd*dfTSM@H8ZAK{;9RaWdbqvpPcHkPQ3o}QOd9QqN%y1S!)TaJ6Ojfm z>BMO2GdHX2oa%x>v4dKuFk+X*P|QbC9Z8NjeJ)PJ4H{?CXrjiquV22PG&O|?nuA2R z1&@Gn0V6XesS83`oB8nbXd)KMh86X_Rx8c8S$x}S^}GL!dj0M-tRF6D9NcLBwBa<- zzGzu#UWyl&Er-*2a^f0pDl17>-~~-_?;h2eFlB^HOU0`jd=KCr;3dWb^?v{wZ<29r zmbSC`F^Fp>VFA2)dja$JX5_rRAnI?yyFsYe12{$a9Or^D4`CkpKI(Kj84@Q4FUN1% zeOs-Y%ii6n-_7D|woi}ssaQ2p-SsKqqi`66CYL9JRWF#bXu&ynyIB(z^D9DvVBdqL zE~x^d=$+}pfZ>HcjqOp0?XG29X-0-74f4*>3w&s${xxb_)%FG9B^TJoZ&t0O1qEsR_dveg2x{5p~3&~ z3R`X8R|+_*aB?zY+lkB?^yWwP#@ycg8R9~tDGY;tXQz$kzQ$01-Y3t1I|n@N3rf}GI=0xgTp!hC=)2PlNM6+IwQIB$5PBQ{Ok(u#)_H_EL)NovU?r z))ti!i=4 zXHMHfH2+azV}&e7_$#TjgNh44rP_3ra(mz8>|<}Ve!0X|D%vEH+0_06y&Tzc+sN@< zUommXMW~9RyvbH2p$-=}Jc^D+2PvP&`%nsb+y}c}{o!FU0 z4>y^gdIM@FVbFbcXq0+kaLgu+v%tZB5j&3U#c*av#^(vOV`pl9HTA}+_QnabKz|0b zhmQV`K1Y7&(&J?WTzYQ2&}lAc-*#r_33lRz(*v3$TY(71-Xx1{&c=j@7p3PL!0kSJ z>G@`Cyb7W$eYomr`!V)>I;78Dk|u^XiTC&AYw)KJ{b_XPgb|G&?iRCA=!?IS0i6Kk zc%gyzW{MCLPm`fz`@TK$4R26?g2_@`RE0quxU@Y#i|Kjj`_xIpcmaWYU3=^zf{Mu-4n9w-?vA&2e1=9m-L_rAb&5%*ne%KZhu0Pk|WFqi^ z4T||cH->Tp_QRgg0S#Ol%lVsZtLr7GAj8OxL070hjN;G%pt*8v0_+kfxmzSDoq@Q_ zLia@wT$ErG-WNfc*^fwEIvvK;hn<1|Rm(fWY8D36j05ReU2 z<}S@fKY_zlR6b|I47N313>l%+ejH|~RP|$wuRSLY3;uFb-+$j9c6-;_P;i_ynDd7N zh;v|4T~L)o;HJ_sf10H3xo@us7{F7G!(hP;T>yO2JvRQo7p=5etKi%*2g-hL38$B( z2#Ei66{IvCL-@KSu)kzr&Cx2$9~vX)AifYQhVgQ2Khx1H{JR~e51jbPEJl5__MKhKc(cv3&!EuYlkn>AmYiuxh{a%pYM9NBpm$}0!WhBb0jQ;$2hV@E`e)cW9S~jMtjXNA+&sq z5I1x(T#7#r1K$g1yR=2qG>ww>(a{9#ayDwgo1-Zc^CLiB#TBK3dE*dO%HyLWe2x9Zd&Jd`m-9e_di?!Gg)FHZvhl7jvj=Uj9@ zm5`g)K|b%@US4U~@Vzn4_`$qU63FIp+wsi^16)rj=Clq-+nD(&knm4T(<}~{)|*CK z3H8VAHX5QPY5e!!|N0M+CJtm5G(3s`4K8)yj8I-tqZ>`aC!#Pl$(OJ2A1yO>xZSNKlK)22nmm}c1Tubl~vn0%f{nj8YqJqbLXpzQm#D(|( zQ;bF9ph?UKEfwB1Fyt{a!$`_FDdOg(>y~AHjkQ8Fg+yqDU_xK6)*|4*9p?j;5zI*f{j*SvAg1FOY zDQ`+zyLy$YNdU%DEQ9jQ4KAJ2fILlUz}x_s-@=KYMzitKcLqPY6@XSu$g%D%eU~mo;7<_FEWi7p4ftq;wLqnihpo)L?xd@&T zZgGNM?mbs)2J3~Ja{ zZ&{YJa!Dqx5}%X#1lbq$1FONqlP`=RUx_eIuE{cqs0A$dR&1#gPe*&s&9BC?63+W6 z1e1qn!tp7Cgj&`Jhi)Uv3?*+#$*%!oVA((sxWb}>%M1#+->#}7e}ITL$piRms-lKP z1VoIZ$dO}Q?86InhxUC|%Ym`N<0=Inohi(4-$Mcjqw@^Xs;tc+9-hJ9cR1r+=J^<0 zTMR38BxfuyQ*^Nm3!^%5>cK9jAjKT{ri9oO+NeT6Zvy0fQ;;oPw`JM3ZQIr<+qP}n zw(FE_`;=|lI%Qj3-+%w^=-W?yqdTG>`Y~6mT)FqoxpR%2bIdVZAF(Ea{g)zz3Lf9t z`M=5~#*K?_h18@W;^IAUc>mD*-0+e`$G@XC{Xt9(ct!1{>PS_vZol9#hz?vUy`41r zqS%|XLc#?Fg~)}9>kQDk=>jt$sFxs_^fp?l$5Nqh+*A(tm$i2y`hb0ARX3O`s?rZh zreA*guYEmF|BPv+frzu&jzH1kr4sVD?_uj)RlMoXK-&CCC4%c9DBN+tNJ(R3G zTl=I@RFO$hO@~0sJs)(JHk#6O{2IPk^&bYtdj0X827fbt$5cTLyw;5Y5eM-2^gPID zqh&{sNCPc1J^PvrC3sc_b^wOIPQR-L3@DA^f@t<8le9cX0&?J(m;cpf3p9Ricv;Pa z7-0=1eZG=H$=aP?ez{)WP1m1(5h#HWqD$=2|O;HyMO_5 z!q?Q4EgG8CF;<&N-xqpMRB$zlWF10!JbIQi*Cu|iGLSx)1(I2;`f4~oqZuA;@OVFZ zEqnfHwFG83WQEyg%3DR+f>^F)33u*&9@?VN7;!)AS&2xMnk?83-}NwRgVF`$dKJ5U~lRCP(`@SF$Jton)R4! zLOigU^A~CgSC0}gP5PA6BU6-2PSGT$vW#l73bKyoHp6x!9kkTs*B@`Y&0A@xykm95 zKXsX8v4qKuIg|!xgXLP^5HAIZo3Lrpzhsf~c^ zp~f`?<2%TNc-1tDuai@HRNOuN!$4D8fVtA{=4b{k8Ur`WFXS4q;NfFRm>_t#;1*W< ztc?`4K4g1EK1i$W%sb{rt3|XlrRgvXg?wR})rZfN!VVO$Ot)uUytSLw_ zhX$Kp;zypfkmwt`xyY>Zr*YBmZI}WF`E_p&{1fSz7erBzQ<QJ8wDUxWz^Af(&V(AK*lQ=h8dKT_mw?p5o#zu?aXT19SqO&nI>>R@y$bL>OZjqQY%zc zbrqTSptLgYlk}7SI7q#W!Cz(d)msqJl(r?6UOHeKsk!VL2jjsBVTP z9>sf5o$S{{l2eI9EMu7A-c`NI<0Gr`LS9k&L>Ar_w|sptBPM#b*=bCgiO;Qjs#PMs z=i(?aCVW9g#oq4Our2)1GAV$6|fq@iTWc` z@LZ7l1S%1fp)T5b#c{_;QYs|0Z|(D;m9=4^Q>hfGn{OYxjquld~C># zXw(Bzv!>*nhp1Q^i+Yv`ZADUi#!-tAdr)LsX^@$g$UOu4R5+H#(t4A9g`0 zyYv|PZ)TgM*eYNp6(DxD|I$)~>4#Vn6(k0Pehv`#xQ7gYFo=gMaW{AU5JEU}ovyzE z0`_4I#s?8>(o2PBMGB*`A{8o}NhbQfcm$C3vdT;YD5qTzE#SuOP1lrEB z`~feaw;||uSSxCBC(93d!{NTg^hsg&_XS}V+|RD#n_9kV)Anp;;_R;O0OWbKeivk# zkjI_9fXlHnN^!Ujb97ZRsYD%Rr6O9BLWYxQSetgx881|=^AaM)N>6)tXix<>A?jOe)QyA zCrzi0$|jm)Z9wYu<8l_wcaV=I~FJWROHIP8AbP6JE5MHpwf zLrV5B=8j`!9w!RuG$xPZ4CU-CR^zs`oxLeBLy$?`zMto}GoeD$VVK9plm$#BdOt+kCzS>3YK*gc$~Vnsb9Da-FA!Y>g6L8*RNZZklr zB{FUS!>ZM1xv=b6=Zfb3qVsE4s^P>G;b87hBQv@&EQWFjP=tkl1(=AgzTHf0To(7F zU4zO)kEatqwrZxv3*qkx-BRrQ*5w7MbrdsKiXfn*2f~w5F0=Y3r&;}G+ghItRC5eK^8n{0zLiN&`_Nce9+t`AD>r+PZe;SW}O`Uha8s7bPp+sFat6Hq}4ugTVd4t|FruhfCR~R>KF8gnqv50QXWB6_Xkr0(5sC3_4 zFY-C-H5W1}m~suUqnK~+LgYO2D2T{8F2nE#)GkGCd&40)@X-(g4h3I%u#zt<^XM6) zM=$l)#{tAVLi-2RFWnSRq1Al-;SK0G8fNzo%UZB$>B2d40 z5}F}Xa6X|Gdym!uhs??r6)s%seC5({Dum3g-j)6kDz?*fTyO)v{ z;twejv%*&&ioSI(B^wd)N%mil-P)no)iOTNz^S*-4%hH&R7NZkqT@@JFY>S?4>EX& z1c=A*ei%Z9Jt&3veN0et3hg|7fu|^H$^9e3xl_vlPbLsJtW^Lvmlrq-wiKXVUL9{mF14bIU?=Ycf+x1vhq0+_)r|W)jB! zLztICl&j2PM9b;9PjJHZ1GgyD35{zy0xqGr8R!L%i*o=8S`0m0+;{I#t2?j z#OA<*1@AjO>|npr5k$vw=vyq4ybqtlpb4s%%Di8N>_wqQ4#_oz_AcY3jxPphN6FN7 z*ljCAXJu_zd_F(3q24xMiSqu=ck|fEjeci;?rZZiq`oqR2*y<2)XYRD+^#`a97iBw zY-|>*l)m|pifwS1(g&edZX!eq?1M6prU^CNUF|?93AS)1-03c^HS)dP^!}C^iLAm~ zwOMN0f8PjgS}!i|$X!vN*@li$){)!z(W`o{Z4(lgC84k;u$cwgFO+9lR<_zRFp8W% zED@3Vk;ThHKJ(jg!AfMg_)%LqbK>{*nRWm-dsUiN4>rJ}0QrTT5n|~;SA&Q}} zQ?LYVWs^)OVXFA>yJmA}pitE zMqJBq7smhgHBdP><%98)w(EAt9g2WZBbJR_VLw5+h6W}_lIk5NKD0S6w zb!9=Lg7!mZPusBb9Fef}Qe(dOod|}#prf?uuy+CTqTbI1ZH;KhN+^WCoT*R=OSC~b zuZ}VT4+~`xV{&u<@?(GBJ2!#Q*!w_ z@MvNk2g&_Y}Ap8ke5)X0|lB1T0`bYvC#LNCsTlRjj^kx zNTXX6JF+rZES+g2OH;cBed#+{lqyv+y(T9F3r%v-bEbc}4oyFLE&-bfB~vIOGfCH| z^UP3ruzEG}6S!H`%5H-V<+Dbw56yxW)y1F`w`1KUsQ4Vs$ib+HdKnN%nYYHe0I57d z8NqGTZ)a&D)qLE6ZBl?Bxa;|5(+U4@qtQA?LlwkeADW;=IWTlwDGTP%`a+uaxRijI zA7o$W&?3He`t|ZJi!WxEH?!ycLFLMooxSi{EjnFI=QU>%51?E7bV+K;gl_3G&r}=8 zo^J#|E4?=w>6_mI62Y`a8HJfKDAa09i#X<^{XiX6))E3|xzT8@p3f~BKAPH!pJ&?r z(1#oB14OW%IZ!T}qTtz^1{lp9%Cn)iH;3}1=oz)#{PlM~XGn6CjDvff0>K6s4Sni% zSmnfN{vl1VJ^gFNi&j87Gbqt8(5(BYw0w!74eA6rFT@u(2<7(`^wAedJwWEnVkG4~ za-azXY2Vu`h@?VGq6;HJ+t9I3Qnc`x31z}E5?>Ld;J$y7mZF&6BfCO)<-R$e3c$WQbFHgk?`%pr&s1m8wnCPMaX zG0oLS2f^Q_Q4M!=7Lwe8to@4&IY}UD2vp&&VURD>r9g+Q;vpf1YM4<3Pxm6IIFX<+ zz%dav5E~X57ZU8=YRidaAo+;^_dy^9uG^8h04dO7+LJUFu2+*0r@!LLHiK&OvGbiF zTjqACbxf|I_v}U`Mi)@HkLX= zmqvIbGLtJ=vY0DOD!BtYOJ*dVi`L8qbXf)z1{lpyYdg|w90Sh$mh%3zAbYuq7k8(g zN}x_v~EUS zW!W6cl~N=^T)#`{@gc%T7Mb>=nZc`M9fUM9HoBH*&JGF~U9Y^$AEwTyu{M6H^x+5b z6_L$L;`?b(-kxEBUh|%a>y&dDI3~*2!5tut7teX}n=$k`z>*&cE}9Sr+Du$B*J>+4 zx%j`YdUEeWIeFE@6dB2A0OT|6Y$6R#<^ zXL%avVZ(5y{D98Trf73P7r^7Md2B`$&U7J>Ui1Yf_=yLF?&q3B-=ADNSnCRVA9NK7 zpB(Tz9uFl8RS=?4F{ecOsQQYR`L2g-r{pc_z5C z_?k&x^;kf)Y$DLbRE0P5-UhS+(buRe`J2SR;-z`$zh?BPr)BPbS-h% z?Cqb#;W4E=M;9Fyi763-Gn?K_BDnZ>#|;NB^w9*9AXiDH=awq1rueWjo4g&KsccG` zwMtcCdHQbfwbFf=hfA1DV~htsRg^D9ystec ztg44i9amjeCZ3p2YO*qG7mb-ir}cmE}A#ju^&>F)kV4^Tz3 zW_8HcD;{~xl9y+T4|HY6>-qUmM|4wDLU+Tpo;c@BYbQej{#;;4{iI#K_^)@TXb;E- zsT2d;28RJom&{I`xQc$RXoA_$RXyd!Dm$t0>^dC0shC2xnCeT0Bir!4-JoE-x$Q4I zL4P4sD1_nfw|9Yq6t^E>b&B}-QmbxNGJoO zQY`bNcG-iT(7hQqeXUC%KMmSwRk zL)q+FdOP9jZfK6Lqw5MaL1ri_wztw7TQ}k?aB9WxvP-y&NT1dP zyu;U|@faMWuvsGxx?>xrhC&uThvGGoj34+N^dGYGhtFI4{n?OR4v1PCA~q?XT57jM z_7Qr+;5`l`5x4|aFI`rGzL7-LLmJ47gv&deUg0{TLT3KETJy@_Uwo**9BRlV1yf)r>h%5l9>=!I14j3> zkI{yRB8id3>SzUMbz(Bbn?hK{{G9idd&-vaZ=dow2$#Bp1mhk7S%hgSx!WL<%^keNvLe$lEoOT?lZwhxGq-OmFw5D z8;#RysgW7d5e09t8Rw&%?v9>uw;u(^z|TE$B{lf2!|c^9N?+@$8%1;8RtoN%hB1O`p>J#;@fR=K zrwO~wgj@m_F`jK3XrUL;h@E2_YWF77+edME@WZ!>Pk^3nfwKDA&r2jrq%&090=d>4 z+#z&mfkp9a69X@FGZgf%$DFN)*dCxQ!sgU ziq4n$LlPx^j81=S6uwY{l7*>I%W06B@(JSAL4LIWi>4e%Yf9B1dr*Qde#c%G5z?Y~ zM=&w}_#dWaXQwPA#=~UfK@l#CIhYO~cZ=|Do|A~oW1b~41>eTx6uUYq(0IPT3VP07 zKgPkwiMer{+`X9|o)v28j0PiABI1=)xEGf9l63J&YA27vn93=ucWE=wz&jME^*Wt))BBPU1TG|rT*gEp&_yb>{hn1`AJtw8b7EAKd) zutINRL8vFXSK@W6-vd`D$Jy^cd-Ek9ESrBHa0>UqbqyUDFIzZ!Q!>DY@usgKOElG& zU>o^ym?3o4{$(xm%XIwl>J*En%*Ice6}kox24QQ4EJp=My#D(8^2Lm*Sx zGKdfY!A+!{xb&bx6)M3b3$!Y;xv^80p7*xL4d1s0|5^T=zIXD4z489#k|A?2Y?Yy9 z6kaC~peP!I&uKLdWA1i_ccb?Tac=by(LQx-PU@-g@7L7E7WehmJNvcY`_al2`2%ln z+5~LcMreXOh%4li27&PodLkl5XsvO6UaB168Qee@N2C7#l6{Hb>SQo z19$@Zrk9`KyLJV{M+zt~7J_;GgtOrSEckKjn)$@m!N-ZU8~Hj;j96-vX28KT_uF}S zV#vCq!x_hj}~f>Iz$?ivlX-H{pI^xl{2i9JhIK7L&S78+6MU0R!lISM;`s zuyYJK4(IW!A&Mm4$&>V(Ok@5(q=>E9zza-T7VP&0*6&BWKvXQnXAU`U%b3u$y~?ve z1*IdF@+`U;h8(}3e`CYN+0y(Gkb04&ebJa0CZgF^4P1zNuVroF;%zM^#gCfDO+-|6 zVVf^hS2eOb0WpQswPA1vmF_ILuyO;8pRBbliX=*cPIscM&?T;6!Aoy0L6Rpb&nQOW zq4aozUG&Tu38t@95NZY$JDbP8nNaKeZ9fMmzs1-OjDhZ^-`?!$iA(Y~t@ag^bn>D? zl(WbXutHUOFrV46v$9FSX(Eb11f9nu*-m5`y1UXkWuzd$yaI46kJ($NT(m00r2#d!Miq3i$+^N9&RR-ng!8 zoKROC6_-kJ)Zn*D*f&8M{E>KWde=I)%_&0tScsr??o_ln9GE^TVEsa5k9{_yV{G|; z@zd$IvbydNt0@=eHxMuYFhFBH{V2umzNCcAv;>W$$}9yP<+wCm1bsP;IBg5-V$<=s z)T9h8ow&5DO87Iylnj-O+juPlgCw&oQwvL769Z%G@qQ$w_%wYKC7lEf)f~VX!2kK0 zagegp^x`u0|I5;rAOK2|6EkZ2ijtBviu*tSHgPgmKlVML*FWC7@}qQ%ajJqQDT=50 zy1dYYSvgGsThfzZ>XP4`?aCmSWW_YX^x>uZ)Bn$zfEZoKlq@x=LKvg8wRF_#kaYr`nmbzqg)`JRfL4l@-4 zu#&I!8&VNQC`rieu>&NQPbgyFB@TPYE-^j+M?koZuq11!c~%O*e&RlW?)si}_G&r} zvqybu{R~lNDAiB}A3lH!a$_`1^KuiD5-!WsKX(EM;6Fy_rzWN+=x2ccefX~l^Z)Y% zaHQe4VvjYC*CUUA&-rJ>B7?J>&Jz?9RQo>$7yotQ;=fP)pBDYOAnX5F@1KKz&$$<$ z8XHrin4uq+P&fnvC|1<5!e&J9Ua4IL6P`xkwoo$Px6(c<843`RzVjq7-rCj>ZS6QC zE{A-*PN37iNXHVYB+t*u$-%rlp}(v!sS+s<5gpBLmGrz?q3wuz9>`8GpKTT*y74ei z&HtUjPmW))aCG`Q_PRDA$7$4eNsZQeUc%Hw_a~!-HTW<}$~r-kr2S8U9z{13O66e) zQBz}{a|GMB-#B*F>@fLc#?X$S#$jk8q_2m|0B7{x%?q6?rWZ{h`+B$|P7=pt22os# zWT;&T9Nrpmko$VwqNEUN88$!-wW)Brzmzl(j zuGu%+lYXowb{o5D+BF;-FnEJrqV5XzKb%=5#H&Q^F{utxDOUv|?M!t#m2+mbAb&*I zLJb-9Zmw7Oot{9Xre|nJ>1Ao>C#a@p4kUjln|Dc#?TXY;ib+z6O3wFEQc#joZH~yy z_YY7^P;Gf1`-dMudFzv1#&r|`Iq`k6|FSdoh{PeI<+Y~%ai^~%giU9IjET^p7S%Yb zN?GS1p)3&U;y$I=9|YC=*huC7e7ek`r6J@jljO>q1Xrsd5VGo{r$dsBuIke&_m7lO z!p~ZO+F6j|<&`x%y*NJ0M4o@?30=#tX3b>l99b^_L~d}gaJ2W?|6b=^wbW6qW5wch zxLHqz1Ie5&a9{lcAvog9;Q^dfm^DhKAHsw`!X1_k^N=Bk{(#MvOAXEhASEV+oBs#7 z*npQ3jdNALj)f|Yu4-M!8zL%h4g+09a1CD3lWVrSHrN*d3s^azXKU^bB)`#iI@+9h z!|rMS_BA3&?haInhDx$(kzq->>{wAgoGrY$8F)U!gA^+?lK@aS*dI~77lKe928}@s zBHbTU8Ud&zQ@dFkz(wI7?PHX#6SXd3gT`68%GCz6a`)NFlaP4Ron-;G2zm(%#P7~> zl~c(nhwaI(jI>U#?=ws*c}xii9+7#fNcS5I)y4)xRacBIWr%%Bm#ikA9}hVd?w!fB zg`@qq7TroX>S09z^CT4oZfHp?VkV(|2$fHRN?54PbA*B1F@9`kk%~es|aTF*}t$pXA6;f}e3HbY4iy%W9+jnXehWG$v z;9LWo4TNX7tk(RCwhUvH7E3Q4(dBVOGvu#HBFJ2R`?>9ZI^8DuLNpR?CYyW($=yFc zfeX`6dqKwN*F@y|TtZt7(d?8F z(6y5_*4gFg^h-RXA^|TT^lx({kuN7qVNxI>u@;Oh6$qomB~AC%s_1tU-Xz$-k9g-r zHezDGU))i%WqnsGnz8@pa12a9HK2)1co^gK3dV`yWUj6*yLL`=@Rh*RQaIX&S-OmS)8vY;7ZJ;&9Bge>hV)!s;d`bVyXY* zT{6x=dRnIWcwcgQhMG>n{{C65PDTFtS+(^T=>NiY`QI0(o|2*)o19S+pO{&hJ*+rZ z1PEL_jP?d~@w0QWs?nqaAdUW4@AKT8~XsC)uN2quLJq ztR2WaIRUFQ^4Yxsjn5yMZdntNZBAZ(IS*l+=Q3!)dii?+zGT|av72OxCx)RP{lEI? z^8Ww&dG()m<-5_~`aonSNhy7t(vPmHk9i^tbbuHI9jkxWP~ibL;%ke8nx8i3rVMaV zu;%+P?hzt=av*TVIukcS>%Vo!T=x~(RQYwJP7cnppYhLhERm;-S^G2^K1GlwX*Ut1y@G|n{b0e46Ip#n|i`! z3M@xIx3F`?w^ObsTfk<{Pl7m-ie3K=1``MNV!9AqG~~cEI~*UwVD7Mb%O78OZS)B} z5DgU93PzZNfdP(CWOnZjK-c{up=G&X#a%30d%5UJG+<{_1!4UYCwG>G#0wL97Q^?$ zxzer^t-5Xy{4e*V%cV~_gvXztU5IS$7GVUSm08^NuUJ-tKS~4Tl@>1Dh7%juYGcSg z%2q$!v?!my^&Ao^rUZ-6xk%Kd1dBIBNI{gsf`7`>HC_vvjya>QV5nz)C&zPL6o*n) z@0(83KC9P!BpZYG%M>W=5R+WQA<$cVg52E?wCE4*0^ad21n>#x2 zbbEY0$=n6labn1Rr)dV0ia~`W)&Hipy&Q}xI))Ny@yE7^WVU_EHR{u`?%KhdO)H`% z1yvux{S+ymHppW_*dApPFPQo~g?(6_e_;T#h1oel0abXKTT?iarD|dbAs7jn(JmfT zs39uH$vm(eK~{xSV+!u`x@zH*cde4IPHC9QlZpxLB~XJTbV7h3Xw#svJK6a|=NAm$ z`rLRF{Wb48#Zhh!=kr&+A<(LT66D=}T`l#SDt5_2IE^7kaX+FD_UL#pHAfFdSG9Q) zyw9I&r<3D4%^H^9SE^AV_!|-oQH=$VUc5Qd++jINmk(aljO}l~p=T)m}wQ(d|i>t4fm*ZDIK5fI-iAfx(k{BvJNFd0u&Wd6vl( zBdiq@H{TO`>DQ0{9oC(!C1_w3lE?FMz1T~UPCSJATB$V`)Mn-L{IqJ(WQY-8cFxj! zr^p^$b0f5j6XqOs?-?XkoKyNvwdXsWqGp_&yj8+ZNJ&%;sBfArcBw7i!g^8QZ{X!y zFAi37+N)(+Pj9M&sJ6jTGf)1r%&>e8OU9mCOPsEtXK>u;`k%x%3c&L-s-1P~DwJj= z#QyHpE>SXxPjdhpBz*+NpkJ%!Ic_We+Z^yi5&2lKXt=fWh2awlTxA0&wHcJIW&c&8 zX$l_sGt!cLWe4JvYj?XIWn_gVV06sjKrIhF^g-1jj9o8i?xHaZ+}IbWr>oq;!juDk zB$69NhKcPSu%1n<;6FvTAHP&;F$!m=x;~j6L~?|ow=sv1%hf}{#R5LN(M6# zY6>jUH-GsRRu;+UQHN4tm?22@)EfzcDSkx1RlUK3jWS+GE5+NSj09Ctav<^6g!pQy73a+nd#a)S7VomDn-2G)BQ8`fu`Ns_e1xQ z3BWl@6ShC46draxgA8EueF1t>C_x(qxRf_}(U}xTDTqhn2AP1K+MFW&JR#IywPvPn zrkBQDM=(A?^TjB{oAAWZ0*8*ek%qziB^uXoaSH?r_o5$sV}aPv=v*x$h`LxxL&4T; zS@urYk}C0E_8cU0{xD#?QGZW!7@RkdJA(Wope5iZJ5Y`jk=4i)R>@b-^S&(^!wbSi zd=$@5q;?l0O96!<7Q%O`kSQs=lpGb9(nr0g1ThYeOPSl*h@)}W_jSV3=P>IYB!E2i z8z`L7O=_Pp$7$FJ@e1-6X*oWDRG_J59jhHVGLc)7)nF{Os3CFTFc5R~$`h5Ev&JhX zYvUzGNeeCPkwUIN8oKxkDPe;iG@7R}3*G1`z>U#O796fYK%SVaCEyO6(2D1ehfryo zhHxzT;_nhK`K-PY3jFr=(h`M5pamH7T=lVjMx+Eo`4C@z6g3vg2yR;$X3x zN12~j;51o1-!2>T5wUkHVKf)uwjC_fgWkP!^DBe z7NQ#I@!_*lt5a|7 zock}~?n>U;-md*%1an~`_DLMe#WA92-m&L0tKY-oSo@<**!k! zUfbd9Vk(j7@c>4rX{<&JpnhyQ(^m>;k)~W~tn-KAS(-oMa3YX`s&|saBoaUxlvKtq z6ist|63j4rt>91ZJrmHc{CIn{VhBv6mv7|07p!l3wPRPalju=2%}tvSq@1|6%F{6g zE3{fQx zwj5oW|1=avnG+jhkBsKAS(ur$*R16sUpP5GuiHJJrXiQnzR_EVC>()F=Mn}L25fme z6VqgNHDlixQDCoq3C$Md8O0btfP)Pa219UZos<=Q^ltQ4T8z5w`?cGq;%qY}=zA>afgO}Ih`?P7HZ zZ|u~QO}bffxUg2&s9Ae>9Z-1yxx{os^$Q>bzxzbeCedLC8i5d|Jf#M1&WOR&dyO2? zi4FdcH8lpy!H%;sE~oW)0it3(=~4DPa=9|-Vz8q`wZS4 zViMG!-MPRR<^P5>fynhr@JU#qv_z<|#3~dcgd3% zjHVnF!r3xb1T*=wxw-clC5Hh&o*ek0hdMBg+u3hTZ>Z_*ikMn#RWZsc}b; zgS94SA#=NWu)6Cm-64PgpLt)OHRv_4e!S35i6MTGV~A+af$$J7Q56hn(0jh}pyaF- zKu?@*1RwEP=@+I`QM>kjSyH!dsk(U)H_M-bb=HMet|t!X-mk07#l}PvJ;8^4QbmQX zDN#n+_m>8il$6+}vW~D+ja|Gf?qt6~>vUU=a+jk*G)Nw9X^mj*CAkU`>6_#>oKo1+ zZ%c4%B{8ALzS=t|Twf!Hcr8xTu}+zY)B;qJoAXQ?__m&1a*JE=@(5Ln`le;lb)dcI zbm(Mvct~3%L3zTdk6hnw<~6rzwf^0!kEY*8BF_E$S!RV7x?eQ(F2slCA;>sAXx*KDX|de_8Lgf6W`f%)6+g795Scj7*3 z)t=qA5}LSX4AQn{!nwq3HaN=UNpvfk(U`f)dYJe)(|? z_jA}EuM2D{L;X&YN@vJuPza5u+7%LxmKbMz+U=x}05_U7MAM#7iidzS>PvbkNmi-! z2m0~FQd;7*8B}S{>P2BF!>W=`G#8{5=JRFvmHwMHN-_a?l^Bo2BJ}rV3w2W5(gq{k zIUPq$rX7&28wU)l2Mk`>Y$aXn*L=NlMuWrJ`hp&KI~|;HsAPZeI#s zzNP2jb1u$~h~DQQI$ZTmzHmLdq7QUn17=`efo$H@yuf;Pwv_jJRw-Z5!xhk(|G*l0C*HJO%TFj@^O2K%r|UWhQ1Sl^N!k=a^a4X2qwd8x_SRXcqvH zB#KW}p-4rG%QLkBQj~4}iPo>5Xf+8ZL6WFovv!`NdxZZ)ahOYMTxgyM16;}AQzdqE#S(pHA|@%^@#FuUX33JVhiXb% z+Xnfz*Q?}i)=o5e@nD^7f)JED=)feLAeF*kWlX7or=;`|FaizXd0J$wY4+0FJlB`V z;pG)W3bU66{*aDMdM&uiTSQE)y}dU8Vs(P`!e2~^e5_AW#}fv-)gTw?fnMOS5DKf0 zg~1|fp&V}{vnel)XO#QG-(1vIvK}~fVaU;e6I=69czgM>Y%9UwP_)dr=78pse0r|n$F1WDI|>Y3EJ7xF2VHpBpKyy5)v zKFcKnuEcUO**ur`wjJqu^eX{)9gJV-+Y@`a2?`FrZ)T3OSWLsYw>`@<5N=p8m9WbB z#o7JizzJQQI^%tIgB&ZFBDk9N?-b+~L$_r33j`+XjnwOAd=ux%Li~E<=|_FkX4b3nqD{VbXDF~sV2`F0*G=LPfV6G}u)$atcdf`reS9X@GsR2U$#{=ohUF@EkZMdshYJ`dRON%F{! z@NTVt<(}o(8)TF4M-k!1s29JN;x2i1Lm8Pp|7nxo92;qyV>hzVqz~YXQFpa z0uas~K&Zsh4sL^5#TaWHZF?&ihW76XGNd-EfT*d-2J>^lk$XuRIBj&X$JGk zM<3E^pHXOU?T*B|ash}02;$yoLFYAJTb?XHk|FBMJfQZmvVygGoY>1hoMl{Aaroh8 zhf#Ua;~x9J5*cT003pgcan!6ZKG(H%%0)s;m)Z^qyJ(Q7ccchH`vyDeO?SpT%m(+2 zB(j~r`#lH7d>uM9`gpEOBz!G1oKI+1Uby*7p#on{assgf5J4-k0lXB~np?#J3~wmC zdN?9bX4MpU>{>6nM8xK5Lg31Bs|r;FB^`4CC|-S@pmVbVHH4+kp$O4>NK)nkQqqJ% za(9Vd#^zlcj2s0e%JYTi5M97ROF=38>xkON&mbHy?T~%!zC~j3UdM#LE2v^y9&LH3 z>?(&VC5sV?W-3ZXQ?Zz$6zzoE zx(nNf(pTw;dO@~JG9Ua2g;_jRS8fLcjGj+y{+sCzzy+PAaaR^tb5xI0-$90@NQ?73 zPZarN-igTQgESCdUF);dRcB5f$`C~Zc#*U7@x(hxJJK+a^fN6U5;)*qA5uqK$>;Be z5cSc+4}IB>sgz|E%ubJ#&nh@>IoD0kWf0#FC~bcz3SMsHd(4&Id`-O$qf$CGjXCe+ zf^5pa8GYf~3I|}zy-%UUFtFi;By3ziIw_g44C*F&U7wbiJ0HzfB%!#Iv(3Kf$NLV{ zy*)%wlO~*rQ}3a4U)0PrOBTYvt>8s`PT>?Xr{o`p?V7 zec1EBjs2rV9)%3$?ZTCNm1Fz+mZ!wbc+v6DmfjBCR2)Cr{kSvEAEzo0o{y?d3+??q z-PP!@TnY}Rk$n8GZCVhMj6ilWZWI$(bMBYkAu12L3=u;@%KQHYXh4_0CJC-`-DO?;didtB?@{kD*VO#{|no6lQGJ6*WV>(gC*dAp~?7kT-v>??mCeS^z>n(F;7TRZmD?A>45uT#nXgdlm6q4mg=}4*66`> ztMpltMTf{CSN5{v(39rUYogpaHCaL&0c9E5-qq6}R5on0IP!_3dHKuE)$$B~AE7xXaNO{u!Od zZkO#dhUb$Zz*hR zEBc8HGoO8quhV_rnUmktoqzcR<_l0k|ND;P(nh6)w!#nb?fCo7`FxJ^>2q;i1c9JA=z_*T z2FErZ+N>+@x8J2Z!=2sj-H-1-^uH9veh^r_)Zk*2r&5giT`Lj}{Ue2lcT(L<=HEdH z;Qb#gR??3z6=i0B708~<3n@St9Ep@={ZmO3P?PRN7*Hv?z8TgzXlYf99y1P4lQp3Z zp#YJ9h({;lFT{pP;ouEr0#w&Bj&80Ad2CEE(ad**YSN_|{A^9Mb7>6|S=mERa_*g+ zx)yV)HJK2>y}@XzY{5f7usO=-7wC3kDYPNIUq6pdz74+qzPum@By2rkYBlb2-WXB$ z$@w_+=7k+{7L(M9(hZV<>CyNa4w;JTrG+PSO@XmWkeaz>y4gX?GbNqIr7dZZuzgEa zJ9fw0A7l6b`LtFhN|d#h2Axml89*yCMvi8gp~8} zs!kpsNp<h0~1|wMO1Cg=N{& zMzPooa+yHbAbq1OtO=IbyugkLj?+P@`@l!P1Te)|Fy~$kF{~W(fX$BUqbZ29eZXJG z4b4E0+gq*(HSlzAuks!=NSJXstS0MfaEnX2wA$Kw8!`>*mc>Z+reK4mp1(%%dOeiS zxQ-UT=A@>|?L}$7$*p^|W{2U=M47p8{pJg|-VPTDxDw`uSHC&q%827&9Q+6KNX^N~ zS18Xf%1Ke=0sx&-QE$^Q5PtWsFjAzX65R$vf>BlkRA_w}6Fl$|De~+~Ysv8$`%GI+ z`|sFEs*P1csD6m!^LO8U{_Z|}bZf_$0%?Q?UIp@6QbRij;qujF#;oWdI3a;ISFyW| ztIxPSd)=Pjo?o0@ynFL@##ju`UDh{icO!fNUtgnwMeH2-+5aUB+gBk8{qCPRKXzS< zc2|GF&Y=ZM>CXlU8Lc=v;@9QejZ8nEmGzhwntC8ewQSp1nITQ zJXJZoB=EZI)$m?UU6yI~GTZ%g0$>X=5>$V?$_t&(FZmOLc!7Q3KbxoK3@+*ERv#RCLsgh~_F13ra z{LS6H_^E!mc=_hVo7b;?KFPA8;#ynI2j90Sip$y6?8Dp3`Ss20>Si#aqSd8$QMa5I z(I*}bPqOJz_G$cRpX?J|@luD#o^BeZS&EJ>ovPSBZWshQcWvha0(IW4bj5)bmCml% zYPNAi3b9)ib*YTm(2^~C6}h6)s1V5PlA_vqa>s{4W-vzq=xY($d-z4IWMe%}w zWTWkpTusVu`h5B{QPi_m*(4=Ao6sN3CeYF|WTE5PsJjUc<7k@cn)0I7hUa{T^AGua z{`1{oU(gTwon-$JlHrHFzjezFany85 z`OatVZ27h0!nqitSKSnr+bo{~)MU4e`ly4lG`k1j(OLc2uoVzIVhShVG&sXwLO0sJ zNZx~9I={StQH$PJwASElA(-$Tmq{4bBBR5-4$Q>eV`g9r@QKJmmAAr)BHYJTTV